通过JNI传递C和Java之间的指针

Vol*_*ker 68 java java-native-interface pointers cuda

目前,我正在尝试创建一个使用CUDA功能的Java应用程序.CUDA和Java之间的连接工作正常,但我有另一个问题,想问一下,如果我的想法是正确的.

当我从Java调用本机函数时,我将一些数据传递给它,函数计算一些东西并返回结果.是否有可能让第一个函数返回一个引用(指针)到这个结果,我可以传递给JNI并调用另一个用结果进行进一步计算的函数?

我的想法是通过将数据保留在GPU内存中并只是传递对它的引用来减少从GPU复制数据所带来的开销,以便其他函数可以使用它.

经过一段时间的尝试,我想,这应该是不可能的,因为指针在应用程序结束后被删除(在这种情况下,当C函数终止时).它是否正确?或者我只是在C中看到解决方案?

编辑:嗯,稍微扩展问题(或使其更清楚):当函数结束时,JNI本机函数分配的内存是否已释放?或者我可以访问它,直到JNI应用程序结束或我手动释放它?

感谢您的输入 :)

Den*_*kiy 44

我使用了以下方法:

在您的JNI代码中,创建一个结构,该结构将保存对您需要的对象的引用.首次创建此结构时,将其指向java的指针返回long.然后,从java中你只需要调用任何方法long作为参数,然后在C中将它转换为指向struct的指针.

结构将在堆中,因此不会在不同的JNI调用之间清除它.

编辑:我不认为你可以使用长ptr = (long)&address;因为地址是一个静态变量.以Gunslinger47建议的方式使用它,即创建类或结构的新实例(使用new或malloc)并传递其指针.

  • 在x32和x64平台上使用long?我很高兴我们不会很快就会转向128位机器...... (9认同)
  • `MyClass*pObject = ...; long lp =(long)pObject; pObject =(*pObject)lp;` (8认同)
  • 我分享@dhardy的担忧.这个解决方案不可移植 - 不能保证`long`足够大以容纳指针. (3认同)
  • 我也分享@dhardy 的担忧。一种方法是传递一个 java 字节数组,其中数组中的每个字节都是指针值的 8 位(并根据系统使数组的长度不同)。但是,这意味着您还必须将指针的大小传递给 JNI 函数。 (2认同)
  • @AlexanderNajafi:在这种情况下,我认为最好为您的数据设置一个已知大小的密钥。保留从键到指针的哈希映射,并将键从本机代码返回到 java。 (2认同)

Dan*_*dei 15

在C++中,您可以使用任何想要分配/释放内存的机制:堆栈,malloc/free,new/delete或任何其他自定义实现.唯一的要求是,如果你分配的内存块有一个机制,你有相同的机制来释放它,所以你不能叫free一个堆栈变量,你不能叫deletemalloc版内存.

JNI有自己的分配/释放JVM内存的机制:

  • NewObject的/ DeleteLocalRef
  • NewGlobalRef/DeleteGlobalRef
  • NewWeakGlobalRef/DeleteWeakGlobalRef

它们遵循相同的规则,唯一的问题是PopLocalFrame当本机方法退出时,可以显式地,使用或隐式地"集体"删除本地引用.

JNI不知道你是如何分配内存的,因此当你的函数退出时它无法释放它.堆栈变量显然会被破坏,因为你仍在编写C++,但你的GPU内存仍然有效.

那么唯一的问题是如何在后续调用中访问内存,然后你可以使用Gunslinger47的建议:

JNIEXPORT jlong JNICALL Java_MyJavaClass_Function1() {
    MyClass* pObject = new MyClass(...);
    return (long)pObject;
}

JNIEXPORT void JNICALL Java_MyJavaClass_Function2(jlong lp) {
    MyClass* pObject = (MyClass*)lp;
    ...
}
Run Code Online (Sandbox Code Playgroud)


Gun*_*r47 10

Java不知道如何处理指针,但它应该能够存储来自本机函数返回值的指针,然后将其移交给另一个本机函数以供其处理.C指针只不过是核心的数值.

另一个contibutor必须告诉你是否在JNI调用之间清除指向图形存储器以及是否有任何解决方法.

  • 关于你的第二段:唯一需要注意的是确保分配的任何内存也被解除分配.建议的方法是在持有引用的对象上使用某种close/dispose()方法.终结者很有吸引力,但它们有一些缺点,如果可能的话,值得避免它们. (2认同)
  • @Volker:这取决于你如何分配它.您必须自行释放/释放内存的规则的唯一例外通常是在堆栈上分配了某些内容.如果是,则在函数退出时向后移动堆栈指针时将"释放"它.因此,如果您使用某种内存分配功能("alloca"除外)分配了内存,则必须释放它.它与java完全没有关系.一旦你进行了JNI跳转,规则就是来自C世界,除非你使用java对象. (2认同)
  • “C 指针只不过是核心的‘long’值。” - C 标准中没有任何内容说明这一点,这是调用未定义的行为。 (2认同)

noa*_*mtm 7

我知道这个问题已经正式回答了,但是我想添加我的解决方案:不是试图传递指针,而是将指针放在Java数组中(索引0处)并将其传递给JNI.JNI代码可以使用GetIntArrayRegion/ 来获取和设置数组元素SetIntArrayRegion.

在我的代码中,我需要本机层来管理文件描述符(一个打开的套接字).Java类包含一个int[1]数组并将其传递给本机函数.本机函数可以对它做任何事情(获取/设置)并将结果放回到数组中.


Mar*_*ise 6

如果要在本机函数内动态(在堆上)分配内存,则不会删除它.换句话说,您可以使用指针,静态变量等在不同的本机函数调用之间保持状态.

可以用不同的方式来考虑:你可以安全地保留一个函数调用,从另一个C++程序调用吗?这同样适用于此.退出函数时,该函数调用的堆栈中的任何内容都将被销毁; 但除非您明确删除它,否则将保留堆上的任何内容.

简短的回答:只要您没有释放返回调用函数的结果,它将在以后重新进入时保持有效.只要确保在完成后清理它.


mal*_*lat 6

虽然@ denis-tulskiy接受的答案确实有道理,但我个人也遵循了这里的建议.

因此,不要使用伪指针类型jlong(或者jint如果你想在32位拱上保存一些空间),而是使用a ByteBuffer.例如:

MyNativeStruct* data; // Initialized elsewhere.
jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct));
Run Code Online (Sandbox Code Playgroud)

您可以在以后重复使用:

jobject bb; // Initialized elsewhere.
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);
Run Code Online (Sandbox Code Playgroud)

对于非常简单的情况,此解决方案非常易于使用.假设你有:

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;
Run Code Online (Sandbox Code Playgroud)

在Java方面,您只需要:

public int getExampleInt() {
  return bb.getInt(0);
}

public short getExampleShort() {
  return bb.getShort(4);
}
Run Code Online (Sandbox Code Playgroud)

这样可以避免编写大量的样板代码!然而,人们应注意字节顺序为解释在这里.