找回密码
 立即注册
查看: 488|回复: 12

如何自动生成lua绑定C++的代码?

[复制链接]
发表于 2021-9-8 14:33 | 显示全部楼层 |阅读模式
这里讨论的议题是“自动生成”
虽然to_lua++提供了生成,但是还算不上自动生成,因为需要提供pkg文件。
我看了一下cocos2dx-3.7.1的实现,它是借用了clang一个叫cindex.py的工具去解析C++语法,得到AST,再用Cheetah去生成代码。
我的问题有以下几个:

1. 想了解clang的cindex.py是什么背景,为什么是一个python的实现呢?没有搜索到有用信息(直觉是自己没找到)。

2. 有没有其他也能提供解析C++语法的三方库?(不包括类似于boost.spirit之类的通用语法解析库,需要不用额外工作就直接提供C++ AST的库,如果没有的话,又有点想造轮子了)

3. 通过这种方式来生成lua代码是最优解吗,是否有其他优秀的方案推荐?因为我在实验中发现有些情况还是需要手动修改(所以cocos2dx又出现了一个manual的文件夹.如果想去修改生成流程,还是蛮费力的,必须读懂cindex和generator的逻辑)

最后,我觉得C++搭配lua是一件吃力不讨好的事情。。。不知道各位怎么看。。。但是手游热更新貌似也就这一个方案了。。。

=========================================================================

补充一下:刚发现,其实神奇的地方在libclang.dll这个动态库里,这里提供了很多API用于语法分析,相信cindex.py的作者已经非常了解clang的细节了。如果努力啃一下clang的reference,应该也可以轻松写出一个C++语法分析器。
发表于 2021-9-8 14:39 | 显示全部楼层
一种方案:静态反射 + lua wrapper
wrapper 能将 c++ 类的变量和函数注册到lua中,但需要手动为各个类注册其每一个变量和函数。
静态反射提供了各c++类的统一接口。
结合两相之力,我们只需要写一个模板函数,内部根据类的静态反射信息,利用wrapper将其注册到lua中。
具体地,我选择的库为
    静态反射:USRefllua wrapper:sol3
详细请参考
C++ 自动绑定 Lua
发表于 2021-9-8 14:44 | 显示全部楼层
Lua 的mail list 有过这个的讨论,一半的人支持template,type safe的binding module, 另外一半坚持手写。

个人推荐手写,binding会随着你的框架规模的增加,binding的过程中也要不断思考究竟给user提供哪些API。

做Lua binding 重要的事情是管理好对象/closure的生命周期,分清userdata/lightuserdata。做不好/误用就会弄出一堆性能瓶颈。
 楼主| 发表于 2021-9-8 14:45 | 显示全部楼层
1. 这个py文件就是LLVM中拿出来的,是libclang的一个python绑定,利用的python的ffi机制(ctypes)写的,你可以在LLVM源码里找到它。
2. 这应该所有c++编译器的前端都有这个功能,比如这个简单点的robertoraggi/cplusplus · GitHub,atomic engine就拿它写了个绑定生成工具。
3. luabind,swig等等轮子好像挺多的,符合自己的需求就行。
发表于 2021-9-8 14:46 | 显示全部楼层
谢邀
tolua++的pkg文件支持直接解析C++源码的, 你可以试下, 他那个已经是差不多比较简单的直接解析的方案了

我用luabridge配合一个配置文件, 自动生成绑定, 只要给出要绑定的名字, 就可以绑定了, 已经很简单了

不推荐SWIG, 效率低, 游戏大多还是用tolua++的绑定

反观, 题主想全自动绑定, 其实有时未必是这样的需求, 有些函数不需要注入到lua中, 所以DSL还是主流做法
发表于 2021-9-8 14:48 | 显示全部楼层
class Module {
public:
      Module(State& state) {
           state << global << key("Func") << state.Adapt(Wrap(this, &Module::Func)) << endtable;
      }
      void Func(State& request, int a, std::string b, double c) {
              cout << a << b << c;
      }
};
使用:
ZLuaState state;
Module module(state);
Ref ref = state.Load("Func(1, 'abc', 3.0)");
state.Call(ref);


这是我自己写的一个简单地将C++成员函数注册到lua state的库,只需要使用state.Adapt(Wrap(this, 成员函数地址))就可以直接注册。所有的参数都是利用变长参数模板自动解析的。如果仅仅做“绑定函数到LUA“这么简单的事情的话,自己搞个PL其实过于麻烦了。

