简体   繁体   中英

Pass parameters per reference from c++ to java via jni

I'm trying to pass a variable from c++ to java per reference via JNI. For the beginning I've tried it with some simple code:

Java

public static void inc(int val) {
  System.out.println("inc called: " + val);
  val++;
}

public static void dec(int val) {
  System.out.println("dec called: " + val);
  val--;

}

This should simply in-/decrement a variable created in c++ code with a Java method. The c++ part looks like this:

C++

jmethodID jDec = env->GetStaticMethodID(cls, "dec", "(I)V");
jmethodID jInc = env->GetStaticMethodID(cls, "inc", "(I)V");

jint val = 10;

printf("%d\n", val);
env->CallStaticVoidMethod(cls, jDec, &val);
printf("%d\n", val);
env->CallStaticVoidMethod(cls, jInc, val);
printf("%d\n", val);

As you see, I tried both, per reference and per value.

Output

10
dec called: -401031272
10
inc called: 10
10

Within the c++ code, the value is all the time 10, in Java it is either the address or the value.

Would be nice if you could give me a hint, thank you very much in advance.

As Wojtek said, Java passes arguments by value. You can add a return value your Java code:

public static int inc2(int val) {
    System.out.println("inc called: " + val);
    return val + 1;
}

and then call it from C++:

jmethodID inc2 = env->GetStaticMethodID(cls, "inc2", "(I)I");
jint result = env->CallStaticIntMethod(cls, inc2, val);
printf("inc2: %d\n", result);

Another approach is to use a wrapper class instead:

public class IntWrapper {

    private int value;

    public IntWrapper(int value) {
        setInt(value);
    }

    public int getInt() {
        return value;
    }

    public void setInt(int value) {
        this.value = value;
    }
}

and the following Java method:

public static void inc3(IntWrapper val) {
    val.setInt(val.getInt()+1);
}

You can then call this from your C++ code:

// create wrapper object
jclass wrapper = env->FindClass("IntWrapper");
jmethodID constructor = env->GetMethodID(wrapper, "<init>", "(I)V");
jobject wrapperObject = env->NewObject(wrapper, constructor, val);

// print value before increment
jmethodID getInt = env->GetMethodID(wrapper, "getInt", "()I");
jint ret = env->CallIntMethod(wrapperObject, getInt);
printf("Wrapper value: %d\n", ret);

// call inc3
jmethodID inc3 = env->GetStaticMethodID(cls, "inc3", "(LIntWrapper;)V");
env->CallStaticVoidMethod(cls, inc3, wrapperObject);

// print result
ret = env->CallIntMethod(wrapperObject, getInt);
printf("Wrapper value after increment: %d\n", ret);

Alternatively you could use an int[] instead of the wrapper class as suggested by Wojtek.

"This should simply in-/decrement a variable created in c++ code with a Java method." no it shouldn't. Java passes arguments by value, so you are incrementing and decrementing not the C++ variable, but variable val local in functions inc and dec .

First of all, the following does not make sense even in a pure C++ context:

jint val = 10;
// ...
env->CallStaticVoidMethod(cls, jDec, &val);

&val returns the address of the local variable val . This normally doesn't even compile in C++. Try it yourself:

int x = 10;
int y = &x; // error

The reason why it compiles here is CallStaticVoidMethod being a variadic function. It is declared like this:

void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)

The ... part at the end means "anything goes, but you are responsible if you pass anything which I cannot handle correctly at run-time."

So this approach is obviously doomed.

The actual problem, however, is the fact that Java does not have references in the C++ sense. Everything is passed by value; there is simply no equivalent to a C++ int & parameter.

However, Java has pointers, even though it mostly calls them, confusingly, "references". A Java Integer reference, for example, would be an Integer * in C++ (supposing the existence of an Integer class in C++). Naively, you could just change your methods' signatures to take Integer arguments and fiddle with the objects on the C++ side, but this won't work either, because Java's Integer class is immutable, so you cannot increment it.

So what can you really do?

You could leverage 1-element arrays , but this is not a very nice solution; it increases the amount of error checking you have to do. To make your code robust enough, you'd have to make sure that the arrays do not contain 0 or > 1 elements. Additionally, arrays will confuse everyone who sees the code.

An alternative solution is to create your own mutable integer class and use that one. Something like this:

public class MutableInteger {

    private int value;

    public MutableInteger(int value) {
        this.value = value;
    }

    public void increment() {
        ++value;
    }

    public void decrement() {
        --value;
    }

    public String toString() {
        return String.valueOf(value);
    }

    public int get() {
        return value;
    }

    // other operations
}

(...)

public static void inc(MutableInteger val) {
  System.out.println("inc called: " + val);
  val.increment();
}

The C++ side of the code will be more complicated as a result of it. You will have to first find your class, find and call its constructor, find and call the increment method, then finally find and call the get method. Ideally, you wrap all of this in a small C++ function:

void increment(JNIEnv *env, int &value)
{
    jclass javaClass = env->FindClass(env, "your/package/MutableInteger");
    jmethodID constructor = env->GetMethodID(env, javaClass, "<init>", "(I)V");
    jobject javaObject = env->NewObject(env, javaClass, constructor, value);
    jmethodID incrementMethod = env->GetMethodID(env, javaClass, "increment", "()V");
    env->CallVoidMethod(javaObject, incrementMethod, value);
    jmethodID getMethod = env->GetMethodID(env, javaClass, "get", "()I");
    value = env->CallIntMethod(javaObject, getMethod);
}

(I've written this off the top of my head, so it may contain errors, and you should of course add a lot of error handling and possibly some jmethodID optimisations in real code.)

Is this worth the trouble? Probably not. It may be justifiable if the Java methods are also called from a lot of other Java code and you want to keep a consistent interface. If the Java methods are only called from C++ code, then the whole approach is nonsense, and the correct solution would then be to change the methods to instead return the incremented value :

public static int inc(int val) {
  System.out.println("inc called: " + val);
  return val + 1;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM