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

Posted

技术标签:

【中文标题】如何使用 GraalVM 从 C++ 调用具有非原始类型作为参数的 Java 入口点方法【英文标题】:How to call a Java entrypoint method with non-primitive types as parameters from C++ using GraalVM 【发布时间】:2020-09-25 08:45:45 【问题描述】:

我正在尝试使用 graalvm 创建一个 java 代码的共享库(带有头文件和 lib 文件的 dll)。

将有一个带有 2 个字符串类型参数的 java 方法,我将从 c++ 中调用它。

我正在使用一个 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) 

        // logic goes here

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

但是当我尝试将代码构建到 dll 中时,我得到了这个错误

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

我进行了搜索,但没有找到适合此问题的解决方案。 谁能告诉我如何使用非原始数据类型从 c++ 调用 java 方法 任何形式的建议都会有很大的帮助,在此先感谢

【问题讨论】:

【参考方案1】:

Java 方法需要满足特定的先决条件才能在 GraalVM 中从 C 或 C++ 成功运行。 在设计将使用@CEntryPoint 注释的Java 入口点方法时,应考虑这些先决条件。提到以下前提条件in the CEntryPoint documentation。

    Java 入口点方法只允许包含原始 Java 类型、字值和枚举。此外,为了实际使用 Enumenum class 必须有 CEnum 注释。In the CEntryPoint documentation。 Java 入口点方法应该是静态的。 Java 入口点方法应该捕获所有异常,因为它不应该抛出任何异常。如果未捕获到异常,则打印该异常,然后终止该过程。然而,@CEntryPoint 文档明确提到要捕获 java 入口点方法中的所有异常。 IsolateThread 参数是必需的。更准确地说

执行上下文必须作为参数传递,并且可以是 特定于当前线程的 IsolateThread,或 Isolate 对于附加了当前线程的隔离。这些指针 可以通过 CurrentIsolate 的方法获得。当有更多 不是这些类型的一个参数,必须是其中一个参数 为 IsolateThread 使用 CEntryPoint.IsolateThreadContext 进行注释, 或 CEntryPoint.IsolateContext 用于隔离。

您问题中的示例会引发此错误,因为 myFunc 方法签名包含对象,例如 String 参数。即xy。根据上面的前提条件 1,这是不允许的。这就是错误描述试图表达的意思。

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

创建并返回 Java 字符串

下面有一个例子,可以在byte[]代表文本时使用。

//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;
         
        
  

使用并返回在 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) 

        //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;
  

使用Java NIO ByteBuffer

对于较大的字节数组,您可以检查CTypeConversion,它可以创建一个具有特定容量的Java NIO ByteBuffer,该容量指的是本机内存。请注意

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

【讨论】:

首先非常感谢,java和c++之间字符串类型的转换我懂了。在我的情况下, byte[] 将不包含文本,它将仅包含字节,它也适用吗? 不客气。是的,我会更新我的答案以包含一个示例。 我的字节数组会很大,我可以一次写入整个字节数组还是必须只在特定索引处写入? 使用 NIO 字节缓冲区本身就是一个主题,也许是一个不同的问题,因为 NIO 是独立于 GraalVM 的东西。你可以在一个新问题中尝试一些东西,如果可能的话我可以提供帮助。例如,请参见javarticles.com/2018/04/java-nio-bytebuffer.html 传递对象是不可能的,正如我的回答和 javadoc 中的前提条件 1 中所述,您可以获得的最接近的是使用独立格式,例如JSON 和根据需要编组/解组。 您好,您的回答再次奏效,我的编码类型有问题。实际上,我需要一个 base64 编码的字符串,但我不知道 CTypeConversion 使用什么编码,因为我无法使用 base64 解码器将它解码为字节数组,你能帮我吗?

以上是关于如何使用 GraalVM 从 C++ 调用具有非原始类型作为参数的 Java 入口点方法的主要内容,如果未能解决你的问题,请参考以下文章

屌炸天,Oracle 发布了一个全栈虚拟机 GraalVM,支持 Python!

使用graalvm.js调用promise

如何从 C# 调用具有 void* 回调和对象参数的 C++ Dll 中的函数

GraalVM LTS版正式发布!可用于生产环境,基于JDK 11

GraalVM,下一代JVM

下一代的多语言JVM:GraalVM