感谢

Lua绑定,指的是C++类暴露给Lua,在Lua中直接可使用。这需要借助脚本工具生成绑定代码,并注册。

一、相关文件

下面是Lua binding时需要用到的相关文件。

  1. # 当前目录是cocos2d-x引擎目录
  2. cocos/
  3. scripting/ // 引擎的脚本部分:lua/js代码、lua/js的binding代码(C++)
  4. js-bindings/ // js的C++绑定代码
  5. lua-bindings/ // lua的C++绑定代码、预置的lua代码
  6. auto/ // 这个是根据下面tolua/*.ini的预置配置,生成的cocos引擎部分的绑定代码
  7. lua_cocos2dx_auto.hpp
  8. lua_cocos2dx_auto.cpp
  9. manual/ // 这个是手写的绑定代码,不是通过ini来配置了,而是从下面的C++代码中修改了。
  10. lua_module_register.h // 手动配置,需要加载的绑定代码
  11. lua_module_register.cpp
  12. ...
  13. proj.***/ // 这些绑定代码在不同平台的编译工程
  14. script/ // 这个是引擎预置的lua代码
  15. tools/ // 预生成工具
  16. bindings-generator/ // js/lua绑定的自动化工具
  17. generator.py // 执行这个脚本,将生成js、lua的C++绑定代码。
  18. README.md // 说明文件,描述该工具的Running Requirments、Usage。
  19. test/ // 这个是简单示范例子,生成的是js绑定代码
  20. tolua/ // 用于生成cocos2dx项目的lua绑定代码的工具,实质是调用上面bindings-generator
  21. ***.ini // 这些是预置的配置文件,用于生成cocos引擎部分的的lua绑定代码。
  22. genbindings.py // 读取上面的配置文件,自动生成lua绑定代码。
  23. README.md // bindings-generator的Running Requirments。

