Deno FFI のドキュメントには文字列や配列を外部関数に渡す方法の解説がない。手探りで調べて動くコードにまとめてみた。
なお、このコードは Deno 1.39.1 で動作確認した。このバージョンで FFI は unstable なため、今後挙動が変わったり動かなくなったりする可能性がある点に注意すること。
// 以下、 deno run で実行するには --unstable --allow-ffi --allowr-read フラグが必要
// libc の関数 int execvp(const char *file, char *const argv[]) の呼び出しを例に取る
function execvp(program: string, args: string[]) {
const libName = Deno.build.os === "linux" ? "libc.so.6" : "libc.dylib";
const lib = Deno.dlopen(libName, {
// 文字列の配列のポインタを取る引数(第2引数)の型は buffer とする
execvp: { parameters: ["buffer", "buffer"], result: "isize" },
});
// JS の文字列を UTF-8 バイト列のバッファに変換する
const programCString = stringToCString(program);
const argCStrings = args.map((arg) => stringToCString(arg));
// バッファのポインタを取得する
const argPointers = argCStrings.map((arg) => arrayBufferToRawPointer(arg));
// execvp() に渡す配列は NULL ポインタで終端する必要がある
argPointers.push(0n);
// 配列を TypedArray (BigUint64Array) に変換する
const argPointersArray = BigUint64Array.from(argPointers);
// execvp() を呼ぶ
lib.symbols.execvp(programCString, argPointersArray);
}
function stringToCString(s: string): ArrayBuffer {
// C 文字列は末尾に \0 を置く必要がある
return new TextEncoder().encode(`${s}\0`).buffer;
}
function arrayBufferToRawPointer(buffer: ArrayBuffer): bigint {
// Deno.UnsafePointer.of() は opaque なポインタオブジェクトを返す
// それを 生のメモリアドレスに変換する
return BigInt(Deno.UnsafePointer.value(Deno.UnsafePointer.of(buffer)));
}
Deno FFI におけるポインタの扱いは以下のページが参考になった。