Implementing Android Event Handlers Using C++ - android

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 call JNI Function from a method in C library

I'm currently developping an android app with android studio, for this project i need to use a custom library written in c/c++ .
So i in order to do that i needed to use NDK.
The library contain methods that i need to implements in order to have access to specifics fonctions in android
My question is : how can I call my jni method inside the method existing in c
which will call a java method to store session key in the android system
So for a pratical exemple since it's maybe not clear:
In a file called lib_exemple.c i have three methods
the first one is the initialisation call to the library (jni to library c) -->working
JNIEXPORT void JNICALL
Java_com_example_libExemple_Libs_ExempleLib_lib_1Exemple_1init
(JNIEnv *env, jobject instance) {
lib_Example_init('0'); // c func of the library
}
then the second one is the jni who call a java method (jni to java) -> working
JNIEXPORT jint
JNICALL Java_com_example_libExemple_Libs_ExempleLib_lib_1Exemple_1store_1session_1key
(JNIEnv * env, jobject jobject1, jbyte jbyte1, jchar jchar1, jbyte jbyte2){
jclass clazz = (*env)->FindClass(env, "com/example/libExemple/Libs/ExempleLib");
jmethodID mCurrentActivityId = (*env)->GetMethodID(env, clazz, "KeyStoreSessionKey", "(BCB)I");
jint result = (*env)->CallIntMethod(env, jobject1, mCurrentActivityId, jbyte1, jchar1, jbyte2);
return result;
}
and the third method is the one the library c have in it (library C to jni)
int lib_Exemple_store_session_key(uint8_t Session, P_KEY_ST_T pKey, uint8_t keyType) {
//i want to call jni func here , so the librairy can access the native android function
return 0;
}
then to configure the ndk in a file called ExempleLib.java i have defined
static {
System.loadLibrary("LibExemple");
}
then the prototype of the initialisation of the library
public native void libExemple_init();
the prototype of the first method in lib_exemple.c
public native int lib_Exemple_store_session_key(byte pKey, char key_length, byte keyIndex);
and the java fonction called by it
protected int KeyStoreSessionKey(byte pKey, char key_length, byte keyIndex) {
...
}
my mainActivity contain
ExempleLib LibFunc = new ExempleLib();
Log.d(TAG, "init lib" );
LibFunc.lib_Exemple_init();
My goal is that after initialising my library and call an init function , it can call the first method in the second method of lib_exemple.c (the one used in the library )
thanks
EDIT :
The solution of my problem is when i initialize the lib , i need to save jvm
so i can access the JNIenv in the library method directly
jobject Savedinstance = NULL;
static JavaVM *cachedJVM;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
cachedJVM = jvm;
return JNI_VERSION_1_6;
}
JNIEnv *AttachJava() {
JavaVMAttachArgs args = {JNI_VERSION_1_6, 0, 0};
JNIEnv *java;
(*cachedJVM)->AttachCurrentThread(cachedJVM, &java, &args);
return java;
}
JNIEXPORT jint JNICALL
Java_com_example_vgosselin_libExemple_Libs_ExempleLib_lib_1init(JNIEnv *env,
jobject instance) {
Savedinstance = instance;
void *element = 0;
jint result;
result = lib_Exemple_init_();
return result;
}
so i can call the java method in
int lib_Exemple_store_session_key(uint8_t Session, P_KEY_ST_T pKey, uint8_t keyType) {
JNIEnv *NewEnv = AttachJava();
jclass clazz = (*NewEnv)->FindClass(NewEnv,"com/example/vgosselin/libExemple/Libs/ExempleLib");
jmethodID mCurrentActivityId = (*NewEnv)->GetMethodID(NewEnv,clazz,"KeystoreStoreKey","([BCB)I");
jint result;
jbyteArray Data = 0;
jchar Key = 0;
jbyte Type = 0;
result = (*NewEnv)->CallIntMethod(NewEnv, Savedinstance, mCurrentActivityId, Data, Key, Type);
return result;
}
and now i don't need the method in jni syntax anymore
JNIEXPORT jint JNICALL Java_com_example_libExemple_Libs_ExempleLib_lib_1Exemple_1store_1session_1key

