簡體   English   中英

jni調用以自定義Java接口作為參數的java方法

[英]jni call java method which take a custom java interface as parameter

我正在cocos2d-x平台上的插件項目上工作,我想編寫一些c ++包裝器接口,以從jar SDK通過JNI調用Java方法。 我知道如何使用JNI調用靜態java方法,但是我對java函數中的interface參數感到困惑。 我有一個cpp函數指針來處理回調:

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

我想編寫一個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); 
}

首先,您需要構建一個類,該類可以將本地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 );
}

我要說的是,您需要一個實現TagCallback的(私有)Java類,該類存儲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);
}

NativeTagCallback.callback()的C ++實現中,您從Java StringSet<String>對象獲取參數並將其轉換為本地C ++對象,然后使用JNI GetFieldID()GetLongField()函數將_callbackPointer移出傳遞給您的JNI C ++函數的jobject objectOrClass參數。

一旦有了_callbackPointer ,就可以將其_callbackPointer轉換為C ++函數指針並進行調用。

要使用適配器類,請在MyCpp::setTags使用JNI FindClass()GetMethodID()NewObject()創建NativeTagCallback的實例,將(long)(void *)callback作為cb傳遞給構造函數。 (這假設您的編譯器生成的函數指針可以容納64位。對於自由函數指針(與方法指針相比),這通常應該是正確的,但值得通過快速測試程序進行檢查。)然后,您將其傳遞給Java setTags()方法的實例作為其“ callback”參數。

保持NativeTagCallback私有非常重要,因為它可以用於調用任意地址! 如果您想變得更偏執,但更難調試,則可以保留C ++ id到功能的映射,並且僅將id存儲在NativeTagCallback (這會將可調用地址限制為當前用於實際回調的地址。可以通過native void callback()取消注冊ID,但是映射必須是線程安全的。)

一切順利(我希望自己能理解,但請耐心等待),我從Cocos2dx的“ CCHttpRequest”庫中得到了這個想法,

在JNI類中,保留一個回調映射。

一個典型的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
                    }
    }

}

調用方將發送類似>>>的內容,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返回時,我們調用了使這些對象實現的方法。

這也可能對您有用,但是您仍然需要將它們保留在地圖中,以便可以標識要調用的對象的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM