lua與c++交互全解析


歡迎轉載,請保留出處:http://www.cnblogs.com/wellbye/

    最近經常見有人在群上問有關lua在c/c++中嵌入使用的問題,但很多問題本身問得就莫名所以,很可能是對一些基本概念還未正確理解就急於使用,遇到難處也沒有認真思考問題的本質是什么,自然會問出一些叫人啼笑皆非、欲答無詞的問題。正好這段時間賦閑在家,希望能把幾年來對lua及c++的理解及經驗總結一下,為同樣喜歡這兩樣語言的同好做一個入門介紹。

    從以下幾個方面逐一解析這個問題:
    1、lua的數據模型
    2、跨語言交互的實質
    3、c++對象模型
    4、核心1:在lua中使用c++對象
    5、核心2:高效地導出c++對象

    一、lua的數據模型。lua是一門非常簡單易用的語言,簡單就簡單在它的數據類型是“封閉”的(相對於python的開放而言)。它所有的類型都由一個TValue表示:

typedef union {
  GCObject *gc;
  void *p;
  lua_Number n;
  int b;
} Value;


/*
** Tagged Values
*/

#define TValuefields    Value value; int tt

typedef struct lua_TValue {
  TValuefields;
} TValue;

    實際上TValue就是一個只有2個字段的結構體,其中tt表示類型,共有9種取值,1種是“nil(空)”,3種是簡單的“值類型”已直接列在Value中即p/n/b,另外5種作為“引用類型”需要gc管理而由額外的GCObject結構表示:

union GCObject {
  GCheader gch;
  union TString ts;
  union Udata u;
  union Closure cl;
  struct Table h;
  struct Proto p;
  struct UpVal uv;
  struct lua_State th;  /* thread */
};

    其中Proto/UpVal是完全由內部使用的數據可以不看。(額外插播:Proto是函數原型信息,UpVal是函數閉包引用的“上值”(即外層函數變量),這些本是純內部實現相關的結構(即它們不會“上棧”以致被使用方“合法訪問”),但它們也是需要動態管理的,那么直接將其當作“標准”數據類型來實現,就可重用那一整套復雜而完善的gc機制了。關於這種“內部功能也使用標准實現”的做法,我在讀Python代碼時也有類似感受,比如最常用的容器dict,其對應的實現在Objects/dictobject.c文件中,有一堆PyDict_XXXX的函數,按照Python C API的規范實現了一套完善的hashmap數據結構,這套功能當然首先是通過注冊導出后,給Python腳本用的,但是在其它一些功能模塊中,當要使用到hashmap功能時,居然也直接用這套PyDict_XXXX(而不是一般c語言里常用的hash庫或是std::map一類的東西),這也算是對代碼的一種自我驗證吧)

    ts是字符串,u是重型userdata,cl是函數閉包,h是hash表,th是協程。在與c++的交互中,u和h就扮演了最重要的角色。

    lua的所有數據類型就到此為止了,且沒法擴展(即在c層面自定義類型),因此要在lua里表現其它語言的數據類型如c++類等,就只有用這幾個現成的類型去組合模擬。相比Python有很大不同,Python擴展開發者可以通過實現自己的PyTypeObject,在c層面創建全新的類型。在我理解中也正是這種差別,導致了Python無法使用像Lua那樣的三色標記法gc,因為自定義結構中各種指針引用字段的存在,讓Python無法追蹤掃描下去轉而只能使用引用記數法,但會導致循環引用,其解決辦法仍然是要求類型創建者提供額外的追蹤掃描函數,在某種程度上達到與Lua gc相同的功效。

    現在重點說明hash表和userdata兩種類型。

    hash表,是在lua語言中表達描述各種數據結構的最佳也是唯一工具,其除了作為容器的一般用途外,還有兩個重要特性:元表和弱表。元表即metatable,它本身是一個普通表,但它的字段描述了其目標表的特殊功能,如對不存在字段的get/set、運算符的重載等;弱表,是一種具有特殊回收機制的表,當它的每一項key或value不再被外部引用(即僅存在於表中)時,會自動銷毀,在純粹的c++里是沒有gc機制的,但利用弱表的這個特性,在結合lua使用時反而可以做到c++對象的自動回收。

    userdata,是lua用來表示外部(宿主語言)數據的類型,又分成lightuserdata和普通userdata兩種。lightuserdata本身沒有運算概念,只具有“保存”和“傳遞”的意義,普通userdata則可以設置其“元表”,從而具有get/set功能,但更重要的是可以設置其gc處理函數,讓c++端的資源享受lua gc的便利。

    在我設計的綁定體系中,會用一個hash表來表示lua對象比如叫luaobj,而luaobj[1]就是一個存儲了對應c++對象指針的userdata,其上掛了gc handler,在此userdata被lua清除時,調用c++ obj的減引用計數函數。另外每一個類也是用一個hash表來表示,里面存儲了該類的所有導出函數,並且又通過元表指向其父類所對應的hash表,整個類層次就這樣串起來。而每一個luaobj也通過元表鏈接到其所對應的類表,這樣在luaobj上就可以找到調用所有其在c++層面的函數了。

    關於lua自身數據模型就先說到這里,整個體系的詳細說明將在后文里繼續描述。


免責聲明!

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



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