# 概述

jside ffi是连接js生态和native生态的桥梁，可以让js宏加载用C/C++/Rust/Zig/go等编程语言编写的动态库，扩宽js宏的生态范围

本文档介绍jside ffi的功能和使用方法。为了准确描述各个函数和类型，本文档使用了typescript来定义api。

# WPS 版本
- windows: WPS 12.1.0.23542 版本及以上

# 功能
1. 用js加载动态库和其中的函数
2. 用js函数合成出C语言函数，用作回调函数等
3. 用js合成C语言的结构体，数组等复合数据类型，其内存布局与C语言相同
4. 用js计算和C语言的指针和读写堆内存
5. 获取平台信息，如操作系统，CPU架构，指针大小等

# 快速入门

1. 用C++语言(或其他native语言)编写导出函数，并编译成动态库

以windows平台为例
```cpp
#include <cstdint>

#if defined(_WIN32) || defined(_WIN64)
    #define C_EXPORT __declspec(dllexport)
#else
    #define C_EXPORT __attribute__((visibility("default")))
#endif

C_EXPORT int32_t add(int32_t a, int32_t b) {
    return a + b;
}

C_EXPORT int32_t sum(int32_t count, int32_t* numbers) {
    int32_t total = 0;
    for (int32_t i = 0; i < count; ++i) {
        total += numbers[i];
    }
    return total;
}
```

2. 用js宏加载函数

```js
const {add, sum} = ffi.LoadLibrary("C:/path/to/library.dll",{
    add: { returnType: "int32", parameters: ["int32","int32"]},
    sum: { returnType: "int32", parameters: ["int32","pointer"]},
})
```

3. 调用函数

```js
console.log(add(1,2))

let array = new Int32Array([1,2,3,4])
console.log(sum(array.length, array.buffer))
```

# 定义

- ffi：foreign function interface，是不同编程语言之间相互调用函数的一种机制。
- C函数：泛指具有与C语言相同的调用约定和数据结构的函数机器码。包括C/C++的函数，zig的函数，Rust的extern "C"函数，它们都可以被js宏通过jside ffi加载和调用。
- 符号名：函数在动态库中的名称。C++函数的符号名会被编译器修饰，C语言函数的符号名与其定义的名称相同。可以使用objdump和readelf等工具查看动态库中的符号名。
可以使用http://demangler.com/网站来转换符号名和函数签名
- 动态库：一种包含可由多个程序同时使用的代码和数据的库文件，在运行时被加载到内存中，可以按照符号名动态获取其中的函数。一般的文件后缀名为`.dll`（Windows系统）、`.so`（Linux系统、鸿蒙系统）、.`dylib`（macOS系统）。
- 闭包：指将js的函数合成成C语言的函数指针。它可以作为回调函数传递给C语言代码，让C语言函数可以调用JS函数。

# 类型系统

类型的详细定义请参考api

## 基础数据类型
基础数据类型在内存中的结构等价于对应的C/C++类型。

基础数据类型包括：
- void: 相当于C语言的`void`类型。只能作为函数返回值类型，不能作为参数类型。
- bool: 相当于C++的bool类型。其值为0时表示false，为1时表示true。bool类型在内存中占1个字节。
- int8, int16, int32, int64: 分别对应C语言的`int8_t`、`int16_t`、`int32_t`、`int64_t`类型。
- uint8, uint16, uint32, uint64: 分别对应C语言的`uint8_t`、`uint16_t`、`uint32_t`、`uint64_t`类型。
- float, double: 分别对应C语言的`float`和`double`类型。分别占4、8个字节。在js中用number类型存储。
- string：指utf8编码的C语言式字符串的指针，以'\0'结尾。
- u16string：指utf16编码的C语言式字符串的字符串的指针，以'\0'结尾。
- pointer：等价于C语言的`void*`指针。指针的值在js中用一个`FFI.KJsFfiPointer`类型的对象表示。

部分基础数据类型有别名：
- `size_t`: 在32位wps上等价于uint32，在64位wps上等价于uint64
- `char*`: 等价于string
- `void*`: 等价于pointer

传参时，请避免整数类型溢出，以免引起不可预期的精度问题。可以使用`Math.imul`函数进行32位整数的乘法运算，使用`Math.fround`函数进行32位浮点数的运算。也可以用`num|0`将整数转为32位整数。
js中 `num|0` 可以将整数转为32位整数类型，这个整数在v8 JS引擎内部是用`int32_t`类型存储和计算，可以有效避免精度问题。int64和uint64类型在js中用bigint类型存储，其他整数类型用number类型存储。


### 相关api
```typescript
type PrimaryType = "void" | "bool" | "int8" | "int16" | "int32" | "int64" | "uint8" | "uint16" | "uint32" | "uint64" | "float" | "double" | "pointer" | "string" | "u16string";
```

### 基础数据类型对应关系

|jside ffi|javascript|C/C++|Rust|zig|golang|
|---|---|---|---|---|---|
|void|undefined|void|()|void|void|
|bool|boolean|bool|bool|bool|bool|
|int8|number|int8_t|i8|i8|int8|
|int16|number|int16_t|i16|i16|int16|
|int32|number|int32_t|i32|i32|int32|
|int64|bigint|int64_t|i64|i64|int64|
|uint8|number|int8_t|u8|u8|uint8|
|uint16|number|uint16_t|u16|u16|uint16|
|uint32|number|uint32_t|u32|u32|uint32|
|uint64|bigint|uint64_t|u64|u64|uint64|
|float|number|float|f32|f32|float32|
|double|number|double|f64|f64|float64|
|pointer|KJsFfiPointer|`void*`|`*mut ()`|`*anyopaque`|unsafe.Pointer|
|string|string|`char*`|CString|`[*:0]u8`|`*C.char`|
|u16string|string|`char16_t*`|U16CString|`[*:0]u16`|`*C.char16_t`|

