載入函式庫

lua-users home
wiki

在 Windows 中,二進位模組是透過 [DLL](http://zh.wikipedia.org/wiki/%E5%8B%95%E6%A8%A1%E9%93%BE%E5%BA%93) 以動態連結的方式載入 Lua 中。Windows 幾乎總是透過 [LoadLibrary](http://msdn.microsoft.com/zh-tw/library/windows/desktop/ms684175.aspx) 或 [LoadLibraryEx](http://msdn.microsoft.com/zh-tw/library/windows/desktop/ms684179.aspx) Win32 API 函式載入 DLL。這些函式擁有 Unicode (LoadLibraryW/LoadLibraryExW) 和 ANSI (LoadLibraryA/LoadLibraryExA) 變數,用於檔案名稱參數。在 Lua 5.1 中,loadlib.c 會呼叫 LoadLibraryA。在 Lua 5.2-beta 中,loadlib.c 會呼叫 LoadLibraryExA,而且其第三個參數會從 define LUA_LLE_FLAGS 中取得(預設值為 0)。

DLL 路徑規則

參閱 [動態連結函式庫搜尋順序](http://msdn.microsoft.com/zh-tw/library/windows/desktop/ms682586.aspx),了解 Windows 搜尋 DLL 的搜尋路徑順序的規則。這些規則很複雜,而且在各種版本的 Windows 中變動很大。(此外,Windows Embedded (CE) [1](http://msdn.microsoft.com/zh-tw/library/ee488148.aspx) / 行動裝置 [2](http://msdn.microsoft.com/zh-tw/library/aa909187.aspx) 還有特別的規則,後文會再說明。) 特別是...

首先,「如果字串指定完整的路徑,函式只會在那條路徑中搜尋該模組。」 [3](http://msdn.microsoft.com/zh-tw/library/windows/desktop/ms684179.aspx)。不會套用以下其他的規則。此外,如果您使用 [DLL 轉向](http://msdn.microsoft.com/zh-tw/library/windows/desktop/ms682600.aspx) 或清單(請參閱底端的清單/WinSxS 連結),這些技術會用於在其他以下技術之前尋找 DLL。

否則,接下來會套用以下兩條規則

如果仍然找不到符合的檔案,會執行搜尋順序(標準或備用),如下所述。

Windows Embedded (CE) [1] / Mobile [2] 具備以下特殊行為:

DLL 的配置

常見的使用案例包含一個應用程式程序 (例如:lua.exe) 會載入二進制的 Lua 模組 (例如:foo.dll)。lua.exefoo.dll 本身也可能直接或間接地依賴其他 (非 Lua) 的程式庫/系統 DLL (例如:bar.dll)。最簡單的檔案組織方式通常是將所有這些檔案都放在同一個目錄中,根據上述的搜尋規則,這大多時候「是」可行的。(不過,如果二進制的 Lua 模組 DLL 和程式庫/系統 DLL 的名稱碰巧發生衝突的話,就有可能造成混淆,這部分後面會再討論。)很多人 (例如:LuaForWindows?) 認為 DLL 會讓包含 EXE 的目錄變得雜亂,他們比較喜歡將 DLL 放置在一個不需要特別查看的子目錄中。你可以將兩種 DLL 放在同一個子目錄,也可以再細分成兩個子目錄。(許多腳本語言的 Linux 發行版會使用後者來避免腳本語言的程式庫散落在系統程式庫中。)不論如何,將這些檔案放置在不同的目錄中可能會讓情況變得複雜,因為你需要有一個機制才能讓檔案互相找到。解決方案有:

注意事項

與使用相對路徑的系統 DLL 名稱衝突:舉例來說,如果你想要命名 Lua 模組為「winmm」,並將它編譯成一個名叫 winmm.dll 的 DLL,會發生這個 DLL 的名稱與 Windows 系統目錄中的 winmm.dll 相同的情況。如果你將 winmm.dll 放在目前的目錄中 (我們假設目錄不同於 EXE 的目錄),那麼下列程式碼

package.loadlib('winmm.dll', 'luaopen_winmm')
package.loadlib('.\\winmm.dll', 'luaopen_winmm')

根據「啟用安全Dll搜尋模式」的「桌面應用程式的標準搜尋順序」都會失敗。如果傳遞給 LoadLibraryEx 的路徑是相對路徑 (正如這裡所探討的兩種情況),Windows 會先在 EXE目錄 (lua.exe 的所在目錄) 中搜尋,然後才會在 Windows 系統目錄中搜尋,最後才是目前的目錄。(此外,如果你的應用程式呼叫 [SetDllDirectory],它就絕對不會在目前的目錄中搜尋。)

注意:[DLL 預載攻擊] 出於安全原因建議,「若永遠不需要從目前目錄載入 DLL,只要呼叫 SetDllDirectory 並輸入參數 "" 即可」。

但是,如果您將 winmm.dll 放在與 lua.exe 相同的目錄中,則上述兩個陳述通常都會執行。然而,如果您的程序(或甚至是作業系統間接載入到您的程序中的 DLL,且在您的控制之外)嘗試載入系統「winmm.dll」(通常會嘗試在沒有絕對路徑的情況下載入),它反而會收到您的 winmm.dll,而該檔案無法正常運作且會當機。(我們也許可以用清單來避免這個問題?)

我們也可以傳遞一個絕對路徑

package.loadlib([[c:\path\to\winmm.dll]], 'luaopen_winmm')

現在,Windows 只會嘗試從指定的位置載入模組,它會開始執行這項動作。然而,如果您的程序稍後(而非較早)嘗試另外載入系統「winmm.dll」(通常沒有絕對路徑),它會失敗,因為它會看到同一個名稱的 DLL 已載入並使用該程式,但這是不正確的。

上述問題在很大程度上似乎不會影響一個案例,也就是您的模組命名為「foo.winmm」,位於 .\foo\winmm.dll,並使用相對路徑載入。當將 .\foo\winmm.dll 傳遞至 LoadLibraryEx 時,Windows 會將 .\foo\winmm.dll 附加至其所有搜尋路徑,當然這些可能都不存在。然而,如果您的程序稍後嘗試在沒有路徑的情況下載入系統「winmm.dll」,我們仍會遇到問題。

如果除了 Lua 之外的其他指令碼語言也嘗試使用類似的模組命名原則,這些問題可能會變得更加複雜。

可以透過確保 Lua 模組 DLL 名稱永遠與系統 DLL 名稱無衝突,來避免上述問題。您可能會這麼做,也就是為 Lua 模組 DLL 名稱加上一個全球唯一的字尾和/或副檔名,例如,不要是「.dll」,而是「.luad」(類似於 Python 的 .pyd,並遵循與 .luac 相似的慣例),「.luadll」、「.lua.dll」(或許不是一個好主意,因為「當 DLL 名稱中有多一個點時,許多事情都會出錯」,MikePall[11] 中提到)或「-lua.dll」。LuaList:2011-09/msg00398.html 。您需要適當調整 package.cpath 以處理這項問題。在 Linux 上,系統共用函式庫是由 lib 為前綴 [12]

但是,上述解決方案對於Windows CE/Mobile/Embedded來說是不夠的,其額外限制為「在決定DLL是否已載入時,會忽略所有路徑 [和副檔名] 資訊」。這導致了LuaSocket mime/core.dllsocket/core.dll 發生衝突 [6]。這也會阻止上述的唯一副檔名解決方案,但像是 -lua.dll 之類的東西除外(唯一的副檔名實際上是檔名的其中一部分)。對於這一點,有一個總體解決方案,至少適用於 Windows 嵌入式版本,那就是使用像 socket_core-lua.dll 之類的 DLL 名稱,其永遠不會與 mime_core-lua.dll 或任何可能的 core.dll 衝突。可以在 loadlib.c 中對 findfile() 進行變更,讓模組名稱中的點以「_」取代「/」,而非相反 [6]。Lua 5.2 提供了 LUA_CSUBSEP 變數供此變更使用。

其他評論

比較

共同的主題是,儘量讓指令碼語言二進制 C 模組和系統函式庫使用不同的 DLL 命名慣例。

可能的結論

(註腳:或許 LUA_PATH 也應該符合 .luac 檔案。另一方面,將 .luac 檔案重新命名為 .lua 可行,而且不會使路徑複雜化。)

DLL 測試程式碼

搭配 package.loadlib,以下測試 DLL 可能有助於了解此行為。

//cl test.c /link /DLL /out:test.dll winmm.lib

#include <stdio.h>
#include <windows.h>

__declspec(dllexport) int __cdecl luaopen_winmm(void * x) {
  printf("called test, %d\n", (int)timeGetTime());
  return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
  printf("load/unload my DLL\n");
  return TRUE;
}

連結

相關主題


最新異動 · 喜好設定
編輯 · 歷史記錄
最後編輯時間為 2011 年 12 月 15 日星期四上午 2:40 (格林威治時間) (diff)