Calling NewObject jni method with parameters in jobjectarray
I am working in JNI with C ++ and I created a method where a series of parameters is passed to my native method as a jobjectarray. I would like to call a constructor in JNI using these parameters. However, the NewObject method does not accept an array of jobs instead of using an ellipsis. How would I accomplish this task? I don't know how many parameters the constructor will take before calling the method and the signature string is passed from java as well. The constructor I call does not take an array as an argument, instead, different functions of the same class can be passed to C ++ functions, each containing a different method signature. I need my C ++ method so that it can create any object with its passed arguments. I am using visual studio as my IDE. I understand that I might need the jvalues ββarray, but I don't understand,how to get this from jobjectarray.
source to share
EDIT:
Sorry, I misunderstood your question. You can achieve this using two other ways the JNI API provides for creating objects (from the 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 put all the arguments to be passed to the constructor in the args array of jvalues, which immediately follows the methodID argument. NewObjectA () takes arguments in this array and in turn passes them to the Java method the programmer wants to call.
NewObjectV
Programmers put all the arguments that must be passed to the constructor in the args argument of type va_list, which immediately follows the methodID argument. NewObjectV () takes these arguments and in turn passes them to the Java method that the programmer wants to call.
So, I made a sample program that shows how to use it.
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();
}
}
Warning: It is still not 100% funciontal because I couldn't figure out how to get the size of the constructor argument based on the constructor signature.
source to share
It's a little tricky because it handed over to you jobjectArray
. This means that primitive types have been boxed (for example, int
are instances java.lang.Integer
in your array) and you need to remove them before passing them to the constructor.
What you need to do is parse the method signature string (not as bad as you might think), loop through each jobject
in your array, and convert it to the appropriate type (using unboxing if necessary).
Unfortunately, JNI does not have a built-in way to perform unboxing, and therefore you have to do it manually by calling the appropriate nested value methods (for example, Integer.intValue
to get int
s).
Main idea:
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
and other conversion functions will be defined as functions that remove the corresponding type (for example, object_to_byte
will use JNI to call java.lang.Byte.byteValue
on a given object).
source to share