Я изучаю emscripten и пытаюсь понять его лучше. Насколько я понимаю, сценарий использования, для которого он в основном предназначен, — это перенос существующего кода C / C ++ — в веб-клиент (браузер) и вызов кода C / C ++ из JavaScript.
Но мне интересно, возможно ли использовать C ++ и Emscripten для веб-страницы (примечание: это больше из любопытства — я знаю, что на данный момент не так много веских причин для этого). Мне удается вызывать функции Javascript из C ++ и передавать им аргументы типов string, int, double и т. Д. Но чего мне не хватает, так это: вызова функции Javascript из C ++ и передачи функции C или C ++ в качестве дескриптора. В качестве простого примера: как бы я написал следующий код Javascript для чистого C ++?
var myfun = function() { /* do something meaningful here */ }
document.onload(myfun);
TL; DR;
Я написал библиотеку: JS-связывание который принимает любое количество аргументов, чтобы сделать это легко:
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.
Например, возьмите обработчики событий привязки XMLHttpRequest из C ++.
Чтобы включить его, вы должны скомпилировать с: --bind -s NO_EXIT_RUNTIME=1
Этого легко добиться с помощью автономных функций и синглтона, как показано здесь:
#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;
}
Обычно в 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;
}
Вот кое-что, что я использовал давным-давно, когда возился с Emscripten в C-коде:
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?
Я всегда удаляю функцию, которая больше не нужна после ее выполнения для сохранения памяти.
Это простой и удобный способ заставить биты кода работать вместе. Очевидно, вы можете изменить это, чтобы сделать все, что вы хотите, кроме распечатки информации. :П
Я не уверен насчет 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, и у вас есть объект C под названием «документ», который получает функцию, которая выполняет некоторые другие действия, ваш код C будет:
void myfun (void) {
/* do something meaningful here */
}
document.onload(&myfun);