
语言运行时嵌入式解释器【免费下载链接】duktapeDuktape - embeddable Javascript engine with a focus on portability and compact footprint项目地址https://gitcode.com/gh_mirrors/du/duktape点击查看免费下载导读在 Duktape 中嵌入 C 代码的常见做法是将其封装为 C 模块本文介绍的Duktape C module conventionC 模块约定定义了一套推荐的模块初始化函数init function编写规范以dukopen_my_module()形式命名、以 Duktape/C 函数身份被调用、把模块导出值压入值栈顶部并返回 1。遵循该约定同一个 C 模块既能被静态链接的项目直接调用也能被 DLL 加载器按文件名推断函数名后动态加载还能与 CommonJS 模块系统含混合 C/ECMAScript 模块协同工作。读完本文你将掌握如何按约定编写可移植的 C 模块、如何手动初始化或通过 DLL 加载它、如何接入 Duktape 1.x 的modSearch()/module.exports机制以及该约定的边界与限制。约定背景为什么需要一套 C 模块约定Duktape 本身是可嵌入的 JavaScript 引擎其模块系统建立在 CommonJS 格式之上。关于模块加载的完整背景可参考 doc/modules.rst其中有几个关键事实Duktape 不内置文件系统感知的模块搜索用户必须提供模块搜索函数Duktape.modSearch()由其负责定位模块、注册符号或返回模块源码字符串这样引擎才能保持可移植性。主库中无内建 C 模块支持为避免异形平台的可移植性问题C 模块静态或 DLL由用户代码在模块搜索函数之上实现。约定非强制你可以为自己的项目设计任何加载器约定但遵循推荐约定的模块更容易在项目之间共享。doc/c-module-convention.rst正是为让 C 模块可共享而给出的这份推荐约定。它允许模块与静态链接配合使用与 DLL 加载配合使用在 Duktape 的 CommonJS 模块加载体系之内或之外使用。模块初始化函数dukopen_ 约定函数签名与基本形态对于模块my_module其初始化函数应命名为dukopen_my_module形如duk_ret_t dukopen_my_module(duk_context *ctx) { /* 以任何最合适的方式初始化模块。 * 该函数作为 Duktape/C 函数被调用。 * * 将模块结果例如包含导出符号的对象或一个函数压入 * 值栈顶部并返回 1 表示存在返回值。与普通 Duktape/C * 函数一样临时值可以留在返回值之下。 */ duk_push_object(ctx); /* 模块结果 */ duk_put_function_list(ctx, -1, my_module_funcs); duk_push_int(ctx, 42); duk_put_prop_string(ctx, -2, meaningOfLife); return 1; /* 返回模块值 */ }要点拆解返回类型duk_ret_t初始化函数本质上就是一个 Duktape/C 函数返回 1 表示值栈顶部有一个模块返回值。导出对象常见的做法是先duk_push_object()压入一个空对象作为模块结果再通过 duk_put_function_list 批量挂入函数、通过duk_put_prop_string等 API 挂入属性。临时值返回值之下的栈元素可以随意保留符合普通 Duktape/C 函数的值栈使用规则调用方无需清理。完整示例来自官方测试用例的模块仓库中 tests/api/test-dev-cmodule-guide.c 提供了与本约定一一对应的完整可运行示例可直接作为编写模板static const duk_function_list_entry my_module_funcs[] { { func1, my_func_1, 3 /*nargs*/ }, { func2, my_func_2, DUK_VARARGS /*nargs*/ }, { NULL, NULL, 0 } }; static const duk_number_list_entry my_module_consts[] { { FLAG_FOO, (double) (1 0) }, { NULL, 0.0 } }; static duk_ret_t dukopen_my_module(duk_context *ctx) { duk_push_object(ctx); duk_put_function_list(ctx, -1, my_module_funcs); duk_put_number_list(ctx, -1, my_module_consts); return 1; }该用例还展示了两种调用路径test_use_module手动压入dukopen_my_module并调用把返回的模块对象挂到全局my_module上再执行my_module.func2()test_modsearch_module把dukopen_my_module封装进一个my_modsearch函数模拟 CommonJS 场景详见后文混合模块小节。函数表条目{ 名称, 函数指针, nargs }中的nargs若传入DUK_VARARGS则该 C 函数可接受可变数量参数。手动初始化静态链接场景静态链接下无需任何加载器直接手动初始化模块即可。约定给出的标准调用序列是duk_push_c_function(ctx, dukopen_my_module, 0 /*nargs*/); duk_call(ctx, 0); /* 或 duk_pcall() 以捕获错误 */ /* 值栈顶部即为模块值 */duk_push_c_function以 0 个参数压入dukopen_my_moduleduk_call(ctx, 0)无参数调用它调用后栈顶是模块导出值若希望捕获初始化过程中抛出的错误改用duk_pcall(ctx, 0)返回值为 0 表示成功、非 0 表示错误。test_use_module中正是先这样调用再把结果用duk_put_global_string(ctx, my_module)挂到全局。DLL 加载文件名即函数名当 C 模块被编译为 DLL 时DLL 文件名应包含模块名上例中的my_module并加上平台相关的前缀/后缀例如my_module.so # Linux my_module.dll # Windows约定规定DLL 加载器应从 DLL 文件名中提取模块名部分并假设初始化函数名为dukopen_ 模块名即dukopen_my_module()。这样加载器拿到my_module.so就能在符号表中定位dukopen_my_module使用与手动初始化完全相同的调用约定去执行它。因此无论静态链接还是 DLL 动态加载初始化函数本身无需任何改动——这正是该约定最大的价值。模块命名规则规避平台陷阱为避免大小写转换和特殊字符带来的平台问题约定建议模块名满足以下正则形式[a-zA-Z_][0-9a-zA-Z_-]*即以字母或下划线开头后续可包含字母、数字、下划线和连字符。这一限制主要服务于 DLL 符号表与文件名转换的跨平台一致性例如某些平台对导出符号名、文件名大小写或特殊字符有额外限制。混合 ECMAScript / C 模块混合模块的形态CommonJS 感知的模块加载器可以支持同一模块同时包含 C 与 ECMAScript 代码my_module.so # C 模块 my_module.js # ECMAScript 模块 (CommonJS)C 部分负责注册原生绑定ECMAScript 部分负责在其上实现更高层逻辑如安全封装、错误处理、参数校验。Duktape 1.x 的 modSearch() 算法约定的原文给出了 Duktape 1.x 下modSearch()应遵循的算法先正常加载 C 模块得到返回值 RET若 RET 是对象把 RET 的自有属性拷贝进 Duktape 创建的exports值然后返回 ECMAScript 模块的源码字符串执行该源码时更多符号被添加到同一个exports值上若 RET 不是对象忽略它正常加载 ECMAScript 模块另一种选择是把 RET 写入固定导出名如exports.value。示例来自 tests/api/test-dev-cmodule-guide.c 的my_modsearch它先调用dukopen_my_module得到 C 模块结果再通过duk_put_prop_string(ctx, 3 /*module*/, exports)用该结果覆盖module.exports从而让require(my_module)直接返回 C 模块对象——这正是 Duktape 1.3 之后允许modSearch()覆盖module.exports的用法static duk_ret_t my_modsearch(duk_context *ctx) { /* 参数: id, require, exports, module */ /* 初始化 C 模块。 */ duk_push_c_function(ctx, dukopen_my_module, 0); duk_call(ctx, 0); /* 结果现在位于栈顶。覆盖 module.exports 使该值成为 * require() 的返回结果。 */ /* [ id require exports module c_module ] */ duk_put_prop_string(ctx, 3 /*module*/, exports); /* module.exports c_module; */ return 0; /* 返回 undefined不提供 ECMAScript 源码 */ }测试期望输出为mod.FLAG_FOO值为 1与mod.func1()的调用痕迹验证了 C 模块符号确实经由require()暴露给了 ECMAScript 代码。Duktape 1.3 的分水岭能否覆盖 module.exports约定特别标注了版本差异Duktape 1.3 及以后modSearch()可以用 C 模块初始化函数返回的对象替换module.exports该值直接成为原始require()调用的结果。Duktape 1.3 之前exports值永远是 Duktape 预创建的对象modSearch()只能向其中添加符号。这带来两点实现约束当 C 模块返回对象时必须由modSearch()手动把对象中的符号逐一拷贝到预创建的exports值当 C 模块返回非对象时可选方案有忽略模块值若 C 模块初始化函数已直接向全局对象注册符号则尚可访问或将模块值拷贝到exports表中的固定名称下约定建议用exports.value。此外约定还提到Duktape 1.x 的 CommonJS 加载在 1.3 之前不支持非对象返回值即所有模块都被视为返回exports表而本约定不限制对象返回值正是为了在 Duktape 1.3 及以后支持非对象模块。关于 Duktape 2.0约定指出 Duktape 2.0 的混合模块算法当时仍在设计中高层思路为先正常加载 C 模块得到 RET若 RET 是对象则在加载 ECMAScript 模块前用它初始化 CommonJS 的exports值使 ECMAScript 模块能使用 C 模块注册的符号并向同一exports添加新符号若 RET 非对象则忽略或用{ value: RET }初始化exports。需要说明的是Duktape 2.x 已将内建模块框架移出主库转为可选 extra见下文。与 Duktape 模块框架的集成方式虽然c-module-convention.rst本身不依赖任何特定加载器但要让 C 模块进入 CommonJS 体系需要一套模块加载框架。两点仓库事实值得注意Duktape 1.x模块框架内建全局require()与Duktape.modSearch()直接可用相关机制在 doc/modules.rst 中有详细描述模块 ID 解析、Duktape.modLoaded缓存、循环引用、模块包装函数(function (require, exports, module) { ... })等。Duktape 2.x内建模块框架被移除见 releases/releases.yaml 中 remove the built-in module loading framework ... now provided as an extra 的条目改为由 extra 提供。仓库中的 extras/module-duktape/README.rst 说明了如何在 2.x 中恢复 1.x 兼容的模块框架将duk_module_duktape.c加入待编译的 C 源文件列表确保duk_module_duktape.h在包含路径中包含头文件并初始化绑定#include duktape.h #include duk_module_duktape.h /* 初始化 Duktape heap 之后或创建带新全局环境的新线程时 */ duk_module_duktape_init(ctx);注意同一全局环境下不要多次调用duk_module_duktape_init()。定义Duktape.modSearch()提供环境相关的模块查找之后全局对象上即注册了require()模块系统即可使用。从 extras/module-duktape/duk_module_duktape.c 的源码duk__require实现约 260–437 行可以印证整个流程的底层细节框架为每次加载创建全新的require函数其require.id记录当前模块的绝对 ID用于相对路径解析源码注释说明这是为了支持相对模块标识符module表在调用modSearch()之前就注册进Duktape.modLoaded[resolved_id]以支持 C 模块的循环引用约 298–304 行框架以Duktape.modSearch(resolved_id, fresh_require, exports, module)的形式调用用户回调约 326–331 行若回调返回的是字符串则拼装成(function(require,exports,module){ ... \n})包装源码后编译执行若返回非字符串则视为纯 C 模块、直接结束加载约 339–348 行模块加载失败modSearch()或模块代码抛错时删除Duktape.modLoaded中的条目并重抛模仿 Node.js 行为使后续require()可以重试约 432–436 行与 doc/modules.rst 中 Since Duktape 1.3 the modLoaded entry will be removed on module load error 一致最终return module.exports并duk_compact()压缩导出表约 427–430 行。这些实现细节表明C 模块通过modSearch()在exports上注册符号、或借助module.exports覆盖导出值都能与框架完全兼容。限制与边界约定文档明确列出了以下几点实践中需注意平台依赖约定并非在所有 Duktape 移植平台上都可用。例如某些平台没有 DLL 支持或文件名限制导致无法按上述规范命名 DLL。并非 CommonJS nativeC 模块不会获得exports表也无法加载子模块至少不能相对于它自己的 CommonJS 标识符。这是有意为之的取舍——为的是让 C 模块约定尽可能简单。版本门槛Duktape 1.3 之前的 CommonJS 模块加载不支持非对象返回值本约定不限制对象返回值正是为了在 1.3 支持非对象模块。DLL 卸载无自动机制doc/modules.rst 补充指出无法自动判断 DLL 何时可卸载——其他模块可能持有单个导出值的引用如var helloFunc require(hello).func;仅跟踪导出表可达性如通过 finalizer是不够的。小结Duktape C module convention 是一份精简而务实的可移植约定dukopen_模块名()命名 Duktape/C 函数形态 返回值压栈 返回 1。它让同一份 C 模块代码同时适用于静态链接的手动初始化与 DLL 加载器的按名定位通过modSearch()与module.exportsC 模块得以无缝融入 Duktape 1.x 的 CommonJS 体系并支持 C/ECMAScript 混合模块。若要在 Duktape 2.x 中使用可借助 extras/module-duktape 恢复 1.x 兼容框架。编写新模块时以 tests/api/test-dev-cmodule-guide.c 为模板、以 doc/modules.rst 为模块体系参考即可快速产出可共享、可移植的 C 模块。赞分享语言运行时嵌入式解释器【免费下载链接】duktapeDuktape - embeddable Javascript engine with a focus on portability and compact footprint项目地址https://gitcode.com/gh_mirrors/du/duktape点击查看免费下载相关推荐10个技巧如何高效使用lxmusic-音源提升音乐体验10个技巧如何高效使用lxmusic 音源提升音乐体验 lxmusic 洛雪音乐是一款提供全网最新最全音源的音乐工具通过合理配置和使用音源用户可以轻松音视频CPython 扩展模块定义指南PyModExport 导出钩子、PyInit 初始化函数与多阶段初始化CPython 扩展模块定义指南PyModExport 导出钩子、PyInit 初始化函数与多阶段初始化 本文基于 CPython 官方 C API 文档 e编程语言语言运行时解释器标准库Unity il2cpp静态构造函数Il2CppDumper初始化流程还原Unity il2cpp静态构造函数Il2CppDumper初始化流程还原 引言静态构造函数解析的痛点与解决方案 你是否在Unity逆向工程中遇到过 il2逆向工程开发工具游戏开发上一篇Lepton 核心组件CodeArea组件的实现原理下一篇MagiskOnWSALocal调试指南ADB连接与logcat分析实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考