JNI: Segfaults when calling Java callbacks from C

I a Library in C that I'm leveraging for an Android application. This library has an audio stream that it occasionally flushes. When this happens it calls a write callback function of my design.
My intent is to have that C callback call a method on a specific Java Object which will handle stuff with the strem.
Currently I have code like so:
methodID compressionHandler=0;
jobject compressionHandlerClass;
int audioBufferChunkSize;
static JavaVM *gJavaVM;
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
gJavaVM = vm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_my_code_init(JNIEnv* env, jobject obj, /*classpath of the class we want to call against*/jstring compressedAudioHandlerPath, /*class instance we want to call against*/jobject callbackClass) {
......
// this is a global ref as per:
//http://stackoverflow.com/questions/14765776/jni-error-app-bug-accessed-stale-local-reference-0xbc00021-index-8-in-a-tabl
compressionHandlerClass = (*env)->NewGlobalRef(env,callbackClass);
// name of the class
const char *classLocation;
// convert jString to c String
classLocation = (*env)->GetStringUTFChars( env, compressedAudioHandlerPath , NULL ) ;
// tmp variable for holding the class location, relates to the above issue with garbage collection
jclass clazz = (*env)->FindClass(env, classLocation);
// the actual method that we want to call, this gets used in the writeCallback
compressionHandler = (*env)->GetMethodID(env, clazz, "handleCompressedAudio", "([B)V");
......
}
The callback method looks like so:
void writeCallback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data) {
JNIEnv *env;
int isAttached = 0;
if ((status = (*gJavaVM)->GetEnv(gJavaVM, (void**)&env, JNI_VERSION_1_6)) < 0) {
if ((status = (*gJavaVM)->AttachCurrentThread(gJavaVM, &env, NULL)) < 0) {
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
isAttached = 1;
}
if(*env!=0 && compressionHandler!=0){
jbyteArray arr = (*env)->NewByteArray(env,bytes);
(*env)->SetByteArrayRegion(env,arr, 0, bytes, (jbyte*)buffer);
(*env)->CallVoidMethod(env,compressionHandlerClass, compressionHandler,arr);
free(arr);
free(env);
free(isAttached);
}
}
I'm getting crashes at the CallVoidMethod, that signature of which is an interface implemented by whatever object I pass in:
public interface CompressedAudioHandler {
void handleCompressedAudio(byte[] buff);
}
I suspect that I am improperly attaining/keep references to these objects, but I haven't found a great way to handle that. Any advice on how I can more correctly handle this?

JNI Method not found

I have been receiving this error for my JNI code while I tried find the method ,using GetMethodID, my Java method is in an Interface.
Here is my interface
public interface printReader
{
public printImg readerPrint(String selectedName) throws Exception;
}
Native code
WprintImgIMPL.h
class WprintImgIMPL: public IWprintReader {
public:
WprintImgIMPL(JNIEnv *env, jobject obj);
~WprintImgIMPL(void);
virtual WprintImg readerPrint(char* readerName) ;
.....
.....
private:
JNIEnv *m_Env;
jobject m_jObj;
}
WprintImgIMPL.cpp
WprintImg WprintImgIMPL::readerPrint(char* readerName) {
jclass cls = m_Env->GetObjectClass (m_jObj);
jmethodID mid = m_Env->GetMethodID (cls, "readerPrint", "(Ljava/lang/String;)Lcom/site/name/printImg;");
.......
.......
}
Java code
public class printReaderIMPL implements printReader {
static final String DEBUG_TAG = "";
android.net.wifi.WifiManager.MulticastLock lock;
Context _context;
public printReaderIMPL (Context context) {
_context = context;
}
#Override
public printImg readerPrint(String selectedName) throws Exception {
Log.e(DEBUG_TAG, "readerPrint");
}
}
Constructor/destructor
WprintImgIMPL(JNIEnv *env, jobject obj){
m_Env = env;
m_jobj = env->NewGlobalRef(obj);
}
~WprintImgIMPL(void) {
m_Env->DeleteGlobalRef(m_jobj);
}
Error: GetMethodID: method not found: Lcom/site/name/NativeCode;.printImg:(Ljava/lang/String;)Lcom/site/name/printImg;
Signature are checked twice , after failure I generated again using Javap tool .
Thank you if you can input /comment and help in fixing this bug.
It is invalid to save a JNIEnv* across JNI method calls. It's only valid for the duration of the JNI method you are currently in. Out of the blue, e.g. in arbitrary C++ code, you need to call AttachCurrentThread() to get a current valid JNIEnv*.
However you can cache the methodID. There no need to look it up every time. Look it up in your constructor.

Fill java class members with c structure members

I have a code something similar to this
struct time
{
long milliscnds;
int secs;
}
In my java file , I had something like this
class jtime
{
long millscnds;
int secs;
}
new jtime time = new jtime();
public int native getTimeFromC(object time);
in native class
getTimeFromc(JNIEnv* env, jobject thiz,jobject jtime)
{
struct time *mytime = getTime();
now to fill the jtime with mytime
}
Suggestions please?
You can simplify your Java class and the required JNI code.
Currently, your native method has some issues:
public int native getTimeFromC(object time);
Parameter is Object but should be jtime.
Return value doesn't seem to have a purpose.
Since the method completely initializes a jtime object, why not create and return a jtime object?
This class definition has a factory method to create the object and a constructor that moves some the initialization work over from the JNI side.
public class jtime {
long millscnds;
int secs;
public jtime(long millscnds, int secs) {
this.millscnds = millscnds;
this.secs = secs;
}
public native static jtime FromC();
}
The factory method can be implemented like this:
JNIEXPORT jobject JNICALL Java_jtime_FromC
(JNIEnv * env, jclass clazz)
{
struct time *mytime = getTime();
jmethodID ctor = (*env)->GetMethodID(env, clazz, "<init>", "(JI)V");
jobject obj = (*env)->NewObject(env, clazz, ctor, mytime->milliscnds, mytime->secs);
return obj;
}
Tip: The javap tool is like javah but shows the signatures of non-native methods. Using javap -s jtime, you can see the signature of the constructor.
Something like the following:
void getTimeFromc(JNIEnv* env, jobject thiz, jobject jtime)
{
struct time *mytime = getTime();
// now to fill the jtime with mytime
jclass jtimeClazz = (*env)->GetObjectClass(jtime); // Get the class for the jtime object
// get the field IDs for the two instance fields
jfieldID millscndsFieldId = (*env)->GetFieldID(jtimeClazz, "milliscnds", "J"); // 'J' is the JNI type signature for long
jfieldID secsFieldId = (*env)->GetFieldID(jtimeClazz, "secs", "I"); // 'I' is the JNI type signature for int
// set the fields
(*env)->SetLongField(jtime, millscndsFieldId, (jlong)mytime.milliscnds);
(*env)->SetIntField(jtime, secsFieldId, (jint)mytime.secs);
}
Ideally you should cache the values of millscndsFieldId and secsFieldId as they won't change during execution (and you could also cache jtimeClazz if you NewGlobalRef it).
All JNI functions are documented here: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html

JNI call fail on non static methods

I'm trying to call a non static java method from C++ using the JNI on an Android project, but GetMethodID always return NULL.
this is my code:
void Java_com_kungfu_rabbit_KungFuRabbitActivity_nativeOnCreate(JNIEnv *env, jobject obj)
{
// this code works fine
jclass cls = env->FindClass("com/kungfu/rabbit/KungFuRabbitActivity");
jmethodID mid = env->GetStaticMethodID(cls, "foo", "()V");
env->CallStaticVoidMethod(cls, mid);
// this one fails:
jclass cls = env->GetObjectClass(obj);
jmethodID mid = env->GetMethodID(cls, "foo2", "()V");
env->CallVoidMethod(obj, mid);
}
I'm calling this native function from the class that extends Activity. foo is a public static void function, and foo2 is a public void function.
I can't understand why it fails...
Can anyone help me to understand?
Thanks in advance

Categories

Resources