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

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
}

Related

How to instantiate shared pointer with JNI to call java implementation

I'm new in JNI and C++. I have to call lib function with shared pointer. My code:
JNIEXPORT jint JNICALL Java_com_test_NativeClient_subscribe(JNIEnv* env, jobject thisObj, jobject handler) {
jclass handlerClass = env->GetObjectClass(handler);
jmethodID starts = env->GetMethodID(handlerClass, "starts", "(I)V");
jmethodID joins = env->GetMethodID(handlerClass, "joins", "(Ljava/lang/String;Ljava/lang/String;)V");
// int subscribe(std::shared_ptr< SomeHandler > handler) // I need implement this
std::shared_ptr<?> sharedPointer = new std::shared_ptr<?>;
return some::lib::subscribe(sharedPointer);
}
SomeHandler it is an interface from lib - some::lib::SomeHamdler, but also I pass java implementation in the method (jobject handler). How I can properly define sharedPointer to call java implementation after subscribe method performed? Thanks in advance.
UPD: Java code:
public native int subscribe(SomeHandler handler); // native method in NativeClient
SomeHandler interface:
public interface SomeHandler {
void starts(int uptime);
void joins(String mac, String name);
SomeHandlerImpl class:
public class SomeHandlerImpl implements SomeHandler {
#Override
public void starts(int uptime) {
System.out.println("uptime is " + uptime);
}
#Override
public void joins(String mac, String name) {
System.out.println("mac: " + mac + ", nName: " + name);
}
All you need to do is store a global reference to the jobject and write some wrapper code:
class JavaWrapperHandler : public some::lib::callback {
jobject java_handler;
public:
JavaWrapperHandler(jobject handler) {
JNIEnv *env = nullptr;
vm->GetEnv(&env, JNI_VERSION_1_6);
java_handler = env->NewGlobalRef(handler);
}
~JavaWrapperHandler() {
JNIEnv *env = nullptr;
vm->GetEnv(&env, JNI_VERSION_1_6);
env->DeleteGlobalRef(java_handler);
}
virtual joins(std::string mac, std::string name) {
JNIEnv *env = nullptr;
vm->GetEnv(&env, JNI_VERSION_1_6);
jclass handlerClass = env->GetObjectClass(java_handler);
jmethodID joins = env->GetMethodID(handlerClass, "joins", "(Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(java_handler, joins, ...);
};
};
And you can instantiate this as follows in your JNI method:
std::make_shared<JavaWrapperHandler>(handler);
Note that you still need to store the shared_ptr again somewhere, otherwise it will immediately be freed. You could for example store it in a std::map<long, shared_ptr<JavaWrapperHandler>> and return the long as a jlong.
Points of note:
This code keeps a global reference to prevent the Java handler object from being garbage collected.
The global reference is freed when the handler is destroyed. Make sure to unregister the callback at some point if you want to free the Java object.
We use the GetEnv method from the JNI Invocation API. It will only produce a useful value if the current (C++) thread has already been attached to the JVM. If it fails, you need to call vm->AttachCurrentThread or vm->AttachCurrentThreadAsDaemon.

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.

Calling JNI function to create object

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

How to call a method of a subclass / child class of an Java Object passed to JNI `jobject`

I have:
public class MyEntity {
private String _myEntityType;
public String get_myEntityType() {
return _myEntityType;
}
public void set_myEntityType(String _myEntityType) {
this._myEntityType = _myEntityType;
}
}
Then :
public class MyObjectEntity extends MyEntity {
public MyObjectEntity() {
super();
}
private String _myObjectDescription;
public String get_myObjectDescription() {
return _myObjectDescription;
}
public void set_myObjectDescription(String _myObjectDescription) {
this._myObjectDescription = _myObjectDescription;
}
}
Now I start getting into JNI.
public class MyPers {
// Load the 'my-pers-lib' library on application startup.
static {
System.loadLibrary("my-pers-lib");
}
public native Long myPersInit(MyEntity myEntity);
}
Then :
#include <jni.h>
#include <android/log.h>
extern "C"
JNIEXPORT jobject JNICALL
Java_my_ca_my_1gen_1lib_1pers_c_1libs_1core_RevPers_myPersInit(JNIEnv *env, jobject instance,
jobject myEntity) {
jclass myEntityClazz = env->GetObjectClass(myEntity);
/** START GET STRING **/
const char *myEntityType;
// and the get_myObjectDescription() method
jmethodID get_myObjectDescription = env->GetMethodID
(myEntityClazz, "get_myObjectDescription", "()Ljava/lang/String;");
// call the get_myObjectDescription() method
jstring s = (jstring) env->CallObjectMethod
(myEntity, get_myObjectDescription);
if (s != NULL) {
// convert the Java String to use it in C
myEntityType = env->GetStringUTFChars(s, 0);
__android_log_print(ANDROID_LOG_INFO, "MyApp", "get_myObjectDescription : %s\n",
myEntityType);
env->ReleaseStringUTFChars(s, myEntityType);
}
/** END GET STRING **/
return myEntityGUID;
}
I start it all up :
MyObjectEntity myObjectEntity = new MyObjectEntity();
myObjectEntity.set_myObjectDescription("A Good Day To Find Answers To Life's Important Questions.");
MyPers revPers = new MyPers();
Log.v("MyApp", "GUID : " + myPers.myPersInit(myObjectEntity));
The error I get is :
JNI CallObjectMethodV called with pending exception java.lang.NoSuchMethodError: no non-static method "Lmy_app/MyEntity;.get_myObjectDescription()Ljava/lang/String;"
QUESTION
How can I go about calling a method of a subclass / child class of an Java Object passed to JNI jobject, myEntity in this case?
extern "C"
JNIEXPORT jobject JNICALL
Java_myPersInit(JNIEnv *env, jobject instance, jobject myEntity)
Thank you all in advance.
How can I go about calling a method of a subclass / child class of an
Java Object passed to JNI jobject, myEntity in this case?
The question does not make sense. Objects do not have subclasses, classes do. Whatever the class of a given object happens to be defines the methods that you may invoke on that object. You seem to be trying to ask how to invoke methods of a subtype of the declared type of the native method parameter, but the premise of such a question runs counter to the actual fact that you do so exactly the same way you invoke any other instance method from JNI.
Consider: in Java, you would downcast to the subtype and then, supposing the cast succeeded, you would invoke the wanted method normally. In JNI, on the other hand, object references are not differentiated by pointed-to object type (they are all jobjects), so no casting is involved; you just go straight to invoking the wanted method normally.
Of course, a JNI error will occur if the class of the target object does not provide such a method. Whether it's a good idea to do what you describe is an entirely separate question.

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.

Categories

Resources