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
Advertisement
Answer
There are specific preconditions that a java method needs to fulfill in order to run successfully from C or C++ in GraalVM.
These preconditions should 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.
- 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
theenum class
must have aCEnum
annotation.In the CEntryPoint documentation. - The Java entry point method should be static.
- 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. - 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.