## 指针类型

指针等价于C语言的`void*`指针。调用函数传参时，指针(`FFI.KJsFfiPointer`)、`ArrayBuffer`、闭包(`FFI.KJsFfiClosure`)、null都可以被转为C语言的`void*`类型。指针可以与bigint类型相互转换。

如果元素类型是一种结构体类型，那么指针类型会带有字段访问器，可以用js对象字段访问的方法操作结构体内存。

`PointerValue`是动态合成的js类型，其read函数可以进行额外的类型转换，比如将结构体值读取并转化为一个实现`StructValue`接口的js对象。其Write函数可以自动处理`FunctionValue`对象。
而`FFI.KJsFfiPointer`是C++层实现的类型，其Read函数只能读取基础数据类型。其他函数也类似。

指针对应的基础数据类型是"pointer"

### 相关api

```typescript
    
declare namespace FFI {

    // 不可变的C语言指针类型, 相当于C语言的void*
    interface KJsFfiPointer {
        // 读取内存. 
        Read(type: PrimaryType, offset: NumberLike): Value;
        // 写入内存
        Write(type: PrimaryType, offset: NumberLike, value: Value): void;
        // 指针转数字
        ToBigInt(): bigint | number;
        // 指针加法操作
        AddOffset(offset: NumberLike): KJsFfiPointer;
        // C语言字符串指针(const char*)转js字符串
        DerefString(): string;
        // C语言宽字符串指针(const char16_t*)转js字符串
        DerefU16String(): string;
    }

    // 数值转指针
    function BigIntToPointer(value: bigint | number): KJsFfiPointer;
}
declare namespace ffi {
    // 这些类型可以作为函数参数, 能自动转为指针类型
    type FfiPointerLike = KJsFfiPointer | ArrayBuffer | KJsFfiClosure | null;
    // 创建指针类型. 
    function Pointer(inner: TypeDecl): PointerType
    // 指针值. 可以为js的null
    type PointerValue = FFI.KJsFfiPointer & {
        type: PointerType,
        // 相当于C语言指针的加法运算. index默认为0
        ref(index?: number): PointerValue,
        // 相当于C语言的指针读取, 或数组元素读取. index默认为0
        read(index?: number): any,
        // 相当于C语言的指针写入, 或数组元素写入. index默认为0
        write(value: any, index?: number): void,
    } | null;
    
    // 指针类型
    interface PointerType extends Type<PointerValue> {
        kind: 'pointer',
        pointeeType: Type<any>,
        create(pointer: FFI.KJsFfiPointer | null): PointerValue,
        // 创建null指针值
        null(): PointerValue,
    }
}
```

## 函数类型

函数类型是指由C语言函数指针封装成的js函数。函数与闭包的JS封装类型统一，都是`FunctionValue`，但值的字段类型有区别。用`isJSFunction`字段区分函数和闭包。

函数类型对应的基础数据类型是`pointer`，C函数对象无法被隐式转换成C函数指针。函数类型不能作为实参，推荐使用其指针作为实参。可以通过`ToPointer()`函数显式获取函数指针。

### 相关api

```typescript
    // 创建函数类型. 
    function Function(typeinfo: FunctionTypeDecl): FunctionType;
    // 函数类型的实例, 是一个带有额外属性的js函数, 或者为null
    type FunctionValue = ((...args: FFI.Value[]) => FFI.Value | undefined) & {
        type: FunctionType,
    } & (
            { isJSFunction: false, inner: FFI.KJsFfiFunction }
            | { isJSFunction: true, inner: FFI.KJsFfiClosure }
        ) | null;
    
    // 函数类型
    interface FunctionType extends Type<FunctionValue> {
        kind: 'function',
        // 参数类型
        parametersType: Type<any>[],
        // 返回值类型
        returnType: Type<any>,
        // 调用约定
        abi: FFI.Abi,
        // js函数转闭包对象
        createJsFunction(value: (...args: FFI.Value[]) => FFI.Value | undefined): FunctionValue,
        // C函数指针转js函数
        wrapCFunction(value: FFI.KJsFfiPointer): FunctionValue,
    }
```

## 闭包类型

闭包类型的底层类型与函数类型不同，但 js 层将闭包和函数类型使用了同一个封装类型：`FunctionValue`，和同一中类型元数据类型`FunctionType`。可以使用`FunctionType.createJsFunction`创建闭包类型。

闭包类型对应的基础数据类型是`pointer`。`FFI.KJsFfiPointer`作为指针类型的参数时，会被隐式转换成C语言的函数指针。如果函数参数声明为一个函数类型，那么函数调用时会自动从`FunctionValue`中取出`FFI.KJsFfiClosure`并转换成C语言的函数指针。

### 例子

```javascript
let echoType = ffi.Function({
    abi: "",
    returnType: "int32",
    parameters: ["int32"],
})

let echoFunction = echoType.createJsFunction((arg0) => {
    return arg0
})
```

