【问题标题】:Passing a C++ function to a javascript function in emscripten将 C++ 函数传递给 emscripten 中的 javascript 函数
【发布时间】:2013-12-23 15:11:58
【问题描述】:

我正在学习 emscripten 并试图更好地理解它。据我了解,它主要用于将现有 C/C++ 代码移植到 Web 客户端(浏览器)并从 JavaScript 调用 C/C++ 代码的用例。

但我想知道是否可以将 C++ 和 Emscripten 用于网页(注意:这更多是出于好奇 - 我知道目前没有太多好的理由这样做)。我设法从 C++ 调用 Javascript 函数并将字符串、int、double 等类型的参数传递给它们。但我缺少的是:从 C++ 调用 Javascript 函数并将 C 或 C++ 函数作为句柄传递。举个简单的例子:我将如何在纯 C++ 中编写以下 Javascript 代码?

var myfun = function() { /* do something meaningful here */ }
document.onload(myfun);

【问题讨论】:

    标签: javascript c++ emscripten


    【解决方案1】:

    我不确定 emscripten,但总而言之,我知道您需要知道的是如何将 C++ 函数作为句柄传递给另一个 C++ 函数。我希望我能帮上忙。

    JavaScript、PHP 和其他更灵活的语言允许传递函数对象。在 C 和 C++ 中略有不同,您必须将 函数指针 作为参数传递给其他函数。在 C 中,它的名称是回调,而不是句柄。

    例如:

    /* This function takes a single callback as a parameter. */
    //here we say that the parameter, that we name numberSource, is a function that receives no parameters itself (void), and return an int
    void printNumber(int (*numberSource)(void)) {
        printf("%d", numberSource());
    }
    
    /* A possible callback */
    int oneExampleFunction(void) {
        return 100;
    }
    
    /* Another possible callback. */
    int otherExampleFunction(void) {
        return 200;
    }
    
    /* This is how we would call printNumber with three different callbacks. */
    
    //with "&" we are referencing the memory address of the function, 
    //since thats what printNumber is expecting
    printNumber(&oneExampleFunction);
    printNumber(&otherExampleFunction);
    printNumber(&rand); //where are using rand(), a system function, that works as well. 
    

    为参数创建自定义类型是一种常见的做法,因此您不需要使用像int (*numberSource)(void) 这样丑陋的东西。它会是这样的:

    //Function pointer called CallbackType that takes a float
    //and returns an int
    typedef int (*NameYouWantForTheType)(void);  
    

    所以 printNumber 函数应该是这样的:

    void printNumber(NameYouWantForTheType numberSource ) {
        printf("%d", numberSource());
    }
    

    所以,就你而言, 如果要翻译这段 JS 代码

    var myfun = function() { /* do something meaningful here */ }
    document.onload(myfun);
    

    对于 C,你有一个名为“document”的 C 对象,它接收一个执行其他操作的函数,你的 C 代码将是:

    void myfun (void) {
      /* do something meaningful here */
    }
    
    document.onload(&myfun);
    

    【讨论】:

      【解决方案2】:

      这是我很久以前在 C 代码中修改 Emscripten 时使用的东西:

      void myfun(void(*f)(void)) { (*f)() }
      

      然后是 JavaScript:

      var theparty = Runtime.addFunction(function() { print("Will there be confetti?") });
      Module.ccall("myfun", "number", ["number"], [theparty]);
      Runtime.removeFunction(theparty); // output => Will there be confetti?
      

      我总是删除执行后不再需要的函数以保留内存。 这是使代码位一起工作的简单且无缝的方法。除了打印信息之外,您显然可以修改它来做任何您想做的事情。 :P

      【讨论】:

        【解决方案3】:

        TL;DR;

        我写了一个库:js-bind,它接受任意数量的参数来轻松做到这一点:

        using namespace std::placeholders;
        using emscripten::val;
        
        // First find the HTML object to attach the event to
        auto clickme_btn = val::global("document").call<val>("getElementById", string("clickme_btn"));
        
        // Bind the event handler for click
        auto onclick = [](val event){ cout << "hello world ! " << endl; };
        clickme_btn.set("onclick", js::bind(onclick, _1));
        

        这个库是一些基于下面解释的宏元编程。

        详细回答:

        你有不同的可能性,比如 emscripten ccall,但我认为更容易使用的是 Embind。

        例如,从 C++ 中获取 XMLHttpRequest 的绑定事件处理程序。

        要启用它,您必须编译:--bind -s NO_EXIT_RUNTIME=1

        Emscripten : 绑定独立函数

        可以通过独立函数和单例轻松实现,如下所示:

        #include <iostream>
        
        #include <emscripten.h>
        #include <emscripten/bind.h>
        #include <emscripten/val.h>
        
        
        namespace xhr {
        
          inline emscripten::val& singleton() {
            using emscripten::val;
            static val instance = val::global("XMLHttpRequest").new_();
            return instance;
          }
        
          void on_load(emscripten::val event) { 
            std::cout << "Successful Query " << std::endl;
            std::cout << "response is : " << singleton()["responseText"].as<std::string>() << std::endl;
          }
        
          void on_error(emscripten::val event) {
            std::cout << "Error on query " << std::endl;
          }
        
          void on_progress(emscripten::val event) {
            std::cout << "Progress on query " << std::endl;
        
            std::cout << event["lengthComputable"].as<bool>() << ": " << event["loaded"].as<unsigned int>() / event["total"].as<unsigned int>() << std::endl;
          }
        
        
          using namespace emscripten;
        
          EMSCRIPTEN_BINDINGS(xhr) {
            function("on_load", &on_load);
            function("on_error", &on_error);
            function("on_progress", &on_progress);
          }
        
        }
        
        
        int main(int argc, char** argv) {
        
          using emscripten::val;
        
          xhr::singleton().call<val>("open", std::string("GET"), std::string("http://127.0.0.1:8080/CMakeCache.txt"), true);
        
          // Here I set the callback to &on_load function registered via the EMSCRIPTEN_BINDINGS macro.
          xhr::singleton().set("onload",val::module_property("on_load"));
          xhr::singleton().set("onprogress",val::module_property("on_progress"));  
          xhr::singleton().set("onerror",val::module_property("on_error"));  
        
          xhr::singleton().call<val>("send");
        
        
          return 0;
        }
        

        Emscripten : 绑定成员函数

        通常在 C++ 中,我们习惯于 std::bind 回调。这也可以实现,以更简洁的方式以 xhr 为例:

        #include <iostream>
        #include <functional>
        #include <memory>
        
        #include <emscripten.h>
        #include <emscripten/bind.h>
        #include <emscripten/val.h>
        
        class MiniXhr : public std::enable_shared_from_this<MiniXhr> {
          using val = emscripten::val;
          using url_t = std::string;
        
          public:
        
            void set_url(const url_t& url) { url_ = url; }
        
            void GET();
        
            /**
             *
             * The member function to be called from javascript.
             */
            void on_readystate(val event) {
              std::cout << "ready " << std::endl;
              std::cout << "xxhr::on_readystate: " 
                  << xhr["readyState"].as<size_t>() << " - " << url_ << " :: "
                  << xhr["status"].as<size_t>() << ": " 
                  << xhr["statusText"].as<std::string>() << std::endl;
            }
        
          private:
            url_t url_;
            val xhr = val::global("XMLHttpRequest").new_();
        };
        
        
        using emscripten::class_;
        EMSCRIPTEN_BINDINGS(MiniXhr) {
        
          /**
           * Binding for the class.
           */
          class_<MiniXhr>("MiniXhr")
            .smart_ptr<std::shared_ptr<MiniXhr>>("shared_ptr<MiniXhr>")
            .function("on_readystate", &MiniXhr::on_readystate)
            ;
        
          /**
           * More generic binding to bind a functor with one argument (event handler get the event)
           * Here std::function call operator from C++ is bound to function opcall() in JS.
           */
          class_<std::function<void(emscripten::val)>>("VoidValFunctor")
            .constructor<>()
            .function("opcall", &std::function<void(emscripten::val)>::operator());
        
        
        }
        
        /**
         *
         * Finally the interesting part : binding the member function on_readystate to the readystatechange event of XMLHttpRequest.
         *
         */
        
         void MiniXhr::GET() { 
        
          /**
           * Here this lambda could be put as function in a library, to do an JS(std::bind), 
           * it should just be overloaded for different argument count. (Im on it).
           */
          auto jsbind = [](val& target, const char* property, auto bind_expression ) {
        
            // Create an std::function from the bind expression
            std::function<void(emscripten::val)> functor = bind_expression;
        
            // We ensure the correct object will always be bound to the this of the function
            auto functor_adapter = val(functor)["opcall"].call<val>("bind", val(functor)); 
        
            // Finally we simply set the eventhandler
            target.set(property, functor_adapter);
          };
        
          // Here we could bind as many member function as we want.
        
        //    jsbind(xhr, "onload", std::bind(&MiniXhr::on_load, shared_from_this(), std::placeholders::_1));
        //    jsbind(xhr, "onerror", std::bind(&MiniXhr::on_error, shared_from_this(), std::placeholders::_1));
        //    jsbind(xhr, "onprogress", std::bind(&MiniXhr::on_progress, shared_from_this(), std::placeholders::_1));
          jsbind(xhr, "onreadystatechange", std::bind(&MiniXhr::on_readystate, shared_from_this(), std::placeholders::_1));
        
          // Note that we bind with shared_from_this(), as the scope where the class was instantiated may be dead
          // and only later our callback will come back.
        
         xhr.call<val>("open", std::string("GET"), url_, true);
         xhr.call<val>("send");
        }
        
        
        int main(int argc, char** argv) {
        
        
          auto x = std::make_shared<MiniXhr>();
          x->set_url("notfound.json");
          x->GET();
        
          return 0;
        }
        

        【讨论】:

        • 这是迄今为止最好的答案...谢谢。
        猜你喜欢
        • 1970-01-01
        • 2018-03-26
        • 2019-08-22
        • 2015-05-02
        • 2012-09-03
        • 2015-06-09
        • 1970-01-01
        • 1970-01-01
        • 2019-07-15
        相关资源
        最近更新 更多