Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jni call java method which take a custom java interface as parameter

I'm working on a plugin project on cocos2d-x platform , I'd like to write some c++ wrapper interface to invoke java method through JNI from jar SDK . I know how to use JNI to invoke a static java method, but I'm confused by the interface parameter in the java function. I hava a cpp function pointer to handling callbacks:

typedef void (* MyCallback)(int responseCode, string arg1, set<string> arg2);

and I want to write a cpp wrapper method like :

static void MyCpp::setTags(set<string> tags, MyCallback callback) //it use `JNI` to invoke java method setTags(Context context, Set<String> tags, TagCallback callback).

The java method I want to invoke in the wrapper is

public static void setTags(Context context, Set<String> tags, TagCallback callback)

and the TagCallback is an interface for API user to implement. So, is it possible to get TagCallback finally callback to MyCallback function? in other words , can I use jni to convert a cpp function pointer to java interface? Thanks for your patience .

EDIT: here is how to use setTag if user want to use java only:

public static void setTags(context, tags, new TagCallback{
    @Override
    public void callback(int arg0, String arg1, Set<String> arg2) {
            // TODO Auto-generated method stub
        }
})

I'd like my SDK user to use my cpp wrapper method like this:

void setTagCallback(int responseCode, string arg1, set<string> arg2){
   //users handle callback themselves.
}

void someOtherMethodInvokeTheCppWrapperMethod(){
    MyCallback callback = setTagCallback;
    set<string> tags;
    MyCpp::setTags(tags,callback); 
}
like image 679
johnMa Avatar asked Apr 22 '14 14:04

johnMa


2 Answers

Well firstly you'll need to build a class that can wrap the native C++ function pointer in a TagCallback compatible base class:

public class NativeTagCallback : TagCallback
{
    protected long      cppCallbackPtr;

    public NativeTagCallback( long callbackPtr )
    {
        cppCallbackPtr = callbackPtr;
    }

    public native void NativeCallback( long callbackPtr, int arg0, String arg1, Set<String> arg2 );

    public void callback(int arg0, String arg1, Set<String> arg2) 
    {
        NativeCallback( cppCallbackPtr, arg0, arg2, arg2 );
    }
}

The native code would be defined as follows:

extern "C" jvoid Java_com_wrapper_NativeTagCallback_NativeCallback( JNIEnv* pEnv, jobject jCaller, jlong cppCallbackPtr, jint arg0, jstring arg1, jobject arg2 )
{
    MyCallback cppCallback = (MyCallback)cppCallbackPtr;
    const char* pCString = pEnv->GetStringUTFChars( arg1, 0);
    string arg1Str( pCString );
    pEnv->ReleaseStringUTFChars( arg1, pCString );

    set< string > arg2Set = ConvertJavaSetToCPPSet( arg2 );  // Perform your java to CPP set conversion here.

    cppCallbackPtr( (int)arg0, arg1Str, arg2Set );
}

Then you would create the relevant class and pass it to your function from C++ as follows:

void MyCpp::setTags(set<string> tags, MyCallback callback)
{
    extern __thread JNIEnv* gpEnv;

    // Get the setTags function.
    jclass      jWrapperClass                   = gpEnv->FindClass( "com/wrapper/cocoswrapper" ); // Insert the correct class name here.    
    jmethodID   jWrapperSetTag                  = gpEnv->GetStaticMethodID( jWrapperClass, "setTags", "(Landroid/content/Context;Ljava/util/Set;Lcom/wrapper/TagCallback)V;" );

    // Get the TagCallback related function
    jclass      jNativeTagCallbackClass         = gpEnv->FindClass( "com/wrapper/NativeTagCallback" );
    jclass      jNativeTagCallbackConstructor   = gpEnv->GetMethodID( jNativeTagCallbackClass, "<init>", "(J)V" );
    jobject     jNativeTagCallbackObject        = gpEnv->NewObject( jNativeTagCallbackClass, jNativeTagCallbackConstructor, (jlong)callback)

    // Make function call.
    gpEnv->CallStaticVoidMethod( jWrapperClass, jWrapperSetTag, jAndroidContext, tags.GetJNIObject(), jNativeTagCallbackObject );
}
like image 78
Goz Avatar answered Oct 03 '22 07:10

Goz


I would say that you need a (private) Java class implementing TagCallback which stores the C++ function pointer and implements the Java-to-C++ callback adaptation:

private class NativeTagCallback implements TagCallback {
  private long _callbackPointer;

  private NativeTagCallback(long cb) { _callbackPointer = cb; }

  @Override
  public native void callback(int arg0, String arg1, Set<String> arg2);
}

In the C++ implementation of NativeTagCallback.callback(), you fetch and convert the arguments from Java String and Set<String> objects to native C++ ones, then use the JNI GetFieldID() and GetLongField() functions to pull the _callbackPointer out of the jobject objectOrClass argument passed to your JNI C++ function.

Once you have the _callbackPointer, you can cast it to a C++ function pointer and call it.

To use the adapter class, in MyCpp::setTags you would use JNI FindClass(), GetMethodID() and NewObject() to create an instance of NativeTagCallback, passing (long)(void *)callback as cb to the constructor. (This assumes that your compiler generates function pointers which can fit into 64 bits. This should be pretty generally true for a free function pointer -- versus a method pointer -- but is worth checking with a quick test program.) Then you pass that instance to the Java setTags() method as its `callback`` parameter.

It is very important to keep NativeTagCallback private, since it can be used to call arbitrary addresses! If you want to be more paranoid but harder to debug, you could keep a C++ id-to-function map and only store ids in NativeTagCallback. (This would limit the callable addresses to ones that are currently being used for actual callbacks. Ids can be unregistered by native void callback(), but the map needs to be thread-safe.)

like image 34
bks Avatar answered Oct 03 '22 07:10

bks