通过jni从c++向java传递每个引用的参数

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

本文关键字:引用 参数 jni c++ java 通过      更新时间:2023-10-16

我试图通过JNI将变量从c++传递到java。一开始,我用一些简单的代码试了试:

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

}

这应该简单地对c++代码中使用Java方法创建的变量进行-/递减。c++部分如下所示:

c++

jmethodID jDec = env->GetStaticMethodID(cls, "dec", "(I)V");
jmethodID jInc = env->GetStaticMethodID(cls, "inc", "(I)V");
jint val = 10;
printf("%dn", val);
env->CallStaticVoidMethod(cls, jDec, &val);
printf("%dn", val);
env->CallStaticVoidMethod(cls, jInc, val);
printf("%dn", val);

如你所见,我分别尝试了每个引用和每个值。

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

在c++代码中,值一直是10,在Java中,它要么是地址,要么是值。

如果你能给我一个提示就太好了,提前谢谢你了

正如Wojtek所说,Java按值传递参数。您可以在Java代码中添加一个返回值:

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

,然后在c++中调用:

jmethodID inc2 = env->GetStaticMethodID(cls, "inc2", "(I)I");
jint result = env->CallStaticIntMethod(cls, inc2, val);
printf("inc2: %dn", result);
另一种方法是使用包装器类代替:
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;
    }
}

和以下Java方法:

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

你可以从你的c++代码中调用它:

// 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: %dn", 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: %dn", ret);

或者您可以使用int[]代替Wojtek建议的包装器类。

首先,即使在纯c++上下文中,以下内容也没有意义:

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

&val返回本地变量val的地址。这在c++中通常都无法编译。你自己试试吧:

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

之所以在这里编译是因为CallStaticVoidMethod是一个可变函数。它是这样声明的:

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

最后的...部分意味着"任何事情都可以进行,但是如果您传递的任何东西在运行时我不能正确处理,您要负责。"

所以这种方法显然是注定要失败的。

然而,实际的问题在于Java没有c++意义上的引用。一切都是按值传递的;根本没有等价于c++ int &形参。

然而,Java有指针,尽管它大多称它们为"引用",令人困惑。例如,Java的Integer引用将是c++中的Integer *(假设c++中存在Integer类)。天真地,你可以改变你的方法的签名来接受Integer参数,并在c++端摆弄对象,但这也行不通,因为Java的Integer类是不可变的,所以你不能增加它。

那你到底能做什么呢?

您可以利用单元素数组,但这不是一个很好的解决方案;它增加了您必须进行的错误检查的数量。为了使代码足够健壮,必须确保数组不包含0或> 1个元素。此外,数组会让所有看到代码的人感到困惑。

另一种解决方案是创建您自己的可变整数类并使用该类。像这样:
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();
}

代码的c++端将因此变得更加复杂。您必须首先找到您的类,找到并调用它的构造函数,找到并调用increment方法,最后找到并调用get方法。理想情况下,您可以将所有这些都封装在一个小的c++函数中:

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

(我已经写下了我的头,所以它可能包含错误,你当然应该在实际代码中添加很多错误处理和可能的一些jmethodID优化。)

值得这么麻烦吗?可能不会。如果Java方法也从许多其他Java代码中调用,并且您希望保持一致的接口,那么这可能是合理的。如果Java方法只能从c++代码中调用,那么整个方法就是无意义的,正确的解决方案是将方法改为返回递增的值:

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

"这应该简单地使用Java方法在c++代码中创建的变量进行-/递减。"不,不应该。Java通过值传递参数,所以你递增和递减的不是c++变量,而是incdec函数中的局部变量val