Javascript 长久以来是浏览器能运行到唯一编程语言,随着
wasm
技术出现,c、c++、rust、go等语言也可以运行在浏览器上,而且接近本地程序运行到速度。
本文用两个事例实践,介绍wasm
中C
与JavaScript
的相互调用。
简介
什么是wasm
wasm
是Web的汇编,是为Web浏览器定制的汇编语言。从高级语言编译器角度看,wasm
是目标代码。从浏览器觉度来看,wasm
最终会被编译成平台相关的机器码。说人话:wasm
能被浏览器解释并执行。
高级语言 > IR > 汇编 > 机器码
兼容性
不考虑IE情况下,主流浏览器都支持
事例实践
emscripten/emsdk 使用
需要使用emsdk
把,.cc
/.cpp
代码编译成.wasm
后缀与.js
后缀的文件。
emsdk
自v1.38.1
版本后编译目标默认为wasm
(.wasm
与.js
)
.wasm
为源文件编译后形成的WebAssembly汇编文件.js
是emsdk
生成的胶水代码,包含emscripten
运行环境和.wasm
文件的封装,.html
文件导入.js
后可自动完成.wasm
文件的载入/编译/实例化、运行时初始化等工作。
方法一 emsdk系统环境安装
webassembly.org.cn/getting-sta…
方法二 使用docker容器编译
JavaScript调用C函数
c代码如下,代码中声明并定义了print_int
,print_float
,print_double
三个方法。
//type_conv.cc
#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
#include<stdio.h>
EM_PORT_API(void) print_int(int a) {
printf("C{print_int*( a:%d)}\n", a);
}
EM_PORT_API(void) print_float(float a) {
printf("C{print_int*( a:%f)}\n", a);
}
EM_PORT_API(void) print_double(double a) {
printf("C{print_int*( a:%lf)}\n", a);
}
复制代码
__EMSCRIPTEN__
用于探测是否 emscripten环境;
__cplusplus
用于探测是否是C++环境;
EMSCRIPTEN_KEEPALIVE
是emscripten
特有宏,用于告知编辑器后续函数在优化时是否保留,并且该函数将被导出至JavaScript环境。
使用emsdk
对.cc
文件进行编译
控制台执行以下指令,生成type_conv.js
文件。
emcc type_conv.cc -o type_conv.js
复制代码
手动创建type_conv.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>type conv</title>
</head>
<body>
<script>
Module = {}
Module.onRuntimeInitialized = function() {
Module._print_int(3.4)
Module._print_int(4.6)
Module._print_int(-3.4)
Module._print_int(-4.6)
Module._print_float(2000000.03125)
Module._print_double(2000000.03125)
}
</script>
<script src="type_conv.js"></script>
</body>
</html>
复制代码
Module.onRuntimeInitialized
为emscripten
运行时准备就绪时的回调。
此时文件目录如下:
|--"/"
|--"type_conv.cc" // 手动创建
|--"type_conv.js" // emcc 工具生成
|--"type_conv.html" // 手动创建
复制代码
此时启动静态资源服务并使用浏览器访问type_conv.html
,并查看控制台:
JavaScript
调用c
的函数成功!
JavaScript函数注入C环境
c代码如下,代码中声明了show_me_the_answer
方法,但并没有去定义。
// closure.cc
#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
#include <stdio.h>
EM_PORT_API(int) show_me_the_answer();
EM_PORT_API(void) func() {
printf("%d\n", show_me_the_answer());
}
复制代码
创建pkg.js
mergeInto(LibraryManager.library,{
show_me_the_answer: function(){
return jsShowMeTheAnswer();
}
})
复制代码
LibraryManager.library
可以简单理解为JavaScript
注入C
环境的库。
控制台执行以下指令,生成closure.js
文件与closure.wasm
文件。
emcc closure.cc -—js-library pkg.js -o closure.js
复制代码
-—js-library pkg.js
表示把pkg.js作为附加库参与链接
手动创建closure.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello world</title>
</head>
<body>
<script>
function f1(){
var answer = 42;
function f2() {
return answer;
}
return f2;
}
var jsShowMeTheAnswer = f1();
Module = {}
Module.onRuntimeInitialized = function() {
console.log(`onRuntimeInitialized`)
Module._func();
console.log(`onRuntimeInitialized after`)
}
</script>
<script src="cc.js"></script>
</body>
</html>
复制代码
此时文件目录如下:
|--"/"
|--"closure.cc" // 手动创建
|--"pkg.js" // 手动创建
|--"closure.js" // emcc 工具生成
|--"closure.wasm" // emcc 工具生成
|--"closure.html" // 手动创建
复制代码
此时启动静态资源服务并使用浏览器访问closure.html
,并查看控制台:
至此,实现了在C
环境中调用JavaScript
方法show_me_the_answer
总结
使用emscripten/emsdk
- 在
c
中定义EM_PORT_API
宏,用来声明方法,可让JavaScript
去调用对应的方法。 - 在
JavaScript
中可以使用mergeInto
方法,结合emcc -—js-library
参数,把JavaScript
定义的方法注入到c
环境库。
参考书籍
《面向WebAssembly编程 应用开发方法与实践》
《WebAssembly 原理与核心技术》