Calling JNI function to create object - java

I'm writing JNI staff in C11 and have a question about strictly-conforming on-heap object creation.
JNI API provides a function to do this with the following signature:
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
As specified in the 6.5.2.2(p7) Standard
The ellipsis notation in a function prototype declarator causes
argument type conversion to stop after the last declared parameter.
Arguments corresponding to the ellipsis notation should be explicitly converted to the expected type in order for the code to be conforming. Consider the following case:
public class Event{
public final int eventType;
public final String meta;
public Event(int eventType, String meta){
this.eventType = eventType;
this.meta = meta;
}
}
What types of the arguments I should convert the parameters corresponding to the ellipsis to?
I can guess that it should look as follows:
jclass event_class = ((*env)->FindClass)(env, "f/q/c/n/Event");
jmethodID ctor = (*env)->GetMethodID(
env,
event_class,
"<init>",
"(ILjava/lang/String;)V"
);
array_element = (*env)->NewObject(
env,
event_class,
ctor,
(jint) 0, (jobject) NULL //corresponds to the ellipsis
);

The types of the arguments are deduced from the method you are calling.
In your case, it is the constructor of the Event class that expects an int and a String.
So it would look like this:
jstring metaStr = (*env)->NewStringUTF(env, "hello");
jobject array_element = (*env)->NewObject(
env,
event_class,
ctor,
(jint)4711, metaStr
);
Test
To perform a brief test, we could write a class that calls a native C function that creates the desired Event object, initializes it, and returns it to the calling Java side.
This Java program would look like this:
import f.q.c.n.Event;
public class JNIEventTest {
static {
System.loadLibrary("native");
}
private native Event retrieveNativeEvent();
public static void main(String[] args) {
JNIEventTest jniEventTest = new JNIEventTest();
Event event = jniEventTest.retrieveNativeEvent();
System.out.println("eventType: " + event.eventType);
System.out.println("meta: " + event.meta);
}
}
The native C side would look like this then:
#include "JNIEventTest.h"
JNIEXPORT jobject JNICALL Java_JNIEventTest_retrieveNativeEvent(JNIEnv *env, jobject thisObject) {
jclass event_class = ((*env)->FindClass)(env, "f/q/c/n/Event");
jmethodID ctor = (*env)->GetMethodID(
env,
event_class,
"<init>",
"(ILjava/lang/String;)V"
);
jstring eventStr = (*env)->NewStringUTF(env, "hello");
jobject array_element = (*env)->NewObject(
env,
event_class,
ctor,
(jint)4711, eventStr
);
return array_element;
}
The debug output in the console then looks like this:
eventType: 4711
meta: hello

Related

JNI calling Android dialog, waiting for user choice [duplicate]

