如何使用GraalVM从C++调用带有非原始类型参数的Java入口方法

huangapple go评论77阅读模式
英文:

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

问题

我正尝试使用GraalVM创建一个包含Java代码的共享库(带有头文件和库文件的dll)。我将从C++中调用一个带有两个String类型参数的Java方法。

我正在使用一个Maven项目,但无法将我的Java代码创建为dll。我正在使用GraalVM将我的Java代码转换为dll。

我的Java代码如下:

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) {

        // 逻辑在这里

        byte[] arr = "字节数组将包含实际字节".getBytes();
        
        return arr;
    }
}

但是当我尝试将代码构建为dll时,出现了以下错误:

错误:入口点方法参数类型仅限于原始类型、字类型和枚举(@CEnum):demo.MyClass.myFunc(IsolateThread, String, String)

我已经搜索过,但没有找到适合解决此问题的方法。这里是否有人可以告诉我如何使用非原始数据类型从C++中调用Java方法?任何类型的建议都将是很大的帮助,提前谢谢。

英文:

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

答案1

得分: 3

有特定的前提条件,一个 Java 方法在 GraalVM 中要想从 C 或 C++ 成功运行,必须满足这些前提条件。在设计将要用 @CEntryPoint 注解标记的 Java 入口方法时,应该考虑到这些前提条件。以下前提条件在 CEntryPoint 文档 中有提到。

  1. Java 入口方法只允许包含原始的 Java 类型、字值和枚举。要使用 Enumenum class 必须具有 CEnum 注解。在 CEntryPoint 文档 中有提到。
  2. Java 入口方法应该是静态的。
  3. Java 入口方法应该捕获所有异常,因为它不应该抛出任何异常。如果异常未被捕获,它会被打印,然后进程会终止。然而,@CEntryPoint 文档明确提到在 Java 入口方法内捕获所有异常。
  4. 需要一个 IsolateThread 参数。更准确地说,

必须传递执行上下文作为参数,可以是特定于当前线程的 IsolateThread,也可以是附加到当前线程的隔离Isolate的 Isolate。可以通过 CurrentIsolate 的方法获取这些指针。当存在多个这些类型的参数时,这些参数中的一个必须用 CEntryPoint.IsolateThreadContext 来注解,用于 IsolateThread,或者用 CEntryPoint.IsolateContext 来注解用于 Isolate。

你问题中的示例抛出了这个错误,因为 myFunc 方法的签名包含了对象,比如 String 参数,例如 xy。根据上述前提条件 1,这是不允许的。这就是错误描述试图表达的内容。

解决方案是使用提供的功能在 Java 类型和 C 类型之间进行转换。在这种情况下,为了在 CJava 之间传递文本,我们可以使用 CCharPointer。由于在 CJava 中对文本进行了不同的建模,Java String 必须转换为 C *char,反之亦然。

创建和返回 Java String

下面是一个示例,用于将 byte[] 表示的文本创建和返回。

//需要的导入语句
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) {
    //将 C *char 转换为 Java String
    final String xString = CTypeConversion.toJavaString(x);
    final String yString = CTypeConversion.toJavaString(y);

    //逻辑在此处

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

使用并返回在 C 中分配的数组

您还可以遵循 C 风格,将一个在 C 中的数组作为参数传递,然后在 Java 中使用该数组来写入结果字节值。方法 CCharPointer.write(int,byte) 可以将 Java byte 值写入 *charchar[] 中的特定数组索引。如果需要,还可以返回字节数组。

@CEntryPoint(name = "myFunc2")
public static CCharPointer myFunc2(IsolateThread thread, CCharPointer x, CCharPointer y,
                                  CCharPointer resultArray, int resultArrayLength) {
    //将 C *char 转换为 Java String
    final String xString = CTypeConversion.toJavaString(x);
    final String yString = CTypeConversion.toJavaString(y);

    //逻辑在此处

    //填充结果数组
    final byte sampleByteValue = 7;
    for (int index = 0; index < resultArrayLength; index++) {
        resultArray.write(index, sampleByteValue);
    }
    return resultArray;
}

使用 Java NIO ByteBuffer

对于较大的字节数组,您可以使用 CTypeConversion 创建一个具有特定容量的 Java NIO ByteBuffer,它引用本地内存。注意,

调用者有责任确保在使用 ByteBuffer 时可以安全访问内存,并在使用后释放内存。

英文:

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.

  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 = &quot;myFunc&quot;)
  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(&quot;Hello from Java&quot;)){
        
                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 = &quot;myFunc2&quot;)
  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&lt;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.

huangapple
  • 本文由 发表于 2020年9月25日 16:45:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/64060787.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定