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

时间:2009-10-27 17:22:53

标签: java pointers java-native-interface cuda

目前,我正在尝试创建一个使用CUDA功能的Java应用程序。 CUDA和Java之间的联系很好,但我有另一个问题,想问一下,如果我的想法是正确的。

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

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

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

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

感谢您的意见:)

7 个答案:

答案 0 :(得分:44)

我使用了以下方法:

在您的JNI代码中

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

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

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

答案 1 :(得分:15)

在C ++中,您可以使用任何想要分配/释放内存的机制:堆栈,malloc / free,new / delete或任何其他自定义实现。唯一的要求是,如果您使用一种机制分配了一块内存,则必须使用相同的机制释放它,因此您无法在堆栈变量上调用free而无法调用{{1在delete ed memory上。

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

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

这些遵循相同的规则,唯一的问题是本地引用可以使用malloc显式地“集体”删除,或者在本机方法退出时隐式删除​​。

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

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

PopLocalFrame

答案 2 :(得分:10)

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

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

答案 3 :(得分:7)

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

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

答案 4 :(得分:6)

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

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

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

答案 5 :(得分:6)

虽然@ denis-tulskiy接受的答案确实有意义,但我个人也遵循here的建议。

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

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

您可以在以后重复使用:

jobject bb; // Initialized elsewhere.
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);

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

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;

在Java方面,你只需要这样做:

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

public short getExampleShort() {
  return bb.getShort(4);
}

这使您免于编写样板代码的批次!然而,应该注意字节排序,如here所述。

答案 6 :(得分:0)

最好这样做就像Unsafe.allocateMemory一样。

创建对象,然后将其键入(uintptr_t),这是一个32/64位无符号整数。

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;

这是唯一正确的方法。

这是检查Unsafe.allocateMemory的完整性。

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