输入参数

原始类型

标准类型

虽然 C 标准允许大多数整数类型的大小存在变化,但 Koffi 对大多数基本类型强制执行相同的定义,如下所示:

C类型
JS类型
Bytes
Signedness
备注

void

Undefined

0

仅作为返回类型有效

int8, int8_t

Number (integer)

1

Signed

uint8, uint8_t

Number (integer)

1

Unsigned

char

Number (integer)

1

Signed

uchar, unsigned char

Number (integer)

1

Unsigned

char16, char16_t

Number (integer)

2

Signed

int16, int16_t

Number (integer)

2

Signed

uint16, uint16_t

Number (integer)

2

Unsigned

short

Number (integer)

2

Signed

ushort, unsigned short

Number (integer)

2

Unsigned

int32, int32_t

Number (integer)

4

Signed

uint32, uint32_t

Number (integer)

4

Unsigned

int

Number (integer)

4

Signed

uint, unsigned int

Number (integer)

4

Unsigned

int64, int64_t

Number (integer)

8

Signed

uint64, uint64_t

Number (integer)

8

Unsigned

longlong, long long

Number (integer)

8

Signed

ulonglong, unsigned long long

Number (integer)

8

Unsigned

float32

Number (float)

4

float64

Number (float)

8

float

Number (float)

4

double

Number (float)

8

从 JS 整数转换为 C 整数时,Koffi 还接受 BigInt 值。如果该值超出了 C 类型的范围,Koffi 会将数字转换为未定义的值。相反,当需要 64 位大整数时,会自动使用 BigInt 值。

Koffi 定义了更多类型,可以根据操作系统和架构改变大小:

C类型
JS类型
Signedness
备注

bool

Boolean

通常为一个字节

long

Number (integer)

Signed

4 或 8 个字节,具体取决于平台(LP64、LLP64)

ulong

Number (integer)

Unsigned

4 或 8 个字节,具体取决于平台(LP64、LLP64)

unsigned long

Number (integer)

Unsigned

4 或 8 个字节,具体取决于平台(LP64、LLP64)

intptr

Number (integer)

Signed

4 或 8 个字节,具体取决于寄存器宽度

intptr_t

Number (integer)

Signed

4 或 8 个字节,具体取决于寄存器宽度

uintptr

Number (integer)

Unsigned

4 或 8 个字节,具体取决于寄存器宽度

uintptr_t

Number (integer)

Unsigned

4 或 8 个字节,具体取决于寄存器宽度

str, string

String

JS 字符串与 UTF-8 相互转换

str16, string16

String

JS 字符串与 UTF-16 (LE) 相互转换

基元类型可以通过名称(在字符串中)或通过以下方式指定koffi.types

// These two lines do the same:
let struct1 = koffi.struct({ dummy: 'long' });
let struct2 = koffi.struct({ dummy: koffi.types.long });

字节序敏感的整数(大小端存储)

Koffi 2.1 中的新功能

Koffi 定义了一堆字节序敏感类型,可以在处理二进制数据(网络负载、二进制文件格式等)时使用。

C
字节长度
Signedness
大小端存储

int16_le, int16_le_t

2

Signed

Little Endian

int16_be, int16_be_t

2

Signed

Big Endian

uint16_le, uint16_le_t

2

Unsigned

Little Endian

uint16_be, uint16_be_t

2

Unsigned

Big Endian

int32_le, int32_le_t

4

Signed

Little Endian

int32_be, int32_be_t

4

Signed

Big Endian

uint32_le, uint32_le_t

4

Unsigned

Little Endian

uint32_be, uint32_be_t

4

Unsigned

Big Endian

int64_le, int64_le_t

8

Signed

Little Endian

int64_be, int64_be_t

8

Signed

Big Endian

uint64_le, uint64_le_t

8

Unsigned

Little Endian

uint64_be, uint64_be_t

8

Unsigned

Big Endian

结构类型

结构体定义

Koffi 将 JS 对象转换为 C 结构,反之亦然。

与函数声明不同,到目前为止,只有一种方法可以使用函数创建结构类型koffi.struct()。该函数有两个参数:第一个是类型的名称,第二个是包含结构成员名称和类型的对象。您可以省略第一个参数来声明匿名结构。

