scriptc FFI回调机制深入外部线程投递与保留回调的4种格式详解【免费下载链接】scriptcTypeScript-to-Native Compiler项目地址: https://gitcode.com/GitHub_Trending/sc/scriptcscriptc 是一个 TypeScript 转原生TypeScript-to-Native编译器它允许你的 TS 代码以原生 C ABI 调用外部函数并支持把 TypeScript 回调函数传给 C 代码执行。本文将深入讲解 scriptc FFI 回调机制4 种回调格式调用期上下文回调、原始 TLS 回调、保留注册表回调、全局槽位回调以及外部线程投递foreign invoke如何安全地把 C 线程上的事件送回脚本事件循环。为什么 FFI 回调是 scriptc 的高阶能力普通 FFI 调用是单向的脚本调用 C 函数C 返回结果。但真实的原生库——比如监听器、定时器、事件系统——几乎总是反过来调用你提供的回调。scriptc 通过FFI 清单manifest描述这种反向调用编译器会自动为每个回调生成一个 C 跳板函数trampoline把 C 调用转发回 TypeScript 闭包并处理捕获变量、字符串拷贝和异常传播。清单文件就是一个 JSON例如项目自带的完整示例 tests/ffi/profile.jsonffi_format清单格式版本当前支持 1~6functions每个 TypeScriptdeclare function签名与一个 C 符号的绑定回调参数通过lifetimecall/retained和invokescript-thread/foreign两个字段区分投递方式清单解析与严格校验逻辑位于 ffi-manifest.ts回调适配器的符号分配在 ffi-callbacks.ts。4 种回调格式详解scriptc 根据「是否有上下文参数」和「生命周期」两个维度把回调分成 4 种格式。每种格式对应不同的 C 侧存储方式格式lifetimecontextC 侧存储位置典型场景调用期上下文回调call有栈上传递C 遍历/访问器当场调用原始 TLS 回调call无线程本地存储槽简单一次性回调保留注册表回调retained有进程级注册表计数事件监听器 add/remove原始全局槽回调retained无进程级可替换槽位全局唯一回调指针格式一调用期上下文回调context call最常见的形态。回调签名中有一个{ context: ... }槽位编译器把闭包指针放进这个 C 参数lifetime: call表示 C 代码只能在本次外层调用期间调用它。TypeScript 侧只需一句声明declare function nativeApply(callback: (value: number) number, value: number): number;C 侧拿到callback(value, context)两个参数即可见 native.c 中的sf_apply。调用结束时闭包引用立即释放C 不能保存指针留待以后使用。格式二原始 TLS 回调raw call如果回调不携带上下文参数编译器不再生成contextC 参数而是让跳板函数从线程本地存储TLS槽里读取当前闭包。这种格式更轻量但同一线程上同一回调 ID 不能并发复用——编译器会自动为每个回调分配不冲突的sc_ffi_cb_*符号即使你的 C 代码恰好定义了同名符号测试中专门用sc_ffi_cb_0制造了碰撞场景来验证。格式三保留注册表回调retained context当事件系统需要「注册 → 多次触发 → 注销」时用lifetime: retained。编译器为每个保留描述符维护一张计数注册表同一个闭包可以注册多次两次注册是两个独立的原生条目remove只释放对应的一条。配套的注销函数用release描述符指向注册函数{ callback: { release: nativeRetainedAdd:tick } }这带来几个安全特性main.ts 中有完整行为测试pump 中移除正在触发其他回调时注销「后面的」条目不会重复触发也不会调用已释放闭包自释放回调内部调用 remove 自己是安全的退出清理进程退出时注册表自动销毁所有存活闭包不泄漏回调内抛异常被捕获为正常的脚本异常可被 TypeScripttry/catch接住格式四原始全局槽回调raw retained保留但不带上下文的回调占用一个进程级可替换槽位set直接替换旧值remove置空。一个有意思的语义是flush-on-replace——替换新回调时旧回调会先被最后一次调用用哨兵值 -1 触发旧闭包保持存活直到 setter 调用返回之后才真正释放。这保证了「替换时冲刷旧回调」这类 C 惯用法不会踩到悬垂指针。外部线程投递invoke foreign 机制这是 scriptc FFI 最有特色的部分。如果保留回调会被C 侧自己的线程调用比如 pthread 工作线程直接在 C 线程里执行脚本代码是危险的——脚本运行时不是为并发执行设计的。解决办法是把invoke设为foreign要求ffi_format 5、lifetime: retained、返回void。此时生成的原生跳板只暂存纯数据并向进程事件循环投递一个任务脚本代码始终异步地、单线程地运行在脚本线程上。投递队列实现在 scr_ffi_queue.c。投递的字符串参数cstring/string/bytes会在入队时拷贝因此 C 线程稍后篡改栈上缓冲也不影响脚本读到的内容——native.c 的测试fixture 在每次回调后故意把 label 缓冲改写成x来验证这一点。项目测试还包含一个压力场景两个 C 线程各并发投递 500 个事件脚本侧用setInterval与之竞争验证事件循环的公平性与每个线程内 FIFO 顺序foreignBurst测试。即使脚本本身没有任何定时器只要存在 foreign 回调运行时也会自动安装并持有事件循环。回调里的异常安全三种异常路径都有明确语义调用期回调抛错异常传播回外层脚本调用C 侧后续的第二次调用会被抑制sf_callback_string_throw测试验证保留回调抛错异常被抛出到 pump 调用方注册表状态保持一致foreign 回调抛错在脚本线程执行按脚本线程的正常未捕获异常处理相关源码与示例导航FFI 清单格式定义与校验ffi-manifest.ts回调适配器与保留生命周期策略ffi-callbacks.ts外部线程投递队列scr_ffi_queue.c、scr_ffi.c完整行为测试覆盖全部 4 种格式 foreign 投递main.ts / native.c / profile.json扩展标量类型与可变字节回调scalars.ts将 scriptc 程序对象与 C FFI 链接为独立可执行文件的示例examples/native-object/README.md小结scriptc 的 FFI 回调机制用两个维度组织了 4 种回调格式调用期 vs 保留决定生命周期上下文 vs 原始决定闭包指针的传递方式再叠加invoke: foreign后C 线程上的事件通过事件循环安全地回到脚本线程。对新手而言只需记住call回调用完即走retained回调要成对注册/注销跨线程投递一律交给事件循环——剩下的符号分配、内存拷贝和异常传播都由编译器替你完成。【免费下载链接】scriptcTypeScript-to-Native Compiler项目地址: https://gitcode.com/GitHub_Trending/sc/scriptc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考