Skip to main content
Glama

CJ-MCP

by PoivronMax
onClick_Callback_Flow_Analysis.md20.9 kB
# onClick 回调函数调用链分析 ## 概述 本文档详细分析了 `onClick` 事件从用户定义到执行的完整调用链,涉及 Cangjie/ArkTS(前端)到 C++(后端)的跨语言回调机制。 ## 架构示意图 ``` 用户代码(Cangjie/ArkTS) ↓ [1. onClick(callback) 调用] Peer层(序列化资源ID和函数指针) ↓ [2. impl_CommonMethod_setOnClick0] C++ Bridge层(反序列化 + 注册) ↓ [3. setOnClick0 → SetOnClick0Impl] C++ Modifier层(创建 lambda + CallbackHelper) ↓ [4. 注册到 UI 框架] UI框架(ViewAbstract::SetOnClick) ↓ [5. 用户点击触发 GestureEvent] Lambda 包装器 ↓ [6. Converter::ArkClickEventSync] ↓ [7. CallbackHelper::InvokeSync] 同步回调函数指针 ↓ [8. callManagedCallback_ClickEvent_VoidSync] Managed Runtime(Cangjie/ArkTS) ↓ [9. 执行用户回调] 用户回调函数 ``` ## 详细调用链 ### 阶段 1: 用户定义回调函数(前端) **位置**: `ArkCommonMethod.cj` ```cangjie public func onClick(callback: Option<((event: ClickEvent) -> Unit)>): This { // 用户可以这样使用: // Button().onClick((event) -> { ... }) } ``` ### 阶段 2: Peer层处理(序列化 + 跨语言调用) **文件**: `ArkCommonMethodPeer.cj` (第271行) ```cangjie public func onClickAttribute(event: Option<((event: ClickEvent) -> Unit)>): Unit { let thisSerializer: SerializerBase = SerializerBase.hold() if (let Some(event) <- event) { thisSerializer.writeInt8(RuntimeType.OBJECT.ordinal) let eventTmpValue = event thisSerializer.holdAndWriteCallback(eventTmpValue) // ← 关键:注册回调资源 } else { thisSerializer.writeInt8(RuntimeType.UNDEFINED.ordinal) } // 调用 C++ 原生函数 ArkUIGeneratedNativeModule._CommonMethod_setOnClick0_Callback_ClickEvent_Void( this.peer.ptr, thisSerializer.asBuffer(), thisSerializer.length()) thisSerializer.release() } ``` **关键操作**: - `holdAndWriteCallback()`: 将回调函数注册到资源管理器,获得 `resourceId` - `SerializerBase` 写入: `resourceId`, `holdPointer`, `releasePointer` - 调用 C++ 的 `impl_CommonMethod_setOnClick0()` **holdAndWriteCallback 的实现** (`SerializerBase.cj` 第176-185行): ```cangjie public func holdAndWriteCallback(callback: Any, hold: KPointer, release: KPointer, call: KPointer, callSync: KPointer): ResourceId { // 1. 注册回调对象到 ResourceHolder,返回 resourceId let resourceId = ResourceHolder.instance().registerAndHold(callback) // 2. 记录这个 resourceId,用于后续自动释放 this.heldResources.add(resourceId) // 3. 序列化到缓冲区 this.writeInt32(resourceId) // 资源ID this.writePointer(hold) // hold 函数指针 this.writePointer(release) // release 函数指针 this.writePointer(call) // 异步调用函数指针 this.writePointer(callSync) // 同步调用函数指针 return resourceId } ``` **ResourceHolder 资源管理机制** (`ResourceManager.cj` 第32-78行): ```cangjie class ResourceInfo { public var resource: Any public var holdersCount: Int32 init(resource: Any, holdersCount: Int32 ) { this.resource = resource this.holdersCount = holdersCount } } public class ResourceHolder { private static var nextResourceId: ResourceId = 100 private var resources: HashMap<ResourceId, ResourceInfo> = HashMap<ResourceId, ResourceInfo>() // 单例模式 public static func instance(): ResourceHolder { ... } // 注册并持有资源,返回 resourceId public func registerAndHold(resource: Any): ResourceId { ResourceHolder.nextResourceId += 1 let resourceId = ResourceHolder.nextResourceId this.resources.add(resourceId, ResourceInfo(resource, 1)) // holdersCount = 1 return resourceId } // 增加持有计数 public func hold(resourceId: ResourceId) { match(this.resources.get(resourceId)) { case Some(resource) => resource.holdersCount++ case _ => throw Exception("Resource ${resourceId} does not exists") } } // 减少持有计数,计数为0时自动删除 public func release(resourceId: ResourceId) { let resource = this.resources.get(resourceId) resource.holdersCount-- if (resource.holdersCount <= 0) { this.resources.remove(resourceId) // 允许 GC } } } class ResourceInfo { public var resource: Any // 实际的回调函数对象 public var holdersCount: Int32 // 引用计数 } ``` **资源生命周期管理**: 1. **注册阶段**: `registerAndHold()` 将用户回调函数存储到 HashMap 中,holdersCount 初始化为1 2. **序列化阶段**: SerializerBase 将 resourceId 和函数指针序列化到缓冲区 3. **释放阶段**: 当 SerializerBase 析构或调用 `release()` 时,自动调用 `ResourceHolder.release(resourceId)`,减少引用计数 4. **自动清理**: 当 holdersCount 降为0时,从 HashMap 中删除,允许 GC 回收回调对象 ### 阶段 3: C++ Bridge层处理 **文件**: `bridge_generated.cc` (第2689行) ```cpp void impl_CommonMethod_setOnClick0(Ark_NativePointer thisPtr, KSerializerBuffer thisArray, int32_t thisLength) { Ark_NodeHandle self = reinterpret_cast<Ark_NodeHandle>(thisPtr); DeserializerBase thisDeserializer(thisArray, thisLength); // 从序列化数据中读取回调资源ID和函数指针 const auto valueValueTempTmpBuf_runtimeType = static_cast<Ark_RuntimeType>(thisDeserializer.readInt8()); Opt_Callback_ClickEvent_Void valueValueTempTmpBuf = {}; valueValueTempTmpBuf.tag = valueValueTempTmpBuf_runtimeType == INTEROP_RUNTIME_UNDEFINED ? INTEROP_TAG_UNDEFINED : INTEROP_TAG_OBJECT; if ((valueValueTempTmpBuf_runtimeType) != (INTEROP_RUNTIME_UNDEFINED)) { valueValueTempTmpBuf.value = { thisDeserializer.readCallbackResource(), // 读取 resourceId reinterpret_cast<void(*)(const Ark_Int32 resourceId, const Ark_ClickEvent event)>( thisDeserializer.readPointerOrDefault(reinterpret_cast<Ark_NativePointer>( getManagedCallbackCaller(Kind_Callback_ClickEvent_Void)))), // 异步回调函数指针 reinterpret_cast<void(*)(Ark_VMContext vmContext, const Ark_Int32 resourceId, const Ark_ClickEvent event)>( thisDeserializer.readPointerOrDefault(reinterpret_cast<Ark_NativePointer>( getManagedCallbackCallerSync(Kind_Callback_ClickEvent_Void)))) // 同步回调函数指针 }; } Opt_Callback_ClickEvent_Void valueValueTemp = valueValueTempTmpBuf; // 将回调信息注册到 UI 组件 GetNodeModifiers()->getCommonMethodModifier()->setOnClick0(self, static_cast<Opt_Callback_ClickEvent_Void*>(&valueValueTemp)); } ``` **关键操作**: - 反序列化接收参数:`resourceId`、`holdPointer`、`releasePointer` - 获取回调函数指针:`getManagedCallbackCaller()` / `getManagedCallbackCallerSync()` - 调用 `CommonMethodModifier::setOnClick0()` 函数指针,实际上指向 `SetOnClick0Impl` ### 阶段 3.5: C++ Modifier层处理 **文件**: `common_method_modifier.cpp` (第2701行) ```cpp void SetOnClick0Impl(Ark_NativePointer node, const Opt_Callback_ClickEvent_Void* value) { auto frameNode = reinterpret_cast<FrameNode *>(node); CHECK_NULL_VOID(frameNode); auto optValue = Converter::GetOptPtr(value); if (!optValue) { // 清除回调 if (frameNode->GetTag() == V2::SPAN_ETS_TAG) { SpanModelNG::ClearOnClick(frameNode); } else if (frameNode->GetTag() == V2::TEXT_ETS_TAG) { TextModelNG::ClearOnClick(frameNode); } else { ViewAbstract::DisableOnClick(frameNode); } return; } // 创建 lambda 包装器,使用 CallbackHelper 管理资源 auto onClick = [callback = CallbackHelper(*optValue)](GestureEvent& info) { const auto event = Converter::ArkClickEventSync(info); callback.InvokeSync(event.ArkValue()); }; // 将回调注册到具体的 UI 组件 if (frameNode->GetTag() == V2::SPAN_ETS_TAG) { SpanModelNG::SetOnClick(frameNode, std::move(onClick)); } else if (frameNode->GetTag() == V2::TEXT_ETS_TAG) { TextModelNG::SetOnClick(frameNode, std::move(onClick)); } else { ViewAbstract::SetOnClick(frameNode, std::move(onClick)); } } ``` **关键操作**: - `CallbackHelper` 构造函数自动调用 `hold()` 持有资源,析构时调用 `release()` 释放 - 创建 lambda 包装器将 GestureEvent 转换为 Ark_ClickEvent - 根据组件类型选择对应的 SetOnClick 实现 - 将回调注册到 UI 框架的事件系统 **CallbackHelper 的作用** (`callback_helper.h` 第57-210行): ```cpp class CallbackHelper { CallbackHelper(const CallbackType &callback) : callback_(callback) { if (callback_.resource.hold) { (*callback_.resource.hold)(callback_.resource.resourceId); // 持有资源 } } ~CallbackHelper() { if (callback_.resource.release) { (*callback_.resource.release)(callback_.resource.resourceId); // 释放资源 } } void InvokeSync(Params&&... args) const { if (callback_.callSync) { Ark_VMContext vmContext = GetVMContext(); (*callback_.callSync)(vmContext, callback_.resource.resourceId, std::forward<Params>(args)...); } } }; ``` ### 阶段 4: 用户点击触发事件(UI框架) 用户在 UI 上点击按钮,UI 框架检测到点击事件并触发 `GestureEvent`。 ### 阶段 5: C++ 层触发回调(通过 CallbackHelper) **文件**: `common_method_modifier.cpp` (第2717-2720行) 当点击事件发生时,之前注册的 lambda 被调用: ```cpp auto onClick = [callback = CallbackHelper(*optValue)](GestureEvent& info) { const auto event = Converter::ArkClickEventSync(info); callback.InvokeSync(event.ArkValue()); // ← 调用同步回调 }; ``` **关键操作**: - `Converter::ArkClickEventSync(info)` 将 UI 框架的 GestureEvent 转换为 Ark_ClickEvent - `callback.InvokeSync()` 调用 `CallbackHelper` 的同步回调方法 - `InvokeSync` 内部调用 `callback_.callSync` 函数指针 ### 阶段 6: 调用同步回调函数 **文件**: `callback_helper.h` (第91-101行) ```cpp void InvokeSync(Params&&... args) const { if (callback_.callSync) { Ark_VMContext vmContext = GetVMContext(); if (vmContext == nullptr) { LOGF_ABORT("InvokeSync %{public}p. VMContext is null.", callback_.callSync); } // 调用 getManagedCallbackCallerSync 返回的函数指针 (*callback_.callSync)(vmContext, callback_.resource.resourceId, std::forward<Params>(args)...); } } ``` 这个函数指针是在 Bridge 层设置时通过 `getManagedCallbackCallerSync(Kind_Callback_ClickEvent_Void)` 获取的,实际上指向 `callManagedCallback_ClickEvent_VoidSync`。 ### 阶段 7: 同步回调实现(可选) **文件**: `callback_managed_caller.cc` 如果使用同步回调(`InvokeSync`),会直接调用: ```cpp void callManagedCallback_ClickEvent_VoidSync(Ark_VMContext vmContext, Ark_Int32 resourceId, Ark_ClickEvent event) { uint8_t dataBuffer[4096]; SerializerBase argsSerializer(dataBuffer, sizeof(dataBuffer), nullptr); argsSerializer.writeInt32(10); // Kind_Callback_ClickEvent_Void argsSerializer.writeInt32(resourceId); ClickEvent_serializer::write(argsSerializer, event); // 同步调用,阻塞直到回调完成 KOALA_INTEROP_CALL_VOID(vmContext, 1, sizeof(dataBuffer), dataBuffer); } ``` **KOALA_INTEROP_CALL_VOID 宏的实现对比**: #### 1. KOALA_CJ 版本(Cangjie,同步回调已打通) **文件**: `convertors-cj.h` 第818-820行 ```cpp #define KOALA_INTEROP_CALL_VOID(venv, id, length, args) \ { \ const int32_t apiKind = 10; \ const int32_t callbackKind = *(reinterpret_cast<const int32_t*>(args)); \ CallCallbackSync(reinterpret_cast<void*>(venv), apiKind, \ callbackKind, reinterpret_cast<void*>(args), \ static_cast<int32_t>(length)); \ } ``` **特点**: - 解析 `args` 缓冲区首个 `int32` 作为 `callbackKind` - 使用 `apiKind=10`(ArkUI)调用 `CallCallbackSync` - 通过 `common-interop` 注册的分发器进入 Cangjie 运行时执行用户回调 **调用链**: C++ Modifier → `CallbackHelper::InvokeSync()` → `callSync` 函数指针 → `KOALA_INTEROP_CALL_VOID(CJ)` → `CallCallbackSync` → Cangjie `deserializeAndCallCallback` → 用户回调 #### 2. KOALA_ANI 版本(ArkTS ANI Runtime) **文件**: `convertors-ani.h` 第1853-1862行 ```cpp #define KOALA_INTEROP_CALL_VOID(venv, id, length, args) \ { \ ani_class clazz = nullptr; \ ani_static_method method = nullptr; \ getKoalaANICallbackDispatcher(&clazz, &method); \ ani_env* env = getKoalaANIContext(venv); \ ani_int result = 0; \ long long args_casted = reinterpret_cast<long long>(args); \ CHECK_ANI_FATAL(env->Class_CallStaticMethod_Int(clazz, method, &result, id, \ args_casted, length)); \ } ``` **特点**: - **完整实现**,通过 ANI Runtime 调用 Managed 层 - 获取回调分发器类和方法 - 通过 `ani_env` 调用静态方法 - 阻塞等待结果返回 - 支持真正的跨语言同步回调 **调用链**: C++ → ANI Runtime → Managed Runtime → 用户回调函数 #### 3. KOALA_ETS 版本(ArkTS ETS Runtime) **文件**: `convertors-ets.h` 第1768-1777行 ```cpp #define KOALA_INTEROP_CALL_VOID(venv, id, length, args) \ { \ ets_class clazz = nullptr; \ ets_method method = nullptr; \ getKoalaEtsNapiCallbackDispatcher(&clazz, &method); \ EtsEnv* etsEnv = reinterpret_cast<EtsEnv*>(vmContext); \ etsEnv->PushLocalFrame(1); \ etsEnv->CallStaticIntMethod(clazz, method, id, args, length); \ etsEnv->PopLocalFrame(nullptr); \ } ``` **特点**: - 使用 ETS NAPI 接口 - 管理本地引用帧(PushLocalFrame/PopLocalFrame) - 避免内存泄漏 - 支持同步调用 #### KOALA_CJ vs KOALA_ANI 的差距 | 特性 | KOALA_CJ (Cangjie) | KOALA_ANI (ArkTS) | |------|-------------------|-------------------| | **实现** | 空宏,无操作 | 完整跨语言调用 | | **同步回调** | ❌ 不支持 | ✅ 支持 | | **跨语言调用** | 仅通过函数指针 | 通过 Runtime API | | **阻塞行为** | 立即返回 | 阻塞等待 | | **用途** | 用于上层是 C++ | 用于上层是 ANI Runtime | | **性能** | 零开销 | 有 Runtime 调用开销 | **关键修复:Cangjie 同步回调链已实现** - 在 C++ 侧实现了 KOALA_CJ 的 `KOALA_INTEROP_CALL_VOID` 宏,转发至 `CallCallbackSync` - 在 `CallbackHelper::InvokeSync` 中,当前端为 `DECLARATIVE_CJ` 时允许 `vmContext == nullptr`,避免误终止 因此,`InvokeSync()` → `callManagedCallback_ClickEvent_VoidSync()` → `KOALA_INTEROP_CALL_VOID(CJ)` → `CallCallbackSync` → Cangjie 侧系统分发 → 反序列化并执行用户回调,全链路可用 ### 阶段 8: 执行用户回调函数 **文件**: Cangjie/ArkTS Runtime 在 Managed Runtime 中,调用用户定义的函数: ```cpp // (event: ClickEvent) -> Unit { // println("Button clicked!") // } ``` ## 关键数据结构 ### 1. CallbackResource (回调资源) ```cpp struct CallbackResource { int32_t resourceId; // 资源ID void(*hold)(int32_t); // 持有资源函数 void(*release)(int32_t); // 释放资源函数 }; ``` ### 2. CallbackBuffer (回调缓冲区) ```cpp struct CallbackBuffer { uint8_t buffer[4096]; // 序列化缓冲区 ResourceHolder resourceHolder; // 资源持有者 }; ``` ### 3. 回调类型枚举 ```cpp enum CallbackKind { Kind_Callback_ClickEvent_Void = 10, // ... 其他回调类型 }; ``` ## 关键技术点 ### 1. 函数指针 + 回调资源机制 - **前端 → 后端**: 序列化 `resourceId`、`holdPointer`、`releasePointer`、`callSync` 函数指针 - **后端 → 前端**: 通过函数指针直接调用同步回调,无需队列 ### 2. 跨语言调用 - **前端 → 后端**: FFI 直接调用 C++ 函数 - **后端 → 前端**: 通过函数指针 + 同步调用机制 ### 3. 资源管理 (RAII) - **CallbackHelper 构造**: 自动调用 `hold()` 防止回调被 GC - **CallbackHelper 析构**: 自动调用 `release()` 允许 GC - **生命周期**: 通过捕获 `CallbackHelper` 的 lambda 自动管理 ### 4. 序列化格式(注册阶段) ``` [RuntimeType (1 byte)] ↓ (如果是 OBJECT) [ResourceId (4 bytes)] [HoldPointer (8 bytes)] # 指向 holdManagedCallbackResource [ReleasePointer (8 bytes)] # 指向 releaseManagedCallbackResource [CallAsyncPointer (8 bytes)] # 指向 getManagedCallbackCaller [CallSyncPointer (8 bytes)] # 指向 getManagedCallbackCallerSync ``` ### 5. Lambda 包装器 - 将 UI 框架的 `GestureEvent` 转换为 Ark 的 `ClickEvent` - 捕获 `CallbackHelper` 实例,自动管理资源生命周期 - 根据组件类型(Span/Text/View)选择对应的注册方法 ## 性能优化 ### 1. 同步回调机制 (onClick 使用) - 直接调用,无需队列 - 在 UI 线程同步执行,避免延迟 - 使用 `InvokeSync` 保证实时响应 ### 2. 避免不必要的序列化 - Bridge 层接收已序列化的数据 - Modifier 层直接使用函数指针调用 - 回调触发时只需序列化事件参数 ## 调试要点 ### 1. 追踪 resourceId 和 CallbackHelper 在关键点打印资源信息: ```cpp // 在 Bridge 层 printf("Registering callback with resourceId=%d\n", callback.resourceId); // 在 Modifier 层 printf("CallbackHelper created with resourceId=%d\n", callback_.resource.resourceId); // 在触发回调时 printf("Invoking callback with resourceId=%d\n", callback_.resource.resourceId); ``` ### 2. 检查回调函数指针 验证 `callSync` 函数指针是否有效: ```cpp if (callback_.callSync == nullptr) { LOG_ERROR("callSync is null for resourceId=%d", callback_.resource.resourceId); } ``` ### 3. 验证 GestureEvent 转换 在 lambda 包装器中检查转换结果: ```cpp auto onClick = [callback = CallbackHelper(*optValue)](GestureEvent& info) { const auto event = Converter::ArkClickEventSync(info); printf("Converted GestureEvent, timestamp=%ld\n", event.timestamp.ArkValue()); callback.InvokeSync(event.ArkValue()); }; ``` ### 4. 监控组件类型 在 `SetOnClick0Impl` 中打印组件类型: ```cpp if (frameNode->GetTag() == V2::SPAN_ETS_TAG) { printf("Setting onClick on SPAN_ETS_TAG\n"); } else if (frameNode->GetTag() == V2::TEXT_ETS_TAG) { printf("Setting onClick on TEXT_ETS_TAG\n"); } else { printf("Setting onClick on VIEW\n"); } ``` ## 总结 onClick 回调机制实现了: 1. **类型安全**: 强类型的序列化/反序列化,包括 `Opt_Callback_ClickEvent_Void` 2. **RAII 内存管理**: `CallbackHelper` 自动管理资源生命周期,防止内存泄漏 3. **同步回调**: 直接调用机制保证低延迟响应 4. **跨语言互操作**: Cangjie/ArkTS ↔ C++ 通过 FFI 和函数指针无缝调用 5. **可扩展性**: 基于枚举的回调类型系统 (`Kind_Callback_ClickEvent_Void`) 6. **组件类型适配**: 根据 Span/Text/View 自动选择对应的注册方法 这是一个成熟的跨语言事件处理机制,适用于复杂的 UI 框架场景,特别是在需要低延迟响应的用户交互事件中。

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