打算近期把这个库发到github上,无奈访问不了。。
发表于 2021-9-8 14:51 | 显示全部楼层
cocos 使用bindings-generator脚本代替了toLua++. 编写效率大大提高。
具体的在本机中分享:http://note.youdao.com/noteshare?id=0f4132271151c4b62f9afb712e8304d9

bindings-generator脚本的工作机制是:
1、不用挨个类地写桥接.pkg和.h文件了,直接定义一个ini文件,告诉脚本哪些类的哪些方法要暴露出来,注册到Lua环境里的模块名是什么
2、摸清了toLua++工具的生成方法,改由Python脚本动态分析C++类,自动生成桥接的.h和.cpp代码,不调用tolua++命令了
3、虽然不再调用tolua++命令了,但是底层仍然使用toLua++的库函数,比如tolua_function,bindings-generator脚本生成的代码就跟使用toLua++工具生成的几乎一样

bindings-generator脚本掌握了生成toLua++桥接代码的主动权,不仅可以省下大量的.pkg和.h文件,而且可以更好地插入自定义代码,达到cocos2d-x环境下的一些特殊目的,比如内存回收之类的。
接下来说怎么用bindings-generator脚本:
1、写自己的C++类,按照cocos2d-x的规矩,继承cocos2d::Ref类,以便使用cocos2d-x的内存回收机制。
2、编写一个.ini文件,让bindings-generator可以根据这个配置文件知道C++类该怎么暴露出来
3、修改bindings-generator脚本,让它去读取这个.ini文件
4、执行bindings-generator脚本,生成桥接C++类方法
5、将自定义的C++类和生成的桥接文件加入工程,不然编译不到
6、修改AppDelegate.cpp,执行桥接方法,自定义的C++类就注册进Lua环境里了

看着步骤挺多,其实都狠简单。下面一步一步来。
1.首先是自定义的C++类。我习惯将文件保存在frameworks/runtime-src/Classes/目录下:
frameworks/runtime-src/Classes/MyClass.h
#include "cocos2d.h"usingnamespace cocos2d;

class MyClass : public Ref
{
public:
  MyClass()   {};
  ~MyClass()  {};
  boolinit(){ returntrue; };
  CREATE_FUNC(MyClass);

