tolua++實現lua層調用c++技術分析


tolua++技術分析 cocos2dx+lua

前言

一直都使用 cocos2dx + lua 進行游戲開發,用 Lua 開發可以專注於游戲邏輯的實現,另外一方面可以實現熱更新;而且 lua 是一個輕量級的腳本語言,庫小但是功能齊全,所以在業內非常受歡迎。之前看了網上很多關於 c/c++ 如何與 lua 互調的講解,也查看了 lua 官網的 lua api 和 c api,感覺大有收獲。最近這一段時間研究了 tolua++ 里面 lua 層調用 c/c++ 的技術實現,准備記錄一下學習心得,這樣可以讓自己對 tolua++ 工作機制理解的更加流暢,也希望自己對原理和 api 的分析能夠對其他人有幫助!


tolua++需要將 c/c++ 中的類型,變量,函數,對象導出到lua

  1. 通過 tolua_reg_types(lua_State* tolua_S) 將類型導出,作用是為每一個需要導出到 lua 中的 c++ 類型創建元表,比如 CCNode 這種類型,就會在注冊表中創建一個元表 CCNode_mt。( 之后會用 _R 代表注冊表 , _G 代表全局表 , type_mt 代表類型為type的元表。 )

  2. 通過 tolua_cclass (lua_State* L, const char* lname, const char* name, const char* base, lua_CFunction col) 把基類設為子類的元表;同時在 _R.tolua_super 中以子類的元表為鍵,創建一張表作為值,而這張表會以基類,基類的基類(遞歸下去)為鍵,true/false 為值 ; 還會讓基類和子類共同享有同一張tolua_ubox表(以c++指針為鍵 , fulluserdata為值)。 最后讓 _G 持有 name_mt,即:_G.lname = name_mt。所以對於一個 c++ 類型,tolua++ 為其創建的元表最終會讓全局表和注冊表共同持有。

  3. 通過 tolua_function (lua_State* L, const char* name, lua_CFunction func) 將成員方法導出到 步驟1 創建的元表中;即:_G.type_mt.name = func

  4. 通過 tolua_variable(lua_State* L, const char* name, lua_CFunction get, lua_CFunction set) 在c++類型對象的元表中准備兩張表_G.type_mt.get = {} , _G.type_mt.set = {} ; 兩張表以變量名為鍵,get/set方法為值。

上面c++數據的導出步驟就是 tolua_Cocos2d_open(lua_State* tolua_S) 的實現,在CCLuaStack 初始化的時候完成。

tolua_Cocos2d_open (lua_State* tolua_S)內容分析

1: tolua_open(tolua_S) 創建一系列全局的table

  • _R.tolua_opened = true

  • _R.tolua_value_root = { }

  • _R.tolua_ubox = { }

  • setmetatable(_R.tolua_ubox,{__mode = v })

  • _R.tolua_super = { }

  • _R.tolua_gc = { }

  • _R.tolua_gc_event = class_gc_event(_R.tolua_gc , _R.tolua_super)

  • _R.tolua_commonclass = { }

  • _G.tolua = { type = tolua_bnd_type , takeownership = tolua_bnd_takeownership , releaseownership = tolua_bnd_releaseownership , cast = tolua_bnd_cast ,isnull = tolua_bnd_isnulluserdata , inherit = tolua_bnd_inherit }

  • 如果lua版本是5.1: _G.tolua.setpeer = tolua_bnd_setpeer ; _G.tolua.getpeer = tolua_bnd_getpeer ;

2: tolua_reg_types (lua_State* tolua_S) 為需要導出到lua中的c++類型注冊元表

  • 對每一個要導出的c++類型調用tolua_usertype完成元表的注冊。跟進tolua_usertype可以發現它做了兩件事情。

Step1: 調用 tolua_newmetatable 創建元表,並且給元表設置一系列的元方法。
Step2: 調用 mapsuper( L , derived_type , base_type ) 在tolua_super表中以 derived_mt(子類型的元表)作為一個字段建立一張映射表 t , 這個t以父類,父類的父類(遞歸下去)的元表為鍵,布爾變量為值。 用偽代碼可以表示為 tolua_super.derived_mt = { base_type = true , base_type_B = true , base_type_C = true} ,這樣可以就可以判斷兩個類的繼承關系。

3 tolua_cclass( L , lname , name , base ,col ) 實現類之間的關聯,讓子類能夠繼承父類

  • mapinheritance(L,derived,base) 將base設為derived的元表,這樣就可以認為derived派生於base.

1: 內部會調用 set_ubox(L) 實現基類與派生類共享同一份tolua_ubox表
2: 然后將基類設置為子類的metatable,如果基類為nil , 就將 _R.tolua_commonclass 設置為子類的元表。

  • mapsuper(L,derived,base)這個方法在上面提到了,就是在 _R.tolua_super 表中創建一個索引表可以用來判斷兩個類之間是否有繼承關系。

