Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
76 views
in Technique[技术] by (71.8m points)

How can I return a JavaScript string from a WebAssembly function

How can I return a JavaScript string from a WebAssembly function?

Can the following module be written in C(++) ?

export function foo() {
  return 'Hello World!';
}

Also: Can I pass this to the JS engine to be garbage collected?

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

WebAssembly doesn't natively support a string type, it rather supports i32 / i64 / f32 / f64 value types as well as i8 / i16 for storage.

You can interact with a WebAssembly instance using:

  • exports, where from JavaScript you call into WebAssembly, and WebAssembly returns a single value type.
  • imports where WebAssembly calls into JavaScript, with as many value types as you want (note: the count must be known at Module compilation time, this isn't an array and isn't variadic).
  • Memory.buffer, which is an ArrayBuffer that can be indexed using (among others) Uint8Array.

It depends on what you want to do, but it seems like accessing the buffer directly is the easiest:

const bin = ...; // WebAssembly binary, I assume below that it imports a memory from module "imports", field "memory".
const module = new WebAssembly.Module(bin);
const memory = new WebAssembly.Memory({ initial: 2 }); // Size is in pages.
const instance = new WebAssembly.Instance(module, { imports: { memory: memory } });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);

If your module had a start function then it got executed at instantiation time. Otherwise you'll likely have an export which you call, e.g. instance.exports.doIt().

Once that's done, you need to get string size + index in memory, which you would also expose through an export:

const size = instance.exports.myStringSize();
const index = instance.exports.myStringIndex();

You'd then read it out of the buffer:

let s = "";
for (let i = index; i < index + size; ++i)
  s += String.fromCharCode(buffer[i]);

Note that I'm reading 8-bit values from the buffer, I'm therefore assuming the strings were ASCII. That's what std::string would give you (index in memory would be what .c_str() returns), but to expose something else such as UTF-8 you'd need to use a C++ library supporting UTF-8, and then read UTF-8 yourself from JavaScript, obtain the codepoints, and use String.fromCodePoint.

You could also rely on the string being null-terminated, which I didn't do here.

You could also use the TextDecoder API once it's available more widely in browsers by creating an ArrayBufferView into the WebAssembly.Memory's buffer (which is an ArrayBuffer).


If, instead, you're doing something like logging from WebAssembly to JavaScript, then you can expose the Memory as above, and then from WebAssembly declare an import which calls JavaScript with size + position. You could instantiate your module as:

const memory = new WebAssembly.Memory({ initial: 2 });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);
const instance = new WebAssembly.Instance(module, {
    imports: {
        memory: memory,
        logString: (size, index) => {
            let s = "";
            for (let i = index; i < index + size; ++i)
                s += String.fromCharCode(buffer[i]);
            console.log(s);
        }
    }
});

This has the caveat that if you ever grow the memory (either through JavaScript using Memory.prototype.grow, or using the grow_memory opcode) then the ArrayBuffer gets neutered and you need to create it anew.


On garbage collection: WebAssembly.Module / WebAssembly.Instance / WebAssembly.Memory are all garbage collected by the JavaScript engine, but that's a pretty big hammer. You likely want to GC strings, and that's currently not possible for objects which live inside a WebAssembly.Memory. We've discussed adding GC support in the future.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...