函数调用
加载库
要声明函数,首先使用 加载共享库koffi.load(filename)
。
// ES6 syntax: import koffi from 'koffi';
const koffi = require('koffi');
const lib = koffi.load('/path/to/shared/library');
// File extension depends on platforms: .so, .dll, .dylib, etc.
一旦对该库的所有引用都消失(包括使用它的所有函数,如下所述),该库将自动卸载。
从Koffi 2.3.20开始,您可以通过调用显式卸载库lib.unload()
。卸载该库后,任何从该库查找或调用函数的尝试都会崩溃。
加载选项
Koffi 2.6 中的新功能
该load
函数可以采用可选的对象参数,具有以下选项
const options = {
lazy: true // Use RTLD_LAZY (lazy-binding) on POSIX platforms (by default, use RTLD_NOW)
};
const lib = koffi.load('/path/to/shared/library.so', options);
如果需要,可以添加更多选项。
函数定义
定义语法
使用 返回的对象koffi.load()
从库中加载 C 函数。为此,您可以使用两种语法:
经典语法,灵感来自node-ffi
类 C 原型
经典语法
要声明函数,您需要指定其未损坏的名称、返回类型和参数。使用省略号作为可变参数函数的最后一个参数。
const printf = lib.func('printf', 'int', ['str', '...']);
const atoi = lib.func('atoi', 'int', ['str']);
Koffi 自动尝试非标准 x86 调用约定的损坏名称。有关此主题的更多信息,请参阅有关调用约定的部分。
类似 C 的原型
如果您愿意,可以使用简单的类似 C 的原型字符串来声明函数,如下所示
const printf = lib.func('int printf(const char *fmt, ...)');
// The parameter name is not used by Koffi, and optional
const atoi = lib.func('int atoi(str)');
您可以将()
or(void)
用于不带参数的函数。
可变参数函数
可变参数函数是用省略号作为最后一个参数来声明的。
为了调用可变参数函数,您必须为每个附加 C 参数提供两个 Javascript 参数,第一个是预期类型,第二个是值。
const printf = lib.func('printf', 'int', ['str', '...']);
// The variadic arguments are: 6 (int), 8.5 (double), 'THE END' (const char *)
printf('Integer %d, double %g, str %s', 'int', 6, 'double', 8.5, 'str', 'THE END');
在 x86 平台上,只有 Cdecl 约定可用于可变参数函数。
调用约定(Calling conventions)
Koffi 2.7 中已更改
默认情况下,调用 C 函数是同步发生的。
大多数体系结构每个进程仅支持一种过程调用标准。32 位 x86 平台是一个例外,Koffi 支持多种 x86 约定
Cdecl
koffi.func(name, ret, params)
(default)
这是默认约定,也是其他平台上唯一的约定
Stdcall
koffi.func('__stdcall', name, ret, params)
__stdcall
此约定在 Win32 API 中广泛使用
Fastcall
koffi.func('__fastcall', name, ret, params)
__fastcall
很少使用,使用 ECX 和 EDX 作为前两个参数
Thiscall
koffi.func('__thiscall', name, ret, params)
__thiscall
很少使用,使用 ECX 作为第一个参数
您可以在非 x86 平台上安全地使用它们,它们只是被忽略。
下面您可以找到一个小示例,展示如何使用非默认调用约定,并使用两种语法:
// ES6 syntax: import koffi from 'koffi';
const koffi = require('koffi');
const lib = koffi.load('user32.dll');
// The following two declarations are equivalent, and use stdcall on x86 (and the default ABI on other platforms)
const MessageBoxA_1 = lib.func('__stdcall', 'MessageBoxA', 'int', ['void *', 'str', 'str', 'uint']);
const MessageBoxA_2 = lib.func('int __stdcall MessageBoxA(void *hwnd, str text, str caption, uint type)');
调用类型
同步调用
一旦声明了本机函数,您就可以像调用任何其他 JS 函数一样简单地调用它。
const atoi = lib.func('int atoi(const char *str)');
let value = atoi('1257');
console.log(value);
对于可变参数函数,您必须指定每个附加参数的类型和值。
const printf = lib.func('printf', 'int', ['str', '...']);
// The variadic arguments are: 6 (int), 8.5 (double), 'THE END' (const char *)
printf('Integer %d, double %g, str %s', 'int', 6, 'double', 8.5, 'str', 'THE END');
异步调用
您可以通过其 async 成员调用该函数来发出异步调用。在这种情况下,您需要提供一个带有参数(err, res)
的回调函数作为最后一个参数。
// ES6 syntax: import koffi from 'koffi';
const koffi = require('koffi');
const lib = koffi.load('libc.so.6');
const atoi = lib.func('int atoi(const char *str)');
atoi.async('1257', (err, res) => {
console.log('Result:', res);
})
console.log('Hello World!');
// This program will print:
// Hello World!
// Result: 1257
这些调用由工作线程执行。您有责任处理本机代码中可能由多线程引起的数据共享问题。
util.promisify()
您可以使用 Node.js 标准库轻松地将这种回调式异步函数转换为基于 Promise 的版本。
可变参数函数不能异步调用。
异步函数在工作线程上运行。如果在线程之间共享数据,则需要处理线程安全问题。
回调必须从主线程调用,或者更准确地说,从与 V8 解释器相同的线程调用。从另一个线程调用回调是未定义的行为,并且可能会导致崩溃或大混乱。
函数指针
Koffi 2.4 中的新功能
您可以通过两种方式调用函数指针:
直接调用函数指针
koffi.call(ptr, type, ...)
将函数指针解码为实际函数
koffi.decode(ptr, type)
下面的示例展示了如何基于以下本机 C 库以两种方式调用 C 函数指针:int (*)(int, int)
typedef int BinaryIntFunc(int a, int b);
static int AddInt(int a, int b) { return a + b; }
static int SubstractInt(int a, int b) { return a - b; }
BinaryIntFunc *GetBinaryIntFunction(const char *type)
{
if (!strcmp(type, "add")) {
return AddInt;
} else if (!strcmp(type, "substract")) {
return SubstractInt;
} else {
return NULL;
}
}
直接调用指针
koffi.call(ptr, type, ...)
用于调用函数指针。前两个参数是指针本身和您尝试调用的函数的类型(如下koffi.proto()
所示声明),其余参数用于调用。
// Declare function type
const BinaryIntFunc = koffi.proto('int BinaryIntFunc(int a, int b)');
const GetBinaryIntFunction = lib.func('BinaryIntFunc *GetBinaryIntFunction(const char *name)');
const add_ptr = GetBinaryIntFunction('add');
const substract_ptr = GetBinaryIntFunction('substract');
let sum = koffi.call(add_ptr, BinaryIntFunc, 4, 5);
let delta = koffi.call(substract_ptr, BinaryIntFunc, 100, 58);
console.log(sum, delta); // Prints 9 and 42
解码指向函数的指针
用于获取 JS 函数,然后您可以像任何其他 Koffi 函数一样使用该函数。koffi.decode(ptr, type)
此方法还允许您使用已解码函数的 async 成员执行异步调用。
// Declare function type
const BinaryIntFunc = koffi.proto('int BinaryIntFunc(int a, int b)');
const GetBinaryIntFunction = lib.func('BinaryIntFunc *GetBinaryIntFunction(const char *name)');
const add = koffi.decode(GetBinaryIntFunction('add'), BinaryIntFunc);
const substract = koffi.decode(GetBinaryIntFunction('substract'), BinaryIntFunc);
let sum = add(4, 5);
let delta = substract(100, 58);
console.log(sum, delta); // Prints 9 and 42
参数转换
默认情况下,Koffi 只会将参数从 Javascript 转发并转换为 C。但是,许多 C 函数使用指针参数作为输出值或输入/输出值。
除此之外,在接下来的几页中,您将了解更多信息:
Last updated