Comment puis-je renvoyer une chaîne JavaScript à partir d'une fonction WebAssembly?
Le module suivant peut-il être écrit en C (++)?
export function foo() {
return 'Hello World!';
}
Aussi: Puis-je transmettre ceci au moteur JS pour être récupéré?
WebAssembly ne prend pas en charge nativement un type de chaîne, il prend plutôt en charge i32
/i64
/f32
/f64
types de valeur comme ainsi que i8
/i16
pour le stockage.
Vous pouvez interagir avec une instance WebAssembly en utilisant:
exports
, où à partir de JavaScript vous appelez dans WebAssembly, et WebAssembly renvoie un type de valeur unique.imports
où WebAssembly appelle en JavaScript, avec autant de types de valeurs que vous le souhaitez (remarque: le nombre doit être connu au moment de la compilation du module, ce n'est pas un tableau et n'est pas variadic).Memory.buffer
, qui est un ArrayBuffer
qui peut être indexé en utilisant (entre autres) Uint8Array
.Cela dépend de ce que vous voulez faire, mais il semble que l'accès direct au tampon soit le plus simple:
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);
Si votre module avait une fonction start
alors il était exécuté au moment de l'instanciation. Sinon, vous aurez probablement une exportation que vous appelez, par exemple instance.exports.doIt()
.
Une fois cela fait, vous devez obtenir la taille de la chaîne + l'index en mémoire, que vous exposeriez également via une exportation:
const size = instance.exports.myStringSize();
const index = instance.exports.myStringIndex();
Vous le liriez alors hors du tampon:
let s = "";
for (let i = index; i < index + size; ++i)
s += String.fromCharCode(buffer[i]);
Notez que je lis les valeurs 8 bits du tampon, je suppose donc que les chaînes étaient ASCII. C'est ce que std::string
Vous donnerait (l'index en mémoire serait ce que .c_str()
renvoie), mais pour exposer autre chose comme UTF-8, vous auriez besoin d'utiliser une bibliothèque C++ supportant UTF- 8, puis lisez vous-même UTF-8 à partir de JavaScript, obtenez les points de code et utilisez String.fromCodePoint
.
Vous pouvez également compter sur une chaîne terminée par un caractère nul, ce que je n'ai pas fait ici.
Vous pouvez également utiliser l'API TextDecoder
une fois qu'elle sera plus largement disponible dans les navigateurs en créant un ArrayBufferView
dans le WebAssembly.Memory
buffer
(qui est un ArrayBuffer
).
Si, au lieu de cela, vous faites quelque chose comme la journalisation de WebAssembly vers JavaScript, vous pouvez exposer le Memory
comme ci-dessus, puis à partir de WebAssembly déclarer une importation qui appelle JavaScript avec size + position. Vous pouvez instancier votre module comme:
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);
}
});
Cela a la mise en garde que si jamais vous augmentez la mémoire (soit via JavaScript en utilisant Memory.prototype.grow
, Soit en utilisant l'opcode grow_memory
) Alors le ArrayBuffer
est neutralisé et vous devez le créer un nouveau.
Sur la collecte des ordures: WebAssembly.Module
/WebAssembly.Instance
/WebAssembly.Memory
Sont toutes des ordures récupérées par le moteur JavaScript, mais c'est un gros marteau. Vous voulez probablement des chaînes GC, et ce n'est actuellement pas possible pour les objets qui vivent à l'intérieur d'un WebAssembly.Memory
. Nous avons discuté ajout du support GC à l'avenir .