IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [WebAssembly]初学笔记 在C++中嵌入Javascript代码

    罗佳(博主)发表于 2024-08-15 17:26:46
    love 0

    如果有看不明白的地方请先看前置说明文章

    前一篇笔记写了如何在js中调用c++的函数,要在c++里执行js代码也有几种方法,另外和从js到c++的交互只能调用函数和操作内存不同,c++里可以编写完整的js代码并获得结果。

    这里依然先附上官方的参考:https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html


    使用emscripten_run_script直接执行代码

    这是emscripten提供的执行js代码的方法,此方法无法获取js代码的运行结果(类似于返回值),可以获取返回值的版本在下文。这个方案的内部实现方式是把这个代码字符串扔外面的js环境用eval()来执行。

    在test.cpp中编写如下代码:

    #include <emscripten.h>
    
    int main() {
    	emscripten_run_script(
    		"console.log('岂因祸福避趋之');\n"
    		"console.log('6');");
    	return 0;
    }

    然后执行命令编译:emcc -sMODULARIZE=1 -sASSERTIONS -sEXPORT_ES6 -o dist/test.mjs test.cpp

    接着修改start.js:

    (async () => {
    	const { default: wasm } = await import('./dist/test.mjs');
    	const instance = await wasm();
    	/* 控制台输出:
    	岂因祸福避趋之
    	6
    	*/
    })();

    这个方法还有2个变体,以解决不能获取返回值的问题:

    • emscripten_run_script_int
    • emscripten_run_script_string

    举例:

    #include <emscripten.h>
    #include <iostream>
    using namespace std;
    int main() {
    	int a = emscripten_run_script_int("1+1;");//方法将返回脚本中最后一个表达式的结果
    	cout << "a:" << a << endl;          //输出:"a:2"
    
    	char* str=emscripten_run_script_string("'niconiconi~ 妮可妮可妮~'");//方法将返回脚本中最后一个表达式的结果
    	cout << "str:" << str << endl;      //输出:"str:niconiconi~ 妮可妮可妮~"
    	return 0;
    }

    以上方法如果在执行js代码的过程中遇到错误将会导致执行直接中断,我尝试了几个编译参数没有找到能在c++中捕获其错误的方法,但按理说应该是有的。


    使用EM_JS、EM_ASYNC_JS、EM_ASM执行Javascript代码

    文档中写这类方法比emscripten_run_script那类的速度要快,但没说明是调用开销小还是执行效率更高。这类方法的本质是在宏内定义js函数体的代码。

    EM_JS和EM_ASYNC_JS是通过宏构造c++胶水函数,然后在需要使用的时候去调用它。一般的js代码就直接用EM_JS,如果代码中存在await,则需要使用EM_ASYNC_JS。

    EM_ASM则是在编写该宏的位置执行js代码,我没有找到它的async版本。EM_ASM系列的宏可以使用占位符添加参数,见示例。

    为了能够获得EM_ASM中脚本执行的返回值,其也有几个变体:

    • EM_ASM_INT
    • EM_ASM_PTR
    • EM_ASM_DOUBLE

    在使用EM_ASYNC_JS时需要在编译参数中添加-sASYNCIFY,C++中嵌入的异步脚本执行是靠这个参数添加的库实现的。

    现在改写test.cpp:

    #include <emscripten.h>
    
    EM_JS(void, call_console, (), {
    	console.log('从call_console调用');
    });
    
    EM_ASYNC_JS(unsigned long long, delay_test, (), {
    	console.log('开始执行EM_ASYNC_JS:' + Date.now());
    	const promise = new Promise((resolve, reject) = > {
    		setTimeout(resolve, 1000);
    	});
    	await promise;
    	console.log('结束执行EM_ASYNC_JS:' + Date.now());
    	return Date.now();
    });
    
    int main() {
    	call_console(); // 调用使用EM_JS宏构造的call_console函数
    
    	auto time = delay_test(); // 调用使用EM_ASYNC_JS宏构造的call_console函数
    
    	EM_ASM({
    		console.log('从EM_ASM调用');
    	});
    
    	//支持返回值的脚本执行,并且在后面添加参数,$0是第一个参数占位符,$1是第二个参数占位符,以此类推
    	int x = EM_ASM_INT({ return $0 + 22; }, 11); //x=33
    
    	return 0;
    }

    一个重要提示:如果你使用编辑器的代码格式化功能,请注意js中的箭头函数 “=>” 在C++代码中可能会被格式化为 “= >” ,这会导致js执行报错。

    然后执行命令编译:emcc -sMODULARIZE=1 -sASSERTIONS -sEXPORT_ES6 -sASYNCIFY -o dist/test.mjs test.cpp,start.js中只要加载wasm即可,无需执行其它代码,运行后将得到以下输出:

    从call_console调用
    开始执行EM_ASYNC_JS:1723741398524  (停顿1秒)
    结束执行EM_ASYNC_JS:1723741399533
    从EM_ASM调用

     

     



沪ICP备19023445号-2号
友情链接