数据指针

指针的使用方法

在 C 中,指针参数用于不同的目的。区分这些用例很重要,因为 Koffi 提供了不同的方法来处理每个用例:

  • 结构指针:C 库对结构指针的使用分为两种情况:避免(可能)昂贵的副本,以及让函数更改结构内容(输出或输入/输出参数)。

  • 不透明指针:库不公开结构的内容,只为您提供指向它的指针(例如FILE *)。只有库提供的函数才能使用该指针执行某些操作,在 Koffi 中我们将其称为不透明类型。这样做通常是出于 ABI 稳定性的原因,并防止库用户直接干扰库内部。

  • 指向原始类型的指针:这种情况比较罕见,通常用于输出或输入/输出参数。Win32 API 有很多这样的API。

  • 数组:在 C 中,动态大小的数组通常传递给带有指针的函数,指针可以是 NULL 终止的(或任何其他标记值),也可以是带有附加长度参数的指针。

指针类型

结构体指针

下面的 Win32 示例使用GetCursorPos()(带有输出参数)来检索并显示当前光标位置。

// ES6 syntax: import koffi from 'koffi';
const koffi = require('koffi');

const lib = koffi.load('user32.dll');

// Type declarations
const POINT = koffi.struct('POINT', {
    x: 'long',
    y: 'long'
});

// Functions declarations
const GetCursorPos = lib.func('int __stdcall GetCursorPos(_Out_ POINT *pos)');

// Get and show cursor position
let pos = {};
if (!GetCursorPos(pos))
    throw new Error('Failed to get cursor position');
console.log(pos);

不透明指针

Koffi 2.0 中的新功能

一些 C 库使用句柄,其行为就像指向不透明结构的指针。Win32 API 中的 HANDLE 类型就是一个例子。如果要重现此行为,可以将命名指针类型定义为不透明类型,如下所示:

const HANDLE = koffi.pointer('HANDLE', koffi.opaque());

// And now you get to use it this way:
const GetHandleInformation = lib.func('bool __stdcall GetHandleInformation(HANDLE h, _Out_ uint32_t *flags)');
const CloseHandle = lib.func('bool __stdcall CloseHandle(HANDLE h)');

指向原始类型的指针

在 javascript 中,不可能通过引用另一个函数来传递原始值。这意味着您不能调用函数并期望它修改其数字或字符串参数之一的值。

但是,数组和对象(以及其他)是引用类型值。将数组或对象从一个变量分配给另一个变量不涉及任何复制。相反,如以下示例所示,新变量引用与第一个变量相同的数组:

let list1 = [1, 2];
let list2 = list1;

list2[1] = 42;

console.log(list1); // Prints [1, 42]

所有这些意味着需要修改其原始输出值(例如参数int *)的 C 函数不能直接使用。然而,由于 Koffi 的透明数组支持,您可以使用 Javascript 数组来近似单元素数组的引用语义。

下面,您可以找到一个加法函数的示例,其中结果存储在int *输入/输出参数中,以及如何使用 Koffi 的该函数。

void AddInt(int *dest, int add)
{
    *dest += add;
}

您可以简单地传递一个单元素数组作为第一个参数:

const AddInt = lib.func('void AddInt(_Inout_ int *dest, int add)');

let sum = [36];
AddInt(sum, 6);

console.log(sum[0]); // Prints 42

数组指针(动态数组)

在 C 中,动态大小的数组通常作为指针传递。长度要么作为附加参数传递,要么从数组内容本身推断,例如使用终止标记值(例如字符串数组中的 NULL 指针)。

Koffi 可以将 JS 数组和 TypedArray 转换为指针参数。但是,由于 C 没有动态大小数组(胖指针)的正确概念,因此您需要根据 API 自行提供长度或标记值。

下面是一个简单的 C 函数示例,它采用以 NULL 结尾的字符串列表作为输入,计算所有字符串的总长度。

// Build with: clang -fPIC -o length.so -shared length.c -Wall -O2

#include <stdlib.h>
#include <stdint.h>
#include <string.h>

int64_t ComputeTotalLength(const char **strings)
{
    int64_t total = 0;

    for (const char **ptr = strings; *ptr; ptr++) {
        const char *str = *ptr;
        total += strlen(str);
    }

    return total;
}
// ES6 syntax: import koffi from 'koffi';
const koffi = require('koffi');

const lib = koffi.load('./length.so');

const ComputeTotalLength = lib.func('int64_t ComputeTotalLength(const char **strings)');

let strings = ['Get', 'Total', 'Length', null];
let total = ComputeTotalLength(strings);

console.log(total); // Prints 14

默认情况下,就像对象一样,数组参数从 JS 复制到 C,但反之则不然。不过,您可以更改输出参数部分中记录的方向。

一次性类型

Koffi 2.0 中的新功能

一次性类型允许您注册一个函数,该函数将在 Koffi 执行每次 C 到 JS 转换后自动调用。例如,这可以用来避免泄漏堆分配的字符串。

某些 C 函数直接或通过输出参数返回堆分配的值。虽然 Koffi 自动将值从 C 转换为 JS(转换为字符串或对象),但它不知道何时需要释放某些内容或如何释放。

对于不透明类型,例如 FILE,这并不重要,因为您将显式调用fclose()它们。但是某些值(例如字符串)会被 Koffi 隐式转换,并且您将无法访问原始指针。如果字符串是堆分配的,这会产生泄漏。

为了避免这种情况,您可以指示 Koffi 在转换完成后在原始指针上调用函数,方法使用koffi.dispose(name, type, func)。这会创建一个从另一种类型派生的类型,唯一的区别是,一旦值被转换并且不再需要,就会使用原始指针调用func 。

可以省略该名称以创建匿名一次性类型。如果func被省略或为空,Koffi 将使用(它在底层koffi.free(ptr)调用标准 C 库自由函数)。

const AnonHeapStr = koffi.disposable('str'); // Anonymous type (cannot be used in function prototypes)
const NamedHeapStr = koffi.disposable('HeapStr', 'str'); // Same thing, but named so usable in function prototypes
const ExplicitFree = koffi.disposable('HeapStr16', 'str16', koffi.free); // You can specify any other JS function

以下示例说明了从str派生的一次性类型的使用。

// ES6 syntax: import koffi from 'koffi';
const koffi = require('koffi');

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

const HeapStr = koffi.disposable('str');
const strdup = lib.cdecl('strdup', HeapStr, ['str']);

let copy = strdup('Hello!');
console.log(copy); // Prints Hello!

当您使用类似原型的语法声明函数时,您可以使用命名的一次性类型或使用“!” 具有兼容类型的快捷方式限定符,如下例所示。此限定符创建一个调用的匿名一次性类型koffi.free(ptr)

// ES6 syntax: import koffi from 'koffi';
const koffi = require('koffi');

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

// You can also use: const strdup = lib.func('const char *! strdup(const char *str)')
const strdup = lib.func('str! strdup(const char *str)');

let copy = strdup('World!');
console.log(copy); // Prints World!

一次性类型只能从指针或字符串类型创建。

在 Windows 上要小心:如果您的共享库使用不同的 CRT(例如 msvcrt),则内存可能已由不同的 malloc/free 实现或堆分配,如果您使用koffi.free().

展开指针

您可以使用指针来获取BigInt 对象koffi.address(ptr)形式的数值。

Last updated