JNI调用Java方法,该方法将自定义的Java接口作为参数传递

5

我正在cocos2d-x平台上开发一个插件项目,我想编写一些c++的包装接口以通过JNIjar SDK调用java方法。我知道如何使用JNI调用静态java方法,但是我对java函数中的接口参数感到困惑。 我有一个处理回调的cpp函数指针:

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

我想编写一个类似于下面这样的Cpp包装器方法: ```cpp ```
static void MyCpp::setTags(set<string> tags, MyCallback callback) //it use `JNI` to invoke java method setTags(Context context, Set<String> tags, TagCallback callback).

我想在包装器中调用的Java方法是

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

TagCallback是API用户实现的接口。 那么,是否可能最终将TagCallback回调到MyCallback函数?换句话说,我可以使用jni将cpp函数指针转换为java接口吗? 感谢您的耐心。

编辑: 以下是仅使用Java时如何使用setTag

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

我希望我的SDK用户能够像这样使用我的cpp包装方法:
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); 
}

请提供一个用户代码示例和整个系统的调用序列。 - Tom Blodget
@TomBlodget 嗨 Tom,我已经编辑了我的答案。 - johnMa
我建议您阅读以下内容:https://dev59.com/sGw15IYBdhLWcg3wO5OXhttps://dev59.com/11nUa4cB1Zd3GeqPWgbQ当我们做类似这样的事情时,我们保留了一个全局函数指针,并在Java调用返回时调用它。当然,对于并行调用,这种方法不起作用。您还可以使用回调(函数指针)的映射表,为它们分配唯一的键,并将它们发送到Java方法中。成功从Java方法返回后(该方法也应返回此键),您可以使用该键来调用关联的回调函数。 - Syed Mauze Rehan
@Al-mo,感谢您的回复。我已经阅读了很多帖子,包括您提供的这两个。正如您所说,它们在并行场景下不起作用。对于您的建议,似乎需要另一个.jar文件?如果您能提供一些代码示例,那将是非常有帮助的。 - johnMa
3个回答

4
首先,您需要构建一个类,可以将本地C++函数指针包装在与TagCallback兼容的基类中:
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 );
    }
}

本地代码将会被定义如下:
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 );
}

然后您将创建相关的类,并按以下方式从C ++将其传递给函数:
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 );
}

我已经解决了,但是和你的答案有点不同。 - johnMa

1
我认为您需要一个(私有)Java类来实现TagCallback,该类存储C++函数指针并实现Java到C++的回调适配:
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);
}

在C++实现的NativeTagCallback.callback()中,你需要从Java的StringSet<String>对象中提取并转换参数为本机C++类型,然后使用JNI的GetFieldID()GetLongField()函数从传递给JNI C++函数的jobject objectOrClass参数中提取_callbackPointer
获取_callbackPointer后,你可以将其转换为C++函数指针并调用它。
要使用适配器类,在MyCpp::setTags中,你需要使用JNI的FindClass()GetMethodID()NewObject()来创建NativeTagCallback的实例,并将(long)(void *)callback作为cb参数传递给构造函数。(假设你的编译器生成的函数指针可以适应64位。这对于自由函数指针(与方法指针相比)通常是正确的,但最好进行快速测试以确认。)然后将该实例作为`callback`参数传递给Java的setTags()方法。
非常重要的是将 NativeTagCallback 保持私有,因为它可以用于调用任意地址!如果你想更加谨慎但难以调试,你可以保留一个 C++ 的 ID 到函数映射,并且只在 NativeTagCallback 中存储 ID。(这将限制可调用的地址仅限于当前用于实际回调的地址。ID 可以通过 native void callback() 注销,但映射需要线程安全。)

我应该把私有的NativeTagCallback类放在哪里?我还想知道当将NativeTagCallback实例作为setTags()方法的callback参数传递时会发生什么。很抱歉我不是很熟悉Java,您能在这里解释一下吗? - johnMa

0

这里是关于编程的内容(请耐心听我讲,希望我表达的清楚明白) 我从Cocos2dx的"CCHttpRequest"库中得到了灵感。

在JNI类中,保持回调函数的Map。

一个典型的JNI调用是(首先是同步调用,这是一种非常简单的方法),

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

}

调用者会发送类似于 >>> this, callfuncND_selector(XyzClass::abcAction) 的内容

现在对于异步调用,上述函数将会改变为

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

现在,在上述JNI调用的回调函数中,

说方法 >>>

               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
        }

我们所做的是,我们制作了包含需要调用功能的类实现一个接口,并将它们传递给JNI,然后在从Java返回时我们调用了这些对象实现的方法。
这对你也可能有用,但你仍然需要将它们保存在映射中,以便您可以确定要调用哪个对象的方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接