Passing pointers between C and Java through JNI
目前,我正在尝试创建一个使用CUDA功能的Java应用程序。CUDA和Java之间的连接工作很好,但是我有另一个问题,我想问一下,如果我的想法是正确的。
当我从Java调用一个本地函数时,我将一些数据传递给它,函数计算某个结果并返回结果。有没有可能,让第一个函数返回一个对这个结果的引用(指针),我可以把它传递给JNI,并调用另一个函数,用这个结果做进一步的计算?
我的想法是通过将数据保留在GPU内存中,并只传递一个对它的引用,以便其他函数可以使用它,从而减少在GPU之间复制数据所产生的开销。
在尝试了一段时间后,我自己想,这不可能,因为指针在应用程序结束后被删除(在本例中,当C函数终止时)。这是正确的吗?或者我只是为了看问题的解决方案而在C中做得太差了?
编辑:好吧,将问题稍微展开一点(或者更清楚地说):当函数结束时,JNI本机函数分配的内存是否被释放?或者,在JNI应用程序结束或手动释放它之前,我是否仍然可以访问它?
感谢您的输入:)
我采用了以下方法:
在JNI代码中,创建一个结构来保存对所需对象的引用。当您首先创建此结构时,将其指针返回给Java作为EDCOX1 0。然后,从Java中调用这个EDCOX1×0作为参数的任何方法,并在C中将其转换为指向结构的指针。
结构将在堆中,因此不会在不同的JNI调用之间清除。
编辑:我认为你不能使用long ptr=
在C++中,您可以使用任何要分配/释放内存的机制:堆栈、MalC/C、Field、NeX/Delphi或任何其他自定义实现。唯一的要求是,如果使用一个机制分配一个内存块,则必须使用相同的机制释放它,这样就不能在堆栈变量上调用
JNI有自己的机制来分配/释放JVM内存:
- 新建对象/删除本地引用
- 新全局参考/删除全局参考
- newweakglobalref/删除weakglobalref
它们遵循相同的规则,唯一的缺点是本地引用可以"集中"删除,可以使用
JNI不知道您是如何分配内存的,所以当您的函数退出时,它无法释放内存。堆栈变量显然会被破坏,因为你仍然在写C++,但是你的GPU内存将保持有效。
唯一的问题是如何在随后的调用中访问内存,然后您可以使用gunslinger47的建议:
1 2 3 4 5 6 7 8 9 | 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; ... } |
Java不知道如何处理指针,但是它应该能够从一个原生函数的返回值存储指针,然后将指针交给另一个原生函数来处理。C指针只不过是核心的数字值。
另一个Continibutor必须告诉您,在JNI调用之间是否清除指向图形内存,以及是否有任何解决方法。
我知道这个问题已经得到了正式答复,但我想补充我的解决方案:不要尝试传递指针,而是将指针放在Java数组(索引0)中,并将其传递给JNI。JNI代码可以使用
在我的代码中,我需要本机层来管理一个文件描述符(一个打开的套接字)。Java类保存一个EDCOX1×9的数组,并将其传递给本机函数。本机函数可以对其执行任何操作(get/set),并将结果放回数组中。
虽然@denis tulskiy接受的答案确实有道理,但我还是个人遵循了这里的建议。
因此,不要使用伪指针类型,如
1 2 | MyNativeStruct* data; // Initialized elsewhere. jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct)); |
以后您可以使用它:
1 2 | jobject bb; // Initialized elsewhere. MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb); |
对于非常简单的情况,这个解决方案非常容易使用。假设你有:
1 2 3 4 | struct { int exampleInt; short exampleShort; } MyNativeStruct; |
在Java方面,您只需要这样做:
1 2 3 4 5 6 7 | public int getExampleInt() { return bb.getInt(0); } public short getExampleShort() { return bb.getShort(4); } |
这样可以避免编写大量样板代码!但是,我们应该注意这里解释的字节顺序。
如果在本机函数内部动态地(在堆上)分配内存,则不会删除内存。换句话说,您可以使用指针、静态变量等在不同的本地函数调用之间保留状态。
想一想另一种方式:你能做什么安全地保持在一个函数调用,从另一个C++程序调用?这里同样适用。当函数退出时,该函数调用的堆栈上的任何内容都将被销毁;但除非显式删除,否则将保留该堆栈上的任何内容。
简短回答:只要不取消分配返回到调用函数的结果,它将在稍后重新进入时保持有效。完成后一定要清理干净。
最好做到这一点,这正是.allocateMemory的不安全之处。
创建对象,然后将其键入(uintptr_t),它是一个32/64位无符号整数。
1 2 3 | return (uintptr_t) malloc(50); void * f = (uintptr_t) jlong; |
这是唯一正确的方法。
这是健全性检查不安全。allocatememory检查不安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | inline jlong addr_to_java(void* p) { assert(p == (void*)(uintptr_t)p,"must not be odd high bits"); return (uintptr_t)p; } UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size)) UnsafeWrapper("Unsafe_AllocateMemory"); size_t sz = (size_t)size; if (sz != (julong)size || size < 0) { THROW_0(vmSymbols::java_lang_IllegalArgumentException()); } if (sz == 0) { return 0; } sz = round_to(sz, HeapWordSize); void* x = os::malloc(sz, mtInternal); if (x == NULL) { THROW_0(vmSymbols::java_lang_OutOfMemoryError()); } //Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize); return addr_to_java(x); UNSAFE_END |