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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1632367/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 18:01:14  来源:igfitidea点击:

Passing pointers between C and Java through JNI

javapointersjava-native-interfacecuda

提问by Volker

At the moment, i'm trying to create a Java-application which uses CUDA-functionality. The connection between CUDA and Java works fine, but i've got another problem and wanted to ask, if my thoughts about it are correct.

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

When i call a native function from Java, i pass some data to it, the functions calculates something and returns a result. Is it possible, to let the first function return a reference (pointer) to this result which i can pass to JNI and call another function that does further calculations with the result?

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

My idea was to reduce the overhead that comes from copying data to and from the GPU by leaving the data in the GPU memory and just passing a reference to it so other functions can use it.

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

After trying some time, i thought for myself, this shouldn't be possible, because pointers get deleted after the application ends (in this case, when the C-function terminates). Is this correct? Or am i just to bad in C to see the solution?

尝试了一段时间后,我自己想,这应该是不可能的,因为在应用程序结束后指针会被删除(在这种情况下,当 C 函数终止时)。这样对吗?或者我只是在 C 中很糟糕才能看到解决方案?

Edit: Well, to expand the question a little bit (or make it more clearly): Is memory allocated by JNI native functions deallocated when the function ends? Or may i still access it until either the JNI application ends or when i free it manually?

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

Thanks for your input :)

感谢您的输入 :)

采纳答案by Denis Tulskiy

I used the following approach:

我使用了以下方法:

in your JNI code, create a struct that would hold references to objects you need. When you first create this struct, return its pointer to java as a long. Then, from java you just call any method with this longas a parameter, and in C cast it to a pointer to your struct.

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

The structure will be in the heap, so it will not be cleared between different JNI calls.

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

EDIT: I don't think you can use long ptr = (long)&address;since address is a static variable. Use it the way Gunslinger47 suggested, i.e. create new instance of class or a struct (using new or malloc) and pass its pointer.

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

回答by Gunslinger47

Java wouldn't know what to do with a pointer, but it should be able to store a pointer from a native function's return value then hand it off to another native function for it to deal with. C pointers are nothing more than numeric values at the core.

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

Another contibutor would have to tell you whether or not the pointed to graphics memory would be cleared between JNI invocations and if there would be any work-arounds.

另一个贡献者必须告诉您是否会在 JNI 调用之间清除指向的图形内存,以及是否有任何变通方法。

回答by Marc Paradise

If you are allocating memory dynamically (on the heap) inside of the native function, it is not deleted. In other words, you are able to retain state between different calls into native functions, using pointers, static vars, etc.

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

Think of it a different way: what could you do safely keep in an function call, called from another C++ program? The same things apply here. When a function is exited, anything on the stack for that function call is destroyed; but anything on the heap is retained unless you explicitly delete it.

换一种方式思考:在从另一个 C++ 程序调用的函数调用中,您可以安全地做什么?同样的事情在这里也适用。当一个函数退出时,该函数调用堆栈上的任何东西都会被销毁;但是堆上的任何内容都会保留,除非您明确删除它。

Short answer: as long as you don't deallocate the result you're returning to the calling function, it will remain valid for re-entrance later. Just make sure to clean it up when you're done.

简短回答:只要您不释放返回给调用函数的结果,它就会在以后重新进入时保持有效。只要确保完成后将其清理干净即可。

回答by Dan Berindei

In C++ you can use any mechanism you want to allocate/free memory: the stack, malloc/free, new/delete or any other custom implementation. The only requirement is that if you allocated a block of memory with one mechanism, you have to free it with the same mechanism, so you can't call freeon a stack variable and you can't call deleteon malloced memory.

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

JNI has its own mechanisms for allocating/freeing JVM memory:

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

  • NewObject/DeleteLocalRef
  • NewGlobalRef/DeleteGlobalRef
  • NewWeakGlobalRef/DeleteWeakGlobalRef
  • 新建对象/删除本地引用
  • 新建全局引用/删除全局引用
  • NewWeakGlobalRef/DeleteWeakGlobalRef

These follow the same rule, the only catch is that local refs can be deleted "en masse" either explicitly, with PopLocalFrame, or implicitly, when the native method exits.

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

JNI doesn't know how you allocated your memory, so it can't free it when your function exits. Stack variables will obviously be destroyed because you're still writing C++, but your GPU memory will remain valid.

JNI 不知道您如何分配内存,因此当您的函数退出时它无法释放它。堆栈变量显然会被销毁,因为您仍在编写 C++,但您的 GPU 内存将保持有效。

The only problem then is how to access the memory on subsequent invocations, and then you can use Gunslinger47's suggestion:

唯一的问题是如何在后续调用中访问内存,然后您可以使用 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;
    ...
}

回答by noamtm

I know this question was already officially answered, but I'd like to add my solution: Instead of trying to pass a pointer, put the pointer in a Java array (at index 0) and pass that to JNI. JNI code can get and set the array element using GetIntArrayRegion/SetIntArrayRegion.

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

In my code, I need the native layer to manage a file descriptor (an open socket). The Java class holds a int[1]array and passes it to the native function. The native function can do whatever with it (get/set) and put back the result in the array.

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

回答by malat

While the accepted answer from @denis-tulskiy does make sense, I've personnally followed suggestions from here.

虽然@denis-tulskiy 接受的答案确实有道理,但我个人遵循了此处的建议。

So instead of using a pseudo-pointer type such as jlong(or jintif you want to save some space on 32bits arch), use instead a ByteBuffer. For example:

因此,不要使用诸如jlong(或者jint如果您想在 32 位架构上节省一些空间)之类的伪指针类型,而是使用ByteBuffer. 例如:

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

which you can later re-use with:

您可以稍后重新使用:

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

For very simple cases, this solution is very easy to use. Suppose you have:

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

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;

On the Java side, you simply need to do:

在 Java 方面,您只需要执行以下操作:

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

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

Which saves you from writing lotsof boilerplate code ! One should however pay attention to byte ordering as explained here.

这使您免于编写大量样板代码!但是,应注意此处解释的字节顺序。

回答by bond

Its best to do this exactly how Unsafe.allocateMemory does.

最好按照 Unsafe.allocateMemory 的方式执行此操作。

Create your object then type it to (uintptr_t) which is a 32/64 bit unsigned integer.

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

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;

This is the only correct way to do it.

这是唯一正确的方法。

Here is the sanity checking Unsafe.allocateMemory does.

这是 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