## 结构体

结构体类型是指C语言式结构体。包含C++的无虚函数的类、Rust的`#[repr(C)]`的结构体、zig的结构体。C语言式结构体不会重排序字段，且每个字段都对齐，结构体大小对齐到其对齐数。

js结构体值是一个`ArrayBuffer`。js封装的结构体的值对象上，每个字段都有访问器，可以直接通过js对象字段的语法获取或修改C结构体字段的值。

结构体类型不对应基础数据类型，不能直接作为参数类型。需要时，请使用`ffi.Pointer(YourStructType)`来创建其指针类型，用指针做参数。

### 相关api

```typescript
    // 创建结构体类型. 返回值本身是个类构造函数, 可以用new创建值
    function Struct(fields: FieldDecl[]): StructType  & (new (value?: Record<string, any>) => StructValue);

    // 结构体值
    type StructValue = ArrayBuffer & {
        type: StructType,
        // 转为js对象
        toObject(): object,
    }
    
    // 结构体类型
    interface StructType extends Type<StructValue> {
        kind: 'struct',
        // 字段列表
        fields: RawFieldDecl[],
        // 按照对象的键值对创建结构体值
        create(value?: object): StructValue,
    }
```

### 例子

```js
let Vector3 = ffi.Struct([
    { name: "x", type: "float" },
    { name: "y", type: "float" },
    { name: "z", type: "float" },
])
let vector3 = new Vector3 ({x:4,y:5,z:6})
vector3.x = 1.0
vector3.y = 2.0
vector3.z = 3.0
console.log(vector3.toObject().x)
```

## 数组

数组类型是指C语言式定长数组。abi上等价于元素连续存储且第一个元素地址代表数组首地址的内存块。它在内存中是一块连续的空间，存储着相同类型的元素，访问数组元素时可通过数组首地址和偏移量来定位。

js中数组的值是一个`ArrayBuffer`。在js中，通过操作这个`ArrayBuffer`就可以对数组元素进行读取和修改。

定长数组类型不对应基础数据类型，不能直接作为参数类型。需要时，请使用`ffi.Pointer(YourArrayType)`来创建指针类型，并用指针作参数。

### 相关api

```typescript
    // 创建定长数组类型. 返回值本身是个类构造函数, 可以用new创建值
    function Array(inner: TypeDecl, length: number): ArrayType & (new (value?: any[]) => ArrayValue); 
    // 定长数组值
    type ArrayValue = ArrayBuffer & {
        type: ArrayType,
        // 获取元素指针. index默认为0
        ref(index?: number): PointerValue,
        // 解码出元素的js值
        get(index: number): any,
        // 编码js值到元素
        set(index: number, value: any): void,
    }
    
    // 定长数组类型
    interface ArrayType extends Type<ArrayValue> {
        kind: 'array',
        // 元素类型
        elementType: Type<any>,
        // 数组长度
        arrayLength: number,
        // 从js数组
        create(value: FFI.Value[]): ArrayValue,
    }
```

#### 例子

```javascript
let Vector4 = ffi.Array("int32", 4)
let value = new Vector4([1,2,3,4])
value.set(1,5)
console.log(value.get(1))
```

## 缓冲区

缓存区类型是一块C式内存空间，可以存储指定数据类型的值，其存储内容与C语言内存布局相同。常用于函数输出数据的内存空间。
js中的缓冲区值是一个带有额外的read、write、ref等属性的`ArrayBuffer`。
缓冲区类型对应的基础数据类型是"pointer"，作为参数时会被隐式转换成指针。

### 相关api

```typescript
    // 创建缓冲区类型. 返回值本身是个类构造函数, 可以用new创建值
    function Buffer(inner: TypeDecl): BufferType & (new (value?: any) => BufferValue);

    // 缓冲区类型, 是一个ArrayBuffer, 用于持有一个C内存结构. 可以作为C api通过指针传出数据的缓冲区
    type BufferValue = ArrayBuffer & {
        type: BufferType,
        // 转指针
        ref(): PointerValue,
        // 解码出js值
        read(): any,
        // 编码js值
        write(value: any): void,
    };
    
    // 缓冲区类型
    interface BufferType extends Type<BufferValue> {
        kind: 'buffer',
        innerType: Type<any>,
        create(value: any): BufferValue,
    }
```

### 例子

```javascript
let BufferInt32 = ffi.Buffer("int32")
let value = new BufferInt32()
value.write(1)
console.log(value.read())
console.log(value.ref().ToBigInt())
```

## 联合体

联合体类型是值C语言式联合体，是多种数据类型在内存空间上重合的结构，其每个变体的指针都是联合体指针。

js中联合体的值是一个`ArrayBuffer`。其每个变体都可以通过js访问器，用访问js对象字段的方式读取和修改。

联合体类型不对应基础数据类型，不能直接作为参数类型。需要时，请使用其指针类型。

### 相关api
```typescript
    // 创建联合体类型. 返回值本身是个类构造函数, 可以用new创建值
    function Union(fields: FieldDecl[]): UnionType & (new () => UnionValue);
    
    // 联合体值
    type UnionValue = ArrayBuffer & {
        type: UnionType
    }
    
    // 联合体类型
    interface UnionType extends Type<UnionValue> {
        kind: 'union',
        // 变体列表
        fields: RawFieldDecl[],
    }
```

### 例子

