news 2026/9/15 20:56:40

Cocos Engine bindings-generator 深度指南:基于 Clang 的 C++ 到 JavaScript 自动绑定代码生成器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cocos Engine bindings-generator 深度指南:基于 Clang 的 C++ 到 JavaScript 自动绑定代码生成器

Cocos Engine bindings-generator 深度指南:基于 Clang 的 C++ 到 JavaScript 自动绑定代码生成器

【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine

本指南围绕 Cocos Engine 仓库内 bindings-generator 的 README 展开,系统讲解这套基于libclang的自动绑定代码生成工具:从环境搭建、命令行用法、.ini配置到 Cheetah 模板机制与源码级实现原理,并结合仓库内 generator.py 与 tojs 集成脚本 给出完整调用链。读完本文,你将掌握如何为自己的 C++ 模块自动生成 JavaScript(JSB)绑定代码、如何编写.ini配置控制导出范围,以及生成器内部"Clang 解析 → 类型归一化 → 模板渲染"的完整工作流程。

工具定位:它是做什么的

bindings-generator是 Cocos 引擎用来自动生成 C/C++ 与脚本语言(当前目标为 SpiderMonkey JavaScript 引擎)之间绑定胶水代码的代码生成器。它不依赖手写大量重复的jsval ↔ native转换逻辑,而是通过以下方式工作:

  1. 读取.ini配置文件,得知要解析哪些头文件、导出哪些类与函数;
  2. 调用libclang(预编译的 clang 12.0 动态库)解析 C++ 头文件,构建 AST;
  3. 将 AST 中的类、方法、字段、枚举、模板类型等归一化为内部的NativeClass/NativeFunction/NativeType等模型;
  4. 基于Cheetah 模板(按目标 VM 分目录存放)渲染出最终的.hpp/.cpp/.js(API 文档)文件。

在 Cocos 引擎中的实际场景是:native/tools/tojs/genbindings.py调用本生成器,为native/cocos下的 C++ 模块产出jsb_xxx_auto.h/.cpp,从而让 JavaScript 层能够直接调用原生引擎能力。官方在 native/tools/tojs/README.mdown 中注明:Cocos Creator 3.7.0 起引擎内部已改用更便捷的 SWIG 方案(见 native/tools/swig-config),但本工具仍可用于为自己的项目生成 JS 绑定代码,本文讲解的机制对理解新旧方案都同样有价值。

环境要求与预编译 libclang 12.0

依赖清单

运行生成器需要以下环境(来自 README):

依赖说明
Python 3.x(64 位)生成器主程序运行环境,generator.py兼容 Python 2/3 两套configparser导入
PyYAML 5.4.1解析目标目录下的${target}.yaml(类型转换配置)
Cheetah3模板引擎,用于渲染绑定代码
libclang 动态库clang 的 Python 绑定(clang.cindex)所需的底层动态库

仓库内已附带预编译的 libclang 12.0,位于 native/tools/bindings-generator/libclang/:

  • libclang.dll(Windows)
  • libclang.dylib(macOS)
  • libclang.so(Linux)

目录中的 VERSION.txt 明确标注了版本为libclang in LLVM 12.0.0

一个关键的版本兼容性提醒

README 特别强调:如果你要让预编译的 libclang 12.0 配合 Android NDK 工作,只有 NDK r21 及以上版本才能与之正确配合(README 原文为 "only the NDK r21 can work corrently with it")。这一约束也贯穿了后面所有平台的环境搭建步骤。

手动下载 libclang(可选)

如果你不使用仓库自带的预编译库,也可以自行下载:

  1. 从 LLVM 12.0.0 官方 release 按平台下载预编译二进制(例如 macOS 对应clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz);
  2. 解压或安装;
  3. 找到libclang.dll(Windows)或libclang.dylib(macOS);
  4. 将动态库复制到bindings-generator/libclang/目录下。

Python 绑定源码

与动态库配套的 Python 绑定(clang.cindexclang.enumerationsclang.__init__)就放在仓库的 native/tools/bindings-generator/clang/ 目录中,generator.py通过from clang import cindex直接使用。

