我写了这段简单的C代码:
#include <string.h>
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
const char * helloWorldStatic( ) {
return "Hello World from C";
}
EMSCRIPTEN_KEEPALIVE
char * helloWorldDynamic( ) {
return strdup("Hello World from C");
}
现在,当我使用 Emscripten 将其编译为 WASM32 时,我可以像这样从 TypeScript 访问任一函数:
async function helloWorldStatic( ): Promise<String>
{
let module = await Module
let result = module.ccall(
"helloWorldStatic", // name of the C function
"string", // return type
[ ], // argument types
[ ] // arguments
)
return result
}
这似乎工作正常。打印字符串时,我得到了预期的值。
但是内存管理呢?我在文档中没有找到任何解释使用
ccall
或 cwrap
时的内存管理的内容,返回值是一个字符串。
Emscripten 是否总是假设所有字符串都是静态的并且永远不需要释放?它是否假设它们是动态的并且总是会释放它们?
在这个例子中,它可能假设
const char *
不需要释放,但即使完全撒谎:
EMSCRIPTEN_KEEPALIVE
char * helloWorldStatic( ) {
return "Hello World from C";
}
EMSCRIPTEN_KEEPALIVE
const char * helloWorldDynamic( ) {
return strdup("Hello World from C");
}
它似乎仍然有效。我不确定字符串是否被释放,并且在尝试释放常量字符串时代码不会崩溃,或者字符串是否会以这种方式泄漏。
根据
ccall()
的实现,我会说如果分配内存就会泄漏:
var ccall = (ident, returnType, argTypes, args, opts) => {
var toC = {
string: str => {
var ret = 0;
if (str !== null && str !== undefined && str !== 0) {
ret = stringToUTF8OnStack(str)
}
return ret
},
array: arr => {
var ret = stackAlloc(arr.length);
writeArrayToMemory(arr, ret);
return ret
}
};
function convertReturnValue(ret) {
if (returnType === "string") {
return UTF8ToString(ret)
}
if (returnType === "boolean") return Boolean(ret);
return ret
}
var func = getCFunc(ident);
var cArgs = [];
var stack = 0;
assert(returnType !== "array", 'Return type should not be "array".');
if (args) {
for (var i = 0; i < args.length; i++) {
var converter = toC[argTypes[i]];
if (converter) {
if (stack === 0) stack = stackSave();
cArgs[i] = converter(args[i])
} else {
cArgs[i] = args[i]
}
}
}
var ret = func(...cArgs);
function onDone(ret) {
if (stack !== 0) stackRestore(stack);
return convertReturnValue(ret)
}
ret = onDone(ret);
return ret
};
基本上,返回值被输入到
convertReturnValue()
中,如果是字符串,则可以这样做:
if (returnType === "string") {
return UTF8ToString(ret)
}
UTF8ToString()
从ret
指向的WASM内存中读取字符串,并将其转换为JS字符串。 JS 字符串存在于 JS 内存中并被垃圾回收,但 WASM 内存中的原始字符串保持不变,因此永远不会被释放。
所以我认为正确处理
helloWorldDynamic()
情况的唯一方法如下:
async function helloWorldStatic( ): Promise<String>
{
let module = await Module
let ptr = module.ccall(
"helloWorldStatic", // name of the C function
"number", // return type (number as it is a pointer!)
[ ], // argument types
[ ] // arguments
)
let string = UTF8ToString(ptr)
module._free(ptr)
return string
}
要实现这一点,您必须确保使用以下选项构建 WASM 代码
-s EXPORTED_FUNCTIONS=_free,...
只有这样,
free()
函数才可以在您的模块上使用(按照 C 命名约定将其命名为 _free()
作为符号)。您也可以通过这种方式导出自己的函数,但对于代码中的函数使用 EMSCRIPTEN_KEEPALIVE
更容易,但不能将该宏用于外部库中定义的函数。