Lua與C交互的棧是一個重要的概念。文章首先解釋了為什麼要引入Lua棧,然後對訪問棧常用的API進行了總結,並使用這些API的注意事項,最後從Lua源代碼來看棧的實現原理。
Lua 語言 15 分鐘快速入門 http://www.linuxidc.com/Linux/2013-06/86582.htm
Lua程序設計(第2版)中文 PDF http://www.linuxidc.com/Linux/2013-03/81833.htm
Lua程序設計(第二版)閱讀筆記 http://www.linuxidc.com/Linux/2013-03/81834.htm
NetBSD 將支持用 Lua 腳本開發內核組件 http://www.linuxidc.com/Linux/2013-02/79527.htm
CentOS 編譯安裝 Lua LuaSocket http://www.linuxidc.com/Linux/2011-08/41105.htm
Lua棧概述
我們知道Lua是一種嵌入式語言,所有的Lua程序最後都需要通過Lua解釋器(即Lua虛擬機)把其解析成字節碼的形式才能執行。 一方面,我們可以在一個應用程序(擁有主動權)中嵌入Lua解釋器,此時使用Lua的目的是方便擴展這個應用程序,用Lua實現相應的工作;另一方面,我們在Lua程序(此時用Lua語言編寫的程序擁有主動權)中也可以使用那些用C語言實現的函數(比如string.find())。
在上面兩個描述中,都涉及到Lua與C之間數據交換,而在這兩種語言交換數據時,我們自然面臨兩個問題,一個是Lua是動態類型語言,在Lua語言中沒有類型定義的語法,每個值都攜帶了它自身的類型信息,而C語言是靜態類型語言;另一個是Lua使用垃圾收集,可以自動管理內存,而C語言要求程序自己釋放分配的內存,需應用程序自身管理內存。為了解決這個兩個問題,Lua引入了一個虛擬棧。
為了方便Lua與C交互,比如在C代碼中調用Lua函數,Lua官方提供了一系列的API和庫。利用這些API,C語言就可以方便從Lua中獲取相應的值,也可以方便地把值返回給Lua,當然,這些操作都是通過棧作為橋梁來實現的。
訪問Lua棧的API
--------------------------------------------------------------------------------
Lua提供了大量的API用於操作棧,這些API方便我們向棧中壓入元素、查詢棧中的元素、修改棧的大小等操作。下面對常用的API使用做一個簡單總結,尤其在使用這些API的需要注意的地方。
1、向棧中壓入元素
向棧中壓入元素的API,通常都是以lua_push*開頭來命名,比如lua_pushnunber、lua_pushstring、lua_pushcfunction、lua_pushcclousre等函數都是向棧頂中壓入一個Lua值。通常在Lua代碼中調用C實現的函數並且被調用的C函數有返回值時,被調用的C函數通常就要用到這些接口,把返回值壓入棧中,返回給Lua(當然這些C函數也要求返回一個值,告訴Lua一共返回(壓入)了多少個值)。值得注意的是,向棧中壓入一個元素時,應該確保棧中具有足夠的空間,可以調用lua_checkstack來檢測是否有足夠的空間。
實質上這些API是把C語言裡面的值封裝成Lua類型的值壓入棧中的,對於那些需要垃圾回收的元素,在壓入棧時,都會在Lua(也就是Lua虛擬機中)生成一個副本。比如lua_pushstring(lua_State *L, const char *s)會向中棧壓入由s指向的以'\0'結尾的字符串,在C中調用這個函數後,我們可以任意釋放或修改由s指向的字符串,也不會出現問題,原因就是在執行lua_pushstring過程中Lua會生成一個內部副本。實質上,Lua不會持有指向外部字符串的指針,也不會持有指向任何其他外部對象的指針(除了C函數,因為C函數總是靜態的)。
總之,一旦C中值被壓入棧中,Lua就會生成相應的結構(實質就是Lua中實現的相應數據類型)並管理(比如自動垃圾回收)這個值,從此不會再依賴於原來的C值。
2、獲取棧中的元素
從棧中獲取一個值的函數,通常都是以lua_to*開頭來命名,比如lua_tonumber、lua_tostring、lua_touserdata、lua_tocfunction等函數都是從棧中指定的索引處獲取一個值。通常在C函數中,可以用這些接口獲取從Lua中傳遞給C函數的參數。如果指定的元素不具有正確的類型,調用這些函數也不會出問題的。在這種情況下,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen會返回0,而其他函數會返回NULL。對於返回NULL的函數,可以直接通過返回值,即可以知道調用是否正確;對於返回0的函數,通常先需要使用lua_is*系列函數,判斷調用是否正確。
注意lua_to*和lua_is*系列函數都是試圖轉換棧中元素為相應中的值。比如lua_isnumber不會檢查是否為數字類型,而是檢查是否能轉換為數字類型;lua_isstring也類似,它對於任意數字,lua_isstring都返回真。要想真正返回棧中元素的類型,可以用函數lua_type。每種類型對應於一個常量(LUA_TNIL,LUA_TBOOLEAN,LUA_TNUMBER等),這些常量定義在頭文件lua.h中。
值得一提是lua_tolstring函數,它的函數原型是const char *lua_tolstring (lua_State *L, int index, size_t *len)。它會把棧中索引為index的Lua值裝換為一個C字符串。若參數Len不為NULL,則*Len會保存字符串的長度。棧中的Lua值必須為string或number類型,否則函數返回NULL。若棧中Lua值為number類型,則該函數實質會改變棧中的值為string類型,由於這個原因,在利用lua_next遍歷棧中的table時,對key使用lua_tolstring尤其需要注意,除非知道key都是string類型。lua_tolstring函數返回的指針,指向的是Lua虛擬機內部的字符串,這個字符串是以'\0'結尾的,但字符串中間也可能包含值為0的字符。由於Lua自身的垃圾回收,因此當棧中的字符串被彈出後,函數返回的指針所有指向的字符串可能就不能再有效了。也說明了,當一個C函數從Lua收到一個字符串參數時,在C函數中,即不能在訪問字符串時從棧中彈出它,也不能修改字符串。
3、其他操作棧的函數
int lua_call (lua_State *L, int nargs, int nresults);
調用棧中的函數,在調用lua_call之前,程序必須首先要保證被調用函數已壓入棧,其次要被調用函數需要的參數也已經按順序壓入棧,也就是說,第一個參數最先被壓入棧,依次類推。nargs是指需要壓入棧中參數的個數,當函數被調用後,之前壓入的函數和參數都會從棧中彈出,並將函數執行的結果按順序壓入棧中,因此最後一個結果壓入棧頂,同時,壓入棧的個數會根據nresults的值做調整。與lua_call相對應的是lua_pcall函數,lua_pcall會以保護模式調用棧中的函數。以保護模式調用意思是,當被調用的函數發生任何錯誤時,該錯誤不會傳播,不像lua_call會把錯誤傳遞到上一層,lua_pcall所調用的棧中函數發送錯誤時,lua_pcall會捕捉這個錯誤,並向棧中壓入一個錯誤信息,並返回一個錯誤碼。在應用程序中編寫主函數時,應該使用lua_pcall來調用棧中的函數,捕獲所有錯誤。而在為Lua編寫擴展的C函數時,應該調用lua_call,把錯誤返回到腳本層。
void lua_createtable (lua_State *L, int narr, int nrec);
創建一個新的table,並把它壓入棧頂,參數narr和nrec分別指新的table將會有多少個數組元素和多少需要hash的元素,Lua會根據這個兩個值為新的table預分配內存。對於事先知道table結構,利用這兩個參數能提高創建新table的性能。對於事先不知道table結構,則可以使用void lua_newtable (lua_State *L),它等價於lua_createtable(L, 0, 0)。
除了上面提到的C API,還有許多其他有用的C API,比如操作table的接口有:lua_getfield、lua_setfield、lua_gettable、lua_settable等接口,在具體使用時,可以參照Lua手冊。
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-05/102530p2.htm