```javascript
let UnionType = ffi.Union([
    { name: "int32", type: "int32"},
    { name: "f32", type: "float" },
])
let value = new UnionType 
value.f32 = Math.PI
console.log(value.int32)
```

## 模板
jside ffi未提供任何模板相关机制，但可以仿照zig语言的模板机制，用函数来实现模板。

### 例子
例如一个简单的结构体模板
```cpp
template<typename T>
struct Vector3 {
    T x;
    T y;
    T z;
};
```

写成FFI的模板结构体，用函数表示模板。
注意ffi.Struct函数本身有一定性能开销，建议将模板实例化后的结构体类型缓存起来重复使用。
```javascript
let Vector3 = (T) => ffi.Struct([
    { name: "x", type: T },
    { name: "y", type: T },
    { name: "z", type: T },
])
let Vector3f = Vector3("float")
let vector3 = new Vector3f ({x:4,y:5,z:6})

```

# 基本原理

- ffi调用：使用libffi的调用功能实现。它封装一段汇编代码，将参数按照调用约定放到指定寄存器和栈位置上，然后用call指令跳转到函数地址上执行，函数返回后将返回值从寄存器中取出并返回给js
- ffi闭包：使用libffi的闭包功能实现。它将一段汇编编译成的机器码放到内存数据结构中，汇编被调用时间寄存器的值保存下来，并按照函数签名整理成函数参数列表，然后转换参数类型并调用JS函数。JS函数返回后，再将返回值转换成C语言类型并放到寄存器中，最后用ret指令返回。

- `ArrayBuffer`内存机制：`ArrayBuffer`是js的内置类型，它封装了一段C++堆内存空间，它的内存空间可以用来存储C语言的数据结构。`ArrayBuffer`的内存空间无法被扩容或缩容，也不会随着GC堆整理而移动，但可以被释放。
`ArrayBuffer`可以转为指针，其指针可以被传递到C函数中。`ArrayBuffer`中存储的数据可以在js中高效读写。

使用时需要注意生命周期问题和内存越界问题。`ArrayBuffer`的生命周期受到GC机制管理，需要避免过早被回收导致野指针问题。如果用`ArrayBuffer`代替了malloc，那么建议在js中保存`ArrayBuffer`的引用，防止其过早的被GC回收。注意内存分配与释放的方法统一，避免让C/C++用free或delete释放了`ArrayBuffer`分配的内存。

- C语言指针：C语言指针可以用来表示函数地址，数组地址，结构体地址等。函数和闭包以及结构体等机制的底层依赖于对指针的封装。

# 二进制接口

abi是应用程序二进制接口，规定了不同编程语言间函数调用的约定和规范。CPU类型以及C语言的调用约定决定了函数的abi类型。
abi 决定了函数参数如何传递，返回值如何传递，寄存器的使用等。
如果函数定义中没有规定abi类型，一般使用默认abi。

### 相关api
```typescript
    type Abi = "" // 默认abi
        | "stdcall" | "thiscall" | "fastcall" | "cdecl" | "pascal" | "sysv" // x86和x64的abi，包括Intel和AMD，兆芯，海光等CPU
        | "win64" /*非windows平台*/ | "gnuw64" | "unix64" // x64架构
        | "win64" | "sysv" // aarch64架构，包括，飞腾，鲲鹏，麒麟，苹果M1/M2等CPU
        | "n64" | "n64_soft_float" // mips64el架构，龙芯3A4000及以下
        | "lp64s" | "lp64f" | "lp64d";  // loongarch64架构，龙芯3A5000及以上
```

# 动态库加载
当调用LoadLibrary函数时, wps会根据提供的动态库名称搜索动态库文件, 并加载到内存中. 然后根据提供的函数声明, 获取动态库中的函数地址, 并封装成js函数。

推荐使用`ffi.LoadLibrary`函数加载动态库, 而不是使用`FFI.LoadLibrary`函数。前者可以同时加载多个函数, 并且可以指定函数的签名，还可以为C语言函数创建js代理函数，代理函数能够自动转换参数类型，更加易用。

## 动态库查找
动态库名可以是绝对路径，也可以是文件名。如果使用文件名， wps按照各操作系统的路径查找规则查早动态库，获取绝对路径。

文件名会被自动加上当前系统的动态库文件后缀。
- windows系统：`.dll`
- linux系统和鸿蒙系统：`.so`
- macos系统：`.dylib`

如果动态库不是绝对路径，wps会按照以下顺序查找动态库文件：
- windows系统：
    1. 系统目录（System32）
    2. Windows目录
    3. `PATH`环境变量中包含的目录

- linux系统：
    1. `LD_LIBRARY_PATH`环境变量中包含的目录
    2. `/lib`
    3. `/usr/lib`

- macos系统
    1. `LD_LIBRARY_PATH`环境变量中包含的目录
    2. `DYLD_LIBRARY_PATH`环境变量中包含的目录
    3. 工作目录
    4. `DYLD_FALLBACK_LIBRARY_PATH`环境变量中包含的目录，默认位`/usr/local/lib`和`/usr/lib`

出于安全策略限制，禁止加载wps的office6目录下的WPS核心动态库。LoadLibrary的安全策略未来有可能会调整

