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++对象,如果可以,那应该怎么怎么做?有兴趣的可以手动试试看。