我是靠谱客的博主 鳗鱼香菇,最近开发中收集的这篇文章主要介绍WebAssembly技术进阶之路(官方文档翻译),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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对它的支持也基本完成。

  1. 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

 

  1. 如何将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

  1. 如何使用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

  1. 如何在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

  1. 如何从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;

}

 

注意:

  1. 如果返回值是int或double,你要指定不同宏,是EM_ASM_INT还是EM_ASM_DOUBLE;
  2. 输入参数用$0,$1等形式表示;
  3. 使用 EM_ASM 注意用‘’,不要用“”,否则会有语法错误;
  4. 返回值用于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

  1. 如何在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技术进阶之路(官方文档翻译)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(56)

评论列表共有 0 条评论

立即
投稿
返回
顶部