ulua 路徑小記
ToLua
在學習ulua時,require
模塊的根路徑可以為項目的Lua文件夾或者ToLua文件夾(Editor下),但是在package.path
和package.cpath
中并沒有看到當前項目的路徑,那require是如何找到Lua和ToLua文件夾的路徑的呢?
- ulua在初始化
LuaState
時,會先解析package.path
, 并將其存入LuaFileUtils
的SearchPaths
中(LuaState.cs line:603),并將Lua和ToLua的目錄添加其中(LuaState.cs line:187)。 - 在LuaState的構造函數中,會調用
ToLua.OpenLib(T)
,該方法定義了一下特殊方法,可以在lua中使用,如typeof
等。 -
OpenLib
方法會在package.loaders
數組中,再添加一個loader,并且將其放到數組的第二個位置。(ToLua.cs line:192)注:Lua require模塊時,會依次調用package.loader中的方法,找到則返回 - loader的作用就是檢測傳入的文件名(module名),是否在
LuaFileUtils
的SearchPaths
,如果存在就讀取文件,若不存在則返回空(LuaFileUitls.cs line:170)
Lua require 相關整理(基于lua 5.1)
摘自http://cloudwu.github.io/lua53doc/manual.html#6.3
根據lua5.1 手冊做了部分修改
package.preload
為指定的模塊,設置loader,在require模塊時,先去查詢這張表,如果有值,則使用preload中的loader(可以用于修改特殊模塊的加載策略)
package.path
這個路徑被 require 在 Lua 加載器中做搜索時用到。
在啟動時,Lua 用環境變量 LUA_PATH
來初始化這個變量。 或采用 luaconf.h
中的默認路徑。 環境變量中出現的所有 ";;
" 都會被替換成默認路徑。
package.cpath
這個路徑被 require
在 C 加載器中做搜索時用到。
Lua 用和初始化 Lua 路徑 package.path
相同的方式初始化 C 路徑 package.cpath
。 它會使用環境變量LUA_CPATH
初始化。 要么就采用 luaconf.h
中定義的默認路徑。
package.loaders(Lua5.3中, 改名為searchers)
用于 require
控制如何加載模塊的表。
這張表內的每一項都是一個 查找器函數。 當查找一個模塊時, require
按次序調用這些查找器, 并傳入模塊名(require
的參數)作為唯一的一個參數。 此函數可以返回另一個函數(模塊的 加載器)加上另一個將傳遞給這個加載器的參數。 或是返回一個描述為何沒有找到這個模塊的字符串 (或是返回 nil 什么也不想說)。
Lua 用四個查找器函數初始化這張表。
第一個查找器就是簡單的在 package.preload
表中查找加載器。
第二個查找器用于查找 Lua 庫的加載庫。 它使用儲存在 package.path
中的路徑來做查找工作。路徑是一個包含有一系列以分號分割的 模板 構成的字符串。 對于每個模板,都會用 name
替換其中的每個問號(如果有的話)。 且將其中的 點替換為系統的目錄分割符(如 Unix中的"/")。 然后嘗試打開這個文件名。
例如,如果路徑是字符串
"./?.lua;./?.lc;/usr/local/?/init.lua"
搜索 foo.a
這個名字將 依次嘗試打開文件 ./foo/a.lua
, ./foo/a.lc
,以及 /usr/local/foo/a/init.lua
。
第三個查找器用于查找 C 庫的加載庫。 它使用儲存在 package.cpath
中的路徑來做查找工作。 例如,如果 C 路徑是這樣一個字符串
"./?.so;./?.dll;/usr/local/?/init.so"
查找器查找模塊 foo
會依次嘗試打開文件 ./foo.so
,./foo.dll
, 以及 /usr/local/foo/init.so
。 一旦它找到一個 C 庫, 查找器首先使用動態鏈接機制連接該庫。 然后嘗試在該庫中找到可以用作加載器的 C 函數。 這個 C 函數的名字是 "luaopen_
" 緊接模塊名的字符串, 其中字符串中所有的下劃線都會被替換成點。 此外,如果模塊名中有橫線, 橫線后面的部分(包括橫線)都被去掉。 例如,如果模塊名為 a.b.c-v2.1
, 函數名就是luaopen_a_b_c
。
第四個搜索器是 一體化加載器。 它從 C 路徑中查找指定模塊的根名字。 例如,當請求 a.b.c
時, 它將查找 a
這個 C 庫。 如果找得到,它會在里面找子模塊的加載函數。 在我們的例子中,就是找 luaopen_a_b_c
。 利用這個機制,可以把若干 C 子模塊打包進單個庫。 每個子模塊都可以有原本的加載函數名。
require(modname)
加載一個模塊。 這個函數首先查找 package.loaded
表, 檢測 modname
是否被加載過。 如果被加載過,require
返回 package.loaded[modname]
中保存的值。(防止重復加載) 否則,它試著為模塊尋找 加載器 。
首先 require
查找 package.preload[modname]
。 如果這里有一個值,這個值(必須是一個函數)就是那個加載器。 否則 require
使用 Lua 加載器去查找 package.path
的路徑。 如果查找失敗,接著使用 C 加載器去查找 package.cpath
的路徑。 如果都失敗了,再嘗試 一體化 加載器 (參見 package.loaders
。
每次找到一個加載器,require
都用一個參數調用加載器: modname
。 如果加載器返回非空值, require
將這個值賦給package.loaded[modname]
。 如果加載器沒能返回一個非空值用于賦給 package.loaded[modname]
, require
會在那里設入 true 。 無論是什么情況,require
都會返回 package.loaded[modname]
的最終值。
如果在加載或運行模塊時有錯誤, 或是無法為模塊找到加載器, require
都會拋出錯誤。