迁移指南

预计阅读时间: 5 分钟

源文档 - Migration guide

Koffi 1.x 升级到 2.x

在 2.x 版本中,API 在几个方面发生了变化,目的是减少一些过于“魔法”的行为,并减少 C 语言和类似 C 的原型之间的语法差异。

如果你使用了以下内容,可能需要更改代码:

  • 回调函数
  • 不透明类型
  • koffi.introspect()

回调类型的变化

在 Koffi 1.x 中,回调函数的定义方式使它们可以直接作为参数和返回类型使用,隐藏了底层指针。现在,你必须通过指针使用它们:void CallIt(CallbackType func) 在 Koffi 1.x 中变为 void CallIt(CallbackType *func),从 2.0 版本开始。

以下是 C 代码示例:

#include <string.h>

int TransferToJS(const char *name, int age, int (*cb)(const char *str, int age))
{
    char buf[64];
    snprintf(buf, sizeof(buf), "Hello %s!", str);
    return cb(buf, age);
}

以下是 Koffi 1.x 和 Koffi 2.x 的 API 差异示例:

// Koffi 1.x

const TransferCallback = koffi.proto("int TransferCallback(const char *str, int age)");

const TransferToJS = lib.func("TransferToJS", "int", ["str", "int", TransferCallback]);
// 等价于:const TransferToJS = lib.func('int TransferToJS(str s, int x, TransferCallback cb)');

let ret = TransferToJS("Niels", 27, (str, age) => {
	console.log(str);
	console.log("Your age is:", age);
	return 42;
});
console.log(ret);
// Koffi 2.x

const TransferCallback = koffi.proto("int TransferCallback(const char *str, int age)");

const TransferToJS = lib.func("TransferToJS", "int", ["str", "int", koffi.pointer(TransferCallback)]);
// 等价于:const TransferToJS = lib.func('int TransferToJS(str s, int x, TransferCallback *cb)');

let ret = TransferToJS("Niels", 27, (str, age) => {
	console.log(str);
	console.log("Your age is:", age);
	return 42;
});
console.log(ret);

Koffi 1.x 仅支持临时回调,必须使用 Koffi 2.x 才能支持注册回调。

记录

koffi.proto() 函数是在 Koffi 2.4 中引入的,早期版本中称为 koffi.callback()

不透明类型的变化

在 Koffi 1.x 中,不透明句柄的定义方式使它们可以直接作为参数和返回类型使用,隐藏了底层指针。现在,在 Koffi 2.0 中,你必须通过指针使用它们,并使用数组作为输出参数。

此外,koffi.handle() 在 Koffi 2.1 中已被弃用并替换为 koffi.opaque()。它们的功能相同,但新代码应使用 koffi.opaque(),前者将在 Koffi 3.0 中被移除。

对于返回不透明指针或通过参数传递它们的函数:

// Koffi 1.x

const FILE = koffi.handle("FILE");
const fopen = lib.func("fopen", "FILE", ["str", "str"]);
const fclose = lib.func("fclose", "int", ["FILE"]);

let fp = fopen("EMPTY", "wb");
if (!fp) throw new Error("Failed to open file");
fclose(fp);
// Koffi 2.1

// 如果你使用 Koffi 2.0:const FILE = koffi.handle('FILE');
const FILE = koffi.opaque("FILE");
const fopen = lib.func("fopen", "FILE *", ["str", "str"]);
const fclose = lib.func("fclose", "int", ["FILE *"]);

let fp = fopen("EMPTY", "wb");
if (!fp) throw new Error("Failed to open file");
fclose(fp);

对于通过输出参数设置不透明句柄的函数(例如 sqlite3_open_v2),现在必须使用单元素数组,如下所示:

// Koffi 1.x

const sqlite3 = koffi.handle("sqlite3");

const sqlite3_open_v2 = lib.func("int sqlite3_open_v2(const char *, _Out_ sqlite3 *db, int, const char *)");
const sqlite3_close_v2 = lib.func("int sqlite3_close_v2(sqlite3 db)");

const SQLITE_OPEN_READWRITE = 0x2;
const SQLITE_OPEN_CREATE = 0x4;

let db = {};

if (sqlite3_open_v2(":memory:", db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
	throw new Error("Failed to open database");

sqlite3_close_v2(db);
// Koffi 2.1

// 如果你使用 Koffi 2.0:const sqlite3 = koffi.handle('sqlite3');
const sqlite3 = koffi.opaque("sqlite3");

const sqlite3_open_v2 = lib.func("int sqlite3_open_v2(const char *, _Out_ sqlite3 **db, int, const char *)");
const sqlite3_close_v2 = lib.func("int sqlite3_close_v2(sqlite3 *db)");

const SQLITE_OPEN_READWRITE = 0x2;
const SQLITE_OPEN_CREATE = 0x4;

let db = null;

let ptr = [null];
if (sqlite3_open_v2(":memory:", ptr, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
	throw new Error("Failed to open database");
db = ptr[0];

sqlite3_close_v2(db);

新的koffi.introspect()

在 Koffi 1.x 中,koffi.introspect() 只能与结构体类型一起使用,并返回传递给 koffi.struct() 的对象以初始化类型。现在,这个函数可以与所有类型一起使用。

你仍然可以获取结构体成员列表:

const StructType = koffi.struct("StructType", { dummy: "int" });

// Koffi 1.x
let members = koffi.introspect(StructType);

// Koffi 2.x
let members = koffi.introspect(StructType).members;