Emscripten 提供了许多方法来在 JavaScript 和编译后的 C 或 c++ 之间连接和交互,我们先来看看 js
调用 WASM
的情况。
一、使用 ccall
或 cwrap
callall()
调用带有指定参数的编译过的 C 函数 并返回结果,而 cwrap()
封装了编译过的 C 函数并返回一个可以正常调用的 JavaScript 函数。因此,如果计划多次调用一个编译后的函数,cwrap()
会更有用。
例如下面的 C main.cpp 文件:
#include <math.h>
extern "C" {
int int_sqrt(int x) {
return sqrt(x);
}
}
使用下面的命令进行编译:
emcc main.cpp -o function.html -s EXPORTED_FUNCTIONS=_int_sqrt -s EXPORTED_RUNTIME_METHODS=ccall,cwrap
EXPORTED_FUNCTIONS
告诉编译器哪些函数我们想要导出(不指定的函数会被删掉),EXPORTED_RUNTIME_METHODS
告诉编译器我们需要用到的运行时方法 ccall
和 cwrap
,否则这些方法也会被优化掉
编译后就可以在 js 中通过 cwrap
使用了:
int_sqrt = Module.cwrap('int_sqrt', 'number', ['number'])
int_sqrt(12) // return 3
int_sqrt(28) // return 5
第一个参数是被 wrap 的 C 函数的名字(没有下划线),第二个参数是函数返回值在类型(如果没有返回值,使用 JavaScript 的 null 类型),第三个参数是一个参数数组(如果没有参数,可以省略)。
JavaScript 的 number
类型对应于 C 语言的 int
、float
或 pointer
类型
JavaScript 的 string
类型对应于 C 语言的字符串类型 char*
JavaScript 的 array
或类型化数组对应于 C 语言的数组类型;对于类型化数组,它必须是 Uint8Array 或 Int8Array
在 JavaScript 中使用 ccall
调用:
// 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
如果要 导出 JS 库函数(例如 SRC/Library*.js
文件中的某些内容),那么除了 EXPORTED_FUNCTIONS
之外,还需要将其添加到 DEFAULT_LIBRARY_FUNCS_TO_INCLUDE
编译参数中,因为后者将强制添加的方法被包含在构建中。
二、js 直接调用导出的接口
直接调用编译成的 js 接口函数(在 C 或 C++ 接口函数名的前面添加 _
)的方式比使用 ccall、cwrap
的调用方式运行更快。
传递给函数和从函数接收的参数必须是基本的数据类型:
- 整数和浮点数可以直接进行传递
- 指针也可以直接传递
- JavaScript 字符串类型
someString
可以通过使用ptr = allocateUTF8(someString)
转换为char *
,注意: 向指针的转换会分配内存,之后需要通过调用free(ptr)
来释放内存(在JavaScript 中调用_free(ptr)
) - 从 C/C++ 接收的
char*
可以使用UTF8ToString(ptr)
转换为 JavaScript 字符串。
案例一: 从 js 中传入 string 类型的参数
C++ 接口代码:
// 接收 js 的 json 字符串,解析 data 项,并将内容打印到控制台
int EMSCRIPTEN_KEEPALIVE jsonParse(const char *jsonStr)
{
// string str = R"({"data": "test json"})";
auto j = nlohmann::json::parse(jsonStr);
if (j.find("data") != j.end())
{
auto v = j["data"].get<string>();
cout << v << endl;
}
return 0;
}
js 端代码:
let jsonstr = JSON.stringify({data:"Hello World!"});
const ptr = allocateUTF8(jsonstr);
Module._jsonParse(ptr);
_free(ptr);
案例二: 在 js 中接收返回的 char* 类型
C++ 接口代码:
char* EMSCRIPTEN_KEEPALIVE outString(char* p){
char s[] = ", Hi there!";
strcat(p, s);
return p;
}
js 端代码:
const ptr1 = allocateUTF8("zhang3");
var retPtr = Module._outString(ptr1);
var resValue = UTF8ToString(retPtr);
console.log(resValue);
_free(retPtr);
案例三: 在 js 中开辟内存,调用 C++ 接口并为内存赋值
C++ 接口代码:
int EMSCRIPTEN_KEEPALIVE outString2(char* p){
char s[] = "Hi there!";
strcpy(p, s);
return 0;
}
js 端代码:
var ptr = _malloc(512);
Module._outString2(ptr);
var str = UTF8ToString(ptr);
console.log(str);
_free(ptr);
参考: