Lua包庫為lua提供簡易的加載及創建模塊的方法,由require、module方法及package表組成
1、module (name [, ···])
功能:建立一個模塊。
module的處理流程:
module(name, cb1, cb2, ...)
a. 如果package.loaded[name]是一個table,那么就把這個table作為一個module
b. 如果全局變量name是一個table,就把這個全局變量作為一個module
c. 當以前兩種情況都不存表name時,將新建一個表,並使其作為全局名name的值,並package.loaded[name],而且設t._NAME為name,t._M為module,t._PACKAGE為包的全名(模塊名-組件a.b.c);最后把此module設t作為當前函數的新環境表和package.loaded[name]的新值(也就是說,舊的環境表將不能訪問,除了加上package.seeall參數外),以被require使用
即:創建table:t = {[name]=package.loaded[name], ["_NAME"]=name, ["_M"]=t, ["_PACKAGE"]=*name*(刪除了最后的".XXXX"部分)}. 如果name是一個以點分割的串,那么得到的mod類似這個樣子:
hello.world==> {["hello"]={["world"]={XXXXXXX}}}
d. 依次調用cbs:
cb1(mod), cb2(mod),...
e. 將當前模塊的環境設置為module,同時把package.loaded[name] = module
module(name)后的可選參數為接收module名的函數,如package.seeall
當在模塊文件中使用module函數的時候,如下所示;
module “mymodule”
實際上等同於以下的語句:
local modname = “mymodule” -– 定義模塊名 local M = {} -- 定義用於返回的模塊表 _G[modname] = M -- 將模塊表加入到全局變量中 package.loaded[modname] = M -- 將模塊表加入到package.loaded中,防止多次加載 setfenv(1,M) -- 將模塊表設置為函數的環境表,這使得模塊中的所有操作是以在模塊表中的,這樣定義函數就直接定義在模塊表中
通過module(),可以方便的編寫模塊中的內容。module 指令運行完后,整個環境被壓棧,所以前面全局的東西再看不見了。比如定義了一個 test 模塊,使用module("test")后,下面不再看的見前面的全局環境。 如果在這個模塊里想調用 print 輸出調試信息怎么辦呢?一個簡單的方法是
local print=print module("test")
這樣 print 是一個 local 變量,下面也是可見的。或者可以用
local _G=_G module("test")
那么 _G.print 也是可以用的。
當然還有一種巧妙的方式,lua 5.1 提供了一個 package.seeall 可以作為 module 的option 傳入module("test",package.seeall)
這樣就 OK 了。至於它們是如何工作的,還是自己讀源碼會理解的清楚一些。
具體的使用比如:聲明myModule包
-- File : myModule.lua module( "myModule", package.seeall) --顯示聲明一個myModule包 function printMsg(msg) print("myModule :" .. msg) end function setMag(msg) test.msg = msg end
調用該myModule包:
-- File : myModuleTest.lua local print = print local mod = require("myModule") print("call myModule start") test = {} mod.setMag("test module~") print(test.msg) mod.printMsg("yes")
輸出結果如下:
all myModule start
test module~
myModule :yes
通過module("...", package.seeall)
來顯示聲明一個包。看很多github上面早期的開源項目使用的都是這種方式,但官方不推薦再使用這種方式。
因為:1, package.seeall這種方式破壞了模塊的高內聚,原本引入oldmodule只想調用它的foo()函數,但是它卻可以讀寫全局屬性,例如oldmodule.os.
2, module函數的side-effect引起的,它會污染全局環境變量。
module("hello.world")會創建一個hello的table,並將這個table注入全局環境變量中,這樣使得不想引用它的模塊也能調用hello模塊的方法。
所以還是 通過return table來實現一個模塊 的更優。使用如下:
--File : module.lua local module = {} function module.foo() print("module.foo called") end function module:setMsg(msg) self.mMsg = msg end function module:getMsg() return self.mMsg end return module
-- File : myModuleTest.lua local mod = require("module") mod:setMsg("test module~") -- mod.setMsg( mod , "test module~") -- 等價上句 print(mod:getMsg())
-- 得到結果如下:test module~
2、require (modname)
功能:加載指定的模塊。
此函數先檢測package.loaded表中是否存在modname,存在則直接返回當中的值,沒有則通過定義的加載器加載modname。
1) require機制相關的數據和函數
package.path:保存加載外部模塊(lua中"模塊"和"文件"這兩個概念的分界比較含糊,因為這個值在不同的時刻會扮演不同的角色)的搜索 路徑,這種路徑是"模板式的路徑",它里面會包含可替代符號"?",這個符號會被替換,然后lua查找這個文件是否存在,如果存在就會調用其中特定的接 口。典型的值為:
"./?.lua;./?.lc;/usr/local/?/init.lua"
如果lua代碼中調用:require("hello.world")
那么lua會依次查找:
./hello/world.lua ==>這里"hello.world"變成了"hello/world",並替換了模型"./?.lua"
./hello/world.lc
.....
(這種處理方式和python類似,只不過不需要__init__.py,也有調用python中的__init__.py)
package.path在虛擬機啟動的時候設置,如果存在環境變量LUA_PATH,那么就用該環境變量作為
它的值,並把這個環境變量中的";;"替換為luaconf.h中定義的默認值,如果不存在該變量就直接使用
luaconf.h定義的默認值
package.cpath:作用和packag.path一樣,但它是用於加載第三方c庫的。它的初始值可以通過環境變量
LUA_CPATH來設置
package.loadlib(libname, func):相當與手工打開c庫libname, 並導出函數func返回,loadlib其實是ll_loadlib
2) 查找加載器順序:
require(在lua中它是ll_require函數)的查找順序如下:
a.首先在package.loaded查找modelname,如果該模塊已經存在,就直接返回它的值
b.在package.preload查找modelname, 如果preload存在,那么就把它作為loader,調用loader(L)
c.根據package.path的模式查找lua庫modelname,這個庫是通過module函數定義的,對於頂層的lua庫,文件名和庫名是一 樣的而且不需要調用顯式地在lua文件中調用module函數(在ll_require函數中可以看到處理方式),也就是說lua會根據lua文件直接完 成一個loader的初始化過程。
d.根據package.cpath查找c庫,這個庫是符合lua的一些規范的(export具有一定特征的函數接口),lua先已動態的方式加載該c庫,然后在庫中查找並調用相應名字的接口,例如:luaopen_hello_world
e.已第一個"."為分割,將模塊名划分為:(main, sub)的形式,根據package.cpath查找main,如果存在,就加載該庫並查詢相應的接口:luaopen_main_sub,例如:先查找 hello庫,並查詢luaopen_hello_world接口
f.得到loder后,用modname作為唯一的參數調用該loader函數。當然參數是通過lua的棧傳遞的,所以loader的原型必須符合lua的規范:int LUA_FUNC(lua_State *L)
ll_require會將這個loader的返回值符給package.loaded[modelname],如果loader不返回值同時 package.loaded[modelname]不存在時, ll_require就會把package.loaded[modelname]設為true。最后ll_reuqire把package.loaded [modelname]返回給調用者。當加載失敗時,require將觸發錯誤
3) require的另一個功能是避免重復加載同一個文件兩次。Lua保留一張所有已經加載的文件的列表(使用table保存)。如果一個加載的文件在表中存在require簡單的返回;表中保留加載的文件的虛名,而不是實文件名。所以如果你使用不同的虛文件名require同一個文件兩次,將會加載兩次該文件。比如require "foo"和require "foo.lua",路徑為"?;?.lua"將會加載foo.lua兩次。我們也可以通過全局變量_LOADED訪問文件名列表,這樣我們就可以判斷文件是否被加載過;同樣我們也可以使用一點小技巧讓require加載一個文件兩次。比如,require "foo"之后_LOADED["foo"]將不為nil,我們可以將其賦值為nil,require"foo.lua"將會再次加載該文件。
也可以根據自己的需要將package.loaded[modname] or _LOADED[modname]置nil,然后重新require,來完成lua熱更新功能。具體的可參見:codedump
3、package.cpath
功能:用於require C loader的搜索路徑
可以通過修改LUA_CPATH變量(luaconf.h)修改此值
4、package.loaded
功能:一個用於讓require知道哪些模塊已加載的記錄表,如果package.loaded已經有require要的值,則直接返回此值
5、package.loadlib (libname, funcname)
功能:通過動態連接C函數庫方式加載Lua擴展庫
libname為庫文件名,funcname為入口函數(此函數必須為純C接口函數 c++則需用 extern "C" {} 進行限制)
6、package.path
功能:用於require Lua loader的搜索路徑
可以通過修改LUA_PATH變量(luaconf.h)修改此值
7、package.preload
功能:一個用於保存特殊模塊加載器的表
8、package.seeall(module)
功能:為module設置一個元表,此元表的__index字段的值為全局環境_G。所以module可以訪問全局環境
注:以此函數作為module()的一個選項(詳細見module())