前言
對(duì)于Lua的基礎(chǔ)總結(jié)總算告一段落了,從這篇博文開始,我們才真正的進(jìn)入Lua的世界,一個(gè)無(wú)聊而又有趣的世界。來(lái)吧。
Lua語(yǔ)言是一種嵌入式語(yǔ)言,它本身的威力有限;當(dāng)Lua遇見了C,那它就展示了它的強(qiáng)大威力。C和Lua是可以相互調(diào)用的。第一種情況是,C語(yǔ)言擁有控制權(quán),Lua是一個(gè)庫(kù),這種形式中的C代碼稱為“應(yīng)用程序代碼”;第二種情況是,Lua擁有控制權(quán),C語(yǔ)言是一個(gè)庫(kù),這個(gè)時(shí)候C代碼就是“庫(kù)代碼”?!皯?yīng)用程序代碼”和“庫(kù)代碼”都使用同樣的API來(lái)與Lua通信,這些API就稱為C API。
C API是一組能使C代碼與Lua交互的函數(shù),包括很多對(duì)Lua代碼的操作。如何操作,操作什么,我們的文章我都會(huì)一一總結(jié)。C API是非常靈活而強(qiáng)大的。為了表示它的NB之處,不先來(lái)一段小的DEMO程序展示一下,怎么能夠行呢?
如果你沒有接觸過C API,對(duì)于上面這段代碼,你肯定不會(huì)明白它是干什么的。什么也不說,你運(yùn)行一下吧。然后輸入Lua語(yǔ)句,看看運(yùn)行結(jié)果。
先對(duì)上述代碼引入的幾個(gè)頭文件進(jìn)行解釋一下:
頭文件lua.h定義了Lua提供的基礎(chǔ)函數(shù),包括創(chuàng)建Lua環(huán)境、調(diào)用Lua函數(shù)、讀寫Lua環(huán)境中全局變量,以及注冊(cè)供Lua調(diào)用的新函數(shù)等等;
頭文件lauxlib.h定義了輔助庫(kù)提供的輔助函數(shù),它的所有定義都以LuaL_開頭。輔助庫(kù)是一個(gè)使用lua.h中API編寫出的一個(gè)較高的抽象層。Lua的所有標(biāo)準(zhǔn)庫(kù)編寫都用到了輔助庫(kù);輔助庫(kù)主要用來(lái)解決實(shí)際的問題。輔助庫(kù)并沒有直接訪問Lua的內(nèi)部,它都是用官方的基礎(chǔ)API來(lái)完成所有工作的;
頭文件lualib.h定義了打開標(biāo)準(zhǔn)庫(kù)的函數(shù)。Lua庫(kù)中沒有定義任何全局變量。它將所有的狀態(tài)都保存在動(dòng)態(tài)結(jié)構(gòu)lua_State中,所有的C API都要求傳入一個(gè)指向該結(jié)構(gòu)的指針。luaL_newstate函數(shù)用于創(chuàng)建一個(gè)新環(huán)境或狀態(tài)。當(dāng)luaL_newstate創(chuàng)建一個(gè)新的環(huán)境時(shí),新的環(huán)境中并沒有包含預(yù)定義的函數(shù)(eg.print)。為了使Lua保持靈活,小巧,所有的標(biāo)準(zhǔn)庫(kù)都被組織到了不同的包中。當(dāng)我們需要使用哪個(gè)標(biāo)準(zhǔn)庫(kù)時(shí),就可以調(diào)用lualib.h中定義的函數(shù)來(lái)打開對(duì)應(yīng)的標(biāo)準(zhǔn)庫(kù);而輔助函數(shù)luaL_openlibs則可以打開所有的標(biāo)準(zhǔn)庫(kù)。
頭文件說完了,如果對(duì)代碼中的extern “C”不懂的同學(xué),請(qǐng)看這里。然后,就沒有然后了,然后我就先不解釋了,等我將后面的內(nèi)容總結(jié)完,再回過頭來(lái)看,你會(huì)明白的更徹底。點(diǎn)擊這里去下載完整項(xiàng)目工程。
棧
Lua和C語(yǔ)言通信的主要方法是一個(gè)無(wú)處不在的虛擬棧。幾乎所有的API調(diào)用都會(huì)操作這個(gè)棧上的值;所有的數(shù)據(jù)交換,無(wú)論是Lua到C語(yǔ)言或C語(yǔ)言到Lua都通過這個(gè)棧來(lái)完成。棧可以解決Lua和C語(yǔ)言之間存在的兩大差異,第一種差異是Lua使用垃圾收集,而C語(yǔ)言要求顯式地釋放內(nèi)存;第二種是Lua使用動(dòng)態(tài)類型,而C語(yǔ)言使用靜態(tài)類型。
為了屏蔽C和Lua之間的差異性,讓彼此之間的交互變的通常,便出現(xiàn)了這個(gè)虛擬棧。棧中的每個(gè)元素都能保存任何類型的Lua值,當(dāng)在C代碼中要獲取Lua中的一個(gè)值時(shí),只需調(diào)用一個(gè)Lua API函數(shù),Lua就會(huì)將指定值壓入棧中;要將一個(gè)值傳給Lua時(shí),需要先將這個(gè)值壓入棧,然后調(diào)用Lua API,Lua就會(huì)獲得該值并將其從棧中彈出。為了將C類型的值壓入棧,或者從棧中獲取不同類型的值,就需要為每種類型定義一個(gè)特定的函數(shù)。是的,我們的確是這么干的。
Lua嚴(yán)格地按照LIFO規(guī)范來(lái)操作這個(gè)棧。但調(diào)用Lua時(shí),Lua只會(huì)改變棧的頂部。不過,C代碼則有更大的自由度,它可以檢索棧中間的元素,甚至在棧的任意位置插入或刪除元素。
壓入棧
對(duì)于每種可以呈現(xiàn)在Lua中的C類型,API都有一個(gè)對(duì)應(yīng)的壓入函數(shù),我這里把它們都列出來(lái):
查詢?cè)?/strong>
API 使用索引來(lái)?xiàng)V械脑?。第一個(gè)壓入棧中的元素索引為1,第二個(gè)壓入的元素所以為2,以此類推,直到棧頂。我們也可以用棧頂作為參考物,使用負(fù)數(shù)來(lái)訪問棧中的元素,此時(shí),-1表示棧頂元素,-2表示棧頂下面的元素,以此類推。有的情況適合使用正數(shù)索引,而有的情況下適合使用負(fù)數(shù)索引,我們可以根據(jù)實(shí)際需求,靈活變通。
為了檢查一個(gè)元素是否為特定的類型,API提供了一系列的函數(shù)lua_is*,其中*可以是任意Lua類型。這些函數(shù)有l(wèi)ua_isnumber、lua_isstring和lua_istable等,所有這些函數(shù)都有同樣的原型:
如果要檢查一個(gè)元素是否為真正的字符串或數(shù)字(無(wú)需轉(zhuǎn)換),也可以使用這個(gè)函數(shù)。
取值
我們一般使用lua_to*函數(shù)用于從棧中獲取一個(gè)值,有以下常用的取值函數(shù):
如果指定的元素不具有正確的類型,調(diào)用這些函數(shù)也不會(huì)有問題。在這種情況下,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen會(huì)返回0,而其它函數(shù)會(huì)返回NULL。lua_tolstring函數(shù)會(huì)返回一個(gè)指向內(nèi)部字符串副本的指針,并將字符串的長(zhǎng)度存入最后一個(gè)參數(shù)len中。這個(gè)內(nèi)部副本不能修改,返回類型中的const也說明了這點(diǎn)。Lua保證只要這個(gè)對(duì)應(yīng)的字符串還在棧中,那么這個(gè)指針就是有效的。當(dāng)Lua調(diào)用的一個(gè)C函數(shù)返回時(shí),Lua就會(huì)清空它的棧。這就有一條非常重要的規(guī)則:
所有l(wèi)ua_tolstring返回的字符串在其末尾都會(huì)有一個(gè)額外的零,不過這些字符串中間也可能有零,字符串的長(zhǎng)度通過第三個(gè)參數(shù)len返回,這才是真正的字符串長(zhǎng)度。
lua_objlen函數(shù)可以返回一個(gè)對(duì)象的“長(zhǎng)度”。對(duì)于字符串和table,這個(gè)值就是長(zhǎng)度操作符“#”的結(jié)果。這個(gè)函數(shù)還可用于獲取一個(gè)“完全userdata”的大小,關(guān)于userdata,后面還會(huì)單獨(dú)總結(jié)。
其它棧操作
除了在C語(yǔ)言和棧之間交換數(shù)據(jù)的函數(shù)外,API還提供了以下這些用于普通棧操作的函數(shù):
現(xiàn)在就來(lái)簡(jiǎn)單的說說這幾個(gè)函數(shù),lua_gettop函數(shù)返回棧中元素的個(gè)數(shù),也可以說是棧頂元素的索引。lua_settop將棧頂設(shè)置為一個(gè)指定的位置,即修改棧中元素的數(shù)量,如果之前的棧頂比新設(shè)置的更高,那么高出來(lái)的這些元素會(huì)被丟棄;反之,會(huì)向棧中壓入nil來(lái)補(bǔ)足大??;比如,調(diào)用以下語(yǔ)句就能清空棧:
C API出錯(cuò)了怎么辦?
沒有十全十美,沒有任何bug的程序的。是的,再NB的人寫的程序,也可能出現(xiàn)問題,有些問題不是我們控制范圍之內(nèi)的。既然我們無(wú)法控制問題的出現(xiàn),但是我們對(duì)問題出現(xiàn)以后的行為進(jìn)行處理,比如:出現(xiàn)問題了,彈出一個(gè)友好的message,這聽起來(lái)還是不錯(cuò)的,很多程序都是這么干的。好吧,伙計(jì),如果C API出錯(cuò)了怎么辦呢?
Lua中所有的結(jié)構(gòu)都是動(dòng)態(tài)的,它們會(huì)根據(jù)需要來(lái)增長(zhǎng),或者縮小。是的,增長(zhǎng)縮小,就涉及到內(nèi)存的開辟與釋放,這有可能會(huì)出錯(cuò)的,雖然我知道這個(gè)概率是很低的,但是對(duì)于程序員來(lái)說,對(duì)于任何可能出現(xiàn)問題的地方都要進(jìn)行處理。這里有兩種情況:
1.C調(diào)用Lua代碼;
2.Lua代碼調(diào)用C。
不是所有的API函數(shù)都會(huì)拋出異常。函數(shù)luaL_newstate、lua_load、lua_pcall和lua_close都是安全的。在第一種情況下,一般都是使用lua_pcall來(lái)運(yùn)行Lua代碼,由于lua_pcall是在保護(hù)的情況下運(yùn)行l(wèi)ua代碼,如果發(fā)生了內(nèi)存分配錯(cuò)誤,lua_pcall會(huì)返回一個(gè)錯(cuò)誤代碼,并將解釋器封固在一致的狀態(tài);如果要保護(hù)那些與Lua交互的C代碼,可以使用lua_cpcall,這個(gè)函數(shù)類似于lua_pcall。
對(duì)于Lua調(diào)用C,當(dāng)將新的C函數(shù)加入Lua時(shí),可能會(huì)破壞內(nèi)存的結(jié)構(gòu)。當(dāng)我們?yōu)長(zhǎng)ua編寫庫(kù)函數(shù)時(shí)(Lua調(diào)用C的函數(shù)),只有一種標(biāo)準(zhǔn)的錯(cuò)誤處理方法。當(dāng)一個(gè)C函數(shù)檢測(cè)到一個(gè)錯(cuò)誤時(shí),它就應(yīng)該調(diào)用lua_error,lua_error函數(shù)會(huì)清理Lua中所有需要清理的東西,然后跳轉(zhuǎn)回發(fā)起執(zhí)行的那個(gè)lua_pcall,并附上一條錯(cuò)誤消息。在后面的博文中,會(huì)有這方面的代碼實(shí)例的。
標(biāo)簽:黑龍江 延邊 嘉峪關(guān) 張掖 宜賓 江西 武漢 新余
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Lua和C語(yǔ)言的交互詳解》,本文關(guān)鍵詞 Lua,和,語(yǔ)言,的,交互,詳解,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。