WebAssembly学习笔记2
JavaScript调用C函数
之前通过Module._main()调用了C/C++入口main()函数,但这只是因为Emscripten会默认导出main函数,普通C函数需要自定义导出方法
定义函数导出宏
为了方便函数导出,我们需要先定义一个函数导出宏。该宏需要完成以下功能:
- 使用C风格的符号修饰。
- 避免函数因为缺乏引用而导致在编译链接时被优化器删除。
- 为了保持足够的兼容性,宏需要根据不同的环境——原生代码环境与Emscripten环境、纯C/C++环境等,自动切换合适的行为
为了满足以上三点,定义宏 EM_PORT_API 如下:
#ifndef EM_PORT_API
# if defined(__EMSCRIPTEN__)
# include <emscripten.h>
# if defined(__cplusplus)
# define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
# else
# define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
# endif
# else
# if defined(__cplusplus)
# define EM_PORT_API(rettype) extern "C" rettype
# else
# define EM_PORT_API(rettype) rettype
# endif
# endif
#endif
EMSCRIPTEN 用于探测是否是Emscripten环境
__cplusplus 用于探测是否是C++环境
EMSCRIPTEN_KEEPALIVE是Emscripten特有的宏,用于告知编译器后续函数在优化时必须保留,并且该函数将被导出至JavaScript环境
在C/C++中使用 EM_PORT_API 宏定义函数声明:
EM_PORT_API(int) func(int param);
在Emscripten中将被展开:
#include <emscripten.h>
extern "C" int EMSCRIPTEN_KEEPALIVE func(int param);
在JavaScript中调用C导出的函数
创建C文件,代码如下:
//此处省略EM_PORT_API宏定义
#include <stdio.h>
EM_PORT_API(int) show_me_the_answer() {
return 42;
}
EM_PORT_API(float) add(float a, float b) {
return a+b;
}
使用emcc编译之,并在html引用编译后的js,JavaScript调用C导出的函数代码如下:
<script>
Module = {};
Module.onRuntimeInitialized = function() {
console.log(Module._add(1,1.22223));
console.log(Module._show_me_the_answer())
}
</script>
<script src="export1.js"></script>
需要注意:
JavaScript是弱类型语言,在调用函数时并不要求调用方与被调用方签名一致,这与C/C++有本质的区别。
在JavaScript环境中,如果给出的参数个数不一致,多出来的会被舍弃(从左至右),少的自动以undefined填充。
自然就有:
console.log(Module._add(1)); //输出NaN
JavaScript函数注入C环境
C函数声明
创建C文件capi.cc
#include <stdio.h>
EM_PORT_API(int) js_add(int i, int j);
EM_PORT_API(void) js_console_log_int(int param);
EM_PORT_API(void) print_the_answer() {
int i = js_add(21, 21);
js_console_log_int(i);
}
EM_PORT_API(int) show_me_the_answer_bi();
EM_PORT_API(void) print_the_answer_bi() {
printf("%d\n",show_me_the_answer_bi());
}
此处js_add和 js_console_log_int show_me_the_answer_bi 只是在C中给出了声明,我们需要在JavaScript中实现
JavaScript实现C函数
创建pkg.js,代码如下:
// mergeInto 注入方法不能直接使用闭包
mergeInto(LibraryManager.library,{
js_add: function(a, b){
console.log("js_add");
return a+b;
},
js_console_log_int: function(param) {
console.log("js_console_log_int: "+param);
},
show_me_the_answer_bi: function() {
return jsShowMeTheAnswer();
}
});
执行以下命令编译:
emcc capi.cc --js-library pkg.js -o capi.js
–js-library pkg.js表示将pkg.js作为附加库参与链接。
按照之前的例子在网页中将生成的capi.js载入,并调用print_the_answer
<script>
function f1() {
var answer = 42;
function f2() {
return answer;
}
return f2;
}
var jsShowMeTheAnswer = f1();
Module = {};
Module.onRuntimeInitialized = function() {
Module._print_the_answer()
Module._print_the_answer_bi()
}
</script>
<script src="export1.js"></script>
由于mergeInto注入方法不能注入闭包,可以通过在注入方法中调用其他方法来间接实现。这种方法既能避免不能直接注入闭包的限制,而且还能动态调整注入函数。
优缺点
优点: 保持C代码的纯净——C代码中不必包含JavaScript的成分
缺点: 需要额外创建一个js库,维护较麻烦,并且还要特别注意保持函数声明和实现时定义的统一