【问题标题】:Sending int[]s between Java and C在 Java 和 C 之间发送 int[]s
【发布时间】:2011-06-18 00:01:07
【问题描述】:

我在 Android 中有一些图像处理 Java 代码,它们作用于两个大型 int 数组。大多数时候,Java 足够快,但我需要通过 JNI 和 NDK 使用 C 来加速一些操作。

我知道可以将数据从 int 数组传递给 C 的唯一方法是使用 ByteBuffer.allocateDirect 创建一个新缓冲区,将数据复制到该缓冲区,然后让 C 代码对缓冲区进行操作。

但是,我看不到任何方法可以在 Java 中操作此缓冲区中的数据,就好像该缓冲区是 int[] 或 byte[]。例如,对 ByteBuffer.array() 的调用将在新创建的缓冲区上失败。有什么办法可以做到吗?

我的内存有限,想减少我需要的数组/缓冲区的数量。例如,如果我可以使用 IntBuffer.wrap(new int[...]) 创建缓冲区,然后直接在 Java 中操作支持缓冲区的数组,那就太好了,但我不能这样做,因为唯一似乎在这里为 JNI 工作的是 ByteBuffer.allocateDirect。

还有其他方法可以在 C 和 Java 之间来回发送数据吗?我可以在 C 端分配内存并让 Java 直接向那里发送数据吗?

编辑:比较缓冲区使用与 int[] 使用的基准:

int size = 1000;
IntBuffer allocateDirect = java.nio.ByteBuffer.allocateDirect(4 * size).asIntBuffer();
for (int i = 0; i < 100; ++i)
{
  for (int x = 0; x < size; ++x)
  {
    int v = allocateDirect.get(x);
    allocateDirect.put(x, v + 1);
  }
}

int[] intArray = new int[size];
for (int i = 0; i < 100; ++i)
{
  for (int x = 0; x < size; ++x)
  {
    int v = intArray[x];
    intArray[x] = v + 1;
  }
}

在 Droid 手机上,缓冲区版本大约需要 10 秒才能完成,而数组版本大约需要 0.01 秒。

【问题讨论】:

  • 等等,ByteBuffer.asIntBuffer 怎么了?你真的需要int[]吗?
  • ...此外,如果您愿意,可以在 IntBuffer 上调用 .array()。
  • .array() 为 Java 中直接分配的数组的 ByteBuffer 和 IntBuffer 引发 Unsupported 异常。我真的很想访问 int[] 这样我就可以在 Java 中做一些不太密集的图像处理工作,例如通过检查哪些像素高于/低于亮度阈值将图像转换为黑白。在 Android 中对每个像素使用 IntBuffer get/put 会太慢而且很笨拙。
  • @rebeccaT,将代码更改为使用 IntBuffer 而不是 int[] 并使用直接缓冲区
  • @rebeccaT,如果 put/get 这么慢,那就是大错特错了。这些方法应该被内联并编译为非常简单的 CPU 指令w/o 任何虚拟方法调用。是的,我建议您转储 int[] 并始终使用直接 IntBuffer

标签: java android c java-native-interface android-ndk


【解决方案1】:

来自http://java.sun.com/docs/books/jni/html/objtypes.html,使用JNI的Get/Release&lt;TYPE&gt;ArrayElements(...)

在这个例子中,我将传递一个数组(为了参数,它是int array = new int[10],然后用 0-9 填充它

 JNIEXPORT jint JNICALL 
 Java_IntArray_doStuffArray(JNIEnv *env, jobject obj, jintArray arr)
 {

     // initializations, declarations, etc
     jint *c_array;
     jint i = 0;

     // get a pointer to the array
     c_array = (*env)->GetIntArrayElements(env, arr, NULL);

     // do some exception checking
     if (c_array == NULL) {
         return -1; /* exception occurred */
     }

     // do stuff to the array
     for (i=0; i<10; i++) {
         c_array[i] = i;
     }

     // release the memory so java can have it again
     (*env)->ReleaseIntArrayElements(env, arr, c_array, 0);

     // return something, or not.. it's up to you
     return 0;
 }

学习第 3.3 节,特别是第 3.3.2 节——这将允许您获取指向 java 内存中数组的指针、修改它并释放它,实际上允许您在本机代码中修改数组。

我刚刚在自己的项目中使用过它(使用短数组),效果很好:)

【讨论】:

  • 感谢这段代码帮助我连接到 FIR 滤波器的 siglib 库方法
  • 获取计数 int arrayLength = (*env)->GetArrayLength(env, arr);
  • 当我尝试这个时,Java doStuffArray(arr) 在本机 Java_IntArray_doStuffArray 完成之前返回。在本机端完成之前,我需要做什么才能阻止 java 端?
【解决方案2】:

如果您使用直接分配的缓冲区,则可以使用 GetDirectBufferAddress 函数直接从 C 访问支持数组。这可以防止复制该区域的区域。

您可以像操作普通的 C 数组一样直接对返回的地址进行操作,它会直接修改 Java 直接分配的缓冲区。

然后,正如临时说明的那样,您可以使用 ByteBuffer.asIntBuffer() 和 family 以模拟各种 Java 原语的数组的方式访问缓冲区。

http://download.oracle.com/javase/1.4.2/docs/guide/jni/jni-14.html

【讨论】:

    【解决方案3】:

    您可以使用回调将数据从原生层发送到 Java。

    在 Java 层中:在我的本机类中,我有以下方法:

    //Native method
    public native String getStrData(int size);
    
    //Callback method
    public void addData(char[] native_data, int size) {
    
        ...
    
    }
    

    在原生层:在我的原生实现中:

    JNIEXPORT jstring JNICALL Java_com_pkg_NativeClass_getStrData 
       (JNIEnv *env, jobject obj, jint size) {
         ...
    
         jclass native_class;           /* Callback: native class */
         jmethodID native_method_id;    /* Callback: native method id */
         jcharArray row;                /* Callback: native data */
    
         ...
    
        /* Start Callback: Native to Java  */   
        native_class = (*env)->GetObjectClass(env, obj);
        native_method_id = (*env)->GetMethodID(env, native_class, "addData", "([CI)V");
        if (native_method_id == 0) {
            return (jstring)ERR_NATIVE_METHODID;
        }
        row = (jcharArray)(*env)->NewCharArray(env, size);
        /* jc has the data to be sent to Java */
        (*env)->SetCharArrayRegion(env, (jcharArray)row, (jsize)0, size, (jchar *)jc);
    
        (*env)->CallVoidMethod(env, obj, native_method_id, row, size);
        /* End Callback */
    
         ...
    }
    

    【讨论】:

    • 能否请您提一下,jc 是什么类型?是char* jc吗?
    • @dma_k :在我的例子中,'jc' 是一个已知大小的数组。
    • 能否给个提示,jc如何操作(例如设置字符数据)?我到处寻找使用SetCharArrayRegion() 的示例,但找不到有关如何创建/初始化jchars 数组的完整示例(例如,来自ASCII 1 字节字符串char*)。谢谢
    猜你喜欢
    • 2014-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多