liao0001

在系列文章二三中描述的绑定C++对象基础篇和进阶篇,都有一个很大的问题,就是每个类需要写大量的代码,从类的元表创建、方法注册到实例创建,都需要自己重复写类似的代码。如果涉及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 *name;\
 13     static luna<obj>::TMethod methods[];
 14 
 15 #define EXPORT_LUNA_FUNCTION_BEGIN(obj) \
 16     const char* obj::name = #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* name; TPfn pf;} TMethod;
 36     public:
 37         static int regist(lua_State* L); 
 38         static int create(lua_State* L); 
 39         static int call(lua_State* L); 
 40         static int gc(lua_State* L); 
 41 };
 42 
 43 template<typename T>
 44 int luna<T>::regist(lua_State* L)
 45 {
 46     //原表Shape
 47     if (luaL_newmetatable(L, T::name))
 48     {   
 49         //注册Shape到全局
 50         lua_newtable(L);
 51         lua_pushvalue(L, -1);
 52         lua_setglobal(L, T::name);
 53 
 54         //设置Shape的原表,主要是__call,使其看起来更像C++初始化
 55         lua_newtable(L);
 56         lua_pushcfunction(L, luna<T>::create);
 57         lua_setfield(L, -2, "__call");
 58         lua_setmetatable(L, -2);
 59         lua_pop(L, 1); //这时候栈只剩下元表
 60 
 61         //设置元表Shape index指向自己
 62         lua_pushvalue(L, -1);
 63         lua_setfield(L, -2, "__index");
 64         lua_pushcfunction(L, luna<T>::gc);
 65         lua_setfield(L, -2, "__gc");
 66     }
 67     return 0;
 68 }
 69 
 70 template<typename T>
 71 int luna<T>::create(lua_State* L)
 72 {
 73     lua_remove(L, 1);
 74     TObject* p = (TObject*)lua_newuserdata(L, sizeof(TObject));
 75     p->_u = new T();
 76 
 77     luaL_getmetatable(L, T::name);
 78     lua_setmetatable(L, -2);
 79 
 80     luaL_getmetatable(L, T::name);
 81     for (auto* l = T::methods; l->name; l++)
 82     {
 83         lua_pushlightuserdata(L,(void*)l);
 84         lua_pushlightuserdata(L,(void*)p);
 85         lua_pushcclosure(L, luna<T>::call, 2);
 86         lua_setfield(L, -2, l->name);
 87     }
 88 
 89     lua_pop(L, 1);
 90 
 91     return 1;
 92 }
 93 
 94 template<typename T>
 95 int luna<T>::call(lua_State* L)
 96 {
 97     TMethod* v = (TMethod*)lua_topointer(L, lua_upvalueindex(1));
 98     cout<<"luna<T>::call:"<<v->name<<endl;
 99 
100     TObject* p = (TObject*)lua_topointer(L, lua_upvalueindex(2));
101 
102 
103     return ((p->_u)->*(v->pf))(L);
104 }
105 
106 template<typename T>
107 int luna<T>::gc(lua_State* L)
108 {
109     TObject* p = (TObject*)lua_touserdata(L, 1);
110     (p->_u)->~T();
111     return 0;
112 }

通过上述代码发现:luna<T>模板类,把一些行为固化下来了。主要改进有几点:

1、  通过EXPORT_LUNA_XXX系列宏定义,把每个业务类需要name和methods列表固化下来,尽可能减少业务层工作量,避免出错。

2、  通过模板的方式,抽象出regist、create、call、gc几个公共接口,流程极为简单。所有不同类都遵循这样的原则。其中:

  • Luna<T>::regist: 注册T::name元表和T::name全局表,成员函数注册到元表,统一通过闭包的方式注册到一个公共的调用函数call进行分发调用。全局表T::name只保留__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调用。

 

整体结构如如下:

 