以下示例说明了如何使用 Koffi 在 C 和 JS 中声明相同的结构:

typedef struct A {
    int a;
    char b;
    const char *c;
    struct {
        double d1;
        double d2;
    } d;
} A;
const A = koffi.struct('A', {
    a: 'int',
    b: 'char',
    c: 'const char *', // Koffi does not care about const, it is ignored
    d: koffi.struct({
        d1: 'double',
        d2: 'double'
    })
});

Koffi 在对齐和填充方面自动遵循平台 C ABI。但是,如果需要,您可以通过以下方式覆盖这些规则:

  • koffi.pack()打包所有成员,不使用(而不是koffi.struct())填充

  • 更改特定成员的对齐方式,如下所示

// This struct is 3 bytes long
const PackedStruct = koffi.pack('PackedStruct', {
    a: 'int8_t',
    b: 'int16_t'
});

// This one is 10 bytes long, the second member has an alignment requirement of 8 bytes
const BigStruct = koffi.struct('BigStruct', {
    a: 'int8_t',
    b: [8, 'int16_t']
})

声明结构后,您可以通过名称(使用字符串,就像对基本类型所做的那样)或通过调用返回的值来使用它koffi.struct()。声明匿名结构时,只有后者是可能的。

// The following two function declarations are equivalent, and declare a function taking an A value and returning A
const Function1 = lib.func('A Function(A value)');
const Function2 = lib.func('Function', A, [A]);

不透明类型

许多 C 库使用某种面向对象的 API,带有一对专用于创建和删除对象的函数。一个明显的例子可以在 stdio.h 中找到,带有不透明FILE *指针。您可以使用fopen()fclose()打开和关闭文件,并使用其他函数(例如fread()ftell())操作不透明指针。

在 Koffi 中,您可以使用不透明类型来管理它。使用 声明不透明类型koffi.opaque(name),并使用指向该类型的指针作为返回类型或某种输出参数(使用双指针)。

下面的完整示例在 C 中实现了迭代字符串生成器(连接器),并从 Javascript 使用它来输出 Hello World 和 FizzBu​​zz 的混合内容。构建器隐藏在不透明类型后面,并使用一对 C 函数创建和销毁:ConcatNew(or ConcatNewOut) 和ConcatFree

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

#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

typedef struct Fragment {
    struct Fragment *next;

    size_t len;
    char str[];
} Fragment;

typedef struct Concat {
    Fragment *first;
    Fragment *last;

    size_t total;
} Concat;

bool ConcatNewOut(Concat **out)
{
    Concat *c = malloc(sizeof(*c));
    if (!c) {
        fprintf(stderr, "Failed to allocate memory: %s\n", strerror(errno));
        return false;
    }

    c->first = NULL;
    c->last = NULL;
    c->total = 0;

    *out = c;
    return true;
}

Concat *ConcatNew()
{
    Concat *c = NULL;
    ConcatNewOut(&c);
    return c;
}

void ConcatFree(Concat *c)
{
    if (!c)
        return;

    Fragment *f = c->first;

    while (f) {
        Fragment *next = f->next;
        free(f);
        f = next;
    }

    free(c);
}

bool ConcatAppend(Concat *c, const char *frag)
{
    size_t len = strlen(frag);

    Fragment *f = malloc(sizeof(*f) + len + 1);
    if (!f) {
        fprintf(stderr, "Failed to allocate memory: %s\n", strerror(errno));
        return false;
    }

    f->next = NULL;
    if (c->last) {
        c->last->next = f;
    } else {
        c->first = f;
    }
    c->last = f;
    c->total += len;

    f->len = len;
    memcpy(f->str, frag, len);
    f->str[len] = 0;

    return true;
}

const char *ConcatBuild(Concat *c)
{
    Fragment *r = malloc(sizeof(*r) + c->total + 1);
    if (!r) {
        fprintf(stderr, "Failed to allocate memory: %s\n", strerror(errno));
        return NULL;
    }

    r->next = NULL;
    r->len = 0;

    Fragment *f = c->first;

    while (f) {
        Fragment *next = f->next;

        memcpy(r->str + r->len, f->str, f->len);
        r->len += f->len;

        free(f);
        f = next;
    }
    r->str[r->len] = 0;

    c->first = r;
    c->last = r;

    return r->str;
}
// ES6 syntax: import koffi from 'koffi';
const koffi = require('koffi');

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