命令行用法与参数说明

生成器入口为 generator.py(main()位于 generator.py#L2271),完整用法如下:

Usage: generator.py [options] {configfile} Options: -h, --help show this help message and exit -s SECTION sets a specific section to be converted -t TARGET specifies the target vm. Will search for TARGET.yaml

main()源码中还可以看到两个额外参数:

  • -o OUTDIR:指定生成代码的输出目录,未指定时默认为bindings-generator/gen/
  • -n OUT_FILE:指定输出文件主名,默认取.ini配置节中的prefix。生成结果会是${out_file}.cpp${out_file}.h${out_file}.json${out_file}.inl四个文件(见 generator.py#L1983-L1986)。

工作方式概括:指定目标 VM(当前唯一目标是spidermonkey)和.ini文件中你想生成代码的配置节(section)。目标 VM 会决定两件事:从targets/目录下选择对应的模板目录,以及加载targets/${target}/conversions.yaml

目标(target)的发现机制

main()会扫描bindings-generator/targets/下的所有子目录作为可用 target 列表,并自动跳过.svn.cvs.git等隐藏目录(generator.py#L2323-L2346)。当前仓库中只有spidermonkey一个 target,位于 native/tools/bindings-generator/targets/spidermonkey/。

关键执行细节:userconf.ini 与 libclang 路径

执行时main()会先读取native/tools/tojs/userconf.ini(由genbindings.py生成),从中取得cxxgeneratordir配置,然后通过cindex.Config.set_library_path()把 libclang 动态库目录设置到libclang/(generator.py#L2293-L2302)。这也解释了为什么 libclang 动态库必须放在bindings-generator/libclang下。

环境搭建与自检测试

README 附带了一个简单测试,用来确认生成器工作正常、环境配置正确。该测试依赖预编译的 libclang 12.0,因此要求 Android NDK r21 或更高版本;同时测试代码使用了<string><stdint.h>,需要提供这些头文件的 C++ 实现,测试脚本默认使用 Android NDK 自带的 LLVM libc++。

macOS 环境搭建

# 1. 安装 python(macOS 10.9 自带 python2.7,若缺失可用 Homebrew) brew install python # 2. 安装 Python 依赖 sudo easy_install pip3 sudo pip3 install PyYAML==5.4.1 Cheetah3 # 3. 下载 NDK r21 # 4.(可选)若 python 为自定义安装,复制 user.cfg.sample 并改名为 user.cfg, # 在其中设置 PYTHON_BIN 为 python 的绝对路径 # 5. 运行测试 export NDK_ROOT=/path/to/android-ndk-r21 ./test.sh

test.sh会生成一个userconf.ini,请检查其中的值是否正确、有无报错。

Windows 7 64bit 环境搭建

# 1. 下载 64 位 python3 # 2. 将 python 安装路径(如 C:\Python39)加入 PATH 环境变量 # 3. 安装 Python 依赖 python -m pip install PyYAML==5.4.1 Cheetah3 # 4. 下载 NDK r21 或更高版本 # 5. 设置环境变量 PYTHON_ROOT 和 NDK_ROOT(也可直接在 test.bat 中填写) # 6. 运行 test.bat,生成的代码位于 simple_test_bindings 目录

预期输出

运行测试后可能出现一些 warning,但不应出现 error。测试会创建一个名为simple_test_bindings的目录,内含 3 个文件:

文件作用
.hpp头文件绑定类的头文件
.cpp实现文件绑定类的实现
.js文档文件说明如何从 JavaScript 调用该 C++ 类暴露的方法

需要说明:README 中提到的test.shtest.batuser.cfg.sample属于文档描述的历史版本遗留物,在当前仓库快照的bindings-generator/目录中并不存在。在当前 Cocos 引擎仓库中,真正的入口是 native/tools/tojs/genbindings.py,它会自动完成userconf.ini的生成与生成器调用(详见下文"与引擎 JSB 流程的集成"一节)。

.ini配置文件详解

.ini是一个简单文本文件,用来描述代码生成器的设置。README 给出了 cocos2d-x 时代使用的默认示例:

[cocos2d-x] prefix = cocos2dx events = CCNode#onEnter CCNode#onExit extra_arguments = -I../../cocos2dx/include -I../../cocos2dx/platform -I../../cocos2dx/platform/ios -I../../cocos2dx -I../../cocos2dx/kazmath/include -arch i386 -DTARGET_OS_IPHONE -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.1.sdk -x c++ headers = ../../cocos2dx/include/cocos2d.h classes = CCSprite functions = my_free_function

必需配置项

配置项含义关键约束
prefix项目前缀,必须是目标 VM 语言的合法标识符。大多数情况下会与类名、函数名交错拼接,因为生成的方法基本都是自由函数,这样做可避免命名冲突。生成结果文件名为${prefix}.cpp${prefix}.hpp必填
events形如ClassName#functionName的标识符列表,表示从原生世界回调到目标 VM 的事件必填
extra_arguments传给 clang 接口的额外参数,可理解为"传给编译器的参数"。若目标是 C++,务必以-x c++结尾来强制以 C++ 模式解析.h文件;否则请把头文件命名为.hpp必填
headers需要解析的头文件列表。通常只添加一个头文件,由它#include其余所有文件必填
classes要解析的类,目前只是字符串,但支持正则表达式必填
functions要绑定的自由函数列表(空格分隔),与classes一样支持正则表达式必填
skip空格分隔的Classes::functionsfunctions列表,表示不为其生成任何代码可选

从源码补充的更多配置项

对照 generator.py#L2351-L2384 的gen_opts构造,实际支持的配置项远比 README 列出的丰富,这里补充几个高频项及其底层行为:

  • remove_prefix:对类名做正则替换,去除指定前缀后再注册到脚本层(Generator.__init__NativeClass中均有使用,见 generator.py#L1595);
  • target_namespace/cpp_namespace:脚本命名空间与 C++ 命名空间映射;cpp_namespace用于限定只导出特定 C++ 命名空间下的类(generator.py#L2104-L2109);
  • abstract_classespersistent_classesclasses_owned_by_cpp:抽象类、持久类、由 C++ 持有的类清单,影响构造函数与析构的绑定方式;
  • getter_setter:以ClassName::field1/getter/setter形式声明将某字段以属性(getter/setter)方式导出到脚本层,未指定时默认用getXxx/setXxx命名(generator.py#L1747-L1788);
  • skip_public_fieldsfield:分别控制"跳过"与"强制绑定"的公开字段;
  • obtain_return_value:标记哪些方法返回值需要以"获取"方式处理;
  • rename_functions/rename_classes/replace_headers:方法重命名、类重命名、头文件替换;
  • class_module_configs/method_module_configs:为类/方法挂接宏判断(macro_judgement),用于按编译宏裁剪绑定;
  • hpp_headers/cpp_headers/win32_clang_flags:补充的 C++ 头文件与 Windows 平台额外的 clang 参数。

这些配置项的解析逻辑大多采用ClassName::[item1 item2 ...]的语法(例如skip = Node::[removeFromParent removeAllChildren]),并通过正则拆分实现,细节可查阅 generator.py#L1653-L1788。

生成器内部实现:源码级工作流

整个生成流程可以划分为五个阶段,全部体现在 generator.py 中。

阶段一:配置解析与目标加载

main()读取.ini(通过configparser),按-s指定的 section(不指定则处理全部 section),逐 section 构造gen_opts字典,并逐一实例化Generator调用generate_code()(generator.py#L2349-L2386)。

Generator.__init__(generator.py#L1576)会做大量预处理,其中包括自动补全 clang include 路径:对每个-I参数,若路径不存在,则尝试在该目录的 clang 版本子目录中查找include并追加;Windows 平台还会附加win32_clang_flags

阶段二:生成元信息文件并调用 clang 解析

generate_code()(generator.py#L1978)首先读取targets/${target}/conversions.yaml作为self.config,随后按输出主名打开 4 个输出文件(.cpp.h.json.inl),并写入模板layout_head.h/.c

接着_parse_headers()(generator.py#L2061)把配置中的每个头文件以#include "..."形式写入临时文件batch_input.h,然后调用:

tu = self.index.parse(header, self.clang_args)

即用cindex.Index对整个批量头文件做一次统一解析,得到 TranslationUnit。解析产生的诊断信息通过_pretty_print()按严重级别输出,一旦出现 Error/Fatal 级别错误就抛异常终止(generator.py#L2047-L2081)。

阶段三:AST 遍历与模型构建

_deep_iterate()(generator.py#L2084)递归遍历 AST cursor:

  • 遇到CLASS_DECL/STRUCT_DECL且匹配classes正则(同时受cpp_ns约束)时,构建NativeClass并调用generate_code()
  • 遇到ENUM_DECL且精确匹配时,构建NativeEnum

NativeClass.parse()内部通过_process_node()(generator.py#L1418)处理各类型节点,核心逻辑包括:

  • 基类:递归构建父类NativeClass,用于继承方法分析与RefCount引用类判定(is_ref_class);
  • 公开方法:过滤private/protectedDEPRECATED(通过get_availability检查 clang availability 属性),并跳过可变参数函数cursor.type.is_function_variadic());
  • 方法重载:同名方法被归并进NativeOverloadedFunction容器(generator.py#L980)。值得注意的是,README 指出当前 SpiderMonkey 实现对重载的支持仅适用于参数个数不同的重载
  • 构造函数:跳过拷贝构造(ClassName(const Class &)形态),其余构造进入methods['constructor'],同样支持重载;
  • 公开字段:struct 的公开字段默认导出,class 的公开字段按field/skip_public_fields配置决定。

NativeFunction(generator.py#L841)负责解析函数签名:逐个参数转换为NativeType,若任一参数类型不支持(not_supported)则整函数不导出;还会通过遍历参数 AST 子节点(default_arg_type_arr,涵盖整型/浮点/字符串/字符/布尔/空指针/声明引用等字面量,见 generator.py#L59-L87)检测默认参数,从而计算min_args(最少可调用参数个数),供脚本层做参数个数校验。

阶段四:类型归一化(NativeType)

NativeType.from_type()(generator.py#L469)递归处理 C++ 类型修饰:

  • POINTERT*)、LVALUEREFERENCET&)、RVALUEREFERENCET&&)分别递归展开并设置is_pointer/is_reference/is_rreference标志;
  • 基础类型通过 type_map(generator.py#L28-L53)映射为 C 原生类型,如INT → intLONGLONG → int64_t
  • std::stringstd::function被特殊识别:前者归一化为std::string,后者解析出返回类型与参数列表,标记为函数类型;
  • STL 容器(std::vectorstd::mapstd::unordered_mapstd::set等)通过 stl_type_map(generator.py#L89-L124)记录模板参数个数,并由normalize_type_str做归一化,其中 map 类容器允许 2 个模板参数、序列容器 1 个;
  • 常量数组被归一化为std::array<T, N>
  • 无法识别的类型标记为INVALID_NATIVE_TYPE"??"),从而把函数标记为不支持而跳过。

阶段五:模板渲染与文件收尾

每个NativeClass.generate_code()(generator.py#L1294)依次写入prelude.h/.c头尾、各方法(含重载)的声明与实现、公开字段、register.c注册段,并把类的 JSON 描述追加进class_json_listgenerate_code()最后写入layout_foot.h/.c,将类的 JSON 列表序列化到${out_file}.json,并通过inplace_change().h文件中的占位符// placeholder for jsb_register_types替换为收集到的reg_types.h内容(generator.py#L2034)。

由此可以理解最终产物:.h是绑定类声明与注册宏占位,.cpp是全部绑定函数实现与注册入口,.json是面向文档生成的类/方法/字段结构化描述,.inl是类型注册片段。

模板系统与 conversions.yaml

生成器采用Cheetah 模板来保持灵活性。设计思路是:对于每个目标环境,都提供一套生成相同 C/C++ 功能的模板;每个模板都可以访问代码/生成器的元信息(函数、类等)。模板存放在templates/${target}/目录,当前为 native/tools/bindings-generator/targets/spidermonkey/templates/。

模板分类(来自 README,结合仓库文件印证)

模板文件用途
prelude.c/prelude.h生成文件的头部
ifunction.c/ifunction.h实例函数的模板
ifunction_overloaded.c重载实例函数实现模板。重载函数与普通函数相同,但内部有一个共享同名函数的数组;当前 SpiderMonkey 实现仅支持参数个数不同的重载
sfunction.c/sfunction.h静态函数模板
sfunction_overloaded.c重载静态函数模板
register.c构造/析构、注册函数与头文件尾部;是最后生成的代码块

除 README 列出的核心模板外,仓库中还有constructor.c/constructor_overloaded.c(构造函数)、ctor.c/ctor_overloaded.c(脚本侧构造)、enum.c(枚举注册)、lambda.cstd::function参数转换)、struct_constructor.c(struct 构造)、public_field.c/public_static_field.c(字段)、layout_head/footreg_types.hapidoc_*(API 文档)等,共同构成完整的绑定生成体系。

模板中的函数命名规范定义在conversions.yamldefinitions段(native/tools/bindings-generator/targets/spidermonkey/conversions.yaml):

definitions: ifunction: "js_${generator.prefix}_${class_name}_${func_name}" sfunction: "js_${generator.prefix}_${class_name}_${func_name}_static" constructor: "js_${generator.prefix}_${class_name}_constructor" ctor: "js_${generator.prefix}_${class_name}_ctor" public_field: "js_${generator.prefix}_${class_name}"

即:实例方法导出名为js_<prefix>_<ClassName>_<funcName>,静态方法追加_static后缀,构造函数为js_<prefix>_<ClassName>_constructor。这也呼应了.iniprefix必须合法且唯一的初衷——所有绑定函数都以它作前缀以避免冲突。

${target}.yaml:类型转换片段

README 指出,${target}.yaml(即conversions.yaml)是整个机制的最后一块拼图:它包含供模板使用的类型转换代码片段。以 SpiderMonkey 为例,这里定义了原生类型与 JS 值互转的例程。该文件主要分四部分:

  1. definitions:上文提到的导出函数命名模板;
  2. native_types:特殊原生类型映射,例如 SpiderMonkey 中bool实际是整数,所以short → int16_tunsigned char → uint8_tchar → int8_tlong long → int64_t,避免直接传bool地址导致转换失败;
  3. ns_map:C++ 命名空间到脚本命名空间的映射,如cc:: → jsb.cc::ui:: → ccui.cc::gfx:: → gfx.se:: → jsb.spine:: → sp.等;
  4. to_native/from_native:JS 值到原生值、原生值到 JS 值的转换代码模板。to_native中可看到大量seval_to_*例程,例如int先转int32_t再窄化、char*先转std::string再取c_str()Vec2/Vec3/Mat4/Size/Color3B/Color4B/Color4F等 Cocos 值类型都有专门转换函数;键名支持@前缀的正则匹配(如@Vector<.*>@map<std::string.*,\s*std::string.*>),NativeType.dict_has_key_re/dict_get_value_re等辅助方法(generator.py#L621-L660)正是为支持这类正则键而存在的。

正是这套"模板 + yaml 转换片段"的架构,让生成器能够用同一套 AST 解析逻辑服务不同的脚本引擎——理论上只需新增targets/<new-vm>/templates/conversions.yaml,即可为新的 VM 生成绑定。

与引擎 JSB 流程的集成(tojs 调用链)

虽然生成器本身是通用工具,但在 Cocos 引擎中它是通过 native/tools/tojs/genbindings.py 接入的。该脚本的关键行为:

  1. 环境检测:从ANDROID_NDK_HOMENDK_ROOT环境变量获取 NDK 路径,从PYTHON_BIN(未设置则用当前解释器)获取 Python 路径(genbindings.py#L21-L47);
  2. 工具链探测:按平台自动定位 NDK 内toolchains/llvm/prebuilt/<platform>-x86_64的 LLVM 路径与 GCC 交叉工具链(genbindings.py#L96-L126);
  3. 生成userconf.ini:把androidndkdirclangllvmdirgcc_toolchain_dircocosdircxxgeneratordir(即tools/bindings-generator)写入native/tools/tojs/userconf.ini(genbindings.py#L132-L144)——这正是generator.py读取的配置文件;
  4. 设置动态库路径:Linux/macOS 下将bindings-generator/libclang加入LD_LIBRARY_PATH,Windows 下加入PATH(genbindings.py#L148-L153);
  5. 调用生成器:对每个 section 执行python generator.py <xxx.ini> -s <section> -t spidermonkey -o <outdir> -n jsb_<xxx>_auto,默认输出到native/cocos/bindings/auto(genbindings.py#L163-L185)。

完整的 JSB 接入流程(创建 ts/cpp 类、配置.ini与 CMakeLists、在jsb_module_register.cpp注册register_all_xxx、在cc.config.jsonmoduleOverrides/NATIVE分组登记native_xxx.jsb.ts等)在 native/tools/tojs/README.mdown 中有逐步图文说明,可以作为本文的延伸阅读。

已知限制

README 明确说明:生成器依赖 clang 获取 C/C++ 代码信息,因此只能获得 clang 能提供的信息。已知不支持的场景是:

  • 可变参数函数(variable number of arguments):生成器会在解析阶段直接跳过(见_process_nodenot cursor.type.is_function_variadic()的过滤),解决方案是手写一个包装函数再进行绑定。

除此之外,从源码还可以推断出另一些边界:重载函数仅支持参数个数不同的情形(SpiderMonkey 目标);无法识别的类型(INVALID_NATIVE_TYPE)会导致包含该类型的函数整体不被导出。

小结

bindings-generator是一套成熟、自洽的"Clang AST → 类型模型 → 模板渲染"三层流水线:.ini决定导出边界,conversions.yaml提供类型转换规则,Cheetah 模板决定输出形态。理解这套机制,无论你是想继续使用它为自己的 C++ 模块生成 JS 绑定,还是想看懂 Cocos 引擎 JSB 绑定代码的来龙去脉,都极具参考价值。相关可继续研读的文件包括:generator.py(核心实现)、conversions.yaml(转换规则)、spidermonkey 模板目录(输出形态)以及 genbindings.py(引擎侧调用入口)。

【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 20:56:28

CentOS 7安装Git全攻略:yum、源码编译与SSH配置详解

前两天帮一台老服务器重装 Git&#xff0c;本来想着几条命令就能搞定的事&#xff0c;结果前前后后折腾了小半个下午。yum 源慢到像拨号上网、编译依赖缺了好几个、装完以后中文文件名还乱码&#xff0c;整个过程基本把 CentOS 7 上装 Git 能踩的坑都踩了一遍。正好趁着这个经历…

作者头像 李华
网站建设 2026/9/15 20:56:20

社交网站建站图解步骤:备案避坑与部署实战指南

社交网站建站图解步骤:备案避坑与部署实战指南 备案流程一头雾水?别慌。很多甲方对接人在启动社交网站建站项目时,最头疼的不是代码怎么写,而是域名解析后网站打不开,或者上传文件报错。其实,只要理清从域名注册到服务器配置的每一步,这些坑都能避开。今天我们就用图解步骤的方式,把社交网站建站的底层逻辑、域名服…

作者头像 李华
网站建设 2026/9/15 20:53:09

ENVI多TIF整合:镶嵌与图层堆叠全流程实操指南

做遥感数据处理的人&#xff0c;十有八九都遇到过同样的问题&#xff1a;手里一堆TIF文件&#xff0c;比如2003年到2011年每年一景&#xff0c;想最终整合成一个完整的TIF&#xff0c;在ENVI里却不知道该点哪个工具&#xff0c;甚至有人把"镶嵌"和"堆叠"混…

作者头像 李华