lua绑定C++对象系列五——lunar模板进阶

在系列第四篇通过luna绑定C++对象的机制,没有解决C++成员变量在lua中直接使用的问题。例如local car = Car(); car.x = 100的用法。本节介绍增强版的lunar模板类,主要有几点不同:

1、  支持export C++变量和函数到lua中。通过重定义元方法__index和__newindex,当调用car.x 或者赋值 car.x = xxx时会分别触发元方法。

2、  Luna版本的宏定义列表全是函数,直接注册成元表的cclosure类型,但lunar因为注册的类型可以是变量或者函数,methods列表必须包含类型和变量偏移信息。根据methods定义的信息,可以直接进行变量读写和函数调用。

整体结构如下:

 

 

代码文件lunar.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 enum class lua_member_type
 12 {
 13     member_none,
 14     member_func,
 15     member_int
 16 };
 17 
 18 #define DECLARE_LUNAR_CLASS(obj) \
 19     static const char *name;\
 20     static lunar<obj>::TMethod methods[];
 21 
 22 #define EXPORT_LUNAR_FUNCTION_BEGIN(obj) \
 23     const char* obj::name = #obj;\
 24     lunar<obj>::TMethod obj::methods[] = {
 25 
 26 #define EXPORT_LUNAR_MEMBER_INT(obj, member) \
 27     {#member, nullptr, lua_member_type::member_int, offsetof(obj, member)},
 28 
 29 #define EXPORT_LUNAR_FUNCTION(obj, func) \
 30     {#func, &obj::func, lua_member_type::member_func, 0},
 31 
 32 #define EXPORT_LUNAR_FUNCTION_END(obj) \
 33     {nullptr, nullptr, lua_member_type::member_none, 0}\
 34     };
 35 
 36 template<typename T>
 37 class lunar
 38 {
 39     public:
 40         typedef struct {T* _u;} TObject;
 41         typedef int (T::*TPfn)(lua_State* L); 
 42         typedef struct {const char* name; TPfn pf; lua_member_type type; int offset;} TMethod;
 43     public:
 44         static int regist(lua_State* L); 
 45         static int create(lua_State* L); 
 46         static int call(lua_State* L); 
 47         static int gc(lua_State* L); 
 48         static int member_index(lua_State* L); 
 49         static int member_new_index(lua_State* L); 
 50 };
 51 
 52 template<typename T>
 53 int lunar<T>::member_index(lua_State* L)
 54 {
 55     int top = lua_gettop(L);
 56     lua_getmetatable(L, 1);
 57     lua_insert(L, -2);
 58     lua_rawget(L, -2);
 59     if (!lua_islightuserdata(L, -1))
 60     {
 61         lua_settop(L, top);
 62         return 0;
 63     }
 64 
 65     TMethod* l = (TMethod*)lua_topointer(L, -1);
 66     TObject* p = (TObject*)lua_topointer(L, 1);
 67 
 68     switch (l->type)
 69     {
 70         case lua_member_type::member_func:
 71             {
 72                 lua_settop(L, top);
 73                 lua_pushlightuserdata(L, (void*)l);
 74                 lua_pushlightuserdata(L, (void*)p);
 75                 lua_pushcclosure(L, &lunar<T>::call, 2);
 76                 break;
 77             }
 78         case lua_member_type::member_int:
 79             {
 80                 int val = *(int *)((char*)(p->_u) + l->offset);
 81                 lua_settop(L, top);
 82                 lua_pushinteger(L, val);
 83                 break;
 84             }
 85         default:
 86             {
 87                 cout<<"member index type error"<<endl;
 88                 break;
 89             }
 90     }
 91 
 92     return 1;
 93 }
 94 
 95 template<typename T>
 96 int lunar<T>::member_new_index(lua_State* L)
 97 {
 98     int top = lua_gettop(L);
 99     lua_getmetatable(L, 1);
100     lua_pushvalue(L, 2);
101     lua_rawget(L, -2);
102     if (!lua_islightuserdata(L, -1))
103     {
104         lua_settop(L, top);
105         return 0;
106     }
107 
108     TMethod* l = (TMethod*)lua_topointer(L, -1);
109     TObject* p = (TObject*)lua_topointer(L, 1);
110 
111     switch (l->type)
112     {
113         case lua_member_type::member_func:
114             {
115                 break;
116             }
117         case lua_member_type::member_int:
118             {
119                 int val = lua_tointeger(L, 3);
120                 *(int *)((char*)(p->_u) + l->offset) = val;
121                 lua_settop(L, top);
122                 break;
123             }
124         default:
125             {
126                 cout <<"member new index type error"<<endl;
127                 break;
128             }
129     }
130 
131     return 0;
132 }
133 
134 template<typename T>
135 int lunar<T>::regist(lua_State* L)
136 {
137     if (luaL_newmetatable(L, T::name))
138     {
139         lua_pushcfunction(L, &lunar<T>::member_index);
140         lua_setfield(L, -2, "__index");
141         lua_pushcfunction(L, &lunar<T>::member_new_index);
142         lua_setfield(L, -2, "__newindex");
143         lua_pushcfunction(L, lunar<T>::gc);
144         lua_setfield(L, -2, "__gc");
145     }
146 
147     //设置方法和成员
148     for (auto* l = T::methods; l->name; l++)
149     {
150         lua_pushstring(L, l->name);
151         lua_pushlightuserdata(L, (void*)l);
152         lua_rawset(L, -3);
153     }
154 
155     lua_getglobal(L, "lunar");
156     if (!lua_istable(L, -1))
157     {
158         lua_pop(L, 1);
159         lua_newtable(L);
160         lua_pushvalue(L, -1);
161         lua_setglobal(L, "lunar");
162     }
163 
164     lua_pushcfunction(L, &lunar<T>::create);
165     lua_setfield(L, -2, T::name);
166     lua_pop(L, 2);
167 
168     return 0;
169 }
170 
171 template<typename T>
172 int lunar<T>::create(lua_State* L)
173 {
174     TObject* p = (TObject*)lua_newuserdata(L, sizeof(TObject));
175     p->_u  = new T();
176 
177     luaL_getmetatable(L, T::name);
178     lua_setmetatable(L, -2);
179 
180     return 1;
181 }
182 
183 template<typename T>
184 int lunar<T>::call(lua_State* L)
185 {
186     TMethod* v = (TMethod*)lua_topointer(L, lua_upvalueindex(1));
187     cout<<"lunar<T>::call:"<<v->name<<endl;
188 
189     TObject* p = (TObject*)lua_topointer(L, lua_upvalueindex(2));
190 
191     return ((p->_u)->*(v->pf))(L);
192 }
193 
194 template<typename T>
195 int lunar<T>::gc(lua_State* L)
196 {
197     if (!lua_isuserdata(L, -1))
198     {
199         cout<<"gc cause error."<<endl;
200     }
201 
202     TObject* p = (TObject*)lua_topointer(L, -1);
203     delete p->_u;
204     return 0;
205 }

这里有一点十分重要,__index = member_index,__newindex = member_new_index,因为mobile.version和mobile.getVersion都会触发member_index调用,但member_index的返回值很重要。例如针对mobile.version这种变量类型,需要最后push一个变量值到栈中;针对mobile.getVersion,需要push一个函数(闭包)到栈中,这样mobile.getVersion就返回一个函数,mobile.getVersion()就是调用这个返回的函数。

 

这里还有一点需要注意一下,通过在全局表定义一个lunar表,在这个表中,T::name作为key,lunar<T>::create作为value,这样也可以把所有的构造函数全部统一管理起来。当后面有越来越多的不同类通过lunar绑定时,就可以在lunar全局统一管理而不至于混乱,造成名字冲突。例如针对类Car\Bus\Train\Airplane注册后,效果如下:

 

 

 

代码文件r_lunar.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 class Mobile
16 {
17     public:
18         Mobile(){}
19         ~Mobile(){cout<<"Delete Mobile,Ver:"<<version<<" Price:"<<price<<endl;}
20 
21         int getVersion(lua_State *L){
22             lua_pushinteger(L, version);
23             return 1;
24         }   
25         int getPrice(lua_State *L){
26             lua_pushinteger(L, price);
27             return 1;
28         }   
29         int setVersion(lua_State *L) 
30         {   
31             int val = lua_tointeger(L, -1);
32             version = val;
33             return 0;
34         }   
35         int setPrice(lua_State *L) 
36         {   
37             int val = lua_tointeger(L, -1);
38             price = val;
39             return 0;
40         }   
41         int print(lua_State *L) 
42         {   
43             cout <<"print version:"<<version<<" price:"<<price<<endl;
44         }   
45     public:
46         DECLARE_LUNAR_CLASS(Mobile);
47     public:
48         int version = 100;
49         int price = 200;
50 };
51 
52 EXPORT_LUNAR_FUNCTION_BEGIN(Mobile)
53 EXPORT_LUNAR_FUNCTION(Mobile, getVersion)
54 EXPORT_LUNAR_FUNCTION(Mobile, getPrice)
55 EXPORT_LUNAR_FUNCTION(Mobile, setVersion)
56 EXPORT_LUNAR_FUNCTION(Mobile, setPrice)
57 EXPORT_LUNAR_FUNCTION(Mobile, print)
58 EXPORT_LUNAR_MEMBER_INT(Mobile, version)
59 EXPORT_LUNAR_MEMBER_INT(Mobile, price)
60 EXPORT_LUNAR_FUNCTION_END(Mobile)
61 
62 int main(int argc, char* argv[])
63 {
64     lua_State *L = luaL_newstate();
65     luaL_openlibs(L);
66 
67     luaL_dofile(L, "tree.lua");
68 
69     //use lunar template and bind to object 
70     lunar<Mobile>::regist(L);
71 
72     luaL_dofile(L, "r_lunar.lua");
73     print_stack(L);
74     lua_settop(L, 0);
75     cout<<endl;
76 
77     lua_close(L);
78     return 0;
79 }

 

对应的r_lunar.lua:

do
print_tree(lunar)
print ""
local mobile = lunar.Mobile();
print_tree(mobile)
print_metatable(mobile)

print ""
print(mobile.version, mobile.price);
mobile.version = 101
mobile.price = 4888
mobile.y = 100
print(mobile.version, mobile.price)
print(mobile.x, mobile.y);
print(mobile.getVersion, mobile.getPrice)
mobile.print();

print ""
local mobile1 = lunar.Mobile();
mobile1.setVersion(201);
mobile1.setPrice(5888);
mobile1.print();
print(mobile1.getVersion(), mobile1.getPrice(), mobile1.version, mobile1.price);
end
collectgarbage("collect");

 

运行结果:

 

table: 0x216b830
Mobile  function: 0x402f9c

Mobile: 0x216c348
not a table
table: 0x216b8c0
setPrice userdata: 0x641400
__index function: 0x402c76
getPrice userdata: 0x6413c0
print   userdata: 0x641420
getVersion userdata: 0x6413a0
__newindex function: 0x402df2
setVersion userdata: 0x6413e0
price   userdata: 0x641460
version userdata: 0x641440
__name  Mobile
__gc    function: 0x402f1c

100     200
101     4888
nil     nil
function: 0x216dc30     function: 0x216dc80
lunar<T>::call:print
print version:101 price:4888

lunar<T>::call:setVersion
lunar<T>::call:setPrice
lunar<T>::call:print
print version:201 price:5888
lunar<T>::call:getVersion
lunar<T>::call:getPrice
201     5888    201     5888
Delete Mobile,Ver:201 Price:5888
Delete Mobile,Ver:101 Price:4888
==========Total:0==========
===========================

 

可以看到,红色标记的部分变成userdata,只记录methods的元素地址,不再直接注册成一个函数。

 

一个小问题:

无论是luna还是lunar,都是通过userdata+metatable绑定C++对象的,那么能否通过table+metatable来绑定C++对象,如果可以,那应该怎么怎么做?有兴趣的可以手动试试看。

 

posted @ 2018-10-15 16:09  liao0001  阅读(1152)  评论(0编辑  收藏  举报