简体   繁体   English

将C ++函数传递给emscripten中的javascript函数

[英]Passing a C++ function to a javascript function in emscripten

I am learning about emscripten and trying to understand it better. 我正在学习emscripten并试图更好地理解它。 As far as I understand the use-case it was mostly designed for is to port existing C/C++-code to a web client (browser) and calling C/C++ code from JavaScript. 据我所知,用例主要是将现有的C / C ++ - 代码移植到Web客户端(浏览器)并从JavaScript调用C / C ++代码。

But I am wondering whether it is possible to use C++ and Emscripten to web page (note: this is more out of curiosity - I know that there are not many good reasons to do that at the moment). 但我想知道是否有可能将C ++和Emscripten用于网页(注意:这更多是出于好奇 - 我知道目前没有太多合理的理由)。 I manage to call Javascript functions from C++ and pass arguments of types string, int, double etc to them. 我设法从C ++调用Javascript函数,并将类型为string,int,double等的参数传递给它们。 But what I am missing is: calling a Javascript function from C++ and passing a C or C++ function as a handle. 但我缺少的是:从C ++调用Javascript函数并将C或C ++函数作为句柄传递。 So as a simple example: How would I write the following Javascript code ind pure C++? 所以作为一个简单的例子:我如何编写以下Javascript代码ind纯C ++?

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

TL;DR; TL; DR;

I wrote a library : js-bind which accepts any number of arguments to do that easily : 我写了一个库: 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));

This library is some Macro metaprogramming based on the explanation below. 该库是基于以下解释的一些宏元编程。

DETAILED ANSWER: 详细解答:

You have different possibilites, like emscripten ccall, but what is easier to use in my opinion is Embind. 你有不同的可能性,比如emscripten ccall,但在我看来更容易使用的是Embind。

For example take binding event handlers of an XMLHttpRequest from within C++. 例如,从C ++中获取XMLHttpRequest的绑定事件处理程序。

To enable it you have to compile with : --bind -s NO_EXIT_RUNTIME=1 要启用它,您必须编译: - --bind -s NO_EXIT_RUNTIME=1

Emscripten : bind freestanding functions Emscripten:绑定独立功能

One could achieve it easily with freestanding functions and a singleton, as show here : 人们可以通过独立功能和单例轻松实现它,如下所示:

#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 : bind member functions Emscripten:绑定成员函数

Typically in C++ we are used to std::bind callbacks. 通常在C ++中,我们习惯于std :: bind回调。 This can also be achieved, taking the example of xhr in a cleaner way : 这也可以通过更简洁的方式以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;
}

I'm not sure about emscripten, but to sum up, I understand that what you need to know is how to pass a C++ function as a handle to another C++ function. 我不确定emscripten,但总而言之,我明白你需要知道的是如何将C ++函数作为另一个C ++函数的句柄传递。 I hope I can help with that. 我希望我可以帮忙。

JavaScript, PHP, and other more flexible languages, allow a function object to be passed through. JavaScript,PHP和其他更灵活的语言允许传递函数对象。 In C and C++, it is slightly different, you have to pass function pointers as arguments to other functions. 在C和C ++中,它稍有不同,您必须将函数指针作为参数传递给其他函数。 In C, the name for this is a Callback, rather than a handle. 在C中,这个名称是Callback,而不是句柄。

For instance: 例如:

/* 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. 

It is a common practice to create a custom type to the argument, so you don't need to use something as ugly as int (*numberSource)(void) . 通常的做法是为参数创建自定义类型,因此您不需要像int (*numberSource)(void)那样使用丑陋的东西。 It will be something like: 它将是这样的:

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

So the printNumber function would be like this: 所以printNumber函数是这样的:

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

So, in your case, if you want to translate this JS code 所以,在你的情况下,如果你想翻译这个JS代码

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

to C, and you have a C object called "document" that receives a function that performs some other actions, your C code will be: 到C,你有一个名为“document”的C对象接收一个执行其他动作的函数,你的C代码将是:

void myfun (void) {
  /* do something meaningful here */
}

document.onload(&myfun);

Here's something I used a long while back when tinkering w/ Emscripten in C code: 这是我在C代码中修改Emscripten时使用了很久的东西:

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

and then here would be the JavaScript: 然后这将是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?

I always remove a function that is no longer needed after its execution to preserve memory. 我总是删除执行后不再需要的函数来保存内存。 This is a simple and seamless way to make code bits work together. 这是使代码位协同工作的简单而无缝的方法。 You can obviously modify this to do whatever you want other than printing out information. 显然,您可以将其修改为除了打印信息之外的任何其他操作。 :P :P

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM