使用 jobjectarray 中的参数调用 NewObject 方法 jni

Call NewObject method jni with params in jobjectarray

我在 JNI 中使用 C++ 工作,我创建了一个方法,其中一系列参数作为 jobjectarray 传递到我的本机方法。我想使用这些参数在 JNI 中调用构造函数。但是,NewObject 方法不接受 jobject 数组,而是使用省略号。我将如何完成这项任务?我不知道在调用方法之前构造函数将采用多少个参数,签名字符串也从 java 传递。我正在调用的构造函数不将数组作为参数,而是可以将相同 class 的不同版本传递给 c++ 函数,每个版本都包含不同的方法签名。我需要我的 c++ 方法通常能够使用其传递的参数创建任何对象。我正在使用 visual studio 作为我的 IDE。我知道我可能需要一个 jvalues 数组,但我不明白如何从 jobjectarray 中获取它。

编辑:

抱歉,我误解了你的问题。您可以使用 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);
    }
}

Bar.java

public class Bar {

    public Bar() {
    }

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

Bar.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

Bar.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% 的功能,因为我不知道如何根据构造函数签名检索构造函数参数大小。

这有点棘手,因为您已经通过了 jobjectArray。这意味着基本类型已被装箱(例如 ints 是数组中的 java.lang.Integer 实例),并且您必须在将它们传递给构造函数之前取消装箱。

你要做的是解析方法签名字符串(它并没有你想象的那么糟糕),遍历数组中的每个 jobject 并将其转换为相应的类型(必要时使用拆箱转换)。

遗憾的是,JNI 中没有执行拆箱的内置方法,因此您将不得不通过调用装箱值的适当方法(例如 Integer.intValue 以获取 ints).

基本思路:

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)。

感谢@LukeHutchinson 的提示,我开始寻找更好的解决方案,并且很高兴地报告说实际上有一种使用反射 API 的更简单的方法。你可以使用JNI函数ToReflectedMethod将一个methodID转换为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;
}