WebAssembly(wasm)中C与JavaScript的相互调用

Javascript 长久以来是浏览器能运行到唯一编程语言,随着wasm技术出现,c、c++、rust、go等语言也可以运行在浏览器上,而且接近本地程序运行到速度。

本文用两个事例实践,介绍wasmCJavaScript的相互调用。

简介

什么是wasm

wasm是Web的汇编,是为Web浏览器定制的汇编语言。从高级语言编译器角度看,wasm是目标代码。从浏览器觉度来看,wasm最终会被编译成平台相关的机器码。说人话:wasm能被浏览器解释并执行。

高级语言 > IR > 汇编 > 机器码 C C++.jpg

兼容性

不考虑IE情况下,主流浏览器都支持 image.png

事例实践

emscripten/emsdk 使用

需要使用emsdk把,.cc/.cpp代码编译成.wasm后缀与.js后缀的文件。

emsdkv1.38.1版本后编译目标默认为wasm(.wasm.js)

  • .wasm为源文件编译后形成的WebAssembly汇编文件
  • .jsemsdk生成的胶水代码,包含emscripten运行环境和.wasm文件的封装,.html文件导入.js后可自动完成.wasm文件的载入/编译/实例化、运行时初始化等工作。

方法一 emsdk系统环境安装

webassembly.org.cn/getting-sta…

方法二 使用docker容器编译

hub.docker.com/r/emscripte…

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_KEEPALIVEemscripten特有宏,用于告知编辑器后续函数在优化时是否保留,并且该函数将被导出至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.onRuntimeInitializedemscripten运行时准备就绪时的回调。

此时文件目录如下:

|--"/"
    |--"type_conv.cc" // 手动创建
    |--"type_conv.js" // emcc 工具生成
    |--"type_conv.html" // 手动创建
复制代码

此时启动静态资源服务并使用浏览器访问type_conv.html,并查看控制台:

image.png

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,并查看控制台:

image.png

至此,实现了在C环境中调用JavaScript方法show_me_the_answer

总结

使用emscripten/emsdk

  • c中定义 EM_PORT_API 宏,用来声明方法,可让JavaScript去调用对应的方法。
  • JavaScript中可以使用mergeInto方法,结合emcc -—js-library 参数,把JavaScript定义的方法注入到c环境库。

参考书籍

《面向WebAssembly编程 应用开发方法与实践》

《WebAssembly 原理与核心技术》

猜你喜欢

转载自juejin.im/post/7050496511288803336