通过脚本bindings-generator自动生成绑定代码的大致过程如下:

  • 1、配置好bindings-generator的Running Requirements。
  • 2、编写ini配置文件
    • 告诉脚本,需要生成哪些类的绑定代码。可以参考预置的ini来写。
  • 3、配置并执行genbindings.py
    • 设置ini配置的读取路径、绑定代码的输出路径。
    • 实际上就是调用bindings-generators脚本,传入的参数就是ini中设置的。
  • 4、注册绑定
    • 在绑定代码的hpp文件中都有一个registerall*方法,在合适的地方调用,即可正式生效。
  • 5、平台编译

    • 修改lua-bindings/目录下各平台的编译配置
    • windows平台,在VS project中添加新生成的hpp/cpp文件即可。
    • android平台,修改Android.mk
    • ……

      二、自动绑定

      1、配置bindings-generator

      最新配置方法可参见上面目录中对应的文件。这里列举其中一个版本的要求:
  • Windows

  • cocos2d-x 3.17.2

  • 1、安装Android NDK

    • android-ndk-r16 or later
  • 2、安装python
  • 3、安装pyyaml
  • 4、安装Cheetah
  • 5、设置环境变量NDK_ROOT、PYTHON_BIN

    2、编写ini配置

    ```
#
cocos2dx_spine : 就是下面genbindings.py里的args[0]
#

[cocos2dx_spine]

#
prefix : 生成的绑定代码的文件名为:lua_prefix_auto.cpp
#

prefix = cocos2dx_spine

#
比如绑定了类Fuck,Bitch,Shit,在lua中就这么访问:sp.Fuck、sp.Bitch、sp.Shit
也可以为空,那就不需要前缀了。

#

1、C++中必须对应也定义在一个namespace中,如果没有,则报错conversion wasn’t set
2、需要在文件conversions.yaml的ns_map中配置好C++的namespace到Lua的namspace的映射。
文件路径:bindings-generator\targets\lua\

#

#

target_namespace = sp

androidheaders = androidflags = -target armv7-none-linux-androideabi -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -DANDROID -D__ANDROID_API=14 -gcc-toolchain %(gcc_toolchain_dir)s —sysroot=%(androidndkdir)s/platforms/android-14/arch-arm -idirafter %(androidndkdir)s/sources/android/support/include -idirafter %(androidndkdir)s/sysroot/usr/include -idirafter %(androidndkdir)s/sysroot/usr/include/arm-linux-androideabi -idirafter %(clangllvmdir)s/lib64/clang/5.0/include -I%(androidndkdir)s/sources/cxx-stl/llvm-libc++/include clang_headers =

#
clang 标签,其中可以添加预编译宏。
#

clangflags = -nostdinc -x c++ -std=c++11 -fsigned-char -U_SSE

#
下面headers文件的搜索路径
#

cocos_headers = -I%(cocosdir)s/cocos -I%(cocosdir)s/cocos/editor-support -I%(cocosdir)s/cocos/platform/android -I%(cocosdir)s/external cocos_flags = -DANDROID cxxgenerator_headers =

#
extra arguments for clang
#

extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s

#
要生成绑定代码的类的头文件,空格隔开。
#

headers = %(cocosdir)s/cocos/editor-support/spine/spine-cocos2dx.h

#
绑定代码需要包含但是不需要被绑定工具扫描的头文件列表。
#

cpp_headers =

#
需要生成绑定代码的类,可以使用正则表达式,比如Menu,在脚本内部将会变成这样”^Menu$”
#

classes = SkeletonRenderer SkeletonAnimation

#
需要在脚本层被继承的类列表,以空格分隔。
#

classes_need_extend =

#
指出不绑定的函数
格式:ClassName::[func1 func2]
类名可以是正则表达式,比如Menu,在脚本内部将会变成这样”^Menu$”
函数名可以是正则表达式,没有上面的^$情况
ClassName::[*],跳过这个类的所有函数
*::[update],跳过所有类的update函数。
#

skip = SkeletonRenderer::[findBone findSlot getAttachment setAttachment update draw createWithData], *::[update draw drawSkeleton], SkeletonAnimation::[setAnimationStateData createWithData addAnimation getCurrent setAnimation onAnimationStateEvent onTrackEntryEvent getState createWithFile]

#
需要被重命名的函数,会将 C++ 中的函数绑定为指定名字的脚本函数,
格式为 ClassName::[cppFunctionName=scriptFunctionName …],不同的类以逗号分隔。
#

rename_functions =

#
需要被重命名的类,会将 C++ 中的类名绑定为指定的脚本类名,
格式为 CppClassName::ScriptClassName,以逗号分割。
#

rename_classes =

#
for all class names, should we remove something when registering in the target VM?
#

remove_prefix =

#
classes for which there will be no “parent” lookup
没有父类的类列表,以空格分隔。
#

classes_have_no_parents =

#
base classes which will be skipped when their sub-classes found them.
#

base_classes_to_skip =

#
classes that create no constructor
Set is special and we will use a hand-written constructor
没有构造函数的类列表,以空格分隔。
#

abstract_classes = Skeleton SkeletonAnimation

#
Determining whether to use script object(js object) to control the lifecycle
of native(cpp) object or the other way around.

#

Supported values are ‘yes’ or ‘no’.
#

script_control_cpp = no

  1. <a name="ZrSgV"></a>
  2. ## 3、配置genbindings.py
  3. genbindings.py是对bindings-generator的再包装,用于生成cocos2d-x项目的lua绑定代码。
  4. ```python
  5. #!/usr/bin/python
  6. # This script is used to generate luabinding glue codes.
  7. # Android ndk version must be ndk-r9b.
  8. ......;
  9. def main():
  10. ......
  11. #####################################################################################################
  12. #####
  13. ##### 在以下目录寻找llvm toolchain
  14. #####
  15. ##### llvm_toolchain_path = ndk_root + '/toolchains/llvm/prebuilt/' + platform + "-x86_64"
  16. ##### llvm_toolchain_path = ndk_root + '/toolchains/llvm/prebuilt/' + platform
  17. ##### llvm_toolchain_path = ndk_root + '/toolchains/llvm/prebuilt/' + platform + "-x86"
  18. #####
  19. ##### platform = windows/linux/darwin
  20. #####
  21. #####################################################################################################
  22. x86_llvm_path = ""
  23. x64_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm/prebuilt', '%s-%s' % (cur_platform, 'x86_64')))
  24. if not os.path.exists(x64_llvm_path):
  25. x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm/prebuilt', '%s' % (cur_platform)))
  26. if not os.path.exists(x86_llvm_path):
  27. x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm/prebuilt', '%s-%s' % (cur_platform, 'x86')))
  28. if os.path.isdir(x64_llvm_path):
  29. llvm_path = x64_llvm_path
  30. elif os.path.isdir(x86_llvm_path):
  31. llvm_path = x86_llvm_path
  32. else:
  33. print 'llvm toolchain not found!'
  34. print 'path: %s or path: %s are not valid! ' % (x86_llvm_path, x64_llvm_path)
  35. sys.exit(1)
  36. #####################################################################################################
  37. #####
  38. ##### 在以下目录寻找gcc toolchain
  39. #####
  40. ##### gcc_toolchain_path = ndk_root + '/toolchains/arm-linux-androideabi-4.9/prebuilt' + platform + "-x86_64"
  41. ##### gcc_toolchain_path = ndk_root + '/toolchains/arm-linux-androideabi-4.9/prebuilt' + platform
  42. ##### gcc_toolchain_path = ndk_root + '/toolchains/arm-linux-androideabi-4.9/prebuilt' + platform + "-x86"
  43. #####
  44. #####################################################################################################
  45. x86_gcc_toolchain_path = ""
  46. x64_gcc_toolchain_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/arm-linux-androideabi-4.9/prebuilt', '%s-%s' % (cur_platform, 'x86_64')))
  47. if not os.path.exists(x64_gcc_toolchain_path):
  48. x86_gcc_toolchain_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/arm-linux-androideabi-4.9/prebuilt', '%s' % (cur_platform)))
  49. if not os.path.exists(x86_gcc_toolchain_path):
  50. x86_gcc_toolchain_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/arm-linux-androideabi-4.9/prebuilt', '%s-%s' % (cur_platform, 'x86')))
  51. if os.path.isdir(x64_gcc_toolchain_path):
  52. gcc_toolchain_path = x64_gcc_toolchain_path
  53. elif os.path.isdir(x86_gcc_toolchain_path):
  54. gcc_toolchain_path = x86_gcc_toolchain_path
  55. else:
  56. print 'gcc toolchain not found!'
  57. print 'path: %s or path: %s are not valid! ' % (x64_gcc_toolchain_path, x86_gcc_toolchain_path)
  58. sys.exit(1)
  59. #####################################################################################################
  60. #####
  61. ##### 设置项目目录
  62. ##### project_root = cocos_root = cocos引擎目录
  63. #####
  64. ##### 设置bindings-generator的目录
  65. ##### cxx_generator_root = project_root + '/tools/bindings-generator'
  66. #####
  67. #####################################################################################################
  68. project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
  69. cocos_root = os.path.abspath(os.path.join(project_root, ''))
  70. cxx_generator_root = os.path.abspath(os.path.join(project_root, 'tools/bindings-generator'))
  71. #####################################################################################################
  72. #####
  73. ##### 这些目录会保存在和当前文件相同目录的userconf.ini中。对应关系如下:
  74. ##### androidndkdir : ndk_root
  75. ##### clangllvmdir : llvm_toolchain_path
  76. ##### gcc_toolchain_dir : gcc_toolchain_path
  77. ##### cocosdir : cocos_root
  78. ##### cxxgeneratordir : bindings-generator
  79. #####
  80. #####################################################################################################
  81. config = ConfigParser.ConfigParser()
  82. config.set('DEFAULT', 'androidndkdir', ndk_root)
  83. config.set('DEFAULT', 'clangllvmdir', llvm_path)
  84. config.set('DEFAULT', 'gcc_toolchain_dir', gcc_toolchain_path)
  85. config.set('DEFAULT', 'cocosdir', cocos_root)
  86. config.set('DEFAULT', 'cxxgeneratordir', cxx_generator_root)
  87. config.set('DEFAULT', 'extra_flags', '')
  88. conf_ini_file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'userconf.ini'))
  89. print 'generating userconf.ini...'
  90. with open(conf_ini_file, 'w') as configfile:
  91. config.write(configfile)
  92. #####################################################################################################
  93. #####
  94. ##### 将bindings-generator目录下的\libclang、\tools\win32添加到 PATH 环境变量中。
  95. #####
  96. #####################################################################################################
  97. if 'linux' in platform or platform == 'darwin':
  98. os.putenv('LD_LIBRARY_PATH', '%s/libclang' % cxx_generator_root)
  99. if platform == 'win32':
  100. path_env = os.environ['PATH']
  101. os.putenv('PATH', r'%s;%s\libclang;%s\tools\win32;' % (path_env, cxx_generator_root, cxx_generator_root))
  102. try:
  103. #####################################################################################################
  104. #####
  105. ##### 下面这些ini配置文件所在的目录:tolua_root = project_root + "/tools/tolua/"
  106. ##### 生成的C++绑定代码的输出目录,output_dir = project_root + "/cocos/scripting/lua-bindings/auto/"
  107. #####
  108. #####################################################################################################
  109. tolua_root = '%s/tools/tolua' % project_root
  110. output_dir = '%s/cocos/scripting/lua-bindings/auto' % project_root
  111. #####################################################################################################
  112. #####
  113. ##### key : 'cocos2dx.ini' : tolua_root目录下的配置文件名
  114. ##### args[0] : 'cocos2d-x' : 指定需要生成绑定代码的section,见ini配置文件中的解释
  115. ##### args[1] : 'lua_cocos2dx_auto' : 输出的绑定代码的文件名
  116. #####
  117. ##### 成功,输出日志:Generating lua bindings succeeds.
  118. ##### 失败,输出日志:Generating lua bindings fails.
  119. #####
  120. #####################################################################################################
  121. cmd_args = {'cocos2dx.ini' : ('cocos2d-x', 'lua_cocos2dx_auto'), \
  122. 'cocos2dx_extension.ini' : ('cocos2dx_extension', 'lua_cocos2dx_extension_auto'), \
  123. 'cocos2dx_ui.ini' : ('cocos2dx_ui', 'lua_cocos2dx_ui_auto'), \
  124. 'cocos2dx_studio.ini' : ('cocos2dx_studio', 'lua_cocos2dx_studio_auto'), \
  125. 'cocos2dx_spine.ini' : ('cocos2dx_spine', 'lua_cocos2dx_spine_auto'), \
  126. 'cocos2dx_physics.ini' : ('cocos2dx_physics', 'lua_cocos2dx_physics_auto'), \
  127. 'cocos2dx_experimental_video.ini' : ('cocos2dx_experimental_video', 'lua_cocos2dx_experimental_video_auto'), \
  128. 'cocos2dx_experimental.ini' : ('cocos2dx_experimental', 'lua_cocos2dx_experimental_auto'), \
  129. 'cocos2dx_controller.ini' : ('cocos2dx_controller', 'lua_cocos2dx_controller_auto'), \
  130. 'cocos2dx_cocosbuilder.ini': ('cocos2dx_cocosbuilder', 'lua_cocos2dx_cocosbuilder_auto'), \
  131. 'cocos2dx_cocosdenshion.ini': ('cocos2dx_cocosdenshion', 'lua_cocos2dx_cocosdenshion_auto'), \
  132. 'cocos2dx_3d.ini': ('cocos2dx_3d', 'lua_cocos2dx_3d_auto'), \
  133. 'cocos2dx_audioengine.ini': ('cocos2dx_audioengine', 'lua_cocos2dx_audioengine_auto'), \
  134. 'cocos2dx_csloader.ini' : ('cocos2dx_csloader', 'lua_cocos2dx_csloader_auto'), \
  135. 'cocos2dx_experimental_webview.ini' : ('cocos2dx_experimental_webview', 'lua_cocos2dx_experimental_webview_auto'), \
  136. 'cocos2dx_physics3d.ini' : ('cocos2dx_physics3d', 'lua_cocos2dx_physics3d_auto'), \
  137. 'cocos2dx_navmesh.ini' : ('cocos2dx_navmesh', 'lua_cocos2dx_navmesh_auto'), \
  138. }
  139. target = 'lua'
  140. generator_py = '%s/generator.py' % cxx_generator_root
  141. for key in cmd_args.keys():
  142. args = cmd_args[key]
  143. cfg = '%s/%s' % (tolua_root, key)
  144. print 'Generating bindings for %s...' % (key[:-4])
  145. command = '%s %s %s -s %s -t %s -o %s -n %s' % (python_bin, generator_py, cfg, args[0], target, output_dir, args[1])
  146. _run_cmd(command)
  147. print '---------------------------------'
  148. print 'Generating lua bindings succeeds.'
  149. print '---------------------------------'
  150. except Exception as e:
  151. if e.__class__.__name__ == 'CmdError':
  152. print '---------------------------------'
  153. print 'Generating lua bindings fails.'
  154. print '---------------------------------'
  155. sys.exit(1)
  156. else:
  157. raise
  158. # -------------- main --------------
  159. if __name__ == '__main__':
  160. main()

