天天看點

Lua Userdata

(一)通過一個簡單的例子來看一下userdata的用法:

寫一個C的Lua庫,讓Lua能夠通路C的數組,借助userdata來實作。

(1)VS中建立一個DLL工程,設定好lua庫的包含目錄、連結庫;

(2)建立一個源檔案main.cpp,代碼如下:

(3)編譯生成名為array.dll的檔案,并将array.dll放在luaforwindows的clibs子目錄下,該目錄下都是為lua寫的c庫,或者将其放到本地注冊的Lua環境變量的某個目錄下;

(4)lua測試:

上面代碼中的關鍵函數:

  void *lua_newuserdata (lua_State *L, size_t size);

  建立full userdata。

  (1)配置設定一塊指定大小的記憶體;

  (2)将該full userdata壓棧;

  (3)傳回該記憶體塊的位址給主機程式,主機程式能夠随意使用這塊記憶體。

  void luaL_argcheck (lua_State *L,int cond,int arg,const char *extramsg);

  檢查條件是否滿足。

(二)利用metatable辨別userdata來增加代碼的安全性

  上面的C庫是有缺陷的,比如我們怎麼確定例子中setarray的第一個參數就是我們想要的數組userdata,而不是别的不相關的userdata呢?userdata是一種lua類型,它可以用來表示宿主語言中的各種自定義類型對象,為了區分特定類型,我們使用的方法是:

  我們單獨為該數組建立一個metatable,每次建立數組userdata時,我們設定其和metatable的關聯。每次我們通路數組的時候,都檢查一下其是否有一個正确的metatable即可。也就是利用不同的metatable來标記不同類型的userdata。因為Lua代碼不能夠改變userdata的metatable,是以Lua不會僞造我們的代碼。

  是以我們對上面的例子進行一些改進,給數組userdata添加一個類型辨別,C庫代碼如下:

  void luaL_newmetatable (lua_State *L, const char *tname);

  建立userdata可用的metatable。

  如果registry已經有tnme鍵值,則函數傳回0;

  否則,建立一個[tname, metatable],并放入registry,并傳回1。

  兩種情況下,都會講tname對應的值入棧。

  堆棧+1

  void *luaL_checkudata (lua_State *L, int index, const char *tname);

  檢查在棧中指定位置的對象是否為帶有給定名字的metatable(registry中鍵tname對應的值)的usertata。是則傳回userdata位址,否則傳回NULL。

  void luaL_getmetatable (lua_State *L, const char *tname);

  擷取registry中的tname對應的metatable,并入棧。注意區分lua_getmetatable函數。

  void luaL_setmetatable (lua_State *L, const char *tname);

  将棧頂對象的metatable設定為registry表中鍵tname對應的值。注意區分lua_setmetatable函數。

  int lua_getmetatable (lua_State *L, int index);

  擷取index對應的table的metatable,并入棧。如果該table沒有metatable,則傳回0,且堆棧不變。

  void lua_setmetatable (lua_State *L, int index);

  将棧頂的table出棧并設定給index處的值作為metatable。

  堆棧-1

 (三)将上面的代碼改造成面向對象的方式

  類型為對象的userdata,可以使用如下的文法來操作對象的執行個體:

  思路大緻如下:

  (1)array表隻包含一個方法,也就是用來生成數組對象的new方法;

  (2)數組userdata帶有metatable用于類型識别;

  (3)userdata的metatable定義__index,那麼,每當通路數組的方法時,都會觸發__index這個metamethod(對于userdata來講,每次被通路的時候元方法都會被調用,因為userdata根本就沒有任何key);

  (4)将metatable.__index設為該表metatable本身(__index可以為函數或者表,這裡使用後者);

  (5)metatable包含其餘所有的數組操作函數。

  那麼每當調用userdata的某個方法時,比如a:size(),它等同于a.size(a),這時會觸發userdata的名為__index的metamethod,metatable的__index就是它本身,而metatable表中有size域,是以調用metatable的size(a)函數,就ok了。

(四)以數組下标的形式通路

  怎樣實作支援下表操作的文法來通路userdata呢,就像下面一樣:

  可以直接在lua中通過以下代碼實作:

  對應到C中的實作方式如下:

  将metatable的__index設為array的get方法,__newindex設為set方法即可。在讀取a[i]的時候會觸發__index,并将對象本身和參數同時傳遞給__index對應的函數,寫a[i]的時候原理一緻。

(五)light userdata

  light userdata不同于full userdata,它有如下特點:

  (1)full userdata代表Lua中的C對象,light userdata代表一個C指針的值(也就是一個void *類型的值)。由于它是一個值,我們不能建立他們(同樣的,我們也不能建立一個數字)。

  (2)僅僅是一個指針,像數字一樣,沒有metatables,light userdata不需要垃圾收集器來管理她。

  (3)可以用于表示不同類型的對象,我們在Lua中使用light userdata表示C對象。

  因為它是一個值,任何指向同一個C位址的light userdata都相等。

  void lua_pushlightuserdata (lua_State *L, void *p);

  将一個light userdata入棧。

 (六)userdata相關的資源釋放

  Lua以__gc元方法的方式提供了finalizers。這個元方法隻對userdata類型的值有效。當一個userdata将被收集的時候,并且userdata有一個__gc域,Lua會調用這個域的值(應該是一個函數):以userdata作為這個函數的參數調用。這個函數負責釋放與userdata相關的所有資源,比如說檔案描述符、視窗句柄等。