【问题标题】:Unboxing a `jobjectArray` into a `jvalue[]` array in JNI code在 JNI 代码中将 `jobjectArray` 拆箱成 `jvalue[]` 数组
【发布时间】:2021-08-22 12:06:10
【问题描述】:

我有以下 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* 只能采用以下三种形式之一:

  1. CallObjectMethod(JNIEnv *env, jobject obj, jmethodID methodID, ...)
  2. CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args)
  3. CallObjectMethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args)

这种方法没有任何形式在最后一个参数位置采用jobjectArray。我假设方法 2 是我要调用的方法,并且我意识到 jobjectArray 元素可能需要拆箱以生成 jvalue 值的数组。我发现了这个关于如何实现的建议:https://stackoverflow.com/a/30961708/3950982

但是,我不敢相信没有使用 JNI API 将jobjectArray 拆箱成jvalue 数组的标准方法。当然必须有一种内置的方法来实现这一点,因为 Java 在使用反射时会在内部执行此操作?

我该如何解决这个问题,最好不要重新发明轮子?

【问题讨论】:

    标签: java java-native-interface native-code


    【解决方案1】:

    由于您已经有一个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 以省去麻烦。

    【讨论】:

    • 感谢您的建议。这很聪明。但在我的情况下,Method.invoke 不起作用,因为 JDK 16+ 正在强制封装,并且该方法不可见。所以我需要一种直接从本机代码调用该方法的方法,这就是为什么我在问题中声明我想使用CallObjectMethod。关于如何在不重新发明轮子的情况下拆箱参数的任何其他想法?
    • 不,我认为 JNI 不提供该功能。恐怕您在问题中链接到的代码是您最大的希望。
    • 我在这里写了我自己版本的拆箱代码:github.com/lukehutch/narcissus/blob/main/native/src/…
    • 整洁!建议您将其放在单独的答案中以使其更具可见性。 SO鼓励这样的自我回答,所以去吧。
    【解决方案2】:

    我写了一个将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.classint.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看到(我是作者)。

    【讨论】:

      猜你喜欢
      • 2018-04-22
      • 2015-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多