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);
}
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 );
}
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With