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

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

    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.

    来源:https://stackoverflow.com/questions/23223058/jni-call-java-method-which-take-a-custom-java-interface-as-parameter



最新帖子

最新内容

  • S

    This question already has answers here:

    Python giving FileNotFoundError for file name returned by os.listdir (2 answers)

    Closed 8 months ago.

    I am trying to read a CSV file. I have two CSVs under two folders. I get the CSV file but when I try to read the content, it says that I dont have such file or directory.

    import os import csv def scanCSVFile(folder, output_file_name): myfile = open(output_file_name, "a") files = os.listdir(folder) for file in files: if file.endswith('.csv'): #gives two csv files with open(file) as csvfile: csvreader = csv.reader(csvfile) next(csvreader) for line in csvreader: print (line) myfile.close() def openCSV(path): scanCSVFile("./src/goi","goi.yml") scanCSVFile("./src/kanji","kanji.yml") openCSV('.')

    Error:

    C:\Users\Peace\Documents\LMS>python csvToYaml.py Traceback (most recent call last): File "csvToYaml.py", line 26, in <module> openCSV('.') File "csvToYaml.py", line 24, in openCSV scanCSVFile("./src/goi","goi.yml") File "csvToYaml.py", line 14, in scanCSVFile with open(file) as csvfile: FileNotFoundError: [Errno 2] No such file or directory: 'Pro.csv'

    read more
  • S

    Below is sample of type of data I have:

    How can I achieve in Elasticsearch something like below with SQL query. My results must include actual data not just a count of documents that fulfill my requirement:

    SELECT firstName, secondName, Country, City, Street, postalCode, House, Phone, Fax, count(*) FROM my_bucket WHERE Country = 'GBR' GROUP BY firstName, secondName, Country, City, Street, postalCode, House, Phone, Fax ORDER BY firstName, secondName, Country, City, Street, postalCode, House, Phone, Fax HAVING count(*) > 1;

    Here is what I have tried:

    { "size" : 0, "query": { "bool": { "must": [ { "term": { "Country": { "value": "GBR" } } } ] } }, "aggs" : { "grouped_firstName" : { "terms" : { "field" : "firstName", "size" : 100000 }, "aggs": { "grouped_secondName": { "terms": { "field": "secondName", "size": 100000 }, "aggs": { "grouped_Country": { "terms": { "field": "Country", "size": 100000 }, "aggs": { "grouped_City": { "terms": { "field": "City", "size": 100000 }, "aggs": { "grouped_Street": { "terms": { "field": "Street", "size": 100000 }, "aggs": { "grouped_postalCode": { "terms": { "field": "postalCode", "size": 100000 }, "aggs": { "grouped_House": { "terms": { "field": "House", "size": 100000 }, "aggs": { "grouped_Phone": { "terms": { "field": "Phone", "size": 100000 }, "aggs": { "grouped_Fax": { "terms": { "field": "Fax", "size": 100000 } }, "poi_mds_filter": { "bucket_selector": { "buckets_path": { "count_over_one": "grouped_Fax" }, "script": "params.count_over_one > 1" } } } } } } } } } } } } } } } } } } } }

    Results from my attempt gives me back only counts: "doc_count": 26772 But what I need is full list of data (except ID column) which grouped count is over 1.

    read more
  • S

    you can use Forms

    <form name="f" action="" method="POST"> <div> <select id="kleidwma" name="variable" class="form-control selectpicker kleidwmata" data-size="10" data- style="btn-white" name="guard_kleidwmata" required > <option value="" >&nbsp;</option> <option value="12">12 MS</option> <option value="14">14 KETE</option> <option value="15">15 ARRIKTON</option> <option value="16">16 KETE</option> <option value="22">22 KETE</option> </select> <input type="submit" value="submit" /> </div> </form>

    in PHP

    <?php if(isset($_POST['variable'])) { echo $_POST['variable']; } ?>

    read more
  • S

    I'm having some trouble passing the value of some HTML elements to PHP variable. I have a PHP web app where I give the users some drop down option. What I want to do is after the user has selected the option I give than to click on a button and make some calculations based on his/her selection.

    I have tried using the POST and GET method but because I'm new to php I probably did it wrong. I also try with ids but no results.

    Here is the HTML code The 1st dropdown menu :

    <div> <select id="kleidwma" onchange="kleidwmata_change(this.value);kwdikos();ypsos();" class="form-control selectpicker kleidwmata" data-size="10" data- style="btn-white" name="guard_kleidwmata" required > <option value="" >&nbsp;</option> <option value="12">12 MS</option> <option value="14">14 KETE</option> <option value="15">15 ARRIKTON</option> <option value="16">16 KETE</option> <option value="22">22 KETE</option> </select> </div>

    The 2nd dropdown menu :

    <div> <select id="sigkratisi" onchange="kwdikos()" name="sigkratisi" class="form-control sigkratisi" required> <option value="" ></option> <option value="metalliki" >Μεταλλική</option> <option value="xilogonia" >Ξυλογωνία</option> </select> </div>

    Here is the button to be clicked after the selection has been made :

    <div class="row" style="margin-top:20px; margin-left: 235px;"> <div class="col-md-7"> <input type="hidden" name="tmp_calculate" value="pending" /> <button onclick="calculate()" type="button" class="btn btn-success" > <?php echo "Υπολογισμός";?></button> </div> </div>

    And finally here is the function that is called :

    function calculate() { $var1 = sigkratisi; $var2 = kleidwma; "code to be executed base on the var1 and var2" }

    I hope it's clear enough. Thanks in advance for your time

    read more
  • S

    jQuery Datepicker has a onSelect event:

    Called when the datepicker is selected. The function receives the selected date as text and the datepicker instance as parameters. this refers to the associated input field.

    Demo:

    <pre class="snippet-code-js lang-js prettyprint-override">``` var chartData1 = []; generateChartData(); function generateChartData() { var firstDate = new Date(); firstDate.setDate(firstDate.getDate() - 500); firstDate.setHours(0, 0, 0, 0); for (var i = 0; i < 500; i++) { var newDate = new Date(firstDate); newDate.setDate(newDate.getDate() + i); var a1 = Math.round(Math.random() * (40 + i)) + 100 + i; var b1 = Math.round(Math.random() * (1000 + i)) + 500 + i * 2; chartData1.push({ "date": newDate, "value": a1, "volume": b1 }); } } var chart = AmCharts.makeChart("chartdiv", { "type": "stock", "extendToFullPeriod": false, "dataSets": [{ "title": "first data set", "fieldMappings": [{ "fromField": "value", "toField": "value" }, { "fromField": "volume", "toField": "volume" }], "dataProvider": chartData1, "categoryField": "date" }], "panels": [{ "showCategoryAxis": false, "title": "Value", "percentHeight": 70, "stockGraphs": [{ "id": "g1", "valueField": "value", "comparable": true, "compareField": "value", "balloonText": "[[title]]:<b>[[value]]</b>", "compareGraphBalloonText": "[[title]]:<b>[[value]]</b>" }], "stockLegend": { "periodValueTextComparing": "[[percents.value.close]]%", "periodValueTextRegular": "[[value.close]]" } }, { "title": "Volume", "percentHeight": 30, "stockGraphs": [{ "valueField": "volume", "type": "column", "showBalloon": false, "fillAlphas": 1 }], "stockLegend": { "periodValueTextRegular": "[[value.close]]" } } ], "chartScrollbarSettings": { "graph": "g1" }, "chartCursorSettings": { "valueBalloonsEnabled": true, fullWidth: true, cursorAlpha: 0.1 }, "periodSelector": { "position": "left", }, "dataSetSelector": { "position": "left" } }); chart.addListener('rendered', function(event) { var dataProvider = chart.dataSets[0].dataProvider; $(".amChartsPeriodSelector .amChartsInputField").datepicker({ "dateFormat": "dd-mm-yy", "minDate": dataProvider[0].date, "maxDate": dataProvider[dataProvider.length - 1].date, "onClose": function() { $(".amChartsPeriodSelector .amChartsInputField").trigger('blur'); }, "onSelect": function(dateText) { console.log('date:', dateText); } }); });

    <pre class="snippet-code-css lang-css prettyprint-override">```
    html, body {
    width: 100%;
    height: 100%;
    margin: 0px;
    font-family: Verdana;
    }

    #chartdiv {
    width: 100%;
    height: 100%;
    }

    <pre class="snippet-code-html lang-html prettyprint-override">``` <!-- jQuery stuff --> <link rel="stylesheet" media="all" href="https://code.jquery.com/ui/1.12.0/themes/ui-lightness/jquery-ui.css" /> <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js" integrity="sha256-eGE6blurk5sHj+rmkfsGYeKyZx3M4bG+ZlFyA7Kns7E=" crossorigin="anonymous"></script> <!-- amCharts --> <script src="https://www.amcharts.com/lib/3/amcharts.js"></script> <script src="https://www.amcharts.com/lib/3/serial.js"></script> <script src="https://www.amcharts.com/lib/3/amstock.js"></script> <div id="chartdiv"></div>

    read more

推荐阅读