2009 年 3 月 30 日 Lua 編程語言是一種小型的腳本語言,用于嵌入到其他程序中。通過使用 Lua 的 C API,可以編寫用于從 C 調(diào)用 Lua 以及從 Lua 調(diào)用 C 的非常干凈、簡單的代碼。對希望使用便捷的運(yùn)行時腳本語言的開發(fā)人員而言,這使他們可以輕松地實現(xiàn)腳本語言所需的基本 API 元素,然后在自己的應(yīng)用程序中使用 Lua 代碼。本文介紹 Lua 語言如何成為一種簡化常見開發(fā)任務(wù)的可行工具,并重點(diǎn)討論嵌入腳本語言的一些原因。 Lua 是一種小型腳本語言。它有多小呢?Lua 使用一個定制模式匹配特性,而不是 POSIX 正則表達(dá)式,因為一個完整的正則表達(dá)式實現(xiàn)比所有標(biāo)準(zhǔn)的 Lua 庫加起來還要大。Lua 提供的字符串匹配要簡單得多,它雖然沒有 POSIX 那么強(qiáng)大,但大小僅是 POSIX 的一小部分。 Lua 變量不是強(qiáng)類型的;雖然可以檢查一個值的類型,但是無法阻止一個變量的類型隨著時間而改變。這兩點(diǎn)正好適合腳本語言。Lua 的類型系統(tǒng)非常簡單,但很靈活。數(shù)組和關(guān)聯(lián)數(shù)組并合并為一種類型,即 table。string、number(只有浮點(diǎn)數(shù))、boolean 和特殊的 對于習(xí)慣使用其他語言的程序員來說,Lua 最令人驚訝的地方是,只有 Lua 是用可移植的 C 編寫的。它還可以與 C++ 一起使用,但是核心語言非常易于移植;雖然有少數(shù)特性需要借助宿主特性,但是 Lua 可脫離平臺依賴良好地運(yùn)行。Lua 無需進(jìn)行大量的 autoconf 測試,因為它嚴(yán)格遵從標(biāo)準(zhǔn)。Lua 是在 MIT 許可下發(fā)布的,可完全免費(fèi)用于任何用途,包括商業(yè)用途。(因此很多程序員隨意將它嵌入到應(yīng)用程序中)。 嵌入一種腳本語言可以帶來很多的好處。我將使用初學(xué) Lua 時用的一個例子:Blizzard 的大規(guī)模多玩家在線 RPG,World of Warcraft(WoW)。WoW 的用戶界面完全使用 Lua 實現(xiàn)的;開發(fā)人員提供一些基本的 API 調(diào)用,以便真正與呈現(xiàn)引擎交互,并請求關(guān)于世界的數(shù)據(jù),然后使用 Lua 作為用戶界面代碼的核心。 這樣一來,將用戶界面代碼置于沙箱中,使之遠(yuǎn)離游戲本身的干擾,從而提高安全性和可靠性,這一點(diǎn)就變得容易得多。反過來,這又意味著 Blizzard 可以向玩家開放用戶界面,使玩家可以定制代碼,改變他們與游戲交互的方式。 通常,對于很多類型的任務(wù),與低級語言相比,腳本語言更易于引入到程序中。具有隱式分配和關(guān)聯(lián)數(shù)組特性,并支持垃圾收集的語言,通常有助于更 快地開發(fā)更簡單的代碼。它也許沒那么快,但是在很多情況下,這并不是問題;例如,用戶界面只需要比用戶的鍵盤輸入或鼠標(biāo)動作快就行了。 使用腳本語言有幾種方式。第一種方式,也是最簡單的方式,是使用它控制一個程序的行為,并使用 C 代碼作為一個真正用 Lua 編寫的程序的實現(xiàn)細(xì)節(jié)。第二種方法是主要用 C 編寫一個程序,然后使用嵌入的 Lua 來存儲和報告數(shù)據(jù)和配置。第三種方式,也是最靈活的方式,是混合上述兩種方式,使用 Lua 腳本編寫一些動作,而用 C 代碼管理其他部分。Lua 與 C 之間的接口非常簡單,因此這種方式非常流行。
對于主要用 Lua 編寫的程序,如果 CPU 時間主要用于逐個的操作,頂層控制的分量很輕,那么可以編寫一個引擎。這有助于將實現(xiàn)細(xì)節(jié)與高級設(shè)計相分離。用 Lua 而不是 C 來實現(xiàn)程序的核心邏輯可以大大減少開發(fā)時間。 對于這種類型的設(shè)計,顯然期望 Lua-to-C 接口主要由將從 Lua 調(diào)用的 C 函數(shù)的定義組成,并期望一旦開始執(zhí)行腳本,將來所有對 C 代碼的使用都從腳本中調(diào)用。
我所知道的每個程序員都至少編寫過一段這樣的代碼,這段代碼什么也不做,只是將配置值存儲到一個文件中,并在以后恢復(fù)那些值。(在我使用過的 配置文件當(dāng)中,Apple 的屬性列表也許是我最喜歡的)。但是,可以使用一種嵌入式腳本語言作為這些文件的格式,使用戶可以擁有壯觀的配置選項陣列。Ion 窗口管理器使用 Lua 作為配置文件,從而使用戶可以編寫強(qiáng)大而靈活的配置。 對用戶來說,最棒的是他們不再局限于簡單的賦值;用 Lua 表達(dá)的配置文件可以有注釋、條件等??梢蕴峁┮粋€優(yōu)先的 API,用于獲取可能影響配置選擇的數(shù)據(jù)。
Lua 與 C 之間可以來回嵌套,因為 Lua 解釋器是可重入的(reentrant)。如果 C 程序在一個腳本上調(diào)用解釋器,該腳本調(diào)用一個 C 函數(shù),而這個 C 函數(shù)又再次使用 Lua 解釋器,這是允許的。 World of Warcraft 基本上就是將這樣的模型用于它的用戶界面;用戶界面中的 Lua 調(diào)用可以反過來調(diào)用引擎,而引擎又將事件交付到用 Lua 編寫的用戶界面。這樣便得到一個靈活的界面,這樣的界面具有良好的隔離性和安全性,為用戶提供了很大的自主空間,并且緩沖區(qū)溢出或崩潰的風(fēng)險很小。在基于 C 的 API 中,嵌入的代碼幾乎都會導(dǎo)致崩潰;當(dāng)使用 Lua 界面時,如果用戶界面代碼導(dǎo)致崩潰,則存在需要修復(fù)的 bug。
構(gòu)建 Lua 很容易;只需運(yùn)行 Lua 的可重入性源于將所有解釋器狀態(tài)保存在一個對象中;可以有多個解釋器,它們之間不共享變量,也沒有共享的全局項。為了與 Lua 交互,必須從創(chuàng)建一個 Lua 狀態(tài)開始: 如果 Lua 狀態(tài)現(xiàn)在可以執(zhí)行代碼了。下面是一個程序中的一個循環(huán),它只是執(zhí)行作為 Lua 代碼的參數(shù):
Lua 解釋器使用一個棧接口來與調(diào)用代碼通信。由 C 代碼將發(fā)送到 Lua 代碼的數(shù)據(jù) push 到棧上;Lua 解釋器返回的響應(yīng)也被 push 到棧上。如果傳遞給 棧上的項有類型和值。 這種接口雖然簡單,但是其功能出奇強(qiáng)大。待執(zhí)行的代碼都被同等對待;首先被 push 到棧上,然后等待 除了要在其上面進(jìn)行操作的 Lua 狀態(tài)外, 無論是對于發(fā)送參數(shù)還是獲取結(jié)果值,Lua 都自動更正值的數(shù)量,以便與傳遞給 如果提供錯誤處理程序,那么它應(yīng)該是用于處理任何發(fā)生的錯誤的 Lua 代碼在棧上的索引。對于這篇概述性的文章,我不會詳細(xì)討論錯誤處理;但是要知道兩點(diǎn),首先,錯誤處理是存在的,其次,錯誤處理是在 Lua 中進(jìn)行的。這樣非常方便。
用 C 編寫 Lua 使用的函數(shù)非常容易。如果您曾經(jīng)編寫過嵌入到其他腳本語言中的代碼,那么您也許會感到震驚。下面是一個 C 函數(shù),它接收一個數(shù)字
該函數(shù)是借助一個 Lua 狀態(tài)參數(shù)來調(diào)用的;同樣,C 與 Lua 之間的所有交互都是通過 Lua 狀態(tài)棧發(fā)生的。返回值是該函數(shù) push 到棧上的對象的數(shù)量。為了使 Lua 可以使用該函數(shù),必須做兩件事。首先是創(chuàng)建一個表示該函數(shù)的 Lua 對象,其次是為它提供一個名稱: 可以使用 棧接口大大簡化了這一點(diǎn);在此,沒有必要定義或聲明函數(shù)所帶的參數(shù)。對于如何調(diào)用代碼,Lua 是非常靈活的。不過您如果愿意,也可以進(jìn)行更仔細(xì)的檢查。
將 Lua 嵌入到其他語言(特別是 C)編寫的代碼中非常簡單,因此常常使用 Lua 提高用其他語言編寫的程序的功能性。相對于發(fā)明自己的配置語言或編寫自己的表達(dá)式解析器而言,這是一個切實可行的替代方案。 習(xí)慣于使用更大型腳本語言的程序員會對一些事情感到驚訝。首先,設(shè)置一個 Lua 解析器的成本非常小;如果想在沙箱中運(yùn)行一些東西,則可以直接這樣做。雖然 Lua 的很多安全性特性我還沒接觸過,但是應(yīng)該清楚,即使在一個 Lua 狀態(tài)中都可以有效地實現(xiàn)沙箱。 僅返回關(guān)于程序狀態(tài)的數(shù)據(jù)的函數(shù)可以成為配置腳本和數(shù)據(jù)的好工具。如果您想開始用 Lua 編寫某種頂層的邏輯,那很容易,并且非常有效。很多腳本語言勉強(qiáng)能夠與其他語言的代碼緊密協(xié)作,而 Lua 則是完全為了與其他語言緊密協(xié)作而設(shè)計的。 |