c/c++(wasm)和js互相调用记录
- 废话 :)
- 准备工作:安装Emscripten
- 初探:C++(wasm)之hello world
- 进一步探究:接口调用
- 1.js调用c++,一些基本类型的传递(char*,int,float)以及返回值
- 2.js向c++注入函数,c++调用js方法
- 3.wasm大工程代码如何管理和编译
- 结语
废话 😃
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;
}
- 使用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.html | xx.html, xx.js, xx.wasm |
-o xx.js | xx.js, xx.wasm |
-o xx.wasm | xx.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
转载请注明出处,如有不当欢迎指正