在 JNI 代码中将 `jobjectArray` 拆箱到 `jvalue[]` 数组中
Unboxing a `jobjectArray` into a `jvalue[]` array in JNI code
我有以下 Java 本机方法声明:
static native Object nativeCallObjectMethod
(Object object, Method method, Object... params);
这会产生以下 C 函数声明(注意 Object... params
映射到 jobjectArray params
):
JNIEXPORT jobject JNICALL Java_pkg_Cls_nativeCallObjectMethod
(JNIEnv *env, jclass ignored, jobject obj, jobject method,
jobjectArray params) { }
我想用 params
作为参数调用 method
:
jmethodID methodID = (*env)->FromReflectedMethod(env, method);
return (*env)->CallObjectMethod(env, obj, methodID, params);
但是,params
是 jobjectArray
类型,而 JNI 方法 callObjectMethod*
只能采用以下三种形式之一:
CallObjectMethod(JNIEnv *env, jobject obj, jmethodID methodID, ...)
CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args)
CallObjectMethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args)
此方法没有在最后一个参数位置采用 jobjectArray
的形式。我假设方法 2 是我要调用的方法,并且我意识到 jobjectArray
元素可能需要取消装箱才能生成 jvalue
值的数组。我发现了关于如何实现这个的建议:
但是,我无法相信没有使用 JNI API 将 jobjectArray
拆箱到 jvalue
数组的标准方法。当然必须有一种内置的方法来实现这一点,因为 Java 在使用反射时在内部执行此操作?
我该如何解决这个问题,最好不要重新发明轮子?
因为您已经有一个 reflect.Method
对象,您可以直接 .invoke
它。这已经为您完成了拆箱操作:
jclass clsMethod = env->GetObjectClass(method);
jmethodID midMethodInvoke = env->GetMethodID(clsMethod, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
jobject result = env->CallObjectMethod(method, midMethodInvoke, obj, params);
对于未来的读者:如果您只有 jclass
+jmethodID
,我建议您使用 ToReflectedMethod
获得 reflect.Method
,然后调用 .invoke
如上所述,省去你的麻烦。
我写了一个将 jobjectArray
拆箱到 jvalue[]
数组的方法:
// Unbox a jobjectArray of method invocation args into a jvalue array. Returns 0 if an exception was thrown, or 1 if unboxing succeeded.
int unbox(JNIEnv *env, jobject method, jobjectArray args, jsize num_args, jvalue* arg_jvalues) {
// Get parameter types of method
jclass methodClass = (*env)->GetObjectClass(env, method);
jmethodID getParameterTypesMethodID = (*env)->GetMethodID(env, methodClass, "getParameterTypes", "()[Ljava/lang/Class;");
jobject parameterTypes = (*env)->CallObjectMethod(env, method, getParameterTypesMethodID);
jsize num_params = (*env)->GetArrayLength(env, parameterTypes);
// Check arg arity
if (num_args != num_params) {
throwIllegalArgumentException(env, "Tried to invoke method with wrong number of arguments");
return 0;
}
// Unbox args
for (jsize i = 0; i < num_args; i++) {
jobject paramType = (*env)->GetObjectArrayElement(env, parameterTypes, i);
jobject arg = (*env)->GetObjectArrayElement(env, args, i);
jclass argType = arg == NULL ? (jclass) NULL : (*env)->GetObjectClass(env, arg);
if ((*env)->IsSameObject(env, paramType, int_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Integer");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Integer_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Integer");
return 0;
} else {
arg_jvalues[i].i = (*env)->CallIntMethod(env, arg, int_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, long_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Long");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Long_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Long");
return 0;
} else {
arg_jvalues[i].j = (*env)->CallLongMethod(env, arg, long_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, short_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Short");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Short_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Short");
return 0;
} else {
arg_jvalues[i].s = (*env)->CallShortMethod(env, arg, short_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, char_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Character");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Character_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Character");
return 0;
} else {
arg_jvalues[i].c = (*env)->CallCharMethod(env, arg, char_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, boolean_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Boolean");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Boolean_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Boolean");
return 0;
} else {
arg_jvalues[i].z = (*env)->CallBooleanMethod(env, arg, boolean_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, byte_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Byte");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Byte_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Byte");
return 0;
} else {
arg_jvalues[i].b = (*env)->CallByteMethod(env, arg, byte_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, float_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Float");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Float_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Float");
return 0;
} else {
arg_jvalues[i].f = (*env)->CallFloatMethod(env, arg, float_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, double_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Double");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Double_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Double");
return 0;
} else {
arg_jvalues[i].d = (*env)->CallDoubleMethod(env, arg, double_value_methodID);
}
} else {
// Arg does not need unboxing, but we need to check if it is assignable from the parameter type
if (arg != NULL && !(*env)->IsAssignableFrom(env, argType, paramType)) {
throwIllegalArgumentException(env, "Tried to invoke function with arg of incompatible type");
return 0;
} else {
arg_jvalues[i].l = arg;
}
}
}
return 1;
}
对于速度,类 的全局引用如 Integer.class
和 int.class
在初始化时按如下方式获得:
jclass Integer_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/lang/Integer"));
jclass int_class = (*env)->NewGlobalRef(env, (*env)->GetStaticObjectField(env, Integer_class, (*env)->GetStaticFieldID(env, Integer_class, "TYPE", "Ljava/lang/Class;")));
jmethodID int_value_methodID = (*env)->GetMethodID(env, Integer_class, "intValue", "()I");
这样调用开箱方法:
JNIEXPORT jint JNICALL Java_narcissus_Narcissus_callIntMethod(JNIEnv *env, jclass ignored, jobject obj, jobject method, jobjectArray args) {
jmethodID methodID = (*env)->FromReflectedMethod(env, method);
jsize num_args = (*env)->GetArrayLength(env, args);
if (num_args == 0) {
return (*env)->CallIntMethod(env, obj, methodID);
}
jvalue arg_jvalues[num_args];
return unbox(env, method, args, num_args, arg_jvalues) ? (*env)->CallIntMethodA(env, obj, methodID, arg_jvalues) : (jint) 0;
}
以上代码不处理可变参数。可以在Narcissus library(我是作者)中看到处理可变参数的完整版本。
我有以下 Java 本机方法声明:
static native Object nativeCallObjectMethod
(Object object, Method method, Object... params);
这会产生以下 C 函数声明(注意 Object... params
映射到 jobjectArray params
):
JNIEXPORT jobject JNICALL Java_pkg_Cls_nativeCallObjectMethod
(JNIEnv *env, jclass ignored, jobject obj, jobject method,
jobjectArray params) { }
我想用 params
作为参数调用 method
:
jmethodID methodID = (*env)->FromReflectedMethod(env, method);
return (*env)->CallObjectMethod(env, obj, methodID, params);
但是,params
是 jobjectArray
类型,而 JNI 方法 callObjectMethod*
只能采用以下三种形式之一:
CallObjectMethod(JNIEnv *env, jobject obj, jmethodID methodID, ...)
CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args)
CallObjectMethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args)
此方法没有在最后一个参数位置采用 jobjectArray
的形式。我假设方法 2 是我要调用的方法,并且我意识到 jobjectArray
元素可能需要取消装箱才能生成 jvalue
值的数组。我发现了关于如何实现这个的建议:
但是,我无法相信没有使用 JNI API 将 jobjectArray
拆箱到 jvalue
数组的标准方法。当然必须有一种内置的方法来实现这一点,因为 Java 在使用反射时在内部执行此操作?
我该如何解决这个问题,最好不要重新发明轮子?
因为您已经有一个 reflect.Method
对象,您可以直接 .invoke
它。这已经为您完成了拆箱操作:
jclass clsMethod = env->GetObjectClass(method);
jmethodID midMethodInvoke = env->GetMethodID(clsMethod, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
jobject result = env->CallObjectMethod(method, midMethodInvoke, obj, params);
对于未来的读者:如果您只有 jclass
+jmethodID
,我建议您使用 ToReflectedMethod
获得 reflect.Method
,然后调用 .invoke
如上所述,省去你的麻烦。
我写了一个将 jobjectArray
拆箱到 jvalue[]
数组的方法:
// Unbox a jobjectArray of method invocation args into a jvalue array. Returns 0 if an exception was thrown, or 1 if unboxing succeeded.
int unbox(JNIEnv *env, jobject method, jobjectArray args, jsize num_args, jvalue* arg_jvalues) {
// Get parameter types of method
jclass methodClass = (*env)->GetObjectClass(env, method);
jmethodID getParameterTypesMethodID = (*env)->GetMethodID(env, methodClass, "getParameterTypes", "()[Ljava/lang/Class;");
jobject parameterTypes = (*env)->CallObjectMethod(env, method, getParameterTypesMethodID);
jsize num_params = (*env)->GetArrayLength(env, parameterTypes);
// Check arg arity
if (num_args != num_params) {
throwIllegalArgumentException(env, "Tried to invoke method with wrong number of arguments");
return 0;
}
// Unbox args
for (jsize i = 0; i < num_args; i++) {
jobject paramType = (*env)->GetObjectArrayElement(env, parameterTypes, i);
jobject arg = (*env)->GetObjectArrayElement(env, args, i);
jclass argType = arg == NULL ? (jclass) NULL : (*env)->GetObjectClass(env, arg);
if ((*env)->IsSameObject(env, paramType, int_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Integer");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Integer_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Integer");
return 0;
} else {
arg_jvalues[i].i = (*env)->CallIntMethod(env, arg, int_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, long_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Long");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Long_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Long");
return 0;
} else {
arg_jvalues[i].j = (*env)->CallLongMethod(env, arg, long_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, short_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Short");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Short_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Short");
return 0;
} else {
arg_jvalues[i].s = (*env)->CallShortMethod(env, arg, short_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, char_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Character");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Character_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Character");
return 0;
} else {
arg_jvalues[i].c = (*env)->CallCharMethod(env, arg, char_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, boolean_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Boolean");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Boolean_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Boolean");
return 0;
} else {
arg_jvalues[i].z = (*env)->CallBooleanMethod(env, arg, boolean_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, byte_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Byte");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Byte_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Byte");
return 0;
} else {
arg_jvalues[i].b = (*env)->CallByteMethod(env, arg, byte_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, float_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Float");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Float_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Float");
return 0;
} else {
arg_jvalues[i].f = (*env)->CallFloatMethod(env, arg, float_value_methodID);
}
} else if ((*env)->IsSameObject(env, paramType, double_class)) {
if (arg == NULL) {
throwIllegalArgumentException(env, "Tried to unbox a null argument; expected Double");
return 0;
} else if (!(*env)->IsSameObject(env, argType, Double_class)) {
throwIllegalArgumentException(env, "Tried to unbox arg of wrong type; expected Double");
return 0;
} else {
arg_jvalues[i].d = (*env)->CallDoubleMethod(env, arg, double_value_methodID);
}
} else {
// Arg does not need unboxing, but we need to check if it is assignable from the parameter type
if (arg != NULL && !(*env)->IsAssignableFrom(env, argType, paramType)) {
throwIllegalArgumentException(env, "Tried to invoke function with arg of incompatible type");
return 0;
} else {
arg_jvalues[i].l = arg;
}
}
}
return 1;
}
对于速度,类 的全局引用如 Integer.class
和 int.class
在初始化时按如下方式获得:
jclass Integer_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/lang/Integer"));
jclass int_class = (*env)->NewGlobalRef(env, (*env)->GetStaticObjectField(env, Integer_class, (*env)->GetStaticFieldID(env, Integer_class, "TYPE", "Ljava/lang/Class;")));
jmethodID int_value_methodID = (*env)->GetMethodID(env, Integer_class, "intValue", "()I");
这样调用开箱方法:
JNIEXPORT jint JNICALL Java_narcissus_Narcissus_callIntMethod(JNIEnv *env, jclass ignored, jobject obj, jobject method, jobjectArray args) {
jmethodID methodID = (*env)->FromReflectedMethod(env, method);
jsize num_args = (*env)->GetArrayLength(env, args);
if (num_args == 0) {
return (*env)->CallIntMethod(env, obj, methodID);
}
jvalue arg_jvalues[num_args];
return unbox(env, method, args, num_args, arg_jvalues) ? (*env)->CallIntMethodA(env, obj, methodID, arg_jvalues) : (jint) 0;
}
以上代码不处理可变参数。可以在Narcissus library(我是作者)中看到处理可变参数的完整版本。