Lua 與 C 交互之UserData(4)


lua作為腳本於要能夠使用宿主語言的類型,不管是宿主基本的或者擴展的類型結構,所以Lua提供的UserData來滿足擴展的需求。在Lua中使用宿主語言的類型至少要考慮到幾個方面:

  1. 數據內存
  2. 生命周期
  3. 數據操作
    下面的內容主要參考《Lua程序設計》,數據保存在Lua堆棧中,通過Metatable對數據進行操作,並通過Lua的Gc進行回收內存。

1 Full UserData

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

This function allocates a new block of memory with the given size, pushes onto the stack a new full userdata with the block address, and returns this address.Userdata represents C values in Lua. A full userdata represents a block of memory. It is an object (like a table): you must create it, it can have its own metatable, and you can detect when it is being collected. A full userdata is only equal to itself (under raw equality).When Lua collects a full userdata with a gc metamethod, Lua calls the metamethod and marks the userdata as finalized. When this userdata is collected again then Lua frees its corresponding memory.

函數按照指定的大小分配一塊內存,將對應的userdatum放到棧內,並返回內存塊的地址。Userdata可以有自己的metatable ,如果Metatable中有__Gc元方法,回收時會調用改方法。回收之后Lua釋放對應的內存。

void *lua_touserdata (lua_State *L, int index);

If the value at the given acceptable index is a full userdata, returns its block address. If the value is a light userdata, returns its pointer. Otherwise, returns NULL.

操作實例
//結構定義
typedef struct NumArray {
    int size;
    double values[1];  /* variable part */
} NumArray;

//創建數組
static int newarray (lua_State *L) {
    int n = luaL_checkint(L, 1);  //檢查整數
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
    a->size = n;
    return 1;  /* new userdatum is already on the stack */
}

//設置數組數值set(a, index, value)
static int setarray (lua_State *L) 
{
    NumArray *a = (NumArray *)lua_touserdata(L, 1);
    int index = luaL_checkint(L, 2);
    double value = luaL_checknumber(L, 3);
    luaL_argcheck(L, a != NULL, 1, "`array' expected");
    luaL_argcheck(L, 1 <= index && index <= a->size, 2, "index out of range");
    a->values[index-1] = value;
    return 0;
}
//獲取數組元素 get(a,index)
static int getarray (lua_State *L) {
    NumArray *a = (NumArray *)lua_touserdata(L, 1);
    int index = luaL_checkint(L, 2);
    luaL_argcheck(L, a != NULL, 1, "'array' expected");
    luaL_argcheck(L, 1 <= index && index <= a->size, 2,"index out of range");
    lua_pushnumber(L, a->values[index-1]);
    return 1;
}

// 庫定義
static const struct luaL_reg arraylib [] = {
    {"new", newarray},{"set", setarray},{"get", getarray},{NULL, NULL}
};
int luaopen_array (lua_State *L) {
    luaL_openlib(L, "array", arraylib, 0);
    return 1;
}

導入上面的庫,我們可以在Lua中進行數組的操作:

a = array.new(1000)   --創建
array.set(a, 1, 10)     --設置
print(array.get(a, 10))   -- 獲取

2 MetaTable

為了區分不同C類型的userData,可以為userData添加不同的metatables。每次我們訪問時候,我們都要檢查他是否有一個正確的 metatable。 Lua 代碼不能改變 userdatum 的metatable,所以他不會偽造我們的代碼。

操作函數
int luaL_newmetatable (lua_State *L, const char *tname);

創建新表(將用作 metatable),將新表放到棧頂並建立表和 registry 中類型名的聯系

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

獲取 registry 中的 tname 對應的 metatable,壓入棧中

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

檢查在棧中指定位置的對象是否為帶有給定名字的 metatable 的 userdata。如果對象不
存在正確的 metatable,返回 NULL,否則,返回userdata的地址

修改案例
//首先創建名字為LuaBook.array的meta 表
int luaopen_array (lua_State *L) {
    luaL_newmetatable(L, "LuaBook.array");
	...
}
//創建數組時,為Userdata添加元表方法
static int newarray (lua_State *L) {
	...
    luaL_getmetatable(L, "LuaBook.array");
    lua_setmetatable(L, -2);
    ...
}
//添加檢測metatable名字是否正確的接口
static NumArray *checkarray (lua_State *L) {
    void *ud = luaL_checkudata(L, 1, "LuaBook.array");
    luaL_argcheck(L, ud != NULL, 1, "`array' expected");
    return (NumArray *)ud;
}

