如何使用 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 类型、字值和枚举。此外,为了实际使用
Enum
,enum class
必须有 CEnum
注释。In the CEntryPoint documentation。
Java 入口点方法应该是静态的。
Java 入口点方法应该捕获所有异常,因为它不应该抛出任何异常。如果未捕获到异常,则打印该异常,然后终止该过程。然而,@CEntryPoint
文档明确提到要捕获 java 入口点方法中的所有异常。
IsolateThread
参数是必需的。更准确地说
执行上下文必须作为参数传递,并且可以是 特定于当前线程的 IsolateThread,或 Isolate 对于附加了当前线程的隔离。这些指针 可以通过 CurrentIsolate 的方法获得。当有更多 不是这些类型的一个参数,必须是其中一个参数 为 IsolateThread 使用 CEntryPoint.IsolateThreadContext 进行注释, 或 CEntryPoint.IsolateContext 用于隔离。
您问题中的示例会引发此错误,因为 myFunc
方法签名包含对象,例如 String
参数。即x
和y
。根据上面的前提条件 1,这是不允许的。这就是错误描述试图表达的意思。
解决方案是使用提供的功能在 Java 类型和 C 类型之间进行转换。在这种情况下,为了在C
和Java
之间传递文本,我们可以使用CCharPointer
。由于 C
和 Java
中的文本建模方式不同,因此必须将 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
值写入*char
或char[]
中的特定数组索引。如果需要,也可以返回字节数组。
@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!
如何从 C# 调用具有 void* 回调和对象参数的 C++ Dll 中的函数