> For the complete documentation index, see [llms.txt](https://nongchatea.gitbook.io/koffi-chinese/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://nongchatea.gitbook.io/koffi-chinese/shu-ru-can-shu.md).

# 输入参数

## 原始类型

### 标准类型

虽然 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`<br>

```typescript
// 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 中声明相同的结构：

```c
typedef struct A {
    int a;
    char b;
    const char *c;
    struct {
        double d1;
        double d2;
    } d;
} A;

```

```typescript
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()`）填充
* 更改特定成员的对齐方式，如下所示

```typescript
// 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()`。声明匿名结构时，只有后者是可能的。

```typescript
// 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)`，并使用指向该类型的指针作为返回类型或某种[输出参数](https://koffi.dev/output)（使用双指针）。

{% hint style="info" %}
不透明类型**在 2.0 版中发生了变化，在 2.1 版中又发生了变化**。

在 Koffi 1.x 中，不透明句柄的定义方式使它们可以直接用作参数和返回类型，从而掩盖了底层指针。

现在，您必须通过指针使用它们，并使用数组作为输出参数。这如下面的示例所示（在 JS 部分中查找对 的调用），并在[输出参数](https://koffi.dev/output)`ConcatNewOut`部分中进行了描述。

除此之外，您应该使用`koffi.opaque()`（在 Koffi 2.1 中引入）而不是`koffi.handle()`已弃用的，并且最终将在 Koffi 3.0 中删除。

有关更多信息，请参阅[迁移指南。](https://koffi.dev/migration)
{% endhint %}

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

```c
// 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;
}
```

```typescript
// 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 创建普通数组。

请参阅下面的示例：

```typescript
// 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 中，动态大小的数组通常作为指针传递。在相关部分中阅读有关[数组指针的更多信息。](https://koffi.dev/pointers#array-pointers-dynamic-arrays)

## 联合类型

[联合类型](https://koffi.dev/unions)的声明和使用将在后面的章节中解释，这里仅在需要时简单提及。
