CALLBACK_FFI_DESIGN.md•40.8 kB
# Cangjie-C 回调函数指针注册机制设计文档
## 目录
1. [设计概述](#设计概述)
2. [架构设计](#架构设计)
3. [初始化流程](#初始化流程)
4. [调用流程](#调用流程)
5. [类型映射](#类型映射)
6. [完整调用链示例](#完整调用链示例)
7. [与 FFIAtCPackage 模式的对比](#与-ffiatcpackage-模式的对比)
---
## 设计概述
### 问题背景
在 Cangjie-C FFI 中,需要实现从 C++ 侧调用 Cangjie 侧的函数。传统的 `@C` 注解方式可能存在符号链接问题。本设计采用**函数指针注册模式**,通过运行时注册机制,将 Cangjie 侧的函数指针传递给 C++ 侧存储,C++ 侧通过存储的函数指针间接调用 Cangjie 函数。
### 设计模式
采用与 `FFIAtCPackage` 相同的设计模式:
- **Cangjie 侧**:定义 `@C struct`,包含 `CFunc` 类型的成员,直接赋值为 `@C` 函数
- **C++ 侧**:定义 `extern "C" struct`,包含函数指针成员
- **注册机制**:Cangjie 侧通过 `foreign func` 调用 C++ 侧的注册函数,传递结构体值
- **存储机制**:C++ 侧使用单例模式存储注册的函数指针
- **调用机制**:C++ 侧通过宏定义调用存储的函数指针
---
## 架构设计
### 组件关系图
```
┌─────────────────────────────────────────────────────────────┐
│ Cangjie Side (InteropOps.cj) │
├─────────────────────────────────────────────────────────────┤
│ │
│ @C func callCallback(id, args, length) -> Int32 │
│ └──> callCallbackImpl() │
│ └──> CallbackRegistry.INSTANCE.call() │
│ │
│ @C struct CallbackFunctionsPackage { │
│ let atCCallCallback: CFunc<...> = callCallback │
│ } │
│ │
│ foreign func RegisterCallbackFunctions( │
│ cjFuncs: CallbackFunctionsPackage) │
│ │
│ public func registerCallbackFunctions() { │
│ RegisterCallbackFunctions(CallbackFunctionsPackage()) │
│ } │
│ │
└────────────────────┬────────────────────────────────────────┘
│ FFI 调用
│ (值传递结构体)
▼
┌─────────────────────────────────────────────────────────────┐
│ C++ Side (convertors-cj.h/.cc) │
├─────────────────────────────────────────────────────────────┤
│ │
│ extern "C" { │
│ struct CallbackFunctionsPackage { │
│ int32_t (*atCCallCallback)( │
│ int32_t, uint64_t, int32_t) = nullptr; │
│ }; │
│ │
│ void RegisterCallbackFunctions( │
│ CallbackFunctionsPackage cjFuncs); │
│ } │
│ │
│ class CallbackFunctionsRegistry { │
│ static CallbackFunctionsRegistry* getInstance(); │
│ void registerFunctions( │
│ const CallbackFunctionsPackage* funcs); │
│ int32_t callCallback( │
│ int32_t id, uint64_t args, int32_t length); │
│ private: │
│ CallbackFunctionsPackage funcs_; │
│ }; │
│ │
│ #define KOALA_INTEROP_CALL_VOID(venv, id, length, args) │
│ CallbackFunctionsRegistry::getInstance() │
│ ->callCallback(id, args_casted, length); │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 关键组件说明
1. **CallbackFunctionsPackage (Cangjie 侧)**
- `@C struct`,定义可被 C 调用的结构体
- 成员 `atCCallCallback` 类型为 `CFunc<(Int32, UInt64, Int32) -> Int32>`
- 直接赋值为 `@C func callCallback`
2. **CallbackFunctionsPackage (C++ 侧)**
- `extern "C" struct`,匹配 Cangjie 侧的结构体布局
- 成员 `atCCallCallback` 为函数指针类型
- 初始值为 `nullptr`
3. **CallbackFunctionsRegistry**
- C++ 单例类,存储注册的函数指针
- 提供 `registerFunctions()` 存储函数指针
- 提供 `callCallback()` 调用存储的函数指针
4. **RegisterCallbackFunctions**
- C++ 函数,接受 `CallbackFunctionsPackage` 值传递
- 将传入的结构体存储到 `CallbackFunctionsRegistry` 单例中
---
## 初始化流程
### 流程图
```
┌──────────────────────────────────────────────────────────────┐
│ 步骤 0: 编译阶段(cjc 编译 InteropOps.cj) │
│ - 将 Cangjie 代码编译成字节码/机器码 │
│ - lambda 表达式被编译为模块初始化代码块 │
│ - ⚠️ 此时代码不执行,只是编译 │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 1: 运行时模块加载 │
│ - 应用启动或模块被 require/import │
│ - 运行时通过 runtime->loadCJModule() 加载模块 │
│ - 模块级初始化代码开始执行 │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 1.5: 模块级立即执行 lambda 自动执行 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ /* 在 InteropOps.cj 文件末尾 */ │ │
│ │ let INJECT_CALLBACK_FUNCTIONS_RESULT = { │ │
│ │ => │ │
│ │ registerCallbackFunctions() │ │
│ │ }() │ │
│ │ │ │
│ │ 说明:这是模块级立即执行的 lambda 表达式 │ │
│ │ 当模块加载时,这个 lambda 会自动执行 │ │
│ │ 从而调用 registerCallbackFunctions() │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 2: 调用 registerCallbackFunctions() │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ public func registerCallbackFunctions() { │ │
│ │ unsafe { │ │
│ │ RegisterCallbackFunctions( │ │
│ │ CallbackFunctionsPackage()) │ │
│ │ } │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 3: 创建 CallbackFunctionsPackage 结构体实例 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ @C struct CallbackFunctionsPackage { │ │
│ │ let atCCallCallback: CFunc<...> = callCallback │ │
│ │ } │ │
│ │ │ │
│ │ CallbackFunctionsPackage() │ │
│ │ └──> 创建结构体,atCCallCallback 被赋值为 │ │
│ │ @C func callCallback 的函数地址 │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 4: 通过 FFI 调用 RegisterCallbackFunctions │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ foreign func RegisterCallbackFunctions( │ │
│ │ cjFuncs: CallbackFunctionsPackage): Unit │ │
│ │ │ │
│ │ └──> 通过 Cangjie FFI 机制调用 C++ 函数 │ │
│ │ (结构体按值传递,函数指针值被复制) │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 5: C++ 侧 RegisterCallbackFunctions 实现 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ extern "C" { │ │
│ │ void RegisterCallbackFunctions( │ │
│ │ CallbackFunctionsPackage cjFuncs) │ │
│ │ { │ │
│ │ CallbackFunctionsRegistry::getInstance() │ │
│ │ ->registerFunctions(&cjFuncs); │ │
│ │ } │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 6: 存储函数指针到单例 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ void CallbackFunctionsRegistry::registerFunctions( │ │
│ │ const CallbackFunctionsPackage* funcs) │ │
│ │ { │ │
│ │ if (funcs != nullptr) { │ │
│ │ funcs_ = *funcs; // 复制整个结构体 │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ └──> funcs_.atCCallCallback 现在指向 │ │
│ │ Cangjie 侧的 callCallback 函数 │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 7: 初始化完成 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CallbackFunctionsRegistry 单例已存储函数指针 │ │
│ │ C++ 侧可以通过 funcs_.atCCallCallback 调用 │ │
│ │ Cangjie 函数 │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
### 代码实现示例
#### Cangjie 侧实现
```cangjie
/* @C 函数:将被注册到 C++ 侧的函数 */
@C
func callCallback(id: Int32, args: UInt64, length: Int32): Int32 {
return CallbackRegistry.INSTANCE.call(id, args, length)
}
/* @C 结构体:匹配 C++ 侧的结构体布局 */
@C
struct CallbackFunctionsPackage {
/* 直接赋值 @C 函数,Cangjie 编译器会自动转换为函数指针 */
let atCCallCallback: CFunc<(Int32, UInt64, Int32) -> Int32> = callCallback
}
/* Foreign 函数声明:C++ 侧的注册函数 */
foreign func RegisterCallbackFunctions(cjFuncs: CallbackFunctionsPackage): Unit
/* 注册函数:在模块加载时自动调用 */
func registerCallbackFunctions(): Unit {
unsafe {
/* 创建结构体实例,结构体成员会自动赋值为 callCallback 函数指针 */
RegisterCallbackFunctions(CallbackFunctionsPackage())
}
}
/* 模块级初始化:自动执行模式 */
/* 这是 Cangjie 中模块级初始化的标准模式 */
/* 当模块加载时,这个立即执行的 lambda 会自动调用 registerCallbackFunctions() */
let INJECT_CALLBACK_FUNCTIONS_RESULT = {
=>
registerCallbackFunctions()
}()
```
#### C++ 侧实现
```cpp
/* C++ 侧结构体:匹配 Cangjie 侧的结构体布局 */
extern "C" {
struct CallbackFunctionsPackage {
/* 函数指针成员,初始值为 nullptr */
int32_t (*atCCallCallback)(int32_t id, uint64_t args, int32_t length) = nullptr;
};
/* 注册函数:接受结构体值传递 */
KOALA_INTEROP_EXPORT void RegisterCallbackFunctions(CallbackFunctionsPackage cjFuncs);
}
/* 单例类:存储注册的函数指针 */
class CallbackFunctionsRegistry {
public:
static CallbackFunctionsRegistry* getInstance();
void registerFunctions(const CallbackFunctionsPackage* funcs);
int32_t callCallback(int32_t id, uint64_t args, int32_t length);
private:
CallbackFunctionsRegistry() = default;
CallbackFunctionsPackage funcs_ = {};
};
/* 注册函数实现 */
extern "C" {
void RegisterCallbackFunctions(CallbackFunctionsPackage cjFuncs) {
/* 将结构体存储到单例中 */
CallbackFunctionsRegistry::getInstance()->registerFunctions(&cjFuncs);
}
}
/* 存储函数指针 */
void CallbackFunctionsRegistry::registerFunctions(const CallbackFunctionsPackage* funcs) {
if (funcs != nullptr) {
funcs_ = *funcs; // 复制整个结构体(包括函数指针值)
}
}
```
---
## 调用流程
### 流程图:从 C++ 调用到 Cangjie 回调执行
```
┌──────────────────────────────────────────────────────────────┐
│ 步骤 1: C++ 代码需要使用回调 │
│ (例如:事件触发、异步回调等) │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 2: 调用 KOALA_INTEROP_CALL_VOID 宏 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ #define KOALA_INTEROP_CALL_VOID(venv, id, length, args)│ │
│ │ { │ │
│ │ uint64_t args_casted = │ │
│ │ reinterpret_cast<uint64_t>(args); │ │
│ │ CallbackFunctionsRegistry::getInstance() │ │
│ │ ->callCallback( │ │
│ │ static_cast<int32_t>(id), │ │
│ │ args_casted, │ │
│ │ static_cast<int32_t>(length)); │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 示例调用: │
│ KOALA_INTEROP_CALL_VOID(vmContext, callbackId, │
│ sizeof(dataBuffer), dataBuffer); │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 3: CallbackFunctionsRegistry::callCallback() │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ int32_t CallbackFunctionsRegistry::callCallback( │ │
│ │ int32_t id, uint64_t args, int32_t length) │ │
│ │ { │ │
│ │ if (funcs_.atCCallCallback != nullptr) { │ │
│ │ /* 通过存储的函数指针调用 Cangjie 函数 */ │ │
│ │ return funcs_.atCCallCallback( │ │
│ │ id, args, length); │ │
│ │ } │ │
│ │ return 0; │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
│ funcs_.atCCallCallback 指向
│ Cangjie 侧的 callCallback 函数
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 4: FFI 调用到 Cangjie 侧 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 通过 Cangjie FFI 机制,函数指针调用被转发到 │ │
│ │ Cangjie 侧的 @C func callCallback │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 5: Cangjie 侧 callCallback 执行 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ @C │ │
│ │ func callCallback( │ │
│ │ id: Int32, args: UInt64, length: Int32): Int32 │ │
│ │ { │ │
│ │ return CallbackRegistry.INSTANCE.call( │ │
│ │ id, args, length); │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 6: CallbackRegistry::call() 查找并执行回调 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ func call(id: Int32, args: KSerializerBuffer, │ │
│ │ length: Int32): Int32 { │ │
│ │ let record = this.callbacks.get(id) │ │
│ │ if (let Some(record) <- record) { │ │
│ │ if (record.autoDisposable) { │ │
│ │ this.dispose(id) │ │
│ │ } │ │
│ │ /* 执行用户注册的回调函数 */ │ │
│ │ return record.callback(args, length) │ │
│ │ } │ │
│ │ return 0 │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 步骤 7: 执行用户定义的回调函数 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ record.callback(args, length) │ │
│ │ └──> 执行用户在 wrapCallback() 中注册的 │ │
│ │ 实际回调函数 │ │
│ │ (例如:处理事件、更新 UI 等) │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
### 调用示例代码
#### C++ 侧调用示例
```cpp
// 场景:事件触发后需要回调到 Cangjie 侧
void OnEventOccurred(_InteropVMContext* vmContext, int callbackId) {
// 准备回调数据
int32_t dataBuffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 通过宏调用 Cangjie 回调函数
KOALA_INTEROP_CALL_VOID(
vmContext, // VM context (当前未使用,保留接口兼容性)
callbackId, // 回调 ID (由 wrapCallback() 返回)
sizeof(dataBuffer), // 数据长度(字节)
dataBuffer // 数据指针
);
}
```
#### Cangjie 侧回调处理示例
```cangjie
// 用户定义的回调函数
let myCallback: CallbackType = {args: KSerializerBuffer, length: Int32 =>
println("Callback received: args=${args}, length=${length}")
// 处理回调逻辑...
return 0
}
// 注册回调,获取回调 ID
let callbackId = wrapCallback(myCallback)
// callbackId 可以传递给 C++ 侧,用于后续触发回调
// 当 C++ 侧调用 KOALA_INTEROP_CALL_VOID 时:
// 1. C++ 调用 CallbackFunctionsRegistry::callCallback(callbackId, args, length)
// 2. 通过函数指针调用到 Cangjie 的 callCallback()
// 3. CallbackRegistry.INSTANCE.call() 查找 callbackId 对应的回调
// 4. 执行 myCallback(args, length)
```
---
## 类型映射
### 函数签名类型映射
| Cangjie 类型 | C++ 类型 | 说明 |
|-------------|---------|------|
| `Int32` | `int32_t` | 32 位有符号整数 |
| `UInt64` | `uint64_t` | 64 位无符号整数 |
| `KSerializerBuffer` | `void*` (转换后) | 序列化缓冲区指针 |
### 类型转换流程
#### C++ 侧 -> Cangjie 侧
```cpp
// C++ 侧原始数据
void* args = /* 某个数据指针 */;
int32_t length = /* 数据长度 */;
// 在 KOALA_INTEROP_CALL_VOID 宏中转换
uint64_t args_casted = reinterpret_cast<uint64_t>(args);
// args_casted: 将指针转换为 uint64_t (在 Cangjie 中对应 UInt64)
// 调用函数指针
funcs_.atCCallCallback(
static_cast<int32_t>(id), // Int32 -> int32_t
args_casted, // void* -> uint64_t (UInt64)
static_cast<int32_t>(length) // Int32 -> int32_t
);
```
#### Cangjie 侧接收和处理
```cangjie
// Cangjie 侧函数签名
@C
func callCallback(id: Int32, args: UInt64, length: Int32): Int32 {
// args: UInt64 类型,实际上是 C++ 侧的 void* 指针值
// 可以将其转换回指针或直接作为整数使用
// 注意:在 Cangjie 中,KSerializerBuffer 是 UInt64 的别名
// KSerializerBuffer = pointer = UInt64
let serializerBuffer: KSerializerBuffer = args
return CallbackRegistry.INSTANCE.call(id, serializerBuffer, length)
}
```
### 类型安全说明
1. **指针传递**:
- C++ 侧使用 `void*` 指针
- 转换为 `uint64_t` 传递到 Cangjie
- Cangjie 侧使用 `UInt64`(别名 `KSerializerBuffer`)接收
- **注意**:这种方式传递的是指针值,不是指针本身
2. **整数类型**:
- `Int32` 和 `int32_t` 在 C/C++ 中大小一致
- `UInt64` 和 `uint64_t` 在 C/C++ 中大小一致
- 确保跨语言调用时类型宽度匹配
---
## 完整调用链示例
### 场景:点击事件回调
假设有一个点击事件需要从 C++ 侧回调到 Cangjie 侧:
#### 1. Cangjie 侧:注册回调
```cangjie
// 用户定义的点击回调处理函数
let onClickCallback: CallbackType = {args: KSerializerBuffer, length: Int32 =>
println("Click event received!")
// 解析 args 中的数据...
// 更新 UI 或执行其他逻辑
return 0
}
// 注册回调,获取回调 ID
let clickCallbackId = wrapCallback(onClickCallback)
// clickCallbackId = 1025 (假设)
// 将 clickCallbackId 传递给 C++ 侧(例如通过某个 C++ API)
// 假设有一个函数 setClickCallback(id: Int32) 用于设置
```
#### 2. C++ 侧:接收回调 ID 并存储
```cpp
// C++ 侧接收回调 ID(假设通过某个 API)
int32_t storedCallbackId = 1025; // 从 Cangjie 侧获得
// 当点击事件发生时
void HandleClickEvent() {
// 准备事件数据
struct ClickData {
int32_t x;
int32_t y;
int64_t timestamp;
} clickData = {100, 200, 1234567890};
// 通过宏触发回调
KOALA_INTEROP_CALL_VOID(
nullptr, // VM context
storedCallbackId, // callbackId = 1025
sizeof(clickData), // length = 16 bytes
&clickData // args = &clickData
);
}
```
#### 3. 宏展开后的调用链
```cpp
// KOALA_INTEROP_CALL_VOID 宏展开:
{
uint64_t args_casted = reinterpret_cast<uint64_t>(&clickData);
// args_casted = 0x7fff12345678 (假设的指针值)
CallbackFunctionsRegistry::getInstance()
->callCallback(
static_cast<int32_t>(1025), // id = 1025
args_casted, // args = 0x7fff12345678
static_cast<int32_t>(16) // length = 16
);
}
```
#### 4. 通过函数指针调用 Cangjie 函数
```cpp
// CallbackFunctionsRegistry::callCallback()
int32_t CallbackFunctionsRegistry::callCallback(
int32_t id, uint64_t args, int32_t length) {
if (funcs_.atCCallCallback != nullptr) {
// 调用注册的函数指针(指向 Cangjie 侧的 callCallback)
return funcs_.atCCallCallback(id, args, length);
// 参数:
// id = 1025
// args = 0x7fff12345678
// length = 16
}
return 0;
}
```
#### 5. Cangjie 侧 callCallback 执行
```cangjie
// @C func callCallback 被调用
@C
func callCallback(id: Int32, args: UInt64, length: Int32): Int32 {
// id = 1025
// args = 0x7fff12345678
// length = 16
// 调用 CallbackRegistry
return CallbackRegistry.INSTANCE.call(id, args, length)
}
```
#### 6. CallbackRegistry 查找并执行回调
```cangjie
// CallbackRegistry::call()
func call(id: Int32, args: KSerializerBuffer, length: Int32): Int32 {
// id = 1025
let record = this.callbacks.get(id)
// 从 HashMap 中查找 ID 为 1025 的回调记录
if (let Some(record) <- record) {
// 找到回调记录
if (record.autoDisposable) {
this.dispose(id) // 如果是自动销毁,先移除
}
// 执行用户注册的回调函数
return record.callback(args, length)
// record.callback 就是之前注册的 onClickCallback
// 参数:args = 0x7fff12345678, length = 16
} else {
println("Callback ${id} is not known")
return 0
}
}
```
#### 7. 用户回调函数执行
```cangjie
// onClickCallback 执行
let onClickCallback: CallbackType = {args: KSerializerBuffer, length: Int32 =>
println("Click event received!")
// args = 0x7fff12345678 (指针值)
// length = 16
// 这里可以解析 args 中的数据
// 注意:在 Cangjie 中,如果需要访问指针内容,
// 可能需要通过 unsafe 块和 CPointer 类型
return 0
}
```
### 调用链时序图
```
C++ Event Handler
│
│ (1) KOALA_INTEROP_CALL_VOID(callbackId, length, args)
▼
CallbackFunctionsRegistry::callCallback()
│
│ (2) funcs_.atCCallCallback(id, args, length)
▼
[FFI Bridge]
│
│ (3) 函数指针调用转发
▼
Cangjie @C func callCallback(id, args, length)
│
│ (4) CallbackRegistry.INSTANCE.call(id, args, length)
▼
CallbackRegistry::call()
│
│ (5) record.callback(args, length)
▼
User-defined Callback (onClickCallback)
│
│ (6) 执行用户逻辑
▼
Return 0
```
---
## 与 FFIAtCPackage 模式的对比
### 相似之处
| 特性 | FFIAtCPackage | CallbackFunctionsPackage |
|-----|--------------|-------------------------|
| Cangjie 侧结构体 | `@C struct` | `@C struct` |
| Cangjie 侧成员类型 | `CFunc<...>` | `CFunc<...>` |
| Cangjie 侧赋值方式 | 直接赋值 `@C` 函数 | 直接赋值 `@C` 函数 |
| C++ 侧结构体 | `extern "C" struct` | `extern "C" struct` |
| C++ 侧成员类型 | 函数指针 | 函数指针 |
| 注册函数 | `FfiOHOSFFIRegisterCJFuncs(...)` | `RegisterCallbackFunctions(...)` |
| 传递方式 | 值传递 | 值传递 |
| 存储方式 | 单例模式 | 单例模式 |
### 不同之处
| 特性 | FFIAtCPackage | CallbackFunctionsPackage |
|-----|--------------|-------------------------|
| 用途 | FFI 数据管理和回调调用器 | 回调函数调用 |
| 函数数量 | 5 个函数指针 | 1 个函数指针 |
| 调用场景 | 内部 FFI 机制 | 用户回调触发 |
| 调用方式 | `CJFFIFnInvoker::InvokeLambda()` | `KOALA_INTEROP_CALL_VOID` 宏 |
### 设计一致性
两个模式遵循相同的设计原则:
1. **运行时注册**:在模块初始化时注册函数指针,避免链接时依赖
2. **值传递结构体**:通过结构体值传递函数指针,类型安全
3. **单例存储**:使用单例模式存储函数指针,全局可访问
4. **空指针检查**:调用前检查函数指针是否为 `nullptr`
---
## 关键设计决策
### 1. 为什么使用函数指针注册而不是直接 `@C` 导出?
**原因**:
- 避免链接时的符号依赖问题
- 提供更好的运行时控制(可以检查函数是否已注册)
- 与现有 `FFIAtCPackage` 模式保持一致
### 2. 为什么使用值传递而不是指针传递?
**原因**:
- 与 `FFIAtCPackage` 模式保持一致
- Cangjie FFI 机制对值传递有更好的支持
- 简化内存管理(函数指针很小)
### 3. 为什么使用单例模式存储函数指针?
**原因**:
- 全局可访问,不需要传递上下文
- 与 `CJFFIFnInvoker` 的设计保持一致
- 简单高效,避免复杂的生命周期管理
### 4. 为什么使用宏而不是直接函数调用?
**原因**:
- 宏可以内联,减少函数调用开销
- 可以处理类型转换(如 `reinterpret_cast`)
- 与现有代码风格保持一致
---
## 总结
本设计通过函数指针注册机制实现了 C++ 到 Cangjie 的回调调用,具有以下特点:
1. **解耦**:运行时注册,避免链接时依赖
2. **类型安全**:通过结构体匹配确保类型正确
3. **一致性**:与 `FFIAtCPackage` 模式保持一致
4. **高效**:使用函数指针直接调用,性能开销小
5. **可维护**:清晰的调用链,易于调试和维护
6. **自动初始化**:通过模块级 lambda 在运行时自动注册,无需手动调用
### 初始化时机总结
- **编译时**:`InteropOps.cj` 被编译为字节码,初始化代码被编译但不执行
- **运行时模块加载时**:
- 当应用启动或模块被加载时(通过 `runtime->loadCJModule()`)
- 模块级变量初始化代码被执行
- `INJECT_CALLBACK_FUNCTIONS_RESULT` 的 lambda 自动执行
- `registerCallbackFunctions()` 被调用,函数指针注册到 C++ 侧
- 每个模块只加载一次,所以初始化也只会执行一次
该设计为 Cangjie-C FFI 提供了可靠的回调机制,支持从 C++ 侧触发 Cangjie 侧的用户回调函数。