Lua 模組與包


模組類似於一個封裝庫,從Lua 5.1 開始,Lua 加入了標準的模組管理機制,可以把一些公用的程式碼放在一個檔案裡,以API 介面的形式在其他地方調用,有利於程式碼的重複使用和降低程式碼耦合度。

Lua 的模組是由變數、函數等已知元素組成的table,因此建立一個模組很簡單,就是建立一個table,然後把需要導出的常數、函數放入其中,最後回傳這個table就行。以下為建立自訂模組module.lua,檔案程式碼格式如下:

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
 
-- 定义一个常量
module.constant = "这是一个常量"
 
-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end
 
local function func2()
    print("这是一个私有函数!")
end
 
function module.func3()
    func2()
end
 
return module

由上可知,模組的結構就是一個table 的結構,因此可以像操作呼叫table 裡的元素那樣來操作呼叫模組裡的常數或函數。

上面的func2 宣告為程式區塊的局部變量,即表示一個私有函數,因此是不能從外部存取模組裡的這個私有函數,必須透過模組裡的公有函數來呼叫.


require 函數

Lua提供了一個名為require的函數用來載入模組。要載入一個模組,只需要簡單地呼叫就可以了。例如:

require("<模块名>")

require "<模块名>"

執行 require 後會傳回一個由模組常數或函數組成的 table,並且也會定義一個包含該 table 的全域變數。

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
 
print(module.constant)
 
module.func3()

以上程式碼執行結果為:

这是一个常量
这是一个私有函数!

或是給載入的模組定義一個別名變量,方便呼叫:

-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")
 
print(m.constant)
 
m.func3()

以上程式碼執行結果為:

这是一个常量
这是一个私有函数!

載入機制

對於自訂的模組,模組檔案不是放在哪個檔案目錄都行,函數require 有它自己的檔案路徑載入策略,它會嘗試從Lua 檔案或C 程式庫中加載模組。

require 用來搜尋 Lua 檔案的路徑是存放在全域變數 package.path 中,當 Lua 啟動後,會以環境變數 LUA_PATH 的值來初始這個環境變數。如果沒有找到該環境變量,則使用一個編譯時定義的預設路徑來初始化。

當然,如果沒有LUA_PATH 這個環境變量,也可以自訂設置,在當前用戶根目錄下打開.profile 檔案(沒有則創建,打開.bashrc 檔案也可以),例如把"~/lua /" 路徑加入LUA_PATH 環境變數裡:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"

檔案路徑以";" 號分隔,最後的2 個";;" 表示新加的路徑後面加上原來的預設路徑。

接著,更新環境變數參數,使其立即生效。

source ~/.profile

這時假設 package.path 的值是:

/Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua

那麼呼叫 require("module") 時就會嘗試開啟以下檔案目錄去搜尋目標。

/Users/dengjoe/lua/module.lua;
./module.lua
/usr/local/share/lua/5.1/module.lua
/usr/local/share/lua/5.1/module/init.lua
/usr/local/lib/lua/5.1/module.lua
/usr/local/lib/lua/5.1/module/init.lua

如果找過目標文件,則會呼叫 package.loadfile 來載入模組。否則,就會去找 C 程式庫。

搜尋的檔案路徑是從全域變數 package.cpath 取得,而這個變數則是透過環境變數 LUA_CPATH 來初始。

搜尋的策略跟上面的一樣,只不過現在換成搜尋的是 so 或 dll 類型的檔案。如果找到,那麼 require 就會透過 package.loadlib 來載入它。


C 套件

Lua和C是很容易結合的,使用C為Lua寫包。

與Lua中寫入套件不同,C套件在使用以前必須先載入並連接,在大多數系統中最容易的實作方式是透過動態連接函式庫機制。

Lua在一個叫做loadlib的函數內提供了所有的動態連結的功能。這個函數有兩個參數:函式庫的絕對路徑和初始化函數。所以典型的呼叫的例子如下:

local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path, "luaopen_socket")

loadlib函數載入指定的函式庫並且連接到Lua,然而它不會開啟函式庫(也就是說沒有呼叫初始化函數),反之他會傳回初始化函數作為Lua的一個函數,這樣我們就可以直接在Lua中呼叫他。

如果載入動態函式庫或在尋找初始化函數時出錯,loadlib會傳回nil和錯誤訊息。我們可以修改前面一段程式碼,使其檢測錯誤然後調用初始化函數:

local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\windows\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f()  -- 真正打开库

一般情況下我們期望二進位的發布庫包含一個與前面程式碼段相似的stub文件,安裝二進位庫的時候可以隨便放在某個目錄,只需要修改stub檔案對應二進位庫的實際路徑即可。

將stub檔案所在的目錄加入LUA_PATH,這樣設定後就可以使用require函式載入C函式庫了。