4、注册绑定

  1. // 比如生成的绑定代码文件如下:
  2. // lua_MotherFucker_auto.hpp
  3. // lua_MotherFucker_auto.cpp
  4. # include "../lua_MotherFucker_auto.hpp"
  5. register_all_MotherFucker(_state);
  6. auto engine = LuaEngine::getInstance();
  7. ScriptEngineManager::getInstance()->setScriptEngine( engine );
  8. lua_State* L = engine->getLuaStack()->getLuaState();
  9. register_all_MotherFucker( L ); // 加载绑定代码。跟引擎部分的注册放一块也可以,见CCLuaStack
  10. // LuaStack* stack = engine->getLuaStack();
  11. // stack->setXXTEAKeyAndSign( "2dxLua", strlen( "2dxLua" ), "XXTEA", strlen( "XXTEA" ) );

三、手动绑定

虽然 Bindings Generator 已经非常强大,但是它依然有其局限性。目前的 Bindings Generator 的局限性主要是以下的几点:

  1. 只能够针对类生成绑定,不可以绑定结构体,独立函数等
  2. 不能够生成 Delegate 类型的 API,因为脚本中的对象是无法继承 C++ 中的 Delegate 类并重写其中的 Delegate 函数的。
  3. 子类中重写了父类的 API 的同时,又重载了这个 API。
  4. 部分 API 实现内容并没有完全体现在其 API 定义中。
  5. 在运行时由 C++ 主动调用的 API。

前三种情况,可以手动编写绑定代码完成。

手写绑定教程见Lua知识部分。
Lua_C API(C与Lua的交互)