4

I have C++ App which calls some java functionality. I'm using JNI. Everything works fine, but still there is one small problem. In C++ App I have a few huge arrays. Java App needs this data also and returns one huge array of results. Currently I just copy the data (you can check some peace of code below). And it would be much better if I access these arrays from Java App directly per pointer. Is it possible? I assume no (because of JVM), but in that case I have to explain it in detail to my boss :) So, yes or no, and how or why?

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);
}
Lawrence Dol
  • 63,018
  • 25
  • 139
  • 189
Andrew
  • 195
  • 2
  • 9
  • 2
    I would create a Java 'array' class that is implemented by its peer class in C++, do all operations will native routines. This way the data itself will be entirely in C++ world without any copying required. – Archie Dec 17 '12 at 22:14
  • Sorry, I don't really understand, what exactly you mean. How the peer class in C++ should look like? And how the Java array class can access it? – Andrew Dec 18 '12 at 08:05

2 Answers2

3

Direct ByteBuffers were introduced in Java 1.4 for this purpose. Along with the new classes on the Java side were the corresponding JNI functions. You can wrap your native arrays in a ByteBuffer (and the corresponding IntBuffer, etc., views) and read and write without making copies.

jpavel
  • 183
  • 1
  • 5
  • Thanks, I'll try. But if I have an array in C++, than I have to create this Buffer on the JVM side and copy data into it, right? In this case it would be pretty the same like in my code. – Andrew Dec 18 '12 at 08:08
  • +1 This is a good idea. If `inputArray` is already a `jdouble` C array, then on the JNI side, call NewDirectByteBuffer() passing `inputArray` and `length * sizeof (jdouble)` as the capacity. On the Java size, create a `DoubleBuffer` view of the `ByteBuffer` via the [ByteBuffer#asDoubleBuffer()](http://docs.oracle.com/javase/6/docs/api/java/nio/ByteBuffer.html#asDoubleBuffer()) method. – Daniel Trebbien Dec 18 '12 at 13:27
  • 1
    And if you take a look at java.nio.Buffer source code, you'll find that it's actually implemented the way we discussed above via embedding native pointer into a Java class (proprietary sun.misc.Unsafe package is used to manipulate native pointers). – Archie Dec 18 '12 at 14:11
1

The best that you can do is use the Get<PrimitiveType>ArrayElements routines. Hopefully the VM supports 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;

If isCopy is JNI_FALSE after the call to GetDoubleArrayElements(), then no copy was made.

EDIT: After re-reading your question, you might want to consider implementing Archie's idea. This depends on how your Java methods use the data. If the Java methods do not use the entire array, or not all at once, then Archie's solution of creating a Java class wrapper of the C++ array, with native accessors, might be a good solution. If, however, the Java method requires all data, then you might just need the quickest way to get the data into the VM, which GetDoubleArrayElements() provides.

If you make changes to some elements of the C++ array and want to make the same changes to the Java copy, that's where SetDoubleArrayRegion() comes into play.

EDIT2: I believe that Archie is referring to something like:

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

The exact details depend on the type of your inputArray (is it a raw C-style array, or a std::vector<double>, something else?). But the idea is to construct a NativeDoubleArrayProxy object on the JNI side, passing it the pointer cast to a jlong. The JNI implementations of getDouble() and getDoubles() implement the copying-from-C++-to-Java code.

Of course, you need to be very careful to make sure that the pointer remains valid.

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

Community
  • 1
  • 1
Daniel Trebbien
  • 38,421
  • 18
  • 121
  • 193
  • Thanks. But still I didn't get an answer to my question, can I access "C++" data from Java App directly or should I copy it to the JVM via jArrays? What do you think about Bufferes, what jpavel wrote? And could you please explain me a bit more in detail, what is Archie's idea? – Andrew Dec 18 '12 at 08:18
  • @Andrew: You *can* access C++ data from Java directly. See my updated answer. You will need to evaluate whether that is best for your project, though. – Daniel Trebbien Dec 18 '12 at 13:24
  • 1
    That's what I meant. Thank you Daniel for elaboration. In Java classpath terminology these are called peer-classes. They are always coupled via native pointer embedded into Java object. The real work of accessing and manipulating your data is done at native side, and the Java class is indeed just a proxy. The whole AWT is developed this way. – Archie Dec 18 '12 at 14:01