如何实现Zig/Wasm与JavaScript之间的字符串类型函数绑定?
Zig/Wasm与JavaScript的字符串类型函数绑定实现方法
你已经实现了i32类型的Zig/Wasm与JS函数绑定,现在需要替换为字符串参数和返回值的sayHello函数绑定,核心难点在于Wasm无原生字符串类型,Zig的[]const u8是指针+长度的组合,需要通过共享线性内存完成字符串传递,以下是完整实现步骤:
一、修改Zig代码(new.zig)
需要处理内存分配、参数拆分与返回值组合,同时暴露内存分配函数供JS调用:
const std = @import("std"); const allocator = std.heap.wasm_allocator; // 声明外部JS函数:接收字符串指针+长度,返回结果字符串指针 extern fn sayHello(ptr: [*]const u8, len: usize) [*]const u8; // 声明外部JS函数:获取返回字符串的长度 extern fn getReturnStrLen() usize; // 暴露给JS的内存分配函数:申请指定长度的内存,返回指针 export fn alloc(len: usize) [*]u8 { const buf = allocator.alloc(u8, len) catch unreachable; return buf.ptr; } // 封装调用逻辑,对外提供友好的字符串传递接口 export fn callSayHello(name_ptr: [*]const u8, name_len: usize) [*]const u8 { const name = name_ptr[0..name_len]; // 调用JS函数,传递字符串的指针和长度 const result_ptr = sayHello(name.ptr, name.len); return result_ptr; } // 暴露获取结果长度的函数,方便JS读取 export fn getResultLen() usize { return getReturnStrLen(); } pub fn main() void {} // 空main避免编译警告
二、修改JavaScript代码(new.js)
需要完成:读取Zig传递的字符串、生成结果并写入Wasm内存、传递指针和长度:
import fs from "fs"; const source = fs.readFileSync("new.wasm"); const typedArray = new Uint8Array(source); let returnStrLen = 0; const importObject = { env: { sayHello: function (ptr, len) { // 从Wasm线性内存中读取Zig传递的字符串 const memory = new Uint8Array(this.memory.buffer, ptr, len); const name = new TextDecoder().decode(memory); // 生成结果字符串 const result = `Hello ${name}`; const encoder = new TextEncoder(); const encodedResult = encoder.encode(result); // 调用Zig暴露的alloc函数,申请Wasm内存存储结果 const resultPtr = this.alloc(encodedResult.length); // 将结果写入Wasm内存 new Uint8Array(this.memory.buffer, resultPtr, encodedResult.length).set(encodedResult); // 存储结果长度,供Zig读取 returnStrLen = encodedResult.length; // 返回结果字符串的指针 return resultPtr; }, getReturnStrLen: function () { return returnStrLen; } } }; (async () => { const { instance } = await WebAssembly.instantiate(typedArray, importObject); const wasm = instance.exports; // 准备要传递给Zig的字符串:"World" const name = "World"; const encoder = new TextEncoder(); const encodedName = encoder.encode(name); // 申请Wasm内存存储输入字符串 const namePtr = wasm.alloc(encodedName.length); // 将输入字符串写入Wasm内存 new Uint8Array(wasm.memory.buffer, namePtr, encodedName.length).set(encodedName); // 调用Zig的callSayHello函数 const resultPtr = wasm.callSayHello(namePtr, encodedName.length); const resultLen = wasm.getResultLen(); // 读取Wasm内存中的结果字符串并输出 const resultStr = new TextDecoder().decode(new Uint8Array(wasm.memory.buffer, resultPtr, resultLen)); console.log(resultStr); // 输出: Hello World })();
三、编译与运行
- 编译Zig代码为Wasm:
zig build-lib -O ReleaseSmall -target wasm32-freestanding new.zig -dynamic
- 运行Node.js代码:
node new.js
输出结果:
Hello World
关键原理说明
- 字符串传递本质:Zig的
[]const u8会被拆分为指针(内存地址)+长度两个值传递,Wasm与JS共享线性内存,因此双方都能通过指针+长度读取内存中的字符串内容。 - 内存管理:通过Zig的
wasm_allocator管理内存,JS调用Zig暴露的alloc函数申请内存,确保内存由Wasm侧统一管理,避免内存泄漏。 - 返回值处理:由于Wasm函数仅支持单个返回值,因此返回字符串时先返回指针,再通过独立函数返回长度,最终组合为可读取的字符串。
内容的提问来源于stack exchange,提问作者Hasan A Yousef
相关产品推荐
相关产品推荐