  intfoo(int i);
};
frameworks/runtime-src/Classes/MyClass.cpp
#include "MyClass.h"int MyClass::foo(int i)
{
  return i + 100;
}
2.然后编写.ini文件。在frameworks/cocos2d-x/tools/tolua/目录下能看到genbindings.py脚本和一大堆.ini文件,这些就是bindings-generator的实际执行环境了。随便找一个内容比较少的.ini文件,复制一份,重新命名为MyClass.ini。大部分内容都可以凑合不需要改,这里仅列出必须要改的重要部分:
frameworks/cocos2d-x/tools/tolua/MyClass.ini
[MyClass]prefix = MyClasstarget_namespace = myheaders = %(cocosdir)s/../runtime-src/Classes/MyClass.hclasses = MyClass
也即在MyClass.ini中指定MyClass.h文件的位置,指定要暴露出来的类,指定注册进Lua环境的模块名。
然后修改genbindings.pyMyClass.ini文件加进去:
3.frameworks/cocos2d-x/tools/tolua/genbindings.py
cmd_args = {'cocos2dx.ini' : ('cocos2d-x', 'lua_cocos2dx_auto'), \
            'MyClass.ini' : ('MyClass', 'lua_MyClass_auto'), \
            ...4.至此,生成桥接文件的准备工作就做好了,执行genbindings.py脚本:
python ./genbindings.py成功执行genbindings.py脚本后,会在frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目录下看到新生成的文件:
每次执行genbindings.py脚本时间都挺长的,因为它要重新处理一遍所有的.ini文件,建议大胆修改脚本文件,灵活处理,让它每次只处理需要的.ini文件就可以了,比如像这个样子:
在frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目录下观察一下生成的C++桥接文件lua_MyClass_auto.cpp,里面的注册函数名字为register_all_MyClass(),这就是将MyClass类注册进Lua环境的关键函数:
5.编辑frameworks/runtime-src/Classes/AppDelegate.cpp文件,首先在文件头加入对lua_MyClass_auto.hpp文件的引用:
然后在正确的代码位置加入对register_all_MyClass函数的调用:
如何是lua工程则在:lua_module_register.h 中添加上述调用。
最后在执行编译前,将新加入的这几个C++文件都加入到Xcode工程中,使得编译环境知道它们的存在:
这其中还有一个小坑,由于lua_MyClass_auto.cpp文件要引用MyClass.h文件,而这俩文件分属于不同的子项目,互相不认识头文件的搜寻路径,因此需要手工修改一下cocos2d_lua_bindings.xcodeproj子项目的User Header Search Paths配置。特别注意一共有几个../:
最后,就可以用cocos compile -p mac命令重新编译整个项目了,不出意外的话编译一定是成功的。
修改main.lua文件中,尝试调用一下MyClass类:
localtest = my.MyClass:create()
print("lua bind: " .. test:foo(99))6.android上运行的话需要做的事情是要将生成的桥接文件lua_MyClass_auto.cpp放到android.mk中。
配置ini时需要注意的选项:
    [title]:要配置将被使用的工具/ tolua的/ gengindings.py脚本的称号。一般来说,标题可以是文件名。
    prefix:要配置一个函数名的前缀,通常,我们还可以使用文件名作为前缀 生成函数一次为前缀。
    target_namespace:要配置在脚本层模块的名字。在这里,我们使用cc作为模块名,当你想在脚本层REF的名称,您必须将一个名为前缀,CC在名称的前面。例如,CustomClass可以参考作为cc.CustomClass。
    headers:要配置所有需要解析的头文件和%(cocosdir)s是的Cocos2d-x的引擎的根路径。
    classes:要配置所有绑定所需的类。在这里,它支持正则表达式。因此,我们可以设置MyCustomClass。*在这里,用于查找多个特定的用法,你可以对照到tools/tolua/cocos2dx.ini。
    skip:要配置需要被忽略的功能。现在绑定发电机无法解析的void *类型,并委托类型,所以这些类型的需要进行手动绑定。而在这种情况下,你应该忽略所有这些类型,然后再手动将它们绑定。你可以对照到配置文件路径下的cocos/scripting/lua-bindings/auto 。
    rename_functions:要配置的功能需要在脚本层进行重命名。由于某些原因,开发者希望更多的脚本友好的API,所以配置选项就是为了这个目的。
    rename_classes:不在使用。
    remove_prefix:不在使用。
    base_classes_to_skip =      #当被它们的子类发现的时候会跳过的基类
    classes_have_no_parents:要配置是过滤器所需要的父类。这个选项是很少修改。
    abstract_classes:要配置的公共构造并不需要导出的类。
    script_control_cpp:是的。要配置脚本层是否管理对象的生命周期。如果没有,那么C++层关心他们的生命周期。
    现在,它是不完善的,以控制原生对象的续航时间在脚本层。所以,你可以简单地把它设置为no。
有几篇值得参考的笔记。都是本人使用过且自动生成成功的经验总结。
文章编辑格式copy自己的有道云笔记,格式太乱。但是笔记可以以看得。如下笔记讲述的是原理和过程。
http://note.youdao.com/noteshare?id=142d2127d439b820f0aeeaf28631d0f5
http://note.youdao.com/noteshare?id=1d2703cd69de404e7237c44097696e02
发表于 2021-9-8 15:00 | 显示全部楼层
luajit的ffi库,可以在lua直接调用c导出函数
发表于 2021-9-8 15:03 | 显示全部楼层
虽然讨论的是自动生成,但是本人用的是手动导出,前后花了2周多把cocos2dx的主要常用接口一个个导出,剩下的接口如果项目要用到再慢慢增加。维护到现在一共有6千多行C++导出成LUA的代码。性能已经优化到极致,为什么这么说?看下面的导出代码就知道
int lua_ccNode_setPosition(lua_State*L){
    auto self = LUA_GET_SELF(ccNode);
    auto x = LUA_GET_NUMBER();
    auto y = LUA_GET_NUMBER();
    self->setPosition(x, y);
    return 0;
}
LUA_GET_SELF是一个宏,用lua_topointer把lua的lightuserdata强转ccNode;LUA_GET_NUMBER直接用的是luaL_checknumber,然后每次调用这些宏的时候,把numArg自增1就可以少掉一些麻烦。如果你还能想到比这个性能更高的方案,请一定要告诉我。
手动导出有更强的可控性和灵活性,虽然会增加出错的概率,时间上估计和自动导出没差多少。
现在这套方案没有像cocoslua那么使用方便,cocoslua只要导出就可以使用。这套方案还需要到lua层进行二次封装,我现在的做法是创一个table,[0]保存c++对象的指针,调用方法时把[0]传进去。虽然麻烦了点,但是性能已经不是cocoslua能比的了!
发表于 2021-9-8 15:04 | 显示全部楼层
最近在调研SWIG, 号称是完全兼容ANSI C++ 的语法解析, 并且target language很丰富。需要输入一个接口文件。不过通常都是直接include head.hxx 了事。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-2-25 21:07 , Processed in 0.117983 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表