代码文件r_luna.cpp

 1 #include <iostream>
 2 #include <cstring>
 3 #include <stdlib.h>
 4 extern "C" {
 5 #include <lua.h>
 6 #include <lualib.h>
 7 #include <lauxlib.h>
 8 }
 9 #include "comm.h"
10 #include "luna.h"
11 #include "lunar.h"
12 
13 using namespace std;
14 
15 
16 class Car 
17 {
18     public:
19         Car(){}
20         ~Car(){cout<<"Delete Car,Length:"<<length<<" Width:"<<width<<endl;}
21 
22         int getLength(lua_State *L){
23             lua_pushinteger(L, length);
24             return 1;
25         }   
26         int getWidth(lua_State *L){
27             lua_pushinteger(L, width);
28             return 1;
29         }   
30         int setLength(lua_State *L) 
31         {   
32             int val = lua_tointeger(L, -1);
33             length = val;
34             return 0;
35         }   
36         int setWidth(lua_State *L) 
37         {   
38             int val = lua_tointeger(L, -1);
39             width = val;
40             return 0;
41         }   
42         int print(lua_State *L) 
43         {   
44             cout <<"print length:"<<length<<" width:"<<width<<endl;
45         }   
46     public:
47         DECLARE_LUNA_CLASS(Car);
48     public:
49         int length = 100;
50         int width = 200;
51 };
52 
53 EXPORT_LUNA_FUNCTION_BEGIN(Car)
54 EXPORT_LUNA_FUNCTION(Car, getLength)
55 EXPORT_LUNA_FUNCTION(Car, getWidth)
56 EXPORT_LUNA_FUNCTION(Car, setLength)
57 EXPORT_LUNA_FUNCTION(Car, setWidth)
58 EXPORT_LUNA_FUNCTION(Car, print)
59 EXPORT_LUNA_FUNCTION_END(Car)
60 
61 int main(int argc, char* argv[])
62 {
63     lua_State *L = luaL_newstate();
64     luaL_openlibs(L);
65 
66     luaL_dofile(L, "tree.lua");
67 
68     //use luna template and bind to object
69     luna<Car>::regist(L);
70 
71     luaL_dofile(L, "r_luna.lua");
72     print_stack(L);
73     lua_settop(L, 0);
74     cout << endl;
75 
76     lua_close(L);
77     return 0;
78 }

可以看到,定义一个业务类Car,把一些共性的东西通过EXPORT_LUNA_XXX宏固化下来,这样定义一个业务类就只用关注自己的成员函数,并且把自己需要导出到lua的成员函数通过EXPORT_LUNA_FUNCTION宏处理一下即可,方便快捷,代码极其简洁。

 

代码文件r_luna.lua

 1 do
 2 local car = Car();
 3 print_tree(car)
 4 print_metatable(car)
 5 print “”
 6 car.print();
 7 car.setLength(11);
 8 car.setWidth(22);
 9 car.print();
10 print(car.getLength(), car.getWidth(), car.x, car.y);
11 end
12 collectgarbage("collect");

 

运行结果:

Car: 0xec8d68
not a table
table: 0xec88c0
print   function: 0xec8f20
setWidth function: 0xec6c60
getLength function: 0xec7e10
getWidth function: 0xec8b70
__gc    function: 0x402cce
__index table: 0xec88c0+
setLength function: 0xec8bc0
__name  Car

luna<T>::call:print
print length:100 width:200
luna<T>::call:setLength
luna<T>::call:setWidth
luna<T>::call:print
print length:11 width:22
luna<T>::call:getLength
luna<T>::call:getWidth
11      22      nil     nil
Delete Car,Length:11 Width:22
==========Total:1==========
idx:-1 type:5(table) 0xec88c0
===========================

由上,红色标记的部分是元表。print(car.getLength(), car.getWidth(), car.x, car.y);这行的输出是11      22      nil     nil,因为变量x和变量y没有定义,所以打印出来是nil。那么能不能继续扩展luna框架,使其不仅能够导出成员函数,也能导出成员变量?这个就是下一节加强版的lunar的特性。

相关文章: