将字符串传递到.wasm模块

时间:2018-11-17 22:18:11

标签: memory native emscripten webassembly

我已经坚持了一段时间,似乎无法找到解决问题的有效资源。我来自“仅C”背景,因此大多数Web开发人员的知识对我来说都是全新的。

我编写了一个C函数float editDistance(char *str1, char *str2),该函数返回2个char数组的编辑距离。现在的目标是从JS环境成功调用此函数。

确保代码与推荐的Emscipten ccall方法一起使用后,我决定继续。现在 我使用Emscripten将标志-O3-s WASM=1-s EXPORTED_FUNCTIONS="['_editDistance']"-s SIDE_MODULE=1 -s的C代码编译为Wasm。我要包装WebAssembly的JS代码是:

// Allocate memory for the wasm module to run in. (65536*256 bit)
let wasmMemory = new WebAssembly.Memory({
    initial: 256
});

let info = {
    env: {
        abort: function() {},
        memoryBase: 0,
        tableBase: 0,
        memory: wasmMemory,
        table: new WebAssembly.Table({initial: 2, element: 'anyfunc'}),
    }
}

// Define the strings
let str1 = "abcd";
let str2 = "abcd";

// Allocate memory on the wasm partition for the HEAPU8
let HEAPU8 = new Uint8Array(wasmMemory.buffer);

// Create the char arrays on the heap from the strings
let stackPtr = 0;
let str1Ptr = stackPtr;
stackPtr = stringToASCIIArray(str1, HEAPU8, stackPtr);

let str2Ptr = stackPtr;
stackPtr = stringToASCIIArray(str2, HEAPU8, stackPtr);

// Read the wasm file and instantiate it with the above environment setup. Then
// call the exported function with the string pointers.
let wasmBinaryFile = 'bin/edit_distanceW.wasm';
fetch(wasmBinaryFile, {credentials:"same-origin"})
    .then((response) => response.arrayBuffer())
    .then((binary) => WebAssembly.instantiate(binary,info))
    .then((wa) => alert(wa.instance.exports._editDistance(str1Ptr, str2Ptr)));

// Converts a string to an ASCII byte array on the specified memory
function stringToASCIIArray(str, outU8Array, idx){
    let length = str.length + 1;

    let i;
    for(i=0; i<length; i++){
        outU8Array[idx+i] = str.charCodeAt(i);
    }
    outU8Array[idx+i]=0;

    return (idx + length);
}

生成的wasm文件在转换为wat时需要进行以下导入:

  (import "env" "abort" (func (;0;) (type 0)))
  (import "env" "memoryBase" (global (;0;) i32))
  (import "env" "tableBase" (global (;1;) i32))
  (import "env" "memory" (memory (;0;) 256))
  (import "env" "table" (table (;0;) 2 anyfunc))

..并导出以下内容:

  (export "__post_instantiate" (func 7))
  (export "_editDistance" (func 9))
  (export "runPostSets" (func 6))
  (elem (;0;) (get_global 1) 8 1))

现在,当我测试代码时,字符串会毫无问题地传递到C模块。在情况恶化之前,甚至对它们进行了一些函数调用(strLen)。在C函数中,有一个讨厌的嵌套循环执行主要的计算,在读取字符串中的字符时,通过2D数组进行迭代(C代码刚从纸上移植了一个丑陋的伪代码,因此请谅解变量名) :

do{
    for(p=0; p<editDistance; p++){
        // Do stuff
    }
    // Do more stuff
    editDistance++;
} while(fkp[len2*2-len1][editDistance] != len1);

在函数进入for()循环之前,模块在存储器str1Ptr=0x00str2Ptr=0x05上仍然具有正确长度和内容的字符串。相反,进入for()循环后,内存立即被垃圾(大多数为0)覆盖,从而破坏了最终结果。我怀疑在范围更改上存在一些堆栈保存和恢复问题,因为使用gcc编译到我的PC的完全相同的代码就像一个魅力。

知道我缺少哪些设置会妨碍C函数正确完成吗?

1 个答案:

答案 0 :(得分:1)

如果您刚开始,则可能要使用由脚本生成的JS胶水。也就是说,不要使用SIDE_MODULE = 1而是将其输出到名为.js的文件中。然后,emscripten编译器将同时生成.js和.wasm文件。然后,您可以在项目中包含.js文件,它将为您处理所有加载和设置。

如果尝试自己加载wasm文件,则需要做很多工作来复制emscripten环境,这将需要很多emscripten内部细节。此外,当您更新到新版本的emscripten时,这些内部细节也会更改,因此您将自己创建更多工作。