Skip to main content
Glama

CJ-MCP

by PoivronMax
CALLBACK_FFI_DESIGN.md40.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 侧的用户回调函数。

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PoivronMax/idlize-cj-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server