I have a layout design in Java that I am currently porting over to C++ via JNI. I am practically done at this point, but I am currently puzzled on how I am supposed to set up event handlers like setOnClickListener for example. I have gone through the JNI specification and have not gotten much luck.
If anyone can port the following snippet to C++ or lead me in the right direction (more reasonable due to how much code the result would be), that would be greatly appreciated.
public void setOnClickListener(boolean modification, int index, int commandIndex, final TextView textView){
final int key = index;
final int command = commandIndex;
if(modification) {
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
changingMenu(key, command, textView);
Runnable r = new Runnable() {
#Override
public void run() {
resetMenu(key, command, textView);
}
};
Handler h = new Handler();
h.postDelayed(r, 250);
}
});
return;
}
menuTitle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
toggleMenu();
}
});
}
EDIT: Passing bad argument to setOnClickListener
Java
Object getProxy (MyInvocationHandler mih) {
ClassLoader classLoader = new ClassLoader() {
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return super.loadClass(name);
}
};
return java.lang.reflect.Proxy.newProxyInstance(classLoader, new Class[] { }, mih);
}
C++
jobject createProxyInstance(JNIEnv *env, jclass cls, CFunc cfunc) {
jclass cls_IH = env->FindClass("com/app/core/MyInvocationHandler");
jmethodID cst_IH = env->GetMethodID(cls_IH, "<init>", "(J)V");
jobject myIH = env->NewObject(cls_IH, cst_IH, (jlong)cfunc);
jclass klass = env->FindClass("com/app/core/Activity");
jmethodID method = env->GetMethodID(klass, "getProxy", "(Lcom/app/core/MyInvocationHandler;)Ljava/lang/Object;");
return env->CallObjectMethod(context, method, myIH); //Returning wrong object?
}
jobject aa (JNIEnv *env, jobject obj, jobject proxy, jobject method, jobjectArray args) {
__android_log_print(ANDROID_LOG_ERROR, "TEST", "SUCCESS");
}
void setListeners() {
jclass klass = env->FindClass("android/view/View");
jmethodID method = env->GetMethodID(klass, "setOnClickListener", "(Landroid/view/View$OnClickListener;)V");
klass = env->FindClass("android/view/View$OnClickListener");
env->CallVoidMethod(imageView, method, createProxyInstance(env, klass, &aa));
}
The syntax you show boils down to creating anonymous inner classes at compile time and inserting calls to create objects of these classes (with the correct variables in scope) in place of the new View.OnClickListener() { ... } expression.
I see the following two options:
For each different interface, you create a small Java class that implements the interface, with a native implementation of the interface's method(s). This is the most direct approach, but it does require you to keep the tens or hundreds of interface implementations straight.
You use java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) to dynamically create objects that implement the necessary interfaces. This will re-route each method invocation to your InvocationHandler implementation, which should be a Java class that has a native Object invoke(Object proxy, Method method, Object[] args) implementation.
To make all this reusable, you can implement this InvocationHandler to wrap a std::function object, so the final call to eg menuTitle.setOnClickListener might look like the following:
env->CallVoidMethod(menuTitle, menuTitle_setOnClickListener,
createProxyInstance(env, cls_View_OnClickListener, [](JNIEnv *env, jobject proxy, jobject method, jobjectArray args) {
...
});
For the latter solution, define the following Java class:
class MyInvocationHandler implements InvocationHandler {
private long cfunc;
MyInvocationHandler(long cfunc) { this.cfunc = cfunc; }
public native static Object invoke(Object proxy, Method method, Object[] args);
}
Which you implement on the C++ side as:
typedef jobject (*CFunc)(JNIEnv *env, jobject obj, jobject proxy, jobject method, jobjectArray args)
extern "C" jobject Java_MyInvocationHandler_invoke(JNIEnv *env, jobject obj, jobject proxy, jobject method, jobjectArray args) {
jclass cls_myIH = env->GetObjectClass(obj);
jfieldID fld_myIH_cfunc = env->GetFieldID(cls_myIH, "cfunc", "J");
CFunc cfunc = (CFunc)env->GetLongField(obj, fld_myIH_cfunc);
cfunc(env, proxy, method, args);
return nullptr;
}
Finally, we can implement createProxyInstance as follows:
jobject createProxyInstance(JNIEnv *env, jclass cls, CFunc cfunc) {
jclass cls_IH = env->GetClass("MyInvocationHandler");
jmethodID cst_IH = env->GetMethodID(cls_ID, "<init>", "(J)V");
jobject myIH = env->NewObject(cls_ID, cst_IH, (jlong)cfunc);
// now call Proxy.createProxyInstance with this object as InvocationHandler
}

Can a shared library loaded in Java call Java methods?

Let's say I have a Java application that has the following class,
class Adder {
public int add(int a, int b) { return a + b; }
}
If I load into Java a shared library (.so file) using System.loadLibrary("libABC.so"), can a native method in libABC invoke adder.add(1,2) where adder is an instantiation of Adder? If yes, an example/pointer in the right direction would be very much appreciated.
You have Adder class with add() method
class Adder {
public int add(int a, int b) { return a + b; }
}
For example, we have an instance named mAdder in class Demo.
public class Demo {
native void nativeEntry();
Adder mAdder = new Adder();
public static void main(String[] args){
System.loadLibrary("JNIBridge");
Demo demo = new Demo();
demo.nativeEntry();
}
}
In JNI, demo object is stored in thiz pointer.
Use GetObjectField() to get mAdder from thiz.
Use CallIntMethod() to invoke add() method of mAdder.
#include <jni.h>
#include <stdio.h>
//use command
//javah -jni Demo
//to generate jni method declaration
JNIEXPORT void JNICALL Java_Demo_nativeEntry(JNIEnv *env, jobject thiz) {
//thiz is the calling object
//in java main(), we call: demo.nativeEntry()
//so, thiz is demo object
//get class Demo
jclass demoCls = (*env)->FindClass(env, "Demo");
//get id of mAdder in Demo
jfieldID adderField = (*env)->GetFieldID(env, demoCls, "mAdder", "LAdder;");
//get object mAdder from object demo
jobject adderObject = (*env)->GetObjectField(env, thiz, adderField);
//get class Adder
jclass adderCls = (*env)->FindClass(env, "Adder");
//get id of method add in Adder
jmethodID addMethod = (*env)->GetMethodID(env, adderCls, "add", "(II)I");
// (II)I: is signature of method Adder.add()
// use command:
//javap -s -p Adder
//to get method signature
//call method add of object mAdder
jint sum = (*env)->CallIntMethod(env, adderObject, addMethod, 10, 20);
//sum = add(10,20)
printf("sum = %d\n", sum);
}
You can get full code at here.
Yes, and often even if add is a private method.

JNI: Can not get array length

I faced with the next problem: I can not do anything with byte[] (jbyteArray) in C code. All functions that work with array in JNI cause JNI DETECTED ERROR IN APPLICATION: jarray argument has non-array type. What's wrong with my code?
C:
#include <stdio.h>
#include <jni.h>
static jstring convertToHex(JNIEnv* env, jbyteArray array) {
int len = (*env)->GetArrayLength(env, array);// cause an error;
return NULL;
}
static JNINativeMethod methods[] = {
{"convertToHex", "([B)Ljava/lang/String;", (void*) convertToHex },
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
jclass cls = (*env)->FindClass(env, "com/infomir/stalkertv/server/ServerUtil");
(*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0]) );
return JNI_VERSION_1_4;
}
ServerUtil:
public class ServerUtil {
public ServerUtil() {
System.loadLibrary("shadow");
}
public native String convertToHex(byte[] array);
}
Main Activity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ServerUtil serverUtil = new ServerUtil();
byte[] a = new byte[]{1,2,3,4,5};
String s = serverUtil.convertToHex(a);
}
}
My environment:
Android Studio 2.0
Experimental Gradle plugin 0.7.0
JAVA 1.8
NDK r11b
Windows 10 x64
Thanks in advance!
The second argument passed to your function isn't a jbyteArray.
Per the JNI documentation, the arguments passed to a native function are:
Native Method Arguments
The JNI interface pointer is the first argument to native methods. The
JNI interface pointer is of type JNIEnv. The second argument differs
depending on whether the native method is static or nonstatic. The
second argument to a nonstatic native method is a reference to the
object. The second argument to a static native method is a reference
to its Java class.
The remaining arguments correspond to regular Java method arguments.
The native method call passes its result back to the calling routine
via the return value.
Your jstring convertToHex(JNIEnv* env, jbyteArray array) is missing the second jclass or jobject argument, so you're treating either a jobject or jclass argument and a jbyteArray.
Your native method signature is incorrect. It should be
static jstring convertToHe(JNIEnv *env, jobject thiz, jbytearray array)

