Question

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); 
}
Was it helpful?

Solution

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 );
}

OTHER TIPS

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.)

Here it goes (bear with me, as I hope I make sense) I got the idea from "CCHttpRequest" library for Cocos2dx,

In the JNI class, keep a Map of callbacks.

A typical JNI call would be (first the Synchronous one, which is a very simple approach),

                void requestForItems(int itemId, CCObject* target, SEL_CallFuncND selector) {

                   //U can have a small struct/class that contains 2 fields, a CCObject* and a SEL_CallFuncND) i.e, the object and its function
                   //Give this callback/target object a uniqueId, Add this pair(uniqueId, callbackObject) to the map)
                   //make sure u retain the "target" so that it stays alive until u call the callback function after the Java call returns
                   JniMethodInfo t;
                   if (JniHelper::getStaticMethodInfo(t, "com/myproj/folder/AbcManager", "requestItems",
                    "(ILjava/lang/String;)Ljava/lang/String;")) {
                    jstring id = (jstring) t.env->CallStaticObjectMethod(t.classID, t.methodID, itemId, uniqueId);
                    const char* _data = t.env->GetStringUTFChars (id, 0);
                    t.env->DeleteLocalRef(t.classID);
                    //_data >>> is the Id returned, but since we are creating "uniqueId" in this function, this won't be of that importance in a synchronous call
                    /// call this selector function
                   if (target && selector) {
                    (target->*selector)(null, null); //first argument is the sender, the next is a void pointer which u can use to send any information
                     target->release(); //as we retained it earlier
                    }
    }

}

The caller would send something like >>> this, callfuncND_selector(XyzClass::abcAction)

Now for Async call, the above function would change to

                    .
                    .
                    .
                    t.env->CallStaticVoidMethod(t.classID, t.methodID, itemId, uniqueId);
                    t.env->DeleteLocalRef(t.classID);
                  }

Now, in the call back function of this JNI call above,

say method >>>

               JNIEXPORT void JNICALL Java_com_myproj_folder_AbcManager_callingAbcRequestCallback(JNIEnv*  env, jobject thiz, jstring data) {
                    const char * _data = env->GetStringUTFChars (data, 0);

                    //here we'll do the remaining work
                    //_data >>> is the Id of the callbackObject
                    //Get the Object from the Map for thie Id
                    // call this selector function
                    if (callbackObject->getTarget() && callbackObject->getSelector()) {
                    (callbackObject->getTarget()->*callbackObject->getSelector())(null, null); 
                    }
                    //Now you can delete the object and remove the map entry
        }

What we did was that we made the classes, that contained the functions we needed to call, implement an Interface and we passed them to the JNI then on returning from Java we called the method we made those objects implement.

This could also work for you but you still need to keep them in a map so that you can identify which object's method to call.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top