有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java调用jobjectarray中带参数的NewObject方法jni

我在C++中使用JNI,并且创建了一系列方法,其中一系列参数作为JObjeTracle传递给我的本地方法。我想使用这些参数调用JNI中的构造函数。但是,NewObject方法不接受jobject数组,而是使用省略号。我将如何完成这项任务?我不知道在调用方法和从java传递签名字符串之前,构造函数将接受多少参数。我所调用的构造函数不把数组作为参数,而是将同一类的不同版本传递给C++函数,每个函数包含不同的方法签名。我需要我的C++方法来泛泛地创建任何带有它的参数的对象。我正在使用VisualStudio作为我的IDE。我知道我可能需要一个jvalues数组,但我不知道如何从jobjectarray获得它


共 (3) 个答案

  1. # 1 楼答案

    这有点棘手,因为您通过了一个jobjectArray。这意味着基元类型已经装箱(例如,数组中的intjava.lang.Integer实例),您必须在将它们传递到构造函数之前取消装箱

    您需要做的是解析方法签名字符串(它没有您想象的那么糟糕),遍历数组中的每个jobject,并将其转换为相应的类型(如果需要,使用取消装箱转换)

    遗憾的是,JNI中没有内置的方法来执行取消装箱,因此您必须通过调用装箱值的适当方法(例如Integer.intValue来获取int来手动执行

    基本思想是:

    jobject createObject(JNIEnv *env, jclass clazz, jmethodID constructor, const char *argstr, jobjectArray *args) {
        int n = env->GetArrayLength(args);
        jvalue *values = new jvalue[n];
        const char *argptr = argstr;
        for(int i=0; i<n; i++) {
            jobject arg = env->GetObjectArrayElement(args, i);
            if(*argptr == 'B') { /* byte */
                values[i].b = object_to_byte(arg);
            }
            /* cases for all of the other primitive types...*/
            else if(*argptr == '[') { /* array */
                while(*argptr == '[') argptr++;
                if(*argptr == 'L')
                    while(*argptr != ';') argptr++;
                values[i].l = arg;
            } else if(*argptr == 'L') { /* object */
                while(*argptr != ';') argptr++;
                values[i].l = arg;
            }
            argptr++;
            env->DeleteLocalRef(arg);
        }
        return env->NewObjectA(clazz, methodID, values);
    }
    

    object_to_byte和其他转换函数将被定义为取消绑定相关类型的函数(例如object_to_byte将使用JNI调用给定对象上的java.lang.Byte.byteValue

  2. # 2 楼答案

    多亏了@LukeHutchinson的提示,我开始寻找更好的解决方案,并且很高兴地报告,实际上有一种更简单的方法使用反射API。您可以使用JNI函数ToReflectedMethodmethodID转换为java.lang.reflect.Methodjava.lang.reflect.Constructor,然后可以分别调用invokenewInstance,这将处理所有必要的取消装箱转换

    这里是一个概念证明,为了清晰起见,省略了错误检查

    test.java

    public class test {
        static {
            System.loadLibrary("native");
        }
        
        public static void main(String[] args) {
            /* This is the constructor String(byte[], int, int).
               This call will print out BCD - the result of creating
               a string from bytes 1-3 of the array */
            System.out.println(new test().makeObject("java/lang/String", "([BII)V", new byte[] { 0x41, 0x42, 0x43, 0x44, 0x45 }, 1, 3));
        }
    
        private native Object makeObject(String clazz, String signature, Object... args);
    }
    

    libnative.c

    #include <jni.h>
    
    JNIEXPORT jobject JNICALL Java_test_makeObject(JNIEnv *env, jobject this, jstring clazzName, jstring signature, jobjectArray args) {
        const char *clazzNameStr = (*env)->GetStringUTFChars(env, clazzName, NULL);
        const char *signatureStr = (*env)->GetStringUTFChars(env, signature, NULL);
    
        jclass clazz = (*env)->FindClass(env, clazzNameStr);
        jmethodID methodID = (*env)->GetMethodID(env, clazz, "<init>", signatureStr);
        jobject reflectedMethod = (*env)->ToReflectedMethod(env, clazz, methodID, JNI_FALSE);
    
        jclass constructorClazz = (*env)->FindClass(env, "java/lang/reflect/Constructor");
        jmethodID newInstanceMethod = (*env)->GetMethodID(env, constructorClazz, "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;");
    
        jobject result = (*env)->CallObjectMethod(env, reflectedMethod, newInstanceMethod, args);
    
        (*env)->ReleaseStringUTFChars(env, clazzName, clazzNameStr);
        (*env)->ReleaseStringUTFChars(env, signature, signatureStr);
        return result;
    }
    
  3. # 3 楼答案

    编辑:

    对不起,我误解了你的问题。您可以通过使用JNI API为创建对象(从docs)提供的其他两种方法来实现这一点:

    jobject NewObjectA(JNIEnv *env, jclass clazz,
    jmethodID methodID, const jvalue *args);
    
    jobject NewObjectV(JNIEnv *env, jclass clazz,
    jmethodID methodID, va_list args);
    

    NewObjectA

    Programmers place all arguments that are to be passed to the constructor in an args array of jvalues that immediately follows the methodID argument. NewObjectA() accepts the arguments in this array, and, in turn, passes them to the Java method that the programmer wishes to invoke.

    NewObjectV

    Programmers place all arguments that are to be passed to the constructor in an args argument of type va_list that immediately follows the methodID argument. NewObjectV() accepts these arguments, and, in turn, passes them to the Java method that the programmer wishes to invoke.

    所以,我制作了一个示例程序来演示如何使用它

    Foo。java

    public class Foo {
    
        private int bar;
        private String baaz;
    
        public Foo(int bar) {
            this(bar, "");
        }
    
        public Foo(int bar, String baaz) {
            this.bar = bar;
            this.baaz = baaz;
        }
    
        public void method1() {
            this.bar++;
    
            System.out.println(bar);
            System.out.println(baaz);
        }
    }
    

    条。java

    public class Bar {
    
        public Bar() {
        }
    
        public static native Foo createFoo(String signature, Object ... params);
    }
    

    条。h

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class Bar */
    
    #ifndef _Included_Bar
    #define _Included_Bar
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     Bar
     * Method:    createFoo
     * Signature: (Ljava/lang/String;[Ljava/lang/Object;)LFoo;
     */
    JNIEXPORT jobject JNICALL Java_Bar_createFoo
      (JNIEnv *, jclass, jstring, jobjectArray);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    条。c

    #include "Bar.h"
    
    #include <stdlib.h>
    
    jobject JNICALL Java_Bar_createFoo
      (JNIEnv * env, jclass class, jstring signature, jobjectArray params) {
    
        // method signature in char *
        const char * signatureChar = (*env)->GetStringUTFChars(env, signature, 0);
    
        jvalue * args;
        int i, size;
    
        // retrieve foo class
        jclass fooClass = (*env)->FindClass(env, "LFoo;");
    
        // retrieve foo construtor
        jmethodID fooConstructor = (*env)->GetMethodID(env, fooClass, "<init>", signatureChar);
    
        // operate over params
        // ...
    
        // TODO: find out a way to retrieve size from constructor
        size = 2;
    
        args = malloc(size * sizeof(jvalue));
    
        for (i = 0; i < size; i++) {
            args[i].l = (*env)->GetObjectArrayElement(env, params, i);
        }
    
        return (*env)->NewObjectA(env, fooClass, fooConstructor, args);
    }
    

    Main。java

    public class Main {
    
        static {
            System.loadLibrary("YOUR_LIBRARY_NAME_HERE");
        }
    
        public static void main(String[] args) {
            Foo foo = Bar.createFoo("(ILjava/lang/String;)V", -12312141, "foo");
    
            System.out.println(foo);
    
            foo.method1();
    
            foo = Bar.createFoo("(I)V", -12312141, "foo");
    
            System.out.println(foo);
    
            foo.method1();
    
            foo = Bar.createFoo("(I)V", -12312141);
    
            System.out.println(foo);
    
            foo.method1();
        }
    }
    

    警告:它仍然不是100%functiontal,因为我无法确定如何根据构造函数签名检索构造函数参数大小