【问题标题】:Access C++ memory from Java App从 Java App 访问 C++ 内存
【发布时间】:2012-12-17 21:43:00
【问题描述】:

我有调用一些 java 功能的 C++ 应用程序。我正在使用 JNI。一切正常,但仍然存在一个小问题。在 C++ 应用程序中,我有一些巨大的数组。 Java App 也需要这些数据并返回一大堆结果。目前我只是复制数据(你可以检查下面的代码)。如果我通过指针直接从 Java App 访问这些数组会更好。是否可以?我假设不是(因为 JVM),但在这种情况下,我必须向我的老板详细解释 :) 那么,是或否,以及如何或为什么?

jdoubleArray in2 = env->NewDoubleArray(1000);
jdouble *elems2 = (jdouble*)calloc(1000, sizeof(jdouble));
for(int i = 0; i < 1000; i++)
{
    elems2[i] = (jdouble)inputArray[i];
}
env->SetDoubleArrayRegion(in2, 0, 1000, elems2);
if(midCD != NULL)
{
    jdouble res1 = env->CallStaticDoubleMethod(cls, midCD, in2);
    double res1c = res1;
    printf("C++.Res1: %f\n", res1c);
}

【问题讨论】:

  • 我将创建一个 Java 'array' 类,它由其在 C++ 中的对等类实现,所有操作都将使用本机例程。这样,数据本身将完全在 C++ 世界中,无需任何复制。
  • 对不起,我不太明白,你到底是什么意思。 C++ 中的对等类应该是什么样子?以及Java数组类如何访问它?

标签: java memory-management java-native-interface


【解决方案1】:

Java 1.4 中为此引入了 Direct ByteBuffers。除了 Java 方面的新类之外,还有 corresponding JNI functions。您可以将原生数组包装在 ByteBuffer(以及相应的 IntBuffer 等视图)中,无需复制即可进行读写。

【讨论】:

  • 谢谢,我试试。但是如果我在 C++ 中有一个数组,那么我必须在 JVM 端创建这个缓冲区并将数据复制到其中,对吗?在这种情况下,它与我的代码中的非常相似。
  • +1 这是个好主意。如果inputArray 已经是jdouble C 数组,那么在JNI 端,调用NewDirectByteBuffer() 传递inputArraylength * sizeof (jdouble) 作为容量。在 Java 大小上,通过ByteBuffer#asDoubleBuffer() 方法创建ByteBufferDoubleBuffer 视图。
  • 如果你看一下 java.nio.Buffer 源代码,你会发现它实际上是按照我们上面讨论的方式实现的,通过将本地指针嵌入到 Java 类(专有的 sun.misc.不安全包用于操作本机指针)。
【解决方案2】:

您能做的最好的事情就是使用Get<PrimitiveType>ArrayElements routines。希望虚拟机支持pinning

jdoubleArray in2 = env->NewDoubleArray(1000);
jboolean isCopy;
jdouble *elems2 = env->GetDoubleArrayElements(in2, &isCopy);
for(int i = 0; i < 1000; i++)
{
    elems2[i] = (jdouble)inputArray[i];
}
env->ReleaseDoubleArrayElements(in2, elems2, 0);
elems2 = NULL;

如果在调用 GetDoubleArrayElements() 之后 isCopyJNI_FALSE,则没有复制。

编辑:重新阅读您的问题后,您可能需要考虑实施Archie's idea。这取决于您的 Java 方法如何使用数据。如果 Java 方法不使用整个数组,或者不是一次全部使用,那么 Archie 使用本地访问器创建 C++ 数组的 Java 类包装器的解决方案可能是一个很好的解决方案。但是,如果 Java 方法需要所有数据,那么您可能只需要 GetDoubleArrayElements() 提供的将数据导入 VM 的最快方法。

如果您对 C++ 数组的某些元素进行了更改,并希望对 Java 副本进行相同的更改,这就是 SetDoubleArrayRegion() 发挥作用的地方。

EDIT2:我相信 Archie 指的是这样的东西:

public class NativeDoubleArrayProxy {

    // This is the native `inputArray' pointer.
    private long p;

    private int length;

    private NativeDoubleArrayProxy(long p, int length) {
        this.p = p;
        this.length = length;
    }

    public int length() {
        return length;
    }

    public native double getDouble(int index);

    public native void getDoubles(int startingIndex, double[] out, int outOffset, int length);
}

具体细节取决于inputArray 的类型(它是原始C 风格的数组,还是std::vector&lt;double&gt;,还是别的什么?)。但这个想法是在 JNI 端构造一个NativeDoubleArrayProxy 对象,将转换为jlong 的指针传递给它。 getDouble() 和 getDouble() 的 JNI 实现实现了从 C++ 到 Java 代码的复制。

当然,您需要非常小心以确保指针保持有效。

另见:What is the 'correct' way to store a native pointer inside a Java object?

【讨论】:

  • 谢谢。但是我仍然没有得到我的问题的答案,我可以直接从 Java App 访问“C++”数据还是应该通过 jArrays 将其复制到 JVM?您如何看待 Bufferes,jpavel 写了什么?您能否详细解释一下,Archie 的想法是什么?
  • @Andrew:您可以直接从 Java 访问 C++ 数据。请参阅我的更新答案。不过,您需要评估这是否最适合您的项目。
  • 这就是我的意思。谢谢丹尼尔的详细说明。在 Java 类路径术语中,这些称为对等类。它们总是通过嵌入到 Java 对象中的本机指针耦合。访问和操作数据的真正工作是在本机端完成的,Java 类确实只是一个代理。整个 AWT 就是这样开发的。
猜你喜欢
  • 1970-01-01
  • 2018-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多