[英]Call NewObject method jni with params in jobjectarray
我正在使用 C++ 在 JNI 中工作,並且我創建了一個方法,其中將一系列參數作為 jobjectarray 傳遞給我的本機方法。 我想使用這些參數在 JNI 中調用構造函數。 但是,NewObject 方法不接受作業數組,而是使用省略號。 我將如何完成這項任務? 我不知道在調用方法之前構造函數將采用多少個參數,並且簽名字符串也從 java 傳遞過來。 我調用的構造函數不接受數組作為參數,而是可以將同一類的不同版本傳遞給 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
程序員將所有要傳遞給構造函數的參數放在緊跟在methodID參數之后的jvalues的args數組中。 NewObjectA()接受此數組中的參數,然后將它們傳遞給程序員希望調用的Java方法。
NewObjectV的
程序員將所有要傳遞給構造函數的參數放在緊跟在methodID參數之后的類型為va_list的args參數中。 NewObjectV()接受這些參數,然后將它們傳遞給程序員希望調用的Java方法。
所以,我制作了一個示例程序,展示了如何使用它。
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
。 這意味着原始類型已被裝箱(例如, int
是數組中的java.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
)。
感謝 @LukeHutchinson 的提示,我開始尋找更好的解決方案,並且很高興地報告說,實際上有一種使用 Reflection API 的更簡單的方法。 您可以使用JNI功能ToReflectedMethod
的轉化methodID
到java.lang.reflect.Method
或java.lang.reflect.Constructor
,之后就可以調用invoke
或newInstance
分別將處理所有必要的拆箱轉換。
這是一個概念證明,為了清楚起見,省略了錯誤檢查。
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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.