1. 程式人生 > >ulua 路徑小記 以及 lua require 機制整理

ulua 路徑小記 以及 lua require 機制整理

ulua 路徑小記

在學習ulua時,require模組的根路徑可以為專案的Lua資料夾或者ToLua資料夾(Editor下),但是在package.pathpackage.cpath中並沒有看到當前專案的路徑,那require是如何找到Lua和ToLua資料夾的路徑的呢?
* ulua在初始化LuaState時,會先解析package.path, 並將其存入LuaFileUtilsSearchPaths中(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名),是否在LuaFileUtilsSearchPaths,如果存在就讀取檔案,若不存在則返回空(LuaFileUitls.cs line:170)

Lua require 相關整理(基於lua 5.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 都會丟擲錯誤。