6

First of all, let me list the best result I could fetch. jni call java method which take a custom java interface as parameter

This does not answer mine. Let me explain my problem. I want to make a call to NDK as follows.

(1)Java -> (2)CPP -> (3)C (new thread) -> (4)CPP -> (5)Java

Code is below.

(1) Java

public interface Callback<T> {
    void success(T result);
}
private native void jniUploadAsync(String imagePath, Callback<String> callback);

jniUploadAsync(file.getAbsolutePath(), new Callback<String>() {
                                        @Override
                                        public void success(final String result) {
                                            Log.v("MyClass: result:: ", result);
                                        }
                                    });

(2) CPP

static JavaVM *jvm;
void imageUploadCallback(char *json, void *completionCallback) {
    JNIEnv *env;
    jint rs = jvm->AttachCurrentThread(&env, NULL);//create JNIEnv from JavaVM    
    jclass cbClass = env->FindClass("org/winster/test/Callback");
    jmethodID method = env->GetMethodID(cbClass, "success", "(Ljava/lang/String;)V");
    env->CallVoidMethod(static_cast<jobject>(completionCallback), method, "abcd");
}

void Java_org_winster_test_MyClass_jniUploadAsync(JNIEnv * env, jobject obj, jstring imagePath, jobject completionCallback) {
    jint rs = env->GetJavaVM(&jvm); //Cache JavaVM here
    CallMyCMethod((char *)filePath, &imageUploadCallback, &completionCallback);
}

(3) C

CallMyCMethod() //please assume that it works. The reason I need void* as the type for completionCallback is because, in ObjC implementation I use this

(4) CPP

//Call comes back to imageUploadCallback()

(5) Java

//I expect this Log.v("MyClass: result:: ", result); to be executed

Please note that, this is not a basic question about how to call Java from C++. The 2 specific points I want to resolve is, how to call the "callback" and how to invoke a method in a Java Interface implementation. I have done this for Obj-C where it is straight forward.

Community
  • 1
  • 1
Winster
  • 943
  • 10
  • 28

1 Answers1

8

(2) First of, you need to store reference to JavaVM so you will be able to obtain JNIEnv later from other thread. Also you need to get new global reference from local variable got from parameter (don't forgot to delete it when it is no longer needed, or it will cause memory leak).

static JavaVM* jvm = 0;

void Java_org_winster_test_MyClass_jniUploadAsync(JNIEnv * env, jobject obj, jstring imagePath, jobject completionCallback) {
    env->GetJavaVM(&jvm); //store jvm reference for later

    jobject globalref = env->NewGlobalRef(completionCallback);

    CallMyCMethod((char *)filePath, &imageUploadCallback, (void *)globalref);
}

(4) When using generics, native side can't know what type they are of, so everywhere you are using T you should be using Object on the JNI/C/CPP part

you are starting new thread in C. If you are willing to fire callback from that thread, you need to connect it to the java virtual machine and detach it afterwards. From what i think you do, you use the callback object only once. In that case you also need to delete global ref to it.

void imageUploadCallback(char *json, void *completionCallback) {
    JNIEnv* env;
    jvm->AttachCurrentThread(&env, NULL);

    jclass cbClass = env->FindClass("org/winster/test/Callback");
    jmethodID method = env->GetMethodID(cbClass, "success", "(Ljava/lang/Object;)V");
    jstring abcd = env->NewStringUTF("abcd");
    env->CallVoidMethod(completionCallback, method, abcd);

    env->DeleteGlobalRef(completionCallback);
    jvm->DetachCurrentThread();
}
V-master
  • 1,957
  • 15
  • 18
  • Sorry, this does not help. Of-course I cache the env; I thought it will be understood. As pointed out, the problem I am trying to solve here is; how to call the callback in Java when you have the instance with Interface as the type. Calling regular class methods and calling them from a native thread are easy things to solve. – Winster Mar 29 '17 at 04:16
  • you can't cache env it is valid only for that thread (and for duration of current jni call?) you must get it from jvm (you probably knew about it, but wrote env instead of jvm). Reading from comment on your main post, i see the error: reference to jobject you get from jni call is local, and you need to make global ref before storing it for later use – V-master Mar 29 '17 at 06:18
  • I updated the code on how I cache jvm. One possible option I am trying now is to save the callback in Java only. But I have to pass the "jobject" from CPP to C. Do you know how to do it? I guess a pointer may help me here. – Winster Mar 29 '17 at 06:34
  • I also added using global reference in jni, i forgot about that earlier. This should be the case with `use of invalid jobject` you mentioned in comment – V-master Mar 29 '17 at 06:40
  • Still not working. 2 changes to the sample code. 1) instead of (void *), I used "&" CallMyCMethod((char *)filePath, &imageUploadCallback, &globalref); 2) cast back to jobject from void * env->CallVoidMethod(static_cast(completionCallback), callback_mid, jsonString); same error "use of invalid jobject" – Winster Mar 29 '17 at 09:36
  • I had created myself quick examle (im my case i was using existing qtandroid application) https://pastebin.com/00CBp1PK and it is working like a charm. was using everything that is in this answer + glue code – V-master Mar 29 '17 at 14:38
  • Additional note for those who copy blindly: You should only detach a thread using `DetachCurrentThread`, *if it is not a Java thread*, but a C (or whatever) thread. See for example https://stackoverflow.com/a/52342198/942774 – Hendrik Nov 24 '21 at 13:34