lua绑定C++对象—luna模板

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 !

参考:https://www.cnblogs.com/liao0001/p/9791495.html (需修改模板)

原文地址:https://www.cnblogs.com/lzpong/p/13427120.html