lua綁定C++對象系列一——基礎知識


本文主要介紹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來自動回收,完全取決於的用戶想怎么控制。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM