输入参数
原始类型
标准类型
虽然 C 标准允许大多数整数类型的大小存在变化,但 Koffi 对大多数基本类型强制执行相同的定义,如下所示:
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 定义了更多类型,可以根据操作系统和架构改变大小:
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 定义了一堆字节序敏感类型,可以在处理二进制数据(网络负载、二进制文件格式等)时使用。
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 和 FizzBuzz 的混合内容。构建器隐藏在不透明类型后面,并使用一对 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 ofuint8_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