函数调用

加载库

要声明函数,首先使用 加载共享库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 约定

约定(conventions)
经典形式(Classic form)
原型形式(Prototype form)
描述(Description)

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