废话 😃

WebAssembly(缩写 Wasm)是基于堆栈虚拟机的二进制指令格式。Wasm为了一个可移植的目标而设计的,可用于编译C/C++/rust/go等语言,使客户端和服务器应用程序能够在Web上部署。
wasm的一些优势:
1.可以使用 C/C++、rust、go等语言编写代码,性能优越;
2.二进制文件,文本占用的存储空间更小;
3.安全 和 JS 有相同的沙盒环境和安全策略,比如同源策略;
4.绝大多数主流浏览器支持。


准备工作:安装Emscripten

要把C/C++代码编译成wasm,就需要一个工具链,这里使用的是比较主流的Emscripten。(本文测试环境是winows11环境
1.安装Emscripten工具链:
官网连接:https://emscripten.org

# Get the emsdk repo
git clone https://github.com/emscripten-core/emsdk.git

# Enter that directory
cd emsdk
# Fetch the latest version of the emsdk (not needed the first time you clone)
git pull

# Download and install the latest SDK tools.
./emsdk install latest

# Make the "latest" SDK "active" for the current user. (writes .emscripten file)
./emsdk activate latest

# Activate PATH and other environment variables in the current terminal
source ./emsdk_env.sh

注意:如果是Windows系统 用 emsdk.bat 代替 ./emsdk, and emsdk_env.bat 代替source ./emsdk_env.sh

安装过程会自动下载python,nodejs,java和clang编译器。如果碰到卡住或者报错就是网络问题,可能要采取“科学上网”才能安装完成。

执行完emsdk_env.bat后,就可以进行安装完成验证:emcc -v
需要注意的是emsdk_env.bat只对本终端生效,如果不想每次执行,可以把emsdk相关的路径声明到系统环境变量,不过系统之前若安装过python,nodejs也有可能产生冲突。具体看自己环境情况而定。

在这里插入图片描述


初探:C++(wasm)之hello world

严格来说以下实例是C接口的函数导出调用, 然后借助c接口调用c++方法。事实上能导出c接口了那么意味着我们就可以在c接口里面去实例化C++类和结构,然后再通过导出的C接口调用C++类里面的方法。

当然Emscripten其实还提供了另外2种绑定方式embind和WebIDL Binder,可以直接将c++类绑定到js进行调用,这里就不再做详细探讨。

1.首先需要编写C++代码,并确保代码可以在本地进行编译和测试

#include <iostream>

int main(int argc, char ** argv)
{
    std::cout << "Hello World, hello WASM!\n";
	return 0;
}
  1. 使用Emscripten编译C++代码,将代码编译成WebAssembly二进制文件,具体文件路径根据自己而定。
em++ C:\Users\003\Desktop\wasm\wasmdemo.cpp -s WASM=1 -o C:\Users\003\Desktop\wasm\wasmdemo.html

在这里插入图片描述
成功的生成了3个文件:html,js,wasm
在这里插入图片描述
这里-o是指定输出文件,指定不同输出得到文件不一样:

参数输出
-o xx.htmlxx.html, xx.js, xx.wasm
-o xx.jsxx.js, xx.wasm
-o xx.wasmxx.wasm

3.测试一下html加载wasm的运行效果
首先先开启http代理服务,不能直接去打开这个html:

emrun --no_browser --port 8080 C:\Users\003\Desktop\wasm\wasmdemo.html

然后再浏览器输入:http://localhost:8080/wasmdemo.html
毫无意外的成功显示出c++代码中的打印。。。Hello World, hello WASM
在这里插入图片描述

进一步探究:接口调用

1.js调用c++,一些基本类型的传递(char*,int,float)以及返回值

wasmdemo.h头文件

#ifndef __WASM_DEMO__
#define __WASM_DEMO__
#include <emscripten.h>

#if defined(__cplusplus)
#define WASM_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
#else
#define WASM_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
#endif

WASM_API(int) mathAdd(int a, int b);

WASM_API(int) str2Num(char* str1);

WASM_API(float) mathMulti(float a, float b);

#endif

wasmdemo.cpp源文件

#include "wasmdemo.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

int mathAdd(int a, int b)
{
    return (a+b);
}

//假定是int范围数字
int str2Num(char* str1)
{
    if(str1 == NULL)
        return -INT_MAX;
        
    int num = atoi(str1);
    return num;
}

float mathMulti(float a, float b)
{
    return (a*b);
}

然后编译代码,这里把c标准库里面的malloc和free导出提给给js调用,后续字符串操作需要用到

em++ C:\Users\003\Desktop\wasm\wasmdemo.cpp -s WASM=1 -s EXPORTED_FUNCTIONS="['_malloc','_free']" -o C:\Users\003\Desktop\wasm\wasmdemo.js

新建一个wasmdemo.html

<!doctype html>

<html>
  <head>
    <meta charset="utf-8">
    <title>Wasm:demo</title>
  </head>
  <body>
    <script>
    Module = {};
    Module.onRuntimeInitialized = function() {
		var str = '31415';
        var strBuff = new TextEncoder().encode(str);
        var strPtr = Module._malloc(strBuff.length + 1);
        Module.HEAPU8.set(strBuff, strPtr);
        Module.HEAPU8[strPtr + strBuff.length] = 0;
        console.log('str2Num:', Module._str2Num(strPtr));
        Module._free(strPtr);

		console.log('mathAdd:', Module._mathAdd(250, 250));
		console.log('mathMulti:', Module._mathMulti(3.14, 3.0));
    }
    </script>
    <script src="wasmdemo.js"></script>
  </body>
</html>

这个html调用了cpp文件里面的三个函数并输出运行结果,需要注意的是字符串传递需要在js层预先申请内存,然后传递到cpp
浏览器html执行结果如下:
在这里插入图片描述
既然可以传递char*了,那么其他类型数组的传递也是不在话下了,都是要预先在js申请好内存,然后传递指针,这里就不再进行探究了。

2.js向c++注入函数,c++调用js方法

新建一个js模块wasmapi.js,然后添加函数如下图所示, 并在c++侧加入此函数声明。这样编译之后即可调用jsLogPrint函数。

em++ C:\Users\003\Desktop\wasm\wasmdemo.cpp --js-library C:\Users\003\Desktop\wasm\wasmapi.js -s WASM=1 -s EXPORTED_FUNCTIONS="['_malloc','_free']" -o C:\Users\003\Desktop\wasm\wasmdemo.js

在这里插入图片描述
浏览器执行访问html结果:
在这里插入图片描述

3.wasm大工程代码如何管理和编译

简单的 c++ 项目,可以直接调用 em++将 c++ 编译为 wasm,但是对于大型项目,都是使用 cmake 等构建工具进行构建的。 好消息是 emscripten 很好的和 cmake 进行了集成,我们只需要进行如下替换:

cmake => 替换为 emcmake cmake
make => 替换为 emmake make

编译步骤:

cd build && emcmake ..    
emmake make // 生成xx.a
emcc xx.a -o xx.js // 生成 xx.wasm和lxx.js  

再仔细研究下的话其实直接使用cmake构建也是可以的,通过官方cmake文件可知,只需要加入
-DCMAKE_TOOLCHAIN_FILE=yourpath/cmake/Modules/Platform/Emscripten.cmake

还有一点需注意的是,Unix系cmake集成编译会简单些,如果是windows那么需要额外安装MinGW编译器,笔者尝试过使用VC++编译失败,Emscripten官方cmake文件提示也只有Unix Makefiles和MinGW Makefiles俩种。
在这里插入图片描述

结语

通过本文可对wasm使用过程有一个初步的了解, 但还有很多功能尚未尝试。例如:前面提到的
embind和WebIDL Binder, ccall, cwrap等等。还有一个有趣的东东Qt for WebAssembly,可以把Qt的东西在浏览器上跑包括GUI程序。
好了剩下的交给时间咯 !😃


作者:费码程序猿
欢迎技术交流:QQ:255895056
转载请注明出处,如有不当欢迎指正

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