Passing pointers between C and Java via JNI

I'm currently trying to create a Java application that uses CUDA functionality. The connection between CUDA and Java is working fine, but I have one more problem and I want to ask if my thoughts on this are correct.

When I call my own function from Java, I pass some data to it, the functions calculate something and return the result. Is it possible for the first function to return a link (pointer) to this result, which I can pass to the JNI and call another function that performs further calculations with the result?

My idea was to reduce the overhead associated with copying data to and from the GPU, leaving the data in the memory of the GPU and simply passing a link to it so that other functions could use it.

After some time, I thought to myself, this should not be possible, because pointers are deleted after the application terminates (in this case, when the C function ends). It's right? Or am I just bad at C to see a solution?

Edit: Well, to broaden the question a bit (or to make it more understandable): Is the memory allocated by the JNI built-in functions freed up when the function terminates? Or can I access it until the JNI application terminates or I manually release it?

Thanks for your input :)

+58
java pointers cuda jni
Oct 27 '09 at 17:22
source share
7 answers

I used the following approach:

in your JNI code, create a structure that will contain references to the objects you need. When you first create this structure, return its pointer to java as long . Then from java, you simply call some method with this long as a parameter, and in C its pointer to your structure.

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

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

+41
Oct 27 '09 at 23:30
source share

In C ++, you can use any mechanism that you want to allocate / free memory: stack, malloc / free, new / delete, or any other custom implementation. The only requirement is that if you allocate a memory block with one mechanism, you must free it using the same mechanism, so you cannot call free in the stack variable, and you cannot call delete on malloc ed.

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

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

They follow the same rule, the only catch is that local links can be removed "en masse" either explicitly, or using a PopLocalFrame , or implicitly when the native method completes.

JNI does not know how you allocated your memory, so it cannot free it when your function exits. Stack variables will obviously be destroyed because you are still writing C ++, but your GPU memory will remain valid.

The only problem then is how to access memory on subsequent calls, and then you can use the Gunslinger47 clause:

 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; ... } 
+13
Mar 19 '10 at 12:42
source share

Java will not know what to do with the pointer, but it should be able to store the pointer from the value of the returned function, and then pass it to another function that it handles. C is nothing more than numerical values ​​in the kernel.

Another proponent will have to tell you if there will be a cleared graphic memory link between JNI calls and if there will be any actions.

+10
Oct 27 '09 at 18:00
source share

If you allocate memory dynamically (on the heap) inside the built-in function, it is not deleted. In other words, you can save state between different calls to native functions using pointers, static vars, etc.

Think of it differently: what can you do safely in a function call called from another C ++ program? The same thing applies to us. When the function is completed, anything on the stack for this function call is destroyed; but anything on the heap is saved unless you explicitly delete it.

The short answer is, until you release the result that you return to the calling function, it will remain valid for re-entry later. Just be sure to clean it when done.

+6
Oct 27 '09 at 22:19
source share

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

In my code, I need my own layer to manage the file descriptor (open socket). The Java class contains an array int[1] and passes it to the native function. A native function can do everything with it (get / set) and return the result to an array.

+6
Nov 28 '11 at 8:18
source share

Although the accepted answer from @ denis-tulskiy makes sense, I personally followed the suggestions here .

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

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

which can later be reused:

 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 just need to do:

 public int getExampleInt() { return bb.getInt(0); } public short getExampleShort() { return bb.getShort(4); } 

This saves you from writing a lot of templates! However, attention should be paid to the byte order as described here .

+6
Apr 17 '15 at 9:38
source share

This is best done as Unsafe.allocateMemory does.

Create your object, then enter it in (uintptr_t), which is an unsigned 32/64 bit integer.

 return (uintptr_t) malloc(50); void * f = (uintptr_t) jlong; 

This is the only right way to do this.

Here is the Unsafe.allocateMemory health check.

 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 
0
Aug 19 '15 at 13:30
source share



All Articles