### 相关api
```typescript
declare namespace ffi {
    // 加载动态库, 以及动态库函数
    function LoadLibrary(name: string, functions: Record<string, LibraryFunctionDecl>): FFI.KJsFFILibrary & Record<string, (...args: any[]) => any>;
    
    interface FunctionTypeDecl {
        parameters?: TypeDecl[],
        returnType?: TypeDecl,
        abi?: FFI.Abi
    }

    interface LibraryFunctionDecl extends FunctionTypeDecl {
        // 函数在动态库中的符号名, 如果与函数名相同, 则可以省略
        symbol?: string,
    }
}
declare namespace FFI {
    // 加载动态库. name可以是绝对路径或名字
    function LoadLibrary(name: string): KJsFFILibrary;

    // 动态库对象
    interface KJsFFILibrary {
        // 加载动态库函数
        LoadFunction(name: string, symbol: string, abi: Abi, returnType: PrimaryType, argumentsType: PrimaryType[]): KJsFfiFunction;
    }
}

```

# 局限性

## 参数无法直接传递结构体

不支持通过参数直接传递结构体。参数传递结构体的行为与编译器和编程语言关联太大。

建议改为使用结构体指针。

## 不支持可变数量参数

不支持C语言式可变数量参数。

## 不支持C++式异常处理

jside本身会捕获C++异常，但由于C++的abi兼容性等问题，无法获取异常对象和调用栈等信息。因此，在调用的C++代码中应避免使用C++式异常处理，可考虑使用返回错误码的方式来处理错误情况。
如果在函数调用过程中捕获到C++异常，那么jside会抛出js异常，但不携带C++异常相关信息。

## 不支持js的异常捕获

由于C语言没有异常机制，且不少C语言程序在C++异常面前没有异常安全性，所以闭包调用机制不支持将JS异常转换为C++异常。
如果闭包调用的JS函数抛出异常，那么闭包对应的C函数会返回未初始化值。
推荐在闭包调用的JS函数中捕获异常，输出异常日志或者用alert显示异常信息，然后按照C语言的方式返回。如果C api本身有某种错误码，推荐返回错误码给调用者。

## 只支持部分调用约定和C语言式数据类型

如果需要更加复杂的数据类型或者golang等语言的函数调用约定，可以用C语言编写一个封装层
- C++: 手写C封装层
- rust: 可以使用代码生成工具(比如`rust-bindgen`库)制作出C abi层。

# 异常处理

## C函数调用失败

- 有可能是因为被调用函数内部抛出野指针、空指针或者数组越界、ffi函数参数类型与实际参数不匹配等问题等情况导致的异常
- 有可能是因为被调用的C++函数内部主动抛异常
- 也有可能是JS传递的实参基础数据类型与形参不匹配，且无法转换

# 完整api

如果使用vscode等支持lsp的ide来编写js宏，可以将完整api复制到一个typescript文件中，然后在vscode等ide中查看。让ai读取这些api，借助ai辅助编程。

推荐使用`ffi`命名空间的api，而不是`FFI`命名空间的api。前者进行了细致的封装，功能更强大，且更易用。

