Passing JS function to Emscripten-generated code

前端 未结 5 1519
自闭症患者
自闭症患者 2020-12-29 06:13

I have a piece of C++ code converted to JavaScript via Emscripten. I would like the converted C++ code to call back to the JavaScript code that calls it. Something like:

相关标签:
5条回答
  • 2020-12-29 06:54

    A thing that is frequently done in Emscripten is to map strong types to simple ones.

    JS:

    function callback(message) {
        alert(message);
    }
    
    var func_map = {
        0: callback
    };
    
    // C/C++ functions get a _ prefix added
    function _invoke_callback(callback_id, text_ptr) {
        func_map[callback_id](Pointer_stringify(text_ptr));
    }
    
    ccall("my_c_function", ..., 0);
    

    C++:

    // In C/C++ you only need to declare the func signature and
    // make sure C is used to prevent name mangling
    extern "C" void invoke_callback(int callback_id, const char* text);
    
    void my_c_function(int callback_id) {
        invoke_callback( callback_id, "Hello World!" );
    }
    

    And of course, you can add some glue code, so this gets very seamless.

    0 讨论(0)
  • 2020-12-29 06:57

    There is a new way of achieving your requirement which is via embind.

    Consider the following piece of C++ code.

    #include <emscripten/bind.h>
    using namespace emscripten;
    
    void cbTest(emscripten::val cb)
    {
        cb();
    }
    
    EMSCRIPTEN_BINDINGS(my_module) {
        function("cbTest", &cbTest);
    }
    

    The cbTest C++ function takes in a emscripten::val. This can be an object of any kind. For us this is a function object. This is how you will call it from JS

    var cbFunc = function() {
        console.log("Hi, this is a cb");
    }
    
    Module.cbTest(cbFunc);
    

    P.S This api is still under construction.

    0 讨论(0)
  • 2020-12-29 06:57

    I needed to write something very similar to what is described in the question. My code ended up looking like this:

    C:

    void call(void (*back)(char*)){
        back("Hello!");
    }
    

    JS:

    function back(text){
        alert(Pointer_stringify(text));
    }
    var pointer = Runtime.addFunction(back);
    var call = Module.cwrap('call', 'void', ['pointer']);
    call(pointer);
    Runtime.removeFunction(pointer);
    

    Note that the pointer returned to the callback has to be dereferenced with Pointer_stringify.

    You can find example code like this on GitHub.

    0 讨论(0)
  • 2020-12-29 07:14

    I believe the accepted answer is a bit outdated.

    Please refer to this bullet point in the "Interacting with code" emscripten tutorial.

    E.g. C:

    void invoke_function_pointer(void(*f)(void)) {
      (*f)();
    }
    

    JS:

    var pointer = Runtime.addFunction(function() { 
      console.log('I was called from C world!'); 
    });
    Module.ccall('invoke_function_pointer', 'number', ['number'], [pointer]);
    Runtime.removeFunction(pointer);
    

    This way the C-code does not need to be aware of that it is transpiled to JS and any bridges required can purely be controlled from JS.

    (code hacked into message composer; may contain errors)

    0 讨论(0)
  • 2020-12-29 07:18

    Here's what I have gathered from several posts and by looking at Emscripten bundled code:

    In C++:

    #include <iostream>
    #include <functional>
    
    extern "C" {
      void registerCallback(void(*back)(const char*));
      void triggerCallback(char* message); // for invoking it from JS, just for this example
    }
    
    // global
    std::function<void(const char*)> gCallback;
    
    void registerCallback(void(*back)(const char*)){
        gCallback = back;
    }
    
    void triggerCallback(char* message){
      if (gCallback) {
        gCallback(message);
      } else {
        std::cerr << "Cannot pass '"<< message <<"' to undefined callback\n";
      }
    }
    

    An important thing, which was missing in other posts, is to compile C++ with RESERVED_FUNCTION_POINTERS=... flag, e.g.:

    em++ -std=c++11 -s RESERVED_FUNCTION_POINTERS=20 source.cpp -s EXPORTED_FUNCTIONS="['_registerCallback','_triggerCallback']" -o try.html
    

    After loading try.html into a browser, you can execute the following JS code in its console:

    // Register a callback function
    function callback(text){ alert("In JS: "+Pointer_stringify(text)); }
    var cb = Runtime.addFunction(callback);
    _registerCallback(cb);
    
    // Invoke it with some "C string"
    var jsStr = "XOXOXO";
    var cStr = allocate(intArrayFromString(jsStr), 'i8', ALLOC_NORMAL)
    _triggerCallback(cStr);
    
    // Free Emscripten heap and release the function pointer
    _free(cStr);
    Runtime.removeFunction(cb);
    

    You should see an alert with "In JS: XOXOXO".

    0 讨论(0)
提交回复
热议问题