Emscripten 字符串返回值的内存管理

问题描述 投票:0回答:1

我写了这段简单的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"); 
}

它似乎仍然有效。我不确定字符串是否被释放,并且在尝试释放常量字符串时代码不会崩溃,或者字符串是否会以这种方式泄漏。

memory emscripten
1个回答
0
投票

根据

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
更容易,但不能将该宏用于外部库中定义的函数。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.