跳轉到主要內容
ArkFFI 在 TypeScript 層管理部分記憶體,但大多數情況下 C 庫的記憶體由 C 庫自身管理。理解各場景的記憶體歸屬,可避免泄漏和野指針。

概覽

場景誰分配誰釋放安全使用方式
基本類型傳參(int、double)值傳遞,無分配
字串傳參(FFIType.CStringNAPI 層NAPI 層(呼叫後立即釋放)無需操心
字串回傳值(callStringC 庫C 庫唯讀,不釋放
const char* 結構體欄位C 庫C 庫fromPtr 讀取指針值,不釋放
結構體傳參(StructSchema使用者(create使用者自行管理create 回傳的 ArrayBuffer 用完後釋放
結構體回傳值(StructSchemaArkFFI(內部 ArrayBuffer使用者呼叫 releasePtr讀取後呼叫 releasePtr,否則 Map 常駐
getSymbolPtr / ffi.ptr僅獲取位址,無分配

基本類型傳參

intdoublechar 等基本類型透過暫存器或堆疊傳遞,不涉及任何記憶體分配。直接傳入即可。
lib.symbols.add(2.0, 3.0); // 無分配,值傳遞

字串傳參

當參數類型為 FFIType.CString 時,NAPI 層會用 napi_get_value_string_utf8 取得 C 字串,在堆上分配 char*,呼叫 C 函數後立即 delete[]。使用者無需管理。
lib.symbols.compute(0, 4.0, 'square');
// ↑ NAPI 內部:allocate char* → call → delete[]

字串回傳值

callString 回傳 C 庫內部持有的 const char*,ArkFFI 只讀取其內容(napi_create_string_utf8),不分配也不釋放。指針的生命週期由 C 庫管理。 結構體 fromPtrconst char* 欄位同樣回傳指針數值,該指針指向 C 庫記憶體,不要透過 releasePtr 釋放
let ptr = result.name; // const char* 指針,指向 C 庫
let name = new CString(ptr).toString(); // 唯讀
// 不要 releasePtr(ptr) ← 非 ArkFFI 管理

結構體傳參

透過 StructSchema.create() 建立的 ArrayBuffer 由使用者分配,傳遞給 C 函數後由使用者自行管理其生命週期。
let buf = Complex.create({ real: 1, imag: 2 });
lib.symbols.complex_add(buf, otherBuf);
// buf 由使用者管理,用完後賦值 null 即可 GC

結構體回傳值(releasePtr

dlopen / CFunctionreturnsStructSchema 時,ArkFFI 內部會:
  1. 呼叫 NAPI 取得 ArrayBuffer
  2. 透過 ffi.ptr 取得其資料指針
  3. ArrayBuffer 存入 g_structResults Map 防止 GC
  4. 回傳指針給使用者
使用者透過 fromPtr 讀取後,應呼叫 releasePtr 釋放 Map 中的參照,使 ArrayBuffer 可被 GC。
import { releasePtr } from 'arkffi';

let ptr = lib.symbols.complex_add(a, b);   // ArkFFI 內部分配 buffer
let result = Complex.fromPtr(ptr);          // 讀取資料
releasePtr(ptr);                            // 釋放參照(允許 GC)

不呼叫 releasePtr 會怎樣?

g_structResults Map 持續持有 ArrayBuffer 的參照,指針始終有效。Map 條目在呼叫 releasePtr 或程式退出前不會被 GC,但一般不構成問題(條目數等於 struct 回傳呼叫次數)。

釋放錯誤指針會怎樣?

releasePtr(ptr) 在 Map 中按 key 查找。如果 ptr 不是由 ArkFFI struct 回傳分配的指針(例如來自 C 庫的 const char*),Map 找不到該 key,靜默無操作,不會崩潰。

指針類型(ptr、callback)

FFIType.ptrFFIType.callback 傳達的是位址值(number),不涉及記憶體分配。位址由 C 庫管理或由 JSCallback 內部槽位管理。
let applyPtr = ffi.getSymbolPtr(handle, 'apply_callback');
// 只獲取位址,無分配

JSCallback ptr 指向內部槽位或 trampoline JSCallback 自己管理
close() 時釋放槽位

核心原則

ArkFFI 只管理自己分配的記憶體。C 庫的記憶體由 C 庫管理。
記憶體來源管理方
napi_get_value_string_utf8 副本ArkFFI(自動釋放)
struct 回傳值內部 ArrayBufferArkFFI(releasePtr 釋放)
C 庫回傳的 const char*C 庫
使用者 StructSchema.create() 產物使用者
dlopen 內部 handleArkFFI(close()dlclose