lua绑定C++对象—luna模板
绑定C++对象到lua,每个类需要写大量的代码,从类的元表创建、方法注册到实例创建,都需要自己重复写类似的代码。
如果涉及N个不同类,会有大量重复的代码,能否创建一个模板类,把这些重复的代码进行简化,通过模板的方式绑定成不同的类?
下面的luna<T>就是完成这样一个壮举,例如针对Car类,只需要luna<Car>::regist(L)即可完成注册。在lua层面 local car = Car()就能自动创建Car对象,然后方便的通过car.xxx()调用成员方法。
代码文件luna.h
1 #include <iostream>
2 #include <cstring>
3 extern "C" {
4 #include <lua.h>
5 #include <lualib.h>
6 #include <lauxlib.h>
7 }
8
9 using namespace std;
10
11 #define DECLARE_LUNA_CLASS(obj)
12 static const char *_className;
13 static luna<obj>::TMethod methods[];
14
15 #define EXPORT_LUNA_FUNCTION_BEGIN(obj)
16 const char* obj::_className = #obj;
17 luna<obj>::TMethod obj::methods[] = {
18
19 //#define EXPORT_LUNA_MEMBER_INT(obj, member)
20 // {#member, nullptr},
21
22 #define EXPORT_LUNA_FUNCTION(obj, func)
23 {#func, &obj::func},
24
25 #define EXPORT_LUNA_FUNCTION_END(obj)
26 {nullptr, nullptr}
27 };
28
29 template<typename T>
30 class luna
31 {
32 public:
33 typedef struct {T* _u;} TObject;
34 typedef int (T::*TPfn)(lua_State* L);
35 typedef struct {const char* _methodName; TPfn pf;} TMethod;
36 public:
37 static int regist(lua_State* L) {
38 //原表Shape
39 if (luaL_newmetatable(L, T::_className))
40 {
41 //注册Shape到全局
42 lua_newtable(L);
43 lua_pushvalue(L, -1);
44 lua_setglobal(L, T::_className);
45
46 //设置Shape的原表,主要是__call,使其看起来更像C++初始化
47 lua_newtable(L);
48 lua_pushcfunction(L, luna<T>::create);
49 lua_setfield(L, -2, "__call");
50 lua_setmetatable(L, -2);
51 lua_pop(L, 1); //这时候栈只剩下元表
52
53 //设置元表Shape index指向自己
54 lua_pushvalue(L, -1);
55 lua_setfield(L, -2, "__index");
56 lua_pushcfunction(L, luna<T>::gc);
57 lua_setfield(L, -2, "__gc");
58 }
59 return 0;
60 }
61 static int create(lua_State* L) {
62 lua_remove(L, 1);
63 TObject* p = (TObject*)lua_newuserdata(L, sizeof(TObject));
64 cout<<"luna<T>::create:"<<T::_className<<endl;
65 p->_u = new T();
66
67 luaL_getmetatable(L, T::_className);
68 lua_setmetatable(L, -2);
69
70 luaL_getmetatable(L, T::_className);
71 for (auto* l = T::methods; l->_methodName; l++)
72 {
73 lua_pushlightuserdata(L,(void*)l);
74 lua_pushlightuserdata(L,(void*)p);
75 lua_pushcclosure(L, luna<T>::call, 2);
76 lua_setfield(L, -2, l->_methodName);
77 }
78
79 lua_pop(L, 1);
80
81 return 1;
82 }
83 static int call(lua_State* L) {
84 TMethod* v = (TMethod*)lua_topointer(L, lua_upvalueindex(1));
85 cout<<"luna<T>::call:"<<v->_methodName<<endl;
86
87 TObject* p = (TObject*)lua_topointer(L, lua_upvalueindex(2));
88
89 return ((p->_u)->*(v->pf))(L);
90 }
91 static int gc(lua_State* L) {
92 TObject* p = (TObject*)lua_touserdata(L, 1);
93 cout<<"luna<T>::gc:"<<T::_className<<endl;
94 (p->_u)->~T();
95 return 0;
96 }
97 };
通过上述代码发现:luna<T>模板类,把一些行为固化下来了。主要改进有几点:
1、通过EXPORT_LUNA_XXX系列宏定义,把每个业务类需要classname和methods列表固化下来,尽可能减少业务层工作量,避免出错。
2、通过模板的方式,抽象出regist、create、call、gc几个公共接口,流程极为简单。所有不同类都遵循这样的原则。其中:
- Luna<T>::regist: 注册T::_className元表和T::_className全局表,成员函数注册到元表,统一通过闭包的方式注册到一个公共的调用函数call进行分发调用。全局表T::_className只保留__call方法,只是为了保留类似local car = Car() 这种类C++的初始化方式。
- Luna<T>::create:在lua层使用local car = Car()创建对象实例时,注册成员函数,并且通过闭包的形式把成员method地址和对象指针都通过pushcclosure绑定在一起
- Luna<T>::call: 当通过car.xxx()调用成员函数时,通过触发call函数,因为闭包的upvalue不同,通过upvalue包含的不同的method信息,也能取到实例句柄,就能触发不同的成员函数调用,相当于统一通过call进行派发。
- Luna<T>::gc:注册元表的__gc方法,当跟对象实例绑定的userdata被gc回收时,会触发gc调用。
整体结构如如下:
代码文件car.cpp
1 #include "luna.h"
2
3 class Car{
4 public:
5 char name[100];
6 int len;
7 //getName()
8 int getName(lua_State *L){
9 lua_pushstring(L, name);
10 return 1;
11 }
12 //setName(char*)
13 int setName(lua_State *L){
14 strcpy(name,lua_tostring(L,-1) );
15 return 0;
16 }
17 //getLen()
18 int getLen(lua_State *L){
19 lua_pushinteger(L, len);
20 return 1;
21 }
22 //setLen(int)
23 int setLen(lua_State *L){
24 len=lua_tointeger(L,-1);
25 return 0;
26 }
27 DECLARE_LUNA_CLASS(Car)
28 };
29
30 EXPORT_LUNA_FUNCTION_BEGIN(Car)
31 EXPORT_LUNA_FUNCTION(Car,getName)
32 EXPORT_LUNA_FUNCTION(Car,setName)
33 EXPORT_LUNA_FUNCTION(Car,getLen)
34 EXPORT_LUNA_FUNCTION(Car,setLen)
35 EXPORT_LUNA_FUNCTION_END(Car)
36
37 extern "C" {
38 //注册 Car 类
39 int luaopen_car(lua_State *L){
40 luna<Car>::regist(L);
41 return 1;
42 }
43 }
编译动态库
g++ -o2 -fPIC -std=c++11 -shared -o car.so car.cpp -llua
代码文件car_test.lua
1 require 'car'
2
3 local car=Car();
4 car.setLen(123);
5 print('car.getLen() = '..car.getLen())
6
7 car.setName("my Car 321")
8 print('car.setName() = '..car.getName())
执行测试
[root@localhost luatest]# lua car_test.lua
luna<T>::create:Car
luna<T>::call:setLen
luna<T>::call:getLen
car.getLen() = 123
luna<T>::call:setName
luna<T>::call:getName
car.setName() = my Car 321
luna<T>::gc:Car
GAME OVER !