```typescript
// 在C++中实现的api, 其类型转换受到C++层的限制. 比如bigint类型的返回值实际可能被转为number
declare namespace FFI {
    type Abi = "" // 默认abi
        | "stdcall" | "thiscall" | "fastcall" | "cdecl" | "pascal" | "sysv" // x86和x64的abi，包括Intel和AMD，兆芯，海光等CPU
        | "win64" /*非windows平台*/ | "gnuw64" | "unix64" // x64架构
        | "win64" | "sysv" // aarch64架构，包括，飞腾，鲲鹏，麒麟，苹果M1/M2等CPU
        | "n64" | "n64_soft_float" // mips64el架构，龙芯3A4000及以下
        | "lp64s" | "lp64f" | "lp64d";  // loongarch64架构，龙芯3A5000及以上
    type PrimaryType = "void" | "bool" | "int8" | "int16" | "int32" | "int64" | "uint8" | "uint16" | "uint32" | "uint64" | "float" | "double" | "pointer" | "string" | "u16string";
    // 大多数jsapi可以自动转为整数的类型
    type NumberLike = number | bigint | string;

    // 不可变的C语言指针类型, 相当于C语言的void*
    interface KJsFfiPointer {
        // 读取内存. 
        Read(type: PrimaryType, offset: NumberLike): Value;
        // 写入内存
        Write(type: PrimaryType, offset: NumberLike, value: Value): void;
        // 指针转数字
        ToBigInt(): bigint | number;
        // 指针加法操作
        AddOffset(offset: NumberLike): KJsFfiPointer;
        // C语言字符串指针(const char*)转js字符串
        DerefString(): string;
        // C语言宽字符串指针(const char16_t*)转js字符串
        DerefU16String(): string;
    }

    // 闭包类型, 是可调用的函数对象, 内部包含运行期生成的机器码
    interface KJsFfiClosure {
        // 用于设置回调函数. 闭包的机器码被调用时会调用回调函数。
        // 如果手动用CreateCallback创建闭包对象, 则必须设置此回调函数。否则调用闭包时只会返回未定义值给C函数
        OnCall?: (...args: Value[]) => Value | undefined;
        // 获取内部的机器码的指针
        ToPointer(): KJsFfiPointer;
    }

    // 函数对象, 用于调用函数
    interface KJsFfiFunction {
        // 调用函数. 要求参数数量正确, 参数类型正确
        Call(...args: Value[]): Value | undefined;
        // 获取函数指针
        ToPointer(): KJsFfiPointer;
    }

    // 动态库对象
    interface KJsFFILibrary {
        // 加载动态库函数
        LoadFunction(name: string, symbol: string, abi: Abi, returnType: PrimaryType, argumentsType: PrimaryType[]): KJsFfiFunction;
    }

    type FfiPointerLike = KJsFfiPointer | ArrayBuffer | KJsFfiClosure | null;
    type Value = number | bigint | string | boolean | FfiPointerLike;
    
    // 加载动态库. name可以是绝对路径或名字
    function LoadLibrary(name: string): KJsFFILibrary;
    // 创建闭包对象
    function CreateCallback(abi: Abi, returnType: PrimaryType, argumentsType: PrimaryType[]): KJsFfiClosure;
    // 函数指针转为函数
    function CreateFunction(pointer: KJsFfiPointer, abi: Abi, returnType: PrimaryType, argumentsType: PrimaryType[]): KJsFfiFunction;

    // 获取js缓冲区中特定未知的指针
    function DerefPointer(array: ArrayBuffer, offset?: number): KJsFfiPointer;
    // 获取js缓冲区开头的指针
    function RefPointer(pointer: ArrayBuffer | KJsFfiPointer): KJsFfiPointer;
    // 数值转指针
    function BigIntToPointer(value: bigint | number): KJsFfiPointer;
    // 分配内存. size会被转为无符号整数
    function Malloc(size: number): KJsFfiPointer;
    // 释放分配的内存
    function Free(pointer: KJsFfiPointer): void;
    
    // 复制内存
    // dest: 复制目标
    // destOffset: 复制目标上的偏移量
    // destSize: 复制目标上的复制区域大小
    // src: 复制源
    // srcOffset: 复制源上的偏移量
    // srcSize: 复制源上的复制区域大小
    function Memcpy(dest: KJsFfiPointer | ArrayBuffer, destOffset: NumberLike, destSize: number, src: KJsFfiPointer | ArrayBuffer, srcOffset: number, secSize: number): void;

    // 当前系统的指针大小(byte). 值为4或8，对应32位或64位WPS
    const PointerSize: number;
}

// ffi模块是在js中实现的, 是对FFI模块的一层封装. 其使用方式更复合js习惯
declare namespace ffi {
    // 转换函数调用的参数
    type ParamsEncoder = (value: any) => any;
    
    // 各种api接受的类型值的类型
    type TypeDecl = Type<any> | FFI.PrimaryType
    
    // 把值编码到Uint8Array中
    type EncoderFn<V> = (buffer: Uint8Array<ArrayBuffer>, offset: number, value: V) => void;
    // 从Uint8Array解码出值
    type DecoderFn<V> = (buffer: Uint8Array<ArrayBuffer>, offset: number) => V;
    // 转换函数调用的参数, 和闭包的返回值
    type ParamsEncodersFn<V> = (value: V) => any;
    // 转换函数调用的返回值, 和闭包的参数
    type ReturnDecodersFn<V> = (value: any) => V;
    // 从指针读取值
    type Reader<V> = (pointer: FFI.KJsFfiPointer, offset: number) => V;
    // 向指针写入值
    type Writer<V> = (pointer: FFI.KJsFfiPointer, offset: number, value: V) => void;
    
    // 类型的定义
    interface Type<V> {
        primaryType: FFI.PrimaryType,
        // 类型大小 以byte为单位
        size: number,
        // 类型的对齐值, 必须是2的n次方
        align: number,
        // Uint8Array编码器
        encoder: EncoderFn<V>,
        // Uint8Array解码器
        decoder: DecoderFn<V>,
        // 指针读取器
        reader: Reader<V>,
        // 指针写入器
        writer: Writer<V>,
        // 参数转换器. 如果为undefined, 则不会转换
        paramEncoder?: ParamsEncodersFn<V>,
        // 返回值转换器. 如果为undefined, 则不会转换
        returnValueDecoder?: ReturnDecodersFn<V>,
    }
    
    // 函数类型的实例, 是一个带有额外属性的js函数, 或者为null
    type FunctionValue = ((...args: FFI.Value[]) => FFI.Value | undefined) & {
        type: FunctionType,
    } & (
            { isJSFunction: false, inner: FFI.KJsFfiFunction }
            | { isJSFunction: true, inner: FFI.KJsFfiClosure }
        ) | null;
    
    // 函数类型
    interface FunctionType extends Type<FunctionValue> {
        kind: 'function',
        // 参数类型
        parametersType: Type<any>[],
        // 返回值类型
        returnType: Type<any>,
        // 调用约定
        abi: FFI.Abi,
        // js函数转闭包对象
        createJsFunction(value: (...args: FFI.Value[]) => FFI.Value | undefined): FunctionValue,
        // C函数指针转js函数
        wrapCFunction(value: FFI.KJsFfiPointer): FunctionValue,
    }
    
    // 缓冲区类型, 是一个ArrayBuffer, 用于持有一个C内存结构. 可以作为C api通过指针传出数据的缓冲区
    type BufferValue = ArrayBuffer & {
        type: BufferType,
        // 转指针
        ref(): PointerValue,
        // 解码出js值
        read(): any,
        // 编码js值
        write(value: any): void,
    };
    
    // 缓冲区类型
    interface BufferType extends Type<BufferValue> {
        kind: 'buffer',
        innerType: Type<any>,
        create(value: any): BufferValue,
    }
    
    // 结构体值
    type StructValue = ArrayBuffer & {
        type: StructType,
        // 转为js对象
        toObject(): object,
    }
    
    // 结构体类型
    interface StructType extends Type<StructValue> {
        kind: 'struct',
        // 字段列表
        fields: RawFieldDecl[],
        // 按照对象的键值对创建结构体值
        create(value?: object): StructValue,
    }
    
    // 联合体值
    type UnionValue = ArrayBuffer & {
        type: UnionType
    }
    
    // 联合体类型
    interface UnionType extends Type<UnionValue> {
        kind: 'union',
        // 变体列表
        fields: RawFieldDecl[],
    }
    
    // 指针值. 可以为js的null
    type PointerValue = FFI.KJsFfiPointer & {
        type: PointerType,
        // 相当于C语言指针的加法运算. index默认为0
        ref(index?: number): PointerValue,
        // 相当于C语言的指针读取, 或数组元素读取. index默认为0
        read(index?: number): any,
        // 相当于C语言的指针写入, 或数组元素写入. index默认为0
        write(value: any, index?: number): void,
    } | null;
    
    // 指针类型
    interface PointerType extends Type<PointerValue> {
        kind: 'pointer',
        pointeeType: Type<any>,
        create(pointer: FFI.KJsFfiPointer | null): PointerValue,
        // 创建null指针值
        null(): PointerValue,
    }
    
    // 定长数组值
    type ArrayValue = ArrayBuffer & {
        type: ArrayType,
        // 获取元素指针. index默认为0
        ref(index?: number): PointerValue,
        // 解码出元素的js值
        get(index: number): any,
        // 编码js值到元素
        set(index: number, value: any): void,
    }
    
    // 定长数组类型
    interface ArrayType extends Type<ArrayValue> {
        kind: 'array',
        // 元素类型
        elementType: Type<any>,
        // 数组长度
        arrayLength: number,
        // 从js数组
        create(value: FFI.Value[]): ArrayValue,
    }
    
    type BottomType = (Type<any> & { kind: undefined }) | FunctionType | StructType | UnionType | PointerType | ArrayType | BufferType;
    
    interface FieldDecl {
        name: string,
        type: TypeDecl,
    }
    
    interface ResolvedField {
        name: string,
        type: Type<any>,
    }
    
    interface RawFieldDecl {
        name: string,
        type: Type<any>,
        offset: number,
    }
    
    interface FunctionTypeDecl {
        parameters?: TypeDecl[],
        returnType?: TypeDecl,
        abi?: FFI.Abi
    }

    interface LibraryFunctionDecl extends FunctionTypeDecl {
        // 函数在动态库中的符号名, 如果与函数名相同, 则可以省略
        symbol?: string,
    }

    // 各种内置类型
    let types: {
        void: Type<void>,
        bool: Type<boolean>,
        int8: Type<number>,
        int16: Type<number>,
        int32: Type<number>,
        int64: Type<bigint>,
        uint8: Type<number>,
        uint16: Type<number>,
        uint32: Type<number>,    
        uint64: Type<bigint>,
        float: Type<number>,
        double: Type<number>,
        string: Type<string>,
        "char*": Type<string>,
        u16string: Type<string>,
        pointer: Type<FFI.KJsFfiPointer | null>,
        "void*": Type<FFI.KJsFfiPointer | null>,
        size_t: Type<number> | Type<bigint>,
    };
    // 加载动态库, 以及动态库函数
    function LoadLibrary(name: string, functions: Record<string, LibraryFunctionDecl>): FFI.KJsFFILibrary & Record<string, (...args: any[]) => any>;
    // 解析类型声明
    function resolveType(type: TypeDecl): Type<any>;
    // 指针大小
    let pointerSize: number;
    // 创建结构体类型. 返回值本身是个类构造函数, 可以用new创建值
    function Struct(fields: FieldDecl[]): StructType  & (new (value?: Record<string, any>) => StructValue);
    // 创建联合体类型. 返回值本身是个类构造函数, 可以用new创建值
    function Union(fields: FieldDecl[]): UnionType & (new () => UnionValue);
    // 创建函数类型. 
    function Function(typeinfo: FunctionTypeDecl): FunctionType;
    // 创建定长数组类型. 返回值本身是个类构造函数, 可以用new创建值
    function Array(inner: TypeDecl, length: number): ArrayType & (new (value?: any[]) => ArrayValue); 
    // 创建指针类型. 
    function Pointer(inner: TypeDecl): PointerType
    // 创建缓冲区类型. 返回值本身是个类构造函数, 可以用new创建值
    function Buffer(inner: TypeDecl): BufferType & (new (value?: any) => BufferValue);
}
```

