本文主要介绍lua绑定C++对象的原理和方法,并能在C/C++定义类和方法,在lua中创建C++类的句柄实例,像面向对象一样去使用C++类实例。为了便于大家理解,系列文章会从基础知识讲解,并通过多个版本的进化,一步步完成从基础到多版本实践的完美结合和深入,彻底理解lua绑定C++对象的原理方法。在阅读本系列文章前,需要具备一定的lua开发经验以及lua与C/C++相互调用操作的知识。
1、基础C/C++和Lua的相互引用调用
我们知道C和lua相互调用,是通过虚拟栈进行数据传递通信的,基础介绍介绍就不在这里赘述。这里介绍一个C函数print_stack和一个lua函数print_tree。
- print_stack
它能够打印出stack当前的状态,方便使用的过程进行调试,我们知道lua虚拟栈是push数据是从下往上的,最顶上的的index为-1,下面的代码是从最顶往下打印。
基本代码文件:comm.h
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 10 using namespace std; 11 12 /* 13 * #define LUA_TNIL 0 14 * #define LUA_TBOOLEAN 1 15 * #define LUA_TLIGHTUSERDATA 2 16 * #define LUA_TNUMBER 3 17 * #define LUA_TSTRING 4 18 * #define LUA_TTABLE 5 19 * #define LUA_TFUNCTION 6 20 * #define LUA_TUSERDATA 7 21 * #define LUA_TTHREAD 8 22 * */ 23 24 char* get_val(lua_State *L, int idx) 25 { 26 static char sData[32]; 27 sData[0] = ‘\0‘; 28 29 int type = lua_type(L, idx); 30 switch (type) 31 { 32 case 0: //nil 33 { 34 snprintf(sData, sizeof(sData), "%s", "nil"); 35 break; 36 } 37 case 1://bool 38 { 39 int val = lua_toboolean(L, idx); 40 snprintf(sData, sizeof(sData), "%s", val == 1 ? "true" : "false"); 41 break; 42 } 43 case 3://number 44 { 45 double val = lua_tonumber(L, idx); 46 snprintf(sData, sizeof(sData), "%f", val); 47 break; 48 } 49 case 4://string 50 { 51 const char* val = lua_tostring(L, idx); 52 snprintf(sData, sizeof(sData), "%s", val); 53 break; 54 } 55 case 2: 56 case 5: 57 case 6: 58 case 7: 59 case 8: 60 default: 61 { 62 const void* val = lua_topointer(L, idx); 63 snprintf(sData, sizeof(sData), "%p", val); 64 break; 65 } 66 67 } 68 69 return sData; 70 } 71 72 int print_stack(lua_State *L) 73 { 74 int iNum = lua_gettop(L); 75 cout<<"==========Total:"<<iNum<<"=========="<<endl; 76 for (int i = iNum; i >= 1; i--) 77 { 78 int idx = i - iNum - 1; 79 int type = lua_type(L, i); 80 const char* type_name = lua_typename(L, type); 81 cout<<"idx:"<<idx<<" type:"<<type<<"("<< type_name<<") "<<get_val(L, i)<<endl; 82 } 83 cout<<"==========================="<<endl; 84 return 0; 85 }
打印效果如下:
==========Total:3========== idx:-1 type:0(nil) nil idx:-2 type:0(nil) nil idx:-3 type:5(table) 0x11251c0 ===========================
- print_tree
lua函数print_tree能够打印table结构,也是为了方便查看table的层次数据。
基本代码文件: tree.lua
1 function print_tree(var, depth) 2 print(var) 3 local bitmap = {} 4 function print_tree_i(var, depth) 5 if type(var) ~= "table" then 6 print("not a table"); 7 return 8 end 9 10 local depth = depth or 0 11 local tab = string.rep(" ", depth); 12 depth = depth + 1 13 14 if depth >= 4 then 15 return 16 end 17 18 bitmap[var] = true 19 20 for k,v in pairs(var) do 21 if type(v) ~= "table" then 22 print(string.format("%s%-7s %s", tab, tostring(k), tostring(v))) 23 else 24 if not bitmap[v] then 25 print(string.format("%s%s(%s)", tab, tostring(k), v)) 26 print_tree_i(v, depth) 27 else 28 print(string.format("%s%-7s %s+", tab, tostring(k), v)) 29 end 30 end 31 end 32 return 33 end 34 35 print_tree_i(var, depth) 36 end 37 38 function print_metatable(tab) 39 if type(getmetatable(tab)) ~= "table" then 40 print("has no metatable"); 41 return 42 end 43 44 print_tree(getmetatable(tab)); 45 end
打印效果如下:
print_tree({[1]=1,[2]=2,[3]=3,[4]=4,[5]=5,[6]=6,[7]=7,[8]=8,[9]=9,[10]=10,[11]={hello="worldha", hello1="worldha"}}) 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11(table: 0x201f200) hello worldha hello1 worldha
2、几个面向对象重要的接口介绍
在通过lua绑定C++对象时,常用的接口有以下几个lua_register、lua_getgloba/lua_setglobal、lua_setfield(L, LUA_REGISTRYINDEX, "xxx")/lua_getfield(L, LUA_REGISTRYINDEX, "xxx")、_G、luaL_newmetatable/luaL_getmetatable,他们调用过程中数据数据存放在哪里,对lua的底层数据结构有什么影响?
针对lua5.3.4,lua虚拟机针对每个进程有个lua_State私有数据,而这些进程共享一个全局数据global_State。global_State中有一个l_registry注册表,这是一个预定义出来的表,可以用来保存任何代码想保存的 Lua 值。 这个表可以用有效伪索引 LUA_REGISTRYINDEX
来定位,当然全局数据也是放在里面。具体的l_registry结构如下:
如上图可以看到,l_registry的index为1指向lua_State对象,index为2指向global表,而所有的库都是初始化到这个表中。下面分情况说明一下:
- 当我们在lua中使用print或者io.open时,相当于是引用l_registry[2] [“print”]和 l_registry[2][“io”][“open”]元素
- 当我们在lua中定义全局函数print_tree时,相当于写入元素l_registry[2][“print_tree”]
- 当我们在代码中使用_G.print_tree和print_tree时,实际是引用l_registry[2][“_G”][“print_tree”] 和 l_registry[2][“print_tree”],两者实际等价。因为l_registry[2][“_G”] = l_registry[2],相当于引用自身。
- luaL_getmetatable/luaL_newmetatable是操作l_registry这个表。当我们使用luaL_getmetatable(L, tabname)进行查找时,实际是在查找l_register[tabname]是否存在。当调用luaL_newmetable(L, tabname)时首先判断l_register[tabname]是否存在,存在返回0.不存在就创建l_register[tabname] = {__name=tabname},并返回1。一种类型的C++对象,元表是一样的,可以共享元表定义,不用每个对象自己单独创建元表。所以使用luaL_newmetatable()会比较合适。
C/C++中调用print_stack():
1 lua_getglobal(L, "print_tree"); 2 lua_pushinteger(L, 2); 3 lua_gettable(L, LUA_REGISTRYINDEX); //将l_registry[2]推入栈中 4 lua_pcall(L, 1, 0, 0); //等价于print_tree(l_register[2])
打印结果如下:
table: 0x1d5a930 //l_registry[2]的地址 select function: 0x425589 require function: 0x1d5cb60 rawget function: 0x424cd3 rawlen function: 0x424c62 dofile function: 0x425453 table(table: 0x1d5cf30) maxn function: 0x42f46a move function: 0x42f75d sort function: 0x43027c insert function: 0x42f536 unpack function: 0x42fbec concat function: 0x42fa06 pack function: 0x42fb4a remove function: 0x42f657 hello nihao assert function: 0x4254f2 os(table: 0x1d5cea0) rename function: 0x42a518 time function: 0x42ac8c remove function: 0x42a4ca tmpname function: 0x42a588 clock function: 0x42a660 exit function: 0x42aef3 getenv function: 0x42a61d setlocale function: 0x42ae77 date function: 0x42aa3c execute function: 0x42a45e difftime function: 0x42ae09 pcall function: 0x4256bc getmetatable function: 0x424aed loadfile function: 0x42515e rawequal function: 0x424c07 print_tree function: 0x1d61390 //print_tree是自己定义的全局函数 _VERSION Lua 5.3 rawset function: 0x424d2f setmetatable function: 0x424b4f tonumber function: 0x4248da tostring function: 0x4257f8 module function: 0x1d5caf0 bit32(table: 0x1d60740) bxor function: 0x4259f0 arshift function: 0x425bbb bnot function: 0x425a59 rshift function: 0x425b6a extract function: 0x425e2d band function: 0x425915 rrotate function: 0x425d34 bor function: 0x425987 lshift function: 0x425b1c lrotate function: 0x425d03 btest function: 0x42594b replace function: 0x425eb4 pairs function: 0x425022 print_tree_i function: 0x1d5de50 print_metatable function: 0x1d60ff0 debug(table: 0x1d5aab0) getlocal function: 0x426c2d getinfo function: 0x426893 gethook function: 0x427564 traceback function: 0x4277f5 getmetatable function: 0x4265ca debug function: 0x4276b0 sethook function: 0x42736d setlocal function: 0x426dd9 setmetatable function: 0x42660f setupvalue function: 0x426ffb setuservalue function: 0x4266c7 getupvalue function: 0x426fdc getuservalue function: 0x42667f upvaluejoin function: 0x4270f2 getregistry function: 0x4265a6 upvalueid function: 0x4270a0 io(table: 0x1d5d230) flush function: 0x429055 write function: 0x428e5b input function: 0x428043 output function: 0x428067 stderr file (0x7fedf80321c0) popen function: 0x427e22 close function: 0x427b64 stdin file (0x7fedf8032640) lines function: 0x428155 stdout file (0x7fedf8032400) read function: 0x428afe type function: 0x427993 tmpfile function: 0x427ed1 open function: 0x427d0f math(table: 0x1d5c050) atan2 function: 0x42952f floor function: 0x429695 asin function: 0x42947d random function: 0x429dfd randomseed function: 0x429f79 acos function: 0x4294d6 ult function: 0x429a54 deg function: 0x429c63 fmod function: 0x4297a7 max function: 0x429d6c log function: 0x429ab0 maxinteger 9223372036854775807 min function: 0x429cdb atan function: 0x42952f tointeger function: 0x4295c3 exp function: 0x429c0a sin function: 0x429372 pi 3.1415926535898 huge inf mininteger -9223372036854775808 ceil function: 0x42971e cosh function: 0x42a02f modf function: 0x4298e8 frexp function: 0x42a1c6 sqrt function: 0x4299fb cos function: 0x4293cb ldexp function: 0x42a23b abs function: 0x4292e0 log10 function: 0x42a2b5 tan function: 0x429424 tanh function: 0x42a0e1 sinh function: 0x42a088 pow function: 0x42a13a type function: 0x429fae rad function: 0x429c9f string(table: 0x1d5bd90) lower function: 0x42b1fb match function: 0x42cbfa len function: 0x42afe8 pack function: 0x42e634 rep function: 0x42b363 upper function: 0x42b2af packsize function: 0x42ec59 char function: 0x42b670 unpack function: 0x42ee87 gsub function: 0x42d173 byte function: 0x42b520 format function: 0x42db29 reverse function: 0x42b153 gmatch function: 0x42ccf4 sub function: 0x42b064 find function: 0x42cbdb dump function: 0x42b77a collectgarbage function: 0x424d9c utf8(table: 0x1d5fed0) codepoint function: 0x43066d codes function: 0x430c2f char function: 0x43088d offset function: 0x430934 charpattern [-[* len function: 0x430500 unpack function: 0x42fbec print function: 0x424628 next function: 0x424fc0 coroutine(table: 0x1d5cbd0) wrap function: 0x426326 resume function: 0x426170 status function: 0x42638d create function: 0x4262c4 yield function: 0x42635b isyieldable function: 0x426495 running function: 0x4264c4 ipairs function: 0x4250aa _G table: 0x1d5a930+ //l_registry[2][“_G”]指向地址就是l_registry[2] error function: 0x424a5a loadstring function: 0x4252fa xpcall function: 0x42574c load function: 0x4252fa type function: 0x424e8b
通过上述的分析,可以很清楚的解释第2点的关系了:
- l_registry[2]、_G、全局表几个概念等价
- lua_register只是把c函数注册到全局table,即注册到l_registry[2]中
- lua_setglobal和lua_getglobal只是修改和查询全局表,即l_registry[2]这个表
- lua_setfield(L, LUA_REGISTRYINDEX, "xxx")/lua_getfield(L, LUA_REGISTRYINDEX, "xxx")只是修改更上层的l_registry这个表,可以用来保存C/C++代码想保存的lua值。
- luaL_newmetatable/luaL_getmetatable底层调用lua_setfield(L, LUA_REGISTRYINDEX, "xxx")/lua_getfield(L, LUA_REGISTRYINDEX, "xxx"),修改l_registry这个表
3、几种面向对象中常见的元方法介绍
下面介绍的几个元方法,在后面lua绑定C++对象的时候会频繁使用,所以这里进行一下简单的介绍:
__index: 索引 table[key]。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。此时,会读出 table 相应的元方法。
尽管名字取成这样,这个事件的元方法其实可以是一个函数也可以是一张表。如果它是一个函数,则以 table 和 key 作为参数调用它。如果它是一张表,最终的结果就是以 key 取索引这张表的结果
__newindex: 索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或是表 table 中不存在 key 这个键的时候。此时,会读出 table 相应的元方法。
同索引过程那样,这个事件的元方法即可以是函数,也可以是一张表。如果是一个函数,则以 table、 key、以及 value 为参数传入。如果是一张表, Lua 对这张表做索引赋值操作
__call: 函数调用操作 func(args)。 当 Lua 尝试调用一个非函数的值的时候会触发这个事件(即 func 不是一个函数)。查找 func 的元方法__call,如果找得到,就调用这个元方法, func 作为第一个参数传入,原来调用的参数(args)后依次排在后面。
__gc: 当一个被标记的对象成为了垃圾后,垃圾收集器并不会立刻回收它。取而代之的是,Lua 会将其置入一个链表。在收集完成后,Lua 将遍历这个链表。 Lua 会检查每个链表中的对象的 __gc 元方法:如果是一个函数,那么就以对象为唯一参数调用它;否则直接忽略它。简而言之,就是当GCOject被回收时,触发__gc元方法执行。
举个小例子:
1 do 2 local ta = {c=2} 3 local me = {} 4 5 me.__call = function(tab, arg) 6 print("__call", tab, arg) 7 return type(tab), type(arg),1 8 end 9 10 me.__newindex = function(tab, key, value) 11 print("__newindex", tab,key,value) 12 rawset(tab, key, value); 13 end 14 15 me.__index = function(tab, key, value) 16 if key == "n" then 17 return function(value) return value * 10 end 18 end 19 20 print("__index", tab, key, value) 21 return 10 22 end 23 24 me.__gc = function(tab) 25 print("__gc", tab) 26 end 27 28 setmetatable(ta, me); 29 30 local t = ta(); 31 print(ta()) 32 ta.a = 1 33 ta.c = 2 34 35 local c = ta.m 36 print("c=" .. c) 37 38 local d = ta.n(12) 39 print("d=" .. d) 40 41 end 42 43 collectgarbage("collect");
运行结果如下:
__call table: 0x1e2e180 nil //local t = ta() __call table: 0x1e2e180 nil //print(ta()) table nil 1 //print(ta()) __newindex table: 0x1e2e180 a 1 //ta.a = 1 __index table: 0x1e2e180 m nil // local c = ta.m c=10 d=120 __gc table: 0x1e2e180
4、对象的存储位置和生存周期
lua userdata和lightuserdata是用来存储C++对象的两种主要方式。
- lightuserdata类型对应为LUA_TLIGHTUSERDATA,实际上就是一个指针void*,需要在C/C++层面创建对象,把对象指针存放为lightuserdata类型,因为这不是一个GC对象,需要由C/C++层面创建和释放,比较适合应用在一些需要在C/C++层面创建一些全局对象的场合。
- userdata类型对应为LUA_TUSERDATA,len+data,属于lua层的GC对象,会通过lua的gc机制进行回收。如果userdata定义了原表的__gc方法,在回收前会调用__gc方法。
在创建C++层的对象时,是在C++层管理对象的生命周期还是在lua层通过gc来自动回收,完全取决于的用户想怎么控制。
原文地址:https://www.cnblogs.com/liao0001/p/9791087.html