const Concat = koffi.opaque('Concat');
const ConcatNewOut = lib.func('bool ConcatNewOut(_Out_ Concat **out)');
const ConcatNew = lib.func('Concat *ConcatNew()');
const ConcatFree = lib.func('void ConcatFree(Concat *c)');
const ConcatAppend = lib.func('bool ConcatAppend(Concat *c, const char *frag)');
const ConcatBuild = lib.func('const char *ConcatBuild(Concat *c)');

let c = ConcatNew();
if (!c) {
    // This is stupid, it does the same, but try both versions (return value, output parameter)
    let ptr = [null];
    if (!ConcatNewOut(ptr))
        throw new Error('Allocation failure');
    c = ptr[0];
}

try {
    if (!ConcatAppend(c, 'Hello... '))
        throw new Error('Allocation failure');
    if (!ConcatAppend(c, 'World!\n'))
        throw new Error('Allocation failure');

    for (let i = 1; i <= 30; i++) {
        let frag;
        if (i % 15 == 0) {
            frag = 'FizzBuzz';
        } else if (i % 5 == 0) {
            frag = 'Buzz';
        } else if (i % 3 == 0) {
            frag = 'Fizz';
        } else {
            frag = String(i);
        }

        if (!ConcatAppend(c, frag))
            throw new Error('Allocation failure');
        if (!ConcatAppend(c, ' '))
            throw new Error('Allocation failure');
    }

    let str = ConcatBuild(c);
    if (str == null)
        throw new Error('Allocation failure');
    console.log(str);
} finally {
    ConcatFree(c);
}

数组类型

固定大小的 C 数组

固定大小的数组用koffi.array(type, length)声明。就像在 C 中一样,它们不能作为函数参数传递(它们退化为指针),也不能按值返回。但是,您可以将它们嵌入到结构类型中。

在将数组传入/传出 C 时,Koffi 应用以下转换规则:

  • JS 到 C:Koffi 可以采用普通数组(例如[1,2])或正确类型的 TypedArray(e.g.Uint8Array for an array of uint8_t numbers)

  • C 到 JS(返回值、输出参数、回调):如果可能,Koffi 将使用 TypedArray。但是,当您使用可选的提示参数创建数组类型时,您可以更改此行为:koffi.array('uint8_t', 64, 'Array')。对于非数字类型,例如字符串或结构数组,Koffi 创建普通数组。

请参阅下面的示例:

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

// Those two structs are exactly the same, only the array conversion hint is different
const Foo1 = koffi.struct('Foo', {
    i: 'int',
    a16: koffi.array('int16_t', 8)
});
const Foo2 = koffi.struct('Foo', {
    i: 'int',
    a16: koffi.array('int16_t', 8, 'Array')
});

// Uses an hypothetical C function that just returns the struct passed as a parameter
const ReturnFoo1 = lib.func('Foo1 ReturnFoo(Foo1 p)');
const ReturnFoo2 = lib.func('Foo2 ReturnFoo(Foo2 p)');

console.log(ReturnFoo1({ i: 5, a16: [6, 8] })) // Prints { i: 5, a16: Int16Array(2) [6, 8] }
console.log(ReturnFoo2({ i: 5, a16: [6, 8] })) // Prints { i: 5, a16: [6, 8] }

固定大小的字符串缓冲区

Koffi还可以在以下情况下将JS字符串转换为固定大小的数组:

  • char 数组用 UTF-8 编码的字符串填充,如果需要的话会被截断。缓冲区始终以 NUL 结尾。

  • char16(或 char16_t)数组用 UTF-16 编码的字符串填充,如果需要的话会被截断。缓冲区始终以 NUL 结尾。

反之亦然,Koffi 可以将 C 固定大小缓冲区转换为 JS 字符串。String对于 char、char16 和 char16_t 数组,默​​认情况下会发生这种情况,但您也可以使用数组提示(例如koffi.array('char', 8, 'String'))显式请求此情况。

动态数组(指针)

在 C 中,动态大小的数组通常作为指针传递。在相关部分中阅读有关数组指针的更多信息。

联合类型

联合类型的声明和使用将在后面的章节中解释,这里仅在需要时简单提及。

Last updated