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

发布于 2024-08-09 00:51:24 字数 475 浏览 5 评论 0原文

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

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

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

尝试了一段时间后,我自己想,这应该是不可能的,因为指针在应用程序结束后被删除(在本例中,当 C 函数终止时)。这是正确的吗?或者我只是 C 语言太差而无法看到解决方案?

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

感谢您的投入:)

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.

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?

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.

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?

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?

Thanks for your input :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

诠释孤独 2024-08-16 00:51:25

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

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

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.

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.

你曾走过我的故事 2024-08-16 00:51:25

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

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

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.

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.

趴在窗边数星星i 2024-08-16 00:51:25

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

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

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

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.

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.

囍笑 2024-08-16 00:51:25

最好按照 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

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

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

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.

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
凌乱心跳 2024-08-16 00:51:25

这是另一种方法,在 java-c C 胶水文件中使用全局静态变量,通过引用将该变量传递给 C,并返回已从 C 更改为 Java 的变量。不过需要两个 Java 函数调用。

下面是一个在 Kotlin 中使用 Android 开发的示例:

首先,C 代码(一个简单的函数,递增通过引用传递的变量):

// lib.c
void cfunction(int *number)
{
    *number += 1;
}

然后我们通常有另一个像胶水一样使用的 C 代码,来管理 Java idiocies特性

// glue.c
static int number;

void cfunction(int *);

JNIEXPORT void JNICALL
Java_com_example_someapp_MainActivity_inc(JNIEnv* env, jobject thiz, jint value)
{
    number = value;
    cfunction(&number);
}

JNIEXPORT jint JNICALL
Java_com_example_someapp_MainActivity_getnumber(JNIEnv* env, jobject thiz)
{
    return number;
}

它使用全局变量number(由于static关键字,只能在文件glue.c内访问),并且可以使用getter函数getnumber<在外部访问/代码>。所以基本上我们需要一个附加函数和一个静态全局变量来调用需要指针并更改其引用的 C 库。

CMakeLists.txt 看起来像:

cmake_minimum_required(VERSION 3.18.1)

project("someapp")

add_library(somelib SHARED
            glue.c
            lib.c)

最后 Android (Kotlin) 部分:

package com.example.someapp

// imports

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // How to use a C call by ref in Kotlin
        inc(4)
        val number = getnumber()
        Toast.makeText(this, number.toString(), Toast.LENGTH_LONG).show()
    }

    external fun inc(number: Int)

    external fun getnumber(): Int

    companion object {
        init {
            System.loadLibrary("somelib")
        }
    }
}

它应该显示 5

Here is another approach, using a global static variable in the java-c C glue file, passing the variable by reference to C, and returning the variable that have been changed from C to Java. Will need two Java function calls though.

Here is an example using Android development in Kotlin:

First, C code (a simple function that increments a variable passed by reference):

// lib.c
void cfunction(int *number)
{
    *number += 1;
}

Then we usually have another C code used like a glue, to manage Java idiocies features

// glue.c
static int number;

void cfunction(int *);

JNIEXPORT void JNICALL
Java_com_example_someapp_MainActivity_inc(JNIEnv* env, jobject thiz, jint value)
{
    number = value;
    cfunction(&number);
}

JNIEXPORT jint JNICALL
Java_com_example_someapp_MainActivity_getnumber(JNIEnv* env, jobject thiz)
{
    return number;
}

It uses a global variable number (accessible only inside the file glue.c thanks to the static keyword) and can be accessed externally using the getter function getnumber. So basically we needed an additional function and a static global variable to call a C lib that expects a pointer and changes its reference.

A CMakeLists.txt would looks like:

cmake_minimum_required(VERSION 3.18.1)

project("someapp")

add_library(somelib SHARED
            glue.c
            lib.c)

Finally the Android (Kotlin) part:

package com.example.someapp

// imports

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // How to use a C call by ref in Kotlin
        inc(4)
        val number = getnumber()
        Toast.makeText(this, number.toString(), Toast.LENGTH_LONG).show()
    }

    external fun inc(number: Int)

    external fun getnumber(): Int

    companion object {
        init {
            System.loadLibrary("somelib")
        }
    }
}

It should display 5.

人疚 2024-08-16 00:51:24

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

因此,不要使用伪指针类型,例如 jlong​​(如果您想在 32 位架构上节省一些空间,则使用 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);
}

这可以让您免于编写大量样板代码!然而,人们应该注意字节顺序,如此处所述。

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

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

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:

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

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

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

很酷不放纵 2024-08-16 00:51:24

我使用了以下方法:

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

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

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

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 long as a parameter, and in C cast it to a pointer to your struct.

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

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.

顾冷 2024-08-16 00:51:24

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

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

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

这些遵循相同的规则,唯一的问题是本地引用可以通过 PopLocalFrame< 显式删除“批量” /code>,或隐式地,当本机方法退出时。

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;
    ...
}

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 free on a stack variable and you can't call delete on malloced memory.

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

  • NewObject/DeleteLocalRef
  • NewGlobalRef/DeleteGlobalRef
  • 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.

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.

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

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;
    ...
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文