概述
WebAssembly技术
——从C/C++到Javascript之路
- 案例描述
随着对无插件预览性能要求的越来越高,原来的Emscripten asm.js技术已显不足,但目前一种新技术——WebAssembly正日趋成熟,WebAssembly可以将低级别编程语言(包括C和C++)编译成二进制字节码,可以较大的提升浏览器的性能。
- 案例分析和解决
WebAssembly作为一种新兴的Web技术,正在快速演变中,Google、苹果、微软和Mozilla的正在联合开发WebAssembly。WebAssembly(wasm)是一种可用于浏览器中的字节码(bytecode),可使浏览器性能提升20倍。字节码是一种机器可读的指令集,与脚本语言相比,字节码的加载速度更快。WebAssembly项目旨在开发全新的字节码,从而让桌面和移动端浏览器变得更高效。使用WebAssembly,我们可以在浏览器中运行一些高性能、低级别的编程语言,可用它将大型的C和C++代码库比如游戏、物理引擎甚至是桌面应用程序导入Web平台。截至目前为止,我们已经可以在Chrome、Firefox中使用WebAssembly,Edge和Safari对它的支持也基本完成。
- WebAssembly编译环境配置
我们使用 Emscripten 将 C 代码编译为 wasm 格式,官方推荐的方式是首先下载 Portable Emscripten SDK for Linux and OS X (emsdk-portable.tar.gz) 然后利用 emsdk 进行安装:
$ git clone https://github.com/juj/emsdk.git $ cd emsdk $ ./emsdk install latest $ ./emsdk activate latest $ source ./emsdk_env.sh --build=Release |
新版Emscripten已经包含了-s WASM=1编译选项,通过该选项可以将C/C++代码直接编译成wasm文件。
除此之外,我们还可以通过官方提供的另外两个工具将.js文件转换成.wasm,工具分别为Binaryen和WABT (WebAssembly Binary Toolkit),安装方法可参考官方 Developer’s Guide 和 Advanced Tools。
虽然Emscripten能生成asm.js和wasm,但是却不能把asm.js转成wasm。因为它是基于LLVM的,然而asm.js没法编译成LLVM IR (Intermediate Representation)。想要把asm.js编译成WebAssembly,就要用到官方提供的Binaryen和WABT (WebAssembly Binary Toolkit)工具了。原理和编译方法参考官方文档,整个过程如下:
Binaryen WABT math.js ---> math.wast ---> math.wasm |
用脚本描述如下:
$ asm2wasm math.js -o math.wast $ wast2wasm math.wast -o math.wasm |
- 如何将C/C++代码编译成.wasm
使用C语言来编写WebAssembly模块,并将其编译成.wasm文件。这些.wasm文件并不能直接被浏览器识别,所以它们需要一种称为JavaScript胶接代码。
首先编写一段C代码,保存为test.c,代码如下:
#include <stdio.h>
#ifdef __cplusplus extern "C" { #endif
void myFunction() { printf("MyFunction Calledn"); }
#ifdef __cplusplus } #endif |
使用Emscripten编译器,采用如下指令进行编译,将test.c代码编译成test.wasm文件,同时还会生成一个test.js文件,该文件就相当与胶接代码,在Javascript中加载该文件,既可以调用用C代码中的接口。
$ emcc -o test.js test.c -O3 -s WASM=1 -s EXPORTED_FUNCTIONS="['_myFunction']" |
各个参数含义如下:
- emcc——代表Emscripten编译器;
- test.c——包含C代码的文件;
- -s WASM=1——指定使用WebAssembly;
- -O3——代码优化级别,3已经是很高的级别了;
- -o test.js——指定生成包含wasm模块所需的全部胶接代码的JS文件;
更多关于WASM编译请查看官方文档:
https://developer.mozilla.org/en-US/docs/WebAssembly/C_to_wasm
- 如何使用Embind绑定C/C++结构体
Embind用于绑定C++函数和类到JavaScript,这样编译代码就能在js中以一种很自然的方式来使用,需要在C/C++代码中添加#include <emscripten/bind.h>头文件。使用EMSCRIPTEN_BINDINGS()块来创建函数、类、值类型、指针(包括原始和智能指针)、枚举和常量的绑定,本节主要介绍如何绑定在C/C++方法中经常作为参数或返回值的结构体;
首先新建一个example.cpp文件,代码如下:
#include <emscripten/bind.h>
using namespace emscripten;
struct Point { int x; int y; };
Point getPoint() { Point point = {0}; point.x = 100; point.x = 200;
return point; }
EMSCRIPTEN_BINDINGS(my_module) { value_object<Point>("Point") .field("x", & Point::x) .field("y", & Point::y) ;
function("_getPoint", &getPoint); } |
使用embind编译上例,请调用emcc的bind选项,编译指令如下:
$ emcc --bind -o example.js example.cpp -O3 -s WASM=1 -s EXPORTED_FUNCTIONS="['_myFunction']" |
在JavaScript中调用如下:
var oPoint = Module._getPoint(); var ix = oPoint.x; var iy = oPoint.y; |
更多关于Embind技术请查看官方文档:https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html
- 如何在Javascript层中调用C/C++接口
JavaScript中调用编译的C函数的最简单的方法是使用ccall()和cwrap()。其中ccall()用具体的参数和返回值调用一个编译的C函数,而cwrap()是一个编译的C函数的包裹,调用它会返回一个JavaScript可以调用的函数。如果打算多次调用一个函数的话,cwrap()用处更大。
例如下面代码是hello_function.cpp文件:
#include <math.h>
extern "C" { int int_sqrt(int x) { return sqrt(x); } } |
使用的编译命令为:
$ emcc --bind -o example.js example.cpp -O3 -s WASM=1 -s EXPORTED_FUNCTIONS="['_myFunction']" |
编译完,下面使用ccall()在JavaScript中调用C/C++代码中的方法,下面代码就是直接用int_sqrt计算28的开方(结果为5):
// Call C from JavaScript var result = Module.ccall('int_sqrt', // name of C function 'number', // return type ['number'], // argument types [28]); // arguments
// result is 5 |
返回类型和参数类型中可用类型有三个,分别是number,string和array。number(是js中的number,对应C中的整型,浮点型,一般指针),string(是JavaScript中的string,对应C中代表字符串的char*),array(是js中的数组或类型数组,对应C中的数组;如果是类型数组,必须为Uint8Array或者Int8Array)。
cwrap()与ccall()类似,下面代码就是直接用int_sqrt计算28的开方:
int_sqrt = Module.cwrap('int_sqrt', 'number', ['number']) int_sqrt(28) |
第一个参数是函数名,第二个参数是函数返回类型,第三个是参数类型。
除上述方法外,JavaScript还可以“直接”调用编译的C/C++代码。C中的函数编译为js中函数后,其实可以直接调用,比如C中有个a(),在编译后js用_a()来调,与ccall()和cwarp()相比,直接调用会稍微复杂点,但速度更快。
var result = Module._int_sqrt(28) |
更多关于C/C++与Javascript交互技术请查看官方文档:
https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html
- 如何从C / C ++调用JavaScript
Emscripten提供两种方法让C/C++调用JavaScript,一种是使用 emscripten_run_script()运行js脚本,另一种是编写“inline JavaScript”。
emscripten_run_script()方式最直接,但略慢,它是通过eval()来实现的。例如在C代码中插下面一行代码,编译后就能在浏览器弹出alert()。
#include <emscripten/emscripten.h>
int callJS() { emscripten_run_script("alert('hi')"); return 1; } |
“inline JavaScript”方式是使用EM_ASM()编写,相比这种方法稍微快些。使用这种方式实现上面的“alert”,代码如下:
#include <emscripten/emscripten.h>
int callJS() { EM_ASM( alert('hello world!'); ); return 1; } |
此外,如果在C中想传值给JavaScript,那就用EM_ASM_(比EM_ASM多了“_”),代码如下:
#include <emscripten/emscripten.h>
int callJS() { EM_ASM_({ Module.print('I received: ' + $0); }, 100); return 1; } |
如果有返回值,可以用EM_ASM_INT,代码如下:
#include <stdio.h> #include <emscripten/emscripten.h>
int callJS() { int x = EM_ASM_INT({ Module.print('I received: ' + $0); return $0 + 1; }, 100); printf("%dn", x); return 1; } |
注意:
- 如果返回值是int或double,你要指定不同宏,是EM_ASM_INT还是EM_ASM_DOUBLE;
- 输入参数用$0,$1等形式表示;
- 使用 EM_ASM 注意用‘’,不要用“”,否则会有语法错误;
- 返回值用于js传给c数据;
上述C代码编译命令如下:
$ emcc -o my_js.js my_js.c -s EXPORTED_FUNCTIONS="['_ callJS ']" |
更多相关技术可查看官方文档:
https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#calling-javascript-from-c-c
- 如何在Javascript中实现C/C++ API
可以在JavaScript中实现一个C / C ++ API, 许多Emscripten库中都使用了这种方法,如SDL1和OpenGL。可以编写js API 让C/C++来调用,为实现它,需要定义接口,并用extern来标记它是个外部API。然后,将其定义添加到library.js(默认情况下添加到library.js,当然也可以添加到自己编写的文件中),即可在JavaScript中实现这些符号。编译C代码时,编译器会在JavaScript库中查找相关的外部符号。
下面主要介绍将Javascript实现添加到自定义文件中的方法,例如,新建my_js.c文件,C部分代码如下:
#include <stdio.h>
extern void my_js(void); extern int js_add(int a, int b);
int JS_implement_C () { int res = js_add(4, 5); printf("res = %dn", res); my_js(); return 1; } |
然后在自定义文件中Javascript接口实现代码如下:
mergeInto(LibraryManager.library, { my_js: function(a, b) { alert('hi'); // Javascript库API }, js_add: function(a, b) { var c = add(a, b); // 用户在Javascript工程中自定义方法 return c; }, }); |
C/C++代码在进行emcc编译时添加--js-library选项,编译指令如下:
$ emcc -o my_js.js my_js.c -s EXPORTED_FUNCTIONS="['_JS_implement_C']" --js-library mylibrary.js |
这样就可以在Javascript中实现C/C++的方法,通过该方式可以在C/C++中回调Javascript接口。
更多相关技术可查看官方文档:
https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#implement-a-c-api-in-javascript
- 经验总结
asm.js与WebAssembly 的异同:
- asm.js是文本,可读性高,比较直观;另外,一般浏览器都支持 asm.js,存在兼容性问题小;
- WebAssembly是二进制字节码,因此运行速度比asm.js更快、体积也更小;由于WebAssembly是目前较新的技术,对浏览器的兼容性比asm.js差,但在Chrome、Firefox中都可以使用;
最后
以上就是鳗鱼香菇为你收集整理的WebAssembly技术进阶之路(官方文档翻译)的全部内容,希望文章能够帮你解决WebAssembly技术进阶之路(官方文档翻译)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复