tolua_super.derived_mt = { base_type = true , base_type_B = true , base_type_C = true}

  • push_collector(L,type,col) 這個地方的col是個lua_CFunction類型;type_mt.collector = col 將col函數設為type的元表collector字段所對應的元方法。c++ 對象釋放的時候會要觸發這個函數。

  • _G.lname = name_mt 最后這個步驟就是把之前c++類型在注冊表中創建的元表讓全局表也持有一份。

4 接下來就是c++類中的方法和成員變量的導出,就以導出 ccColor3B類中的成員方法和變量 為例子

  • tolua_cclass(tolua_S,"ccColor3B","ccColor3B","",tolua_collect_ccColor3B) 先將類導出到_G中,並且設置好繼承關系.

  • tolua_beginmodule(tolua_S,"ccColor3B"); 將 _G.ccColor3B 壓入棧中(此時lua棧負1位置是ccColor3B_mt,負2位置是_G).

  • tolua_function(tolua_S,"new",tolua_Cocos2d_ccColor3B_new00); 將方法導出到c++類型所對應的元表中即:
    _G.ccColor3B.new = tolua_Cocos2d_ccColor3B_new00

  • tolua_function(tolua_S,"new_local",tolua_Cocos2d_ccColor3B_new00_local);
    _G.ccColor3B.new_local = tolua_Cocos2d_ccColor3B_new00_local 同理下面的一系列tolua_function都會將c++方法注冊到對應類型的元表中。

  • tolua_variable(tolua_S,"r",tolua_get_ccColor3B_unsigned_r,tolua_set_ccColor3B_unsigned_r)
    tolua_variable(tolua_S,"g",tolua_get_ccColor3B_unsigned_g,tolua_set_ccColor3B_unsigned_g);
    tolua_variable(tolua_S,"b",tolua_get_ccColor3B_unsigned_b,tolua_set_ccColor3B_unsigned_b);

tolua_variable 的邏輯是為每一個類型創建一張get表,一張set表,然后將變量對應的 get / set 方法放到 get表 / set表中;這樣在lua層訪問成員變量最終就會索引到對應的存取方法。 即:_G.ccColor3B.get = { "r" = tolua_get_ccColor3B_unsigned_r , "g" = tolua_get_ccColor3B_unsigned_g , "b" = tolua_get_ccColor3B_unsigned_b }

_G.ccColor3B.set = { "r" = tolua_set_ccColor3B_unsigned_r , "g" = tolua_set_ccColor3B_unsigned_g , "b" = tolua_set_ccColor3B_unsigned_b }

tolua++ 膠水函數分析 , 還是以ccColor3B為例

1. tolua_Cocos2d_ccColor3B_new00(lua_State* tolua_S) 將C++對象的指針以full userdata 的形式傳入到 lua 層

  • ccColor3B* tolua_ret = (ccColor3B*) Mtolua_new((ccColor3B)())先創建一個c++對象,獲取到指針。
  • tolua_pushusertype(tolua_S,(void*)tolua_ret,"ccColor3B") 函數在 c++ 層創建對象,然后創建full userdata壓入棧中,函數最后會把 userdata 地址返回到lua層,lua 要想操作c++對象就得操作 fulluserdata, 又因為fulluserdata 的元表是 c++對象在lua中的元表,所以最終 lua 就是通過操作c++類型對應的元表來控制c++對象。這也是前面一系列步驟的意義。

壓入c++對象時候,以 light userdata 為鍵,full userdata 為值把這一對key-value存入tolua_ubox中。 即:
ccColor3B_mt.tolua_ubox.tolua_ret = userdata
setmetatable(userdata,ccColor3B_mt)

2. tolua_Cocos2d_ccColor3B_new00_local 與前者相比多了一個tolua_register_gc方法,其他的都一樣

  • tolua_register_gc : 以c++指針為鍵,c++類型對應的元表為值,將這對key-value放於_R.tolua_gc中。

3. tolua_get_ccColor3B_unsigned_r(lua_State* tolua_S) 獲取ccColor3B類中的r值

  • 首先通過 tolua_tousertype 從棧頂拿到userdata中的指針ptr, 把 ptr 轉型為(ccColor3B*)
  • 然后將 (lua_Number)ptr->r 壓入棧中,最后返回給lua層。

總結

  1. tolua++ 為需要導出到lua中的 c++類型 創建元表,這個元表由 注冊表 和 全局表共同持有,同時在元表中注冊了一系列元方法。

  2. tolua++ 將父類型設為子類型的元表;父子類共同持有同一份tolua_ubox;同時在tolua_super中為c++類型准備了一張類型映射表,可以通過該表來查詢自身有哪些父類。 這樣就可以在lua層實現類的繼承。

  3. 通過調用 tolua_register_gc 方法,以c++類型的指針為鍵,c++類型對應的元表為值 作為key-value插入到_R.tolua_gc中 來管理創建c++對象的內存。

  4. tolua++ 對 c++ 對象內存的管理,以及 c++對象在lua層的擴展准備放下一篇文章再寫!

再添加一張圖,這樣更加清晰


免責聲明!

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



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