模塊就是一個程序庫,而包是一系列模塊。Lua中可以通過require來加載模塊,然后得到一個全局變量表示一個table。Lua將其所有的全局變量保存在一個被稱為“環(huán)境”的常規(guī)table中。本文首先介紹環(huán)境的一些實用技術(shù),然后介紹如何引用模塊及編寫模塊的基本方法。
1. 環(huán)境
Lua將環(huán)境table保存在一個全局變量_G中,可以對其訪問和設(shè)置。有時我們想操作一個全局變量,而它的名稱卻存儲在另一個變量中,或者需要通過運行時的計算才能得到,可以通過value = _G[varname]來獲得動態(tài)名字的全局變量。
關(guān)于“環(huán)境”的一大問題是它是全局的,任何對它的修改都會影響程序的所有部分。Lua 5允許每個函數(shù)擁有一個子集的環(huán)境來查找全局變量,可以通過setfenv來改變一個函數(shù)的環(huán)境,第一個參數(shù)若是1則表示當(dāng)前函數(shù),2則表示調(diào)用當(dāng)前函數(shù)的函數(shù)(依次類推),第二個參數(shù)是一個新的環(huán)境table。
a = 1
setfenv(1, {})
print(a) -- 會報錯,print是一個nil。這是因為一旦改變環(huán)境,所有的全局訪問都會使用新的table
為了避免上述問題,可以使用setfenv(1, {_G = _G})將原來的環(huán)境保存起來,然后用_G.print來引用。另一種組裝新環(huán)境的方法是使用繼承,下面的代碼新環(huán)境從源環(huán)境中繼承了print和a,任何賦值都發(fā)生在新的table中。
a = 1
local newgt = {}
setmetatable(newgt, {__index = _G})
setfenv(1, newgt)
print(a)
2. 模塊與包
2.1 調(diào)用模塊
要調(diào)用模塊mod中的foo方法,可以用require函數(shù)來加載,如:
require "mod"
mod.foo()
-- 或者
local m = require "mod"
m.foo()
require函數(shù)的行為: (關(guān)于require使用的路徑查找策略不贅述)
在package.loaded這個table中檢查模塊是否已加載
=> 已加載,就返回相應(yīng)的值(可見一個模塊只會加載一次)
=> 未加載,就試著在package.preload中查詢傳入的模塊名
===> 找到一個函數(shù),就以該函數(shù)作為模塊的加載器
===> 找不到,則嘗試從Lua文件或C程序庫中加載模塊
=====> 找到Lua文件,通過loadfile來加載文件
=====> 找到C程序庫,通過loadlib來加載文件
2.2 使用環(huán)境
下面的代碼說明了如何用環(huán)境來創(chuàng)建一個復(fù)數(shù)(complex)模塊:
-- 模塊設(shè)置
local modname = "complex"
local M = {}
_G[modname] = M
package.loaded[modname] = M
-- 聲明模塊從外界所需的所有東西
local _G = _G -- 保留舊環(huán)境的引用,使用時需要像_G.print這樣用
local io = io
-- 運行這句之后環(huán)境就變了
setfenv(1, M)
function new(r, i) return {r=r, i=i} end
function add(c1, c2)
return new(c1.r + c2.r, c1.i + c2.i)
end
這樣聲明函數(shù)add時,就成為了complex.add,調(diào)用同一模塊的其他函數(shù)也不需要加前綴。
2.3 module函數(shù)
Lua 5.1提供了一個新函數(shù)module,囊括了上面一系列定義環(huán)境的功能。在開始編寫一個模塊時,可以直接用module("modname", package.seeall)來取代前面的設(shè)置代碼。在一個模塊文件開頭有這句調(diào)用后,后續(xù)所有代碼都不需要限定模塊名和外部名字,同樣也不需要返回模塊table了。
2.4 子模塊與包
Lua支持具有層級的模塊名,用一個點來分隔名稱中的層級。例如一個模塊名為mod.sub,就是mod的一個子模塊。一個包(package)就是一個完整的模塊樹,它是Lua中發(fā)型的單位。注意,當(dāng)搜索一個子模塊文件時,require會把點號當(dāng)做目錄分隔符來搜索,也就是說調(diào)用require "a.b"會嘗試打開./a/b.lua,/usr/local/lua/a/b.lua,/usr/local/lua/a/b/init.lua。通過這種加載策略,可以將包的所有模塊組織到一個目錄中。
2.5 以自定義方式加載 lua 模塊
從 Lua 5.1 以后,Lua 有了標(biāo)準(zhǔn)的模塊管理庫。所以所有的模塊加載都是通過 require 來完成。 require 的設(shè)計是頗具擴展性的,它會從若干個定義好的 loader 中逐個嘗試加載新的模塊。系統(tǒng)庫中提供了四個 loader ,分別實現(xiàn)已加載模塊,Lua 模塊,和 C 擴展模塊(用了兩個 loader 來實現(xiàn) C 擴展模塊的加載)。這些 loader 以 CFunction 的形式放在 require 的環(huán)境中的一個 table 里。
如果我們想改變 lua 模塊的加載形式,只需要替換或增加一個新的 loader 就可以了。
要做的只需要模仿 loadlib.c 中的 loader_Lua 函數(shù)做一個自己的實現(xiàn),比如在我們的項目中,就允許從自定義格式數(shù)據(jù)包中,加載一個被加密過的 Lua 代碼文件。然后寫幾行 C 代碼,獲得 require 的環(huán)境(使用 lua_getfenv ),然后取出其中 "loaders" 這個 table ,把新的自定義 loader 插入到 index 2 的地方。
具體的代碼就不詳述了,仔細(xì)閱讀一下 ll_require 的實現(xiàn)(在 loadlib.c 中)就很容易明白。我們的整個工作從分析到實現(xiàn)沒有超過兩個小時,這真是得益于 Lua 良好的設(shè)計啊 :D 甚至如果你想從一個網(wǎng)絡(luò)連接的數(shù)據(jù)流中加載 Lua 模塊,或是通過 http/ftp 協(xié)議下載,也是行的通的吧。
您可能感興趣的文章:- 解決nginx+lua搭建文件上傳下載服務(wù)問題
- Lua在windows下的安裝及環(huán)境配置
- 安裝Nginx+Lua開發(fā)環(huán)境
- Mac平臺中編譯安裝Lua運行環(huán)境及Hello Lua實例
- Lua在各個操作系統(tǒng)中的開發(fā)環(huán)境配置教程
- Lua的函數(shù)環(huán)境、包實例講解
- linux系統(tǒng)安裝Nginx Lua環(huán)境
- 如何使用Vim搭建Lua開發(fā)環(huán)境詳解