也就是說創建相關類型的metatable,然后在創建UserData的時候設置metatable,在獲取時首先檢測meta是否正對

3 面向對象訪問

可以通過擴展userdata中metatable的元方法來實現對C對象進行面向對象方式的訪問方式。

C中定義
// Metatable增加__index , __newindex方法
int luaopen_array (lua_State *L) 
{
	 luaL_newmetatable(L, "LuaBook.array");
	 lua_pushstring(L, "__index");
	 lua_pushvalue(L, -2);    /* pushes the metatable */
	 lua_settable(L, -3); /* metatable.__index = metatable */
	 
    //第一次調用,當我們傳遞一個NULL作為庫名時,luaL_openlib並沒有創建任何包含函數的表;相反,他認為封裝函數的表在棧內,位於臨時的upvalues的下面
    luaL_openlib(L, NULL, arraylib_m, 0);
 
	 //根據給定的數組名創建一個新表,並在表中注冊指定的函數
	 luaL_openlib(L, "array", arraylib_f, 0);
	 return 0;
}

// 定義Userdata中的方法
static const struct luaL_reg arraylib_f [] = {
    {"new", newarray}, {NULL, NULL}
};

//定義Metatable中方法
static const struct luaL_reg arraylib_m [] = {
    {"set", setarray},{"get", getarray},{NULL, NULL}
};

通過為Metatable擴展元方法的方式實現對象結構在Lua中訪問方便。
luaL_openlib(L, NULL, arraylib_m, 0)應該是將arraylib_m 中的方法添加到metatable中,有點不好理解,其實也可以用settable的方式將set、get方法注入到metatable中。

Lua 中定義

另外也可以在Lua中設置userdata的元表,不過需要在array庫導入之后執行。

function RegsterMetatable(a)
	local metaarray = getmetatable(a)
	metaarray.__index = metaarray
	metaarray.set = array.set
	metaarray.get = array.get
	metaarray.size = array.size
end

3 關於full Userdata的生命周期

上面定義的操作流程 是通過lua_newuserdata 在Lua中創建內存,並返回地址給C,C提供方法對內存進行操作。通過UserData的Metatable方式為Lua中訪問提供便利。但是,C中如果保存了lua_newuserdata 返回的指針,那么Lua中釋放之后,C中保存的指針就會失效. 關於UserData的生命周期問題可以參考下雲風的文章

4. Light UserData

一個light userdatum是一個表示C指針的值(也就是一個void *類型的值)。由於它是一個值,我們不能創建他們,使用函數lua_pushlightuserdata將一個light userdatum入棧:

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

Light userdata不是一個緩沖區,僅僅是一個指針,沒有metatables。像數字一樣,light userdata不需要垃圾收集器來管理她。有些人把light userdata作為一個低代價的替代實現,來代替full userdata,但是這不是light userdata的典型應用。首先,使用light userdata你必須自己管理內存,因為他們和垃圾收集器無關。第二,盡管從名字上看有輕重之分,但full userdata實現的代價也並不大,比較而言,他只是在分配給定大小的內存時候,有一點點額外的代價。
Light userdata真正的用處在於可以表示不同類型的對象。當full userdata是一個對象的時候,它等於對象自身;另一方面,light userdata表示的是一個指向對象的指針,同樣的,它等於指針指向的任何類型的userdata。所以,我們在Lua中使用light userdata表示C對象。

在雲風的博客中找到的一些應用場景:
[Lua 中寫 C 擴展庫時用到的一些技巧][http://blog.codingnow.com/2006/11/lua_c.html]
[向 lua 虛擬機傳遞信息][http://blog.codingnow.com/2006/01/_lua.html]
[去掉 full userdata 的 GC 元方法][http://blog.codingnow.com/2013/08/full_userdata_gc.html]

結束語

本來不想去花時間寫一些書上應該有的,不過在看ulua的實現中總是對其中的各種userdata的使用完全理解明白,所以還是來認真總結一下。當然中間也做了很多其他功課,后面會對其應用做進一步的介紹。

補充:

Lua5.1支持userdata中使用__gc元方法,Lua5.2之后對普通table也支持,在設置Api交互的時候比較便利。具體可以查看:
[Lua GC 之 Finalizer][http://www.cnblogs.com/JesseFang/archive/2012/12/27/2836160.html]


免責聲明!

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



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