Store a c++ object instance inside JNI jobject and retrieve later

I have a JNI class with methods init() work(), and cleanup(). On the C++ side I create an instance of a C++ class Foo during init(), then call some methods on it during work(), and finally delete it inside cleanup(). Right now I store instance of Foo as a global singleton on the C++ so that I can retrieve it from the different JNI calls. What I would really like to do is store a pointer to the Foo instance inside the jobject instance that gets passed to each JNI call, so that I can avoid having a global singleton and also so that I can support multiple instances of Foo. Is something like this possible?
You can store a pointer to your C++ object as a Java class member. For example, in Java:
class Foo
{
public long ptr = 0;
public native void init();
public native void work();
public native void cleanup();
}
And in C++:
jfieldID getPtrFieldId(JNIEnv * env, jobject obj)
{
static jfieldID ptrFieldId = 0;
if (!ptrFieldId)
{
jclass c = env->GetObjectClass(obj);
ptrFieldId = env->GetFieldID(c, "ptr", "J");
env->DeleteLocalRef(c);
}
return ptrFieldId;
}
class Foo
{
/* ... */
};
extern "C"
{
void Java_Foo_init(JNIEnv * env, jobject obj)
{
env->SetLongField(obj, getPtrFieldId(env, obj), (jlong) new Foo);
}
void Java_Foo_work(JNIEnv * env, jobject obj)
{
Foo * foo = (Foo *) env->GetLongField(obj, getPtrFieldId(env, obj));
foo->work();
}
void Java_Foo_cleanup(JNIEnv * env, jobject obj)
{
Foo * foo = (Foo *) env->GetLongField(obj, getPtrFieldId(env, obj));
delete foo;
}
}
Absolutely.
Create a Foo instance in JNI. Simply return the pointer (points to the instance created) as a jlong type. So you can use it as a handler later. Here is an example:
JNIEXPORT jlong JNICALL Java_com_example_init(JNIEnv *env, jobject thiz) {
Foo* pFoo = new Foo();
if (NULL == pFoo) {
// error handling
}
pFoo->initialize();
return reinterpret_cast<jlong>(pFoo);
}
JNIEXPORT void JNICALL Java_example_start(JNIEnv *env, jobject thiz,
jlong fooHandle) {
Foo* pFoo = reinterpret_cast<Foo*>(fooHandle);
pFoo->start();
}
You can do it with a long in java, however I would argue that its not a very good idea to put a pointer to some native memory address in an instance variable of a language that is expected to be operating in a sandbox. Its sloppy and it could be a exploit vector depending on what your doing.
I am guessing you are running into this problem because your native code is very close to your JNI code. If you structure your JNI layer as translation between your native code and Java, you may find it easier to work with.

JNI: NoSuchFieldError

Output of: javap -s SomeClass
public org.someapp.SomeClass$_data data;
Signature: Lorg/someapp/SomeClass$_data;
Definition of SomeClass in Java:
class SomeClass
{
private class _data {
byte[] something = new byte[1234];
}
public _data data;
}
Definition of native function in Java:
public static native int NativeFunction(SomeClass something);
Java implementation:
SomeClass x = new SomeClass();
NativeInterface.NativeFunction(x);
However, when the following code is executed:
JNIEXPORT jint JNICALL Java_org_someapp_NativeInterface_NativeFunction(JNIEnv* env, jobject obj, jobject someobject) {
jclass some_class = (*env)->GetObjectClass(env, someobject);
jfieldID data = (*env)->GetFieldID(env, some_class, "data", "Lorg/someapp/SomeClass$_data");
}
Java throws a "NoSuchFieldError;: data" exception on the GetFieldID call. I don't get it.. The signature is just fine (copied straight from javap).
Note that getting the field ID for a simpler variable, like an unsigned short (with signature "S") works just fine.
I have a feeling that "Lorg/someapp/SomeClass$_data" should be "Lorg/someapp/SomeClass$_data;". Note the semicolon.

Categories

Resources