浅析C++绑定到Lua的方法

本文探讨了如何将C++对象的方法绑定到Lua,以满足在实际项目中扩展和修改第三方库的需求,包括获取和设置对象成员变量、处理类继承等功能。
摘要由CSDN通过智能技术生成
注:原文也在公司内部论坛上发了 
概述
      虽然将C++对象绑定到Lua已经有 tolua++(Cocos2d-x 3.0用的就是这个)、 LuaBridge(我们游戏客户端对这个库进行了改进)和 luabind等各种库可以直接使用了(lua-users.org上有对各种语言绑定到lua库的 汇总),但弄清楚C++对象绑定到Lua的常见方法,不但有助于更深的了解Lua的机制,还可以方便修改第三方库以满足实际项目需求。本文通过分析第三方库 Lunar(我们游戏服务端用的是 Luna,Lunar是Luna增加版,但仍然足够简洁)的实现,来理解C++对象绑定到Lua的通常方法。 Lunar的测试代码放在我的github上。
 
Lunar实现的功能以及原理
       Lunar实现非常简洁,同时实现了C++绑定到Lua主要功能。使用Lunar至少可以做到以下几点:
       1、在脚本中,可以使用注册过的类创建C++对象,此类C++对象,由Lua控制何时释放。
       2、在C++创建的对象,可以压入栈中,供脚本使用这个对象,并且提供一个可选参数,来决定这个对象是由C++控制释放,还是Lua控制释放。
       3、脚本中的C++类对象,可以调用注册过的类成员函数。
       4、在C++中,可以获取在脚本中创建的对象,并且在C++中可以调用这个对象的成员函数。
       5、可以在脚本中定义对象的成员函数,并且能在C++中调用这些用脚本实现的成员函数。
 
       在脚本中创建C++对象,实质返回给脚本是一个userdata类型的值ud,ud中保存一个指针,该指针指向所创建的C++对象。这时候Lua控制对象何时释放,即在Lua在回收userdata时,同时利用userdata的元表__gc字段来回收动态分配的对象, 这时候就不需要C++释放内存了。注意这里返回给脚本不能是lightuserdata,因为lightuserdata实质上只是一个指针,不受垃圾回收收集器的管理,并且它也没有元表。
 
       在脚本中创建的对象是由Lua来维护对象的生命周期。在Lunar中还可以使用Lunar<T>::push(lua_State *L, T *obj, bool gc=false)向栈中压入对象供脚本使用,其中第三个参数可以决定创建的对象是由C++控制释放,还是Lua控制释放。 其原理是在把要传递给Lua的对象obj压入栈时,它首先利用对象地址在lookup表中查找(lookup是一个mode为"v"的weak table,保存所有在脚本中使用的对象,该表的key是对象地址,value是对象对应的userdata),若不在lookup中,则会创建一个新的userdata,并把它保存在lookup中,若第三个参数为false,即由C++控制对象释放,还会把上面的userdata保存在一个nottrash表中,nottrash是一个mode为"k"的weak table,保存所有不会随userdata回收其相应对象也释放的userdata,该表key为userdata,value为true。这样处理后,在Lua回收userdata时,首先检测userdata是否在nottrash中,若不在,则删除userdata所指向对象,否则需要C++自己释放所创建的对象。
 
       在调用Lunar<T>::Register(lua_State *L)向脚本注册类时,会创建两个表,一个是methods表,该表的key为函数名,value为函数地址(函数可以是C++中定义类的方法,也可以在Lua中定义函数),在Lua中调用的对象方法,都保存在该表中;另一个是metatable表,做为userdata的元表,该metatable表保存了上面的lookup表和nottrash表,并且设置了metatable.__metatable = methods,这样在脚本中就隐藏了metatable表,也就是说在Lua中,调用getmetatable(userdata)得到的是methods表,而不是metatable表,在脚本中,给对象添加成员方法也是会保存在methods表中。
 
Lunar源码分析
      下面逐行分析了Luanr的实现,在附件中是Lunar的测试代码,如下:
001 extern "C" {
002     #include "lua.h"
003     #include "lauxlib.h"
004 }
005  
006 #define LUNAR_DECLARE_METHOD(Class, Name) {#Name, &Class::Name}
007  
008 template <typename T> class Lunar {
009     public:
010  
011     //C++可以向Lua注册的函数类型
012     typedef int (T::*mfp)(lua_State *L);   
013  
014     //向Lua中注册的函数名字以及对应的函数地址
015     typedef struct const char *name; mfp mfunc; } RegType;   
016  
017     //用来注册C++定义的类供Lua使用
018     static void Register(lua_State *L) {
019         //创建method table,该table key为函数名,
020         //value为函数地址(函数可以是C++中定义类的方法,也可以在Lua中定义函数)
021         //在Lua中,以table的key为函数名,调用相应的方法。
022         lua_newtable(L);
023         int methods = lua_gettop(L);
024  
025         //创建userdata的元表
026         luaL_newmetatable(L, T::className);
027         int metatable = lua_gettop(L);
028  
029         //把method table注册到全局表中,这样在Lua中可以直接使用该table,
030         //这样可以在这个table中增加Lua实现的函数
031         lua_pushvalue(L, methods);
032         set(L, LUA_GLOBALSINDEX, T::className);
033  
034         //隐藏userdata的实质的元表,也就是说在Lua中
035         //调用getmetatable(userdata)得到的是methods table,而不是metatable table
036         lua_pushvalue(L, methods);
037         set(L, metatable, "__metatable");
038  
039         //设置metatable table的元方法
040         lua_pushvalue(L, methods);
041         set(L, metatable, "__index");
042  
043         lua_pushcfunction(L, tostring_T);
044         set(L, metatable, "__tostring");
045      
046         //设置__gc元方法,这样方便在Lua回收userdata时,
047         //可以做一些其他操作,比如释放其相应的对象
048         lua_pushcfunction(L, gc_T);
049         set(L, metatable, "__gc");
050  
051         lua_newtable(L);                //创建methods的元表mt
052         lua_pushcfunction(L, new_T);
053         lua_pushvalue(L, -1);           // 把new_T再次压入栈顶
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值