杂项

预计阅读时间: 7 分钟

源文档 - Miscellaneous

类型

自省

Koffi 2.0 新增:koffi.resolve(),Koffi 2.2 新增:koffi.offsetof()

记录

introspect() 返回的值在 2.0 版本和 2.2 版本 中发生了变化。

在 Koffi 1.x 中,它只能与结构体类型一起使用,并返回传递给 koffi.struct() 的对象,其中包含成员名称和类型。

从 Koffi 2.2 开始,每个记录成员都被暴露为一个对象,其中包含名称、类型以及在记录中的偏移量。

更多详细信息请参考迁移指南

使用 koffi.introspect(type) 来获取关于类型的详细信息:名称、原始类型、大小、对齐方式、成员(记录类型)、引用类型(数组、指针)以及长度(数组)。

const FoobarType = koffi.struct("FoobarType", {
	a: "int",
	b: "char *",
	c: "double",
});

console.log(koffi.introspect(FoobarType));

// 在 64 位机器上的预期结果:
// {
//     name: 'FoobarType',
//     primitive: 'Record',
//     size: 24,
//     alignment: 8,
//     members: {
//         a: { name: 'a', type: [External: 4b28a60], offset: 0 },
//         b: { name: 'b', type: [External: 4b292e0], offset: 8 },
//         c: { name: 'c', type: [External: 4b29260], offset: 16 }
//     }
// }

Koffi 还提供了一些额外的实用函数来获取这些信息的子集:

  • koffi.sizeof(type) 用于获取类型的大小
  • koffi.alignof(type) 用于获取类型的对齐方式
  • koffi.offsetof(type, member_name) 用于获取记录成员的偏移量
  • koffi.resolve(type) 用于从类型字符串中获取已解析的类型对象

与之前一样,你可以通过名称或通过 koffi.types 来引用原始类型:

// 这两行代码的作用相同:
console.log(koffi.sizeof("long"));
console.log(koffi.sizeof(koffi.types.long));

别名

Koffi 2.0 新增

你可以使用 koffi.alias(name, type) 为类型创建别名。别名类型与原类型完全等价。

循环引用

Koffi 2.10.0 新增

在某些情况下,复合类型可以相互引用,从而相互依赖。这种情况也可能发生在函数接受一个指向结构体的指针时,而该结构体也包含一个函数指针。

为了解决这个问题,你可以创建一个不透明类型,然后稍后将其重新定义为具体的结构体或联合类型,如下所示。

const Type1 = koffi.opaque("Type1");

const Type2 = koffi.struct("Type2", {
	ptr: "Type1 *",
	i: "int",
});

// 将 Type1 重新定义为具体类型
koffi.struct(Type1, {
	ptr: "Type2 *",
	f: "float",
});
记录

在重新定义类型时,必须使用正确的类型对象。如果你只有名称,请使用 koffi.resolve() 从类型字符串中获取类型对象。

const MyType = koffi.opaque("MyType");

// 这样做是不行的,你必须使用 MyType 对象,而不是类型字符串
koffi.struct("MyType", {
	ptr: "Type2 *",
	f: "float",
});

设置

内存使用

对于同步/常规调用,Koffi 使用两个预先分配的内存块:

  • 一个用于构建 C 栈并分配寄存器,随后由平台特定的汇编代码使用(默认为 1 MiB)
  • 一个用于分配字符串和对象/结构体(默认为 2 MiB)

除非使用了非常大的字符串或对象(至少超过一页内存),否则在调用或回调期间,Koffi 不会直接分配任何额外内存。然而,请注意,JS 引擎(如 V8)可能会。

可以更改这些预先分配块的大小(以字节为单位)。使用 koffi.config() 获取包含设置的对象,使用 koffi.config(obj) 应用新的设置。

let config = koffi.config(); console.log(config);

对于异步调用也是如此。当发起异步调用时,如果仍有未使用的(驻留)块集可用,Koffi 将不会分配新的块集。一旦异步调用完成,如果剩余的块集数量超过 resident_async_pools,这些块集将被释放。

同时运行的异步调用数量不能超过 max_async_calls

默认设置

设置默认值描述
syncstacksize1 MiB同步调用的栈大小
syncheapsize2 MiB同步调用的堆大小
asyncstacksize256 kiB异步调用的栈大小
asyncheapsize512 kiB异步调用的堆大小
residentasyncpools2异步调用的驻留池数量
maxasynccalls64同时进行的异步调用的最大数量
maxtypesize64 MiBKoffi 类型的最大大小(用于数组和结构体)

使用统计

Koffi 2.3.2 新增

你可以使用 koffi.stats() 获取与 Koffi 相关的一些统计信息。

POSIX 错误代码

Koffi 2.3.14 新增

你可以使用 koffi.errno() 获取当前的 errno 值,使用 koffi.errno(value) 修改它。

标准的 POSIX 错误代码可在 koffi.os.errno 中找到,如下所示:

const assert = require("assert");

// ES6 语法:import koffi from 'koffi';
const koffi = require("koffi");

const lib = koffi.load("libc.so.6");

const close = lib.func("int close(int fd)");

close(-1);
assert.equal(koffi.errno(), koffi.os.errno.EBADF);

console.log("使用无效的文件描述符调用 close() 是符合 POSIX 标准的!");

重置内部状态

Koffi 2.5.19 新增

你可以使用 koffi.reset() 清除一些 Koffi 内部状态,例如:

  • 解析器类型名称
  • 异步函数代理(有助于避免 jest --detectOpenHandles 的误报)

此函数主要用于测试代码,当你反复执行相同的代码并且需要重用类型名称时。

警告

尝试使用在重置之前最初定义的函数或类型是未定义行为,很可能会导致崩溃!