# 例子

以操作sqlite数据库为例。sqlite是一个轻量级的嵌入式数据库，广泛应用于各种软件中。sqlite以动态库形式加载，通过C语言API操作sqlite。

提前安装sqlite3
- windows 64位: `choco install sqlite`
- windows 32位：从sqlite官网下载动态库压缩包：https://www.sqlite.org/download.html
- linux ubuntu: `sudo apt install libsqlite3-dev`
- macos: `brew install sqlite`

```javascript
// sqlite 的api中没有结构体, 定长数组和联合体类型. 几乎所有参数都是C语言基础数据类型
    
// 数据库指针类型
let DbPtrType = "pointer"
// 数据库二重指针类型,传参时其值可以被转为二重指针
let DbPtrPtrType = ffi.Buffer(DbPtrType)
// 字段名列表类型
let FieldArray = ffi.Pointer("string")
// 值列表类型
let DataArray = ffi.Pointer("string")
// sqlite select时的回调函数类型, 传参时会被转为可调用的C函数指针类型
let CallbackType = ffi.Function({ffi:"",returnType:"int32",parameters:[DbPtrType, "int32", FieldArray, DataArray]})
// 报错字符串类型,传参时其值可以被转为二重指针
let ErrorStringType = ffi.Buffer("string")

// 按照平台设置动态库路径
let os = Platform.OS()
let arch = Platform.Arch()
let sqlite_path = ""
if (os=="Darwin") {
    sqlite_path = "/usr/lib/sqlite3/libtclsqlite3.dylib"
}else if(os=="Linux"){
    // 以ubuntu的路径为例
    if (arch == "aarch64")
    {
        sqlite_path = "/usr/lib/aarch64-linux-gnu/libsqlite3.so"
    }else
        sqlite_path = "/usr/lib/x86_64-linux-gnu/libsqlite3.so"
}else {
    // 用choco安装sqlite后, 其动态库在这个路径
    // 动态库的位数要与wps匹配
    if (Platform.PointerSize() == 4)
        // 替换成你的下载的路径
        sqlite_path = "D:/temp/sqlite3.dll"
    else
        sqlite_path = "C:/ProgramData/chocolatey/lib/SQLite/tools/sqlite3.dll"
}

// 加载sqlite动态库和函数
const sqlite_lib = ffi.LoadLibrary(sqlite_path, {
    // 用Buffer类型(DbPtrPtrType)来代替二重指针类型
    sqlite3_open: { returnType: "int32", parameters: ["string",DbPtrPtrType]},
    sqlite3_close: { returnType: "int32", parameters: [DbPtrType]},
    // 这里使用CallbackType类型而不是pointer, 传参时自动把FunctionValue类型转为C++能识别的FfiClosure, 进而被转为函数指针
    sqlite3_exec: {returnType: "int32",parameters: [DbPtrType,"string",CallbackType,"pointer",ErrorStringType ]},
})
// 枚举值定义
const SQLITE_OK = 0
const SQLITE_DONE = 101

// 对sqlite的封装
class Sqlite{
    constructor(path){
        let db_ptr_ptr = new DbPtrPtrType()
        // 打开数据库. db_ptr_ptr是个ArrayBuffer, 会被转为指针
        this.call_api(()=>sqlite_lib.sqlite3_open(path, db_ptr_ptr))
        // 取出db_ptr_ptr内部被写入的指针
        this.db = db_ptr_ptr.read()
        
    }
    // 对错误处理流程进行封装
    call_api(func) {
        let code = func.apply(this)
        if (code != SQLITE_OK && code != SQLITE_DONE){
            throw new Error("sqlite api error: "+code)
        }
    }
    // 关闭数据库
    close(){
        this.call_api(()=>sqlite_lib.sqlite3_close(this.db))
    }
    // 调用sql语句
    exec(sql, callback){
        console.log("exec: " + sql)
        let error_string_ptr = new ErrorStringType()
        // sqlite执行sql语句. 这里闭包类型(CallbackType)会被转为函数指针, Buffer类型(ErrorStringType)会被取指针
        let code = sqlite_lib.sqlite3_exec(this.db,sql,callback || null,null,error_string_ptr)
        // 错误处理
        if (code != SQLITE_OK && code != SQLITE_DONE){
            let message = error_string_ptr.read()
            throw new Error(`sqlite api error: ${code}, message: ${message}`)
        }
    }
    // 调用select语句, 并获取数据
    // types是字段类型的字符串解析函数的数组
    select(sql,types) {
        let table = []
        // 合成闭包
        let callback = CallbackType.createJsFunction((p, cols /*: number*/ , argv /*: PointerValue*/, colv /*: PointerValue*/)=>{
            // 闭包内部需要用try-catch , 因为js异常和C++异常无法被C语言处理
            try{
                let record = {}
                // 遍历C语言式数组指针
                for (let i =0; i< cols; i++){
                    // 读取字段名
                    let colName = colv.read(i)
                    // 读取字段值
                    let colData = argv.read(i)
                    record[colName] = types[i](colData)
                }
                table.push(record)
                return SQLITE_OK;
            } catch (e) {
                alert(e)
            }
            
        })
        this.exec(sql,callback)
        
        return table
    }
}

function macro_main(){
    
    let db_path = ":memory:"
    let db = new Sqlite(db_path)
    // js没有RAII, 需要用finally语句来关闭资源
    try {
        // 创建数据库表
        db.exec("CREATE TABLE IF NOT EXISTS foo (field1 REAL, field2 REAL);")
        db.exec("delete from foo;")
        let records = [
            [1.1, 2.2],
            [3.3, 4.4],
            [5.5, 6.6],
        ] 
        // 插入记录
        for (const row of records) {
            let field1 = Number(row[0])
            let field2 = Number(row[1])
            db.exec(`INSERT INTO foo VALUES(${field1}, ${field2});`)
        }
        
        // 读取数据库表
        let data = db.select("select field1, field2 from foo;",[Number, Number])
        console.log("table foo: " + JSON.stringify(data))
    } finally {
        // 关闭数据库
        db.close()
    }

}
```
