How to call a Java entrypoint method with non-primitive types as parameters from C++ using GraalVM

Tags: , , , ,



I am trying to create a shared library(dll with header and lib files) of a java code using graalvm.

there will be one java method with 2 parameters of type String which I will call from c++.

I am using a maven project, I am not able to create the dll out of my java code, I am using graalvm to convert my java code to dll.

My java code looks like this:

package demo;

import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CEntryPoint;

public class MyClass{
    
    @CEntryPoint (name = "myFunc")
    public static byte[] myfunc(IsolateThread thread, String x, String y) {

        // logic goes here

        byte[] arr = "byte array will contain actual bytes".getBytes();
        
        return arr;
    }

but when I try to build the code into dll, I got this error

Error: Entry point method parameter types are restricted to primitive types, word types and enumerations (@CEnum): demo.MyClass.myFunc(IsolateThread, String, String)

I searched but didn’t find suitable solutions to this problem. can anyone here please tell me how to call java methods from c++ with non-primitive datatypes any suggestions of any kind will be a great help, thanks in advance

Answer

There are specific preconditions that the a java method needs to fulfill in order to be successfully run from C or C++ in GraalVM. These should also be taken into account when designing the Java entry point methods that are going to be annotated with @CEntryPoint. The following preconditions are mentioned in the CEntryPoint documentation.

  1. The Java entry point method is only allowed to include primitive Java types, word values and enums. Also in order to actually use an Enum the enum class must have a CEnum annotation.In the CEntryPoint documentation.
  2. The Java entry point method should be static.
  3. The Java entry point method should catch all exceptions, since it should NOT throw ANY exception. In case an exception is not caught, it is printed and then the process is terminated. However the @CEntryPoint documentation explicitly mention to catch all exceptions inside the java entry point methods.
  4. An IsolateThread parameter is required. More precisely

An execution context must be passed as a parameter and can be either an IsolateThread that is specific to the current thread, or an Isolate for an isolate in which the current thread is attached. These pointers can be obtained via the methods of CurrentIsolate. When there is more than one parameter of these types, exactly one of the parameters must be annotated with CEntryPoint.IsolateThreadContext for IsolateThread, or CEntryPoint.IsolateContext for Isolate.

The sample in your question throws this error since the myFunc method signature includes objects, for instance the String parameters. Namely x and y. This is not allowed according to the precondition 1 from above. This is what the error description tries to say.

The solution is to use the functionality provided to convert between Java types and C types. In this case in order to pass text between C and Java we can use a CCharPointer. Since text is modeled differently in C and Java, a Java String must be converted to C *char and vice versa.

Creating and returning a Java String

There is an example below that can be used when byte[] represents text.

//These are the imports needed
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;

@CEntryPoint(name = "myFunc")
  public static CCharPointer myFunc(IsolateThread thread, CCharPointer x, CCharPointer y) {
        //Convert C *char to Java String
        final String xString= CTypeConversion.toJavaString(x);
        final String yString= CTypeConversion.toJavaString(y);

        //logic goes here

        //Convert Java String to C *char
        try(final CTypeConversion.CCharPointerHolder holder=CTypeConversion.toCString("Hello from Java")){
        
                final CCharPointer result=holder.get();
                return result;
         }
        
  }

Using and returning an array allocated in C

You can also follow a C style and pass as an argument an array in C and then use this array to write the result byte values in Java. The method CCharPointer.write(int,byte) can write Java byte values to specific array indexes in the *char or char[]. The byte array can also be returned, if needed.

@CEntryPoint(name = "myFunc2")
  public static CCharPointer myFunc2(IsolateThread thread
         , CCharPointer x, CCharPointer y                                                                                  
         , CCharPointer resultArray, int resultArrayLength) {

        //Convert C *char to Java String
        final String xString= CTypeConversion.toJavaString(x);
        final String yString= CTypeConversion.toJavaString(y);

        //logic goes here

        //Fill in the result array
        final byte sampleByteValue=7;
        for(int index =0; index<resultArrayLength; index++){
        resultArray.write(index, sampleByteValue);
        }
        return resultArray;
  }

Using a Java NIO ByteBuffer

For larger byte arrays you can check CTypeConversion that can create a Java NIO ByteBuffer with a specific capacity that refers to the native memory. Note that

the caller is responsible for ensuring that the memory can be safely accessed while the ByteBuffer is used, and for freeing the memory afterwards.



Source: stackoverflow