載入函式庫 |
|
LoadLibraryW/LoadLibraryExW
) 和 ANSI (LoadLibraryA/LoadLibraryExA
) 變數,用於檔案名稱參數。在 Lua 5.1 中,loadlib.c 會呼叫 LoadLibraryA
。在 Lua 5.2-beta 中,loadlib.c 會呼叫 LoadLibraryExA
,而且其第三個參數會從 define LUA_LLE_FLAGS
中取得(預設值為 0)。
參閱 [動態連結函式庫搜尋順序](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。
否則,接下來會套用以下兩條規則
foo.dll
」已載入,然後您使用絕對路徑嘗試載入自己的「foo.dll
」,這兩個檔案都會載入。
kernel32.dll
」已載入,然後您使用絕對路徑嘗試載入自己的「kernel32.dll
」,這兩個檔案都會載入。如果仍然找不到符合的檔案,會執行搜尋順序(標準或備用),如下所述。
Dll
搜尋
模式的桌面應用程式標準搜尋順序」中 [4],這通常是預設的行為,Windows 會在以下搜尋:「應用程式載入的目錄」(亦即 EXE 檔案所在位置),然後是各種 Windows 系統目錄,接下來是目前的目錄,最後是 PATH
環境變數中的目錄。Windows 系統目錄中的 DLL 有可能發生干擾,而在 Windows 7 中此類 DLL 有將近 1500 個。
LoadLibraryEx
,「如果 lpFileName
指定一個相對路徑,完整的相對路徑會附加到 DLL 搜尋路徑中的每個權杖。若要從相對路徑載入模組但不搜尋其他路徑,請使用 [GetFullPathName] 取得非相對路徑,並使用非相對路徑呼叫 LoadLibraryEx
。如果將模組載入為資料檔,而且相對路徑以 .\
或 ..\
開頭,相對路徑會視為絕對路徑。」[3](LoadLibrary
是否也是如此?)由於 Lua 模組以影像形式載入,而非資料檔形式載入 (LOAD_LIBRARY_AS_DATAFILE
),LoadLibraryEx
會將「.\?.dll
」的 package.cpath
視為絕對路徑(如下所述),如果您想建立絕對路徑,則需要使用像 GetFullPathName
之類的函數。 [9][10]
GetFullPathName
有個令人擔憂的聲明:「多執行緒應用程式和共用函式庫程式碼不應使用 GetFullPathName
函數,並且應避免使用相對路徑名稱,[...] 以免造成資料錯誤」。 [5]當將相對路徑傳遞給 LoadLibrary/LoadLibraryEx
時,是否也會發生同樣的錯誤?
LOAD_WITH_ALTERED_SEARCH_PATH
的 LoadLibraryEx
,「如果使用此值,而且 lpFileName
指定絕對路徑,系統會使用在備註區段中論及的替代檔案搜尋策略,以尋找已指定模組造成載入的相關可執行模組。」[3]換句話說,如果您的二進位 Lua 模組 DLL 使用 LoadLibrary
在沒有絕對路徑的狀況下進一步載入其他 DLL,LOAD_WITH_ALTERED_SEARCH_PATH
會開始針對包含您的 DLL 的目錄執行相對搜尋(這個目錄可能與包含 lua.exe
的目錄不同)。
LoadLibraryEx + LOAD_WITH_ALTERED_SEARCH_PATH
時,若指定了「lpFileName
」的相對路徑,「行為未定義」。此外,「LOAD_WITH_ALTERED_SEARCH_PATH
不適用於相對路徑」[3]。這表示如果 Lua 5.2.0-beta 執行檔二進位模組 DLL 是透過 ./?.dll
這種相對路徑,而非絕對路徑來找尋的話,那麼 LUA_LLE_FLAGS=LOAD_WITH_ALTERED_SEARCH_PATH
將無法正確執行。
.\\winmm.dll
」載入 LoadLibraryEx + LOAD_WITH_ALTERED_SEARCH_PATH
時,似乎會優先從「目前所在目錄」而非系統目錄載入。Windows Embedded (CE) [1] / Mobile [2] 具備以下特殊行為:
Sample.cpl
進行 LoadLibrary
呼叫,作業系統不會載入 Sample.cpl
,而會再次載入 Sample.dll
。名稱相同但位於不同目錄的模組也會有類似的限制。例如,如果對 \\Windows\Sample.dll
進行 LoadLibrary
呼叫,然後再對 \\MyDir\Sample.dll
進行 LoadLibrary
呼叫的話,會再次載入 \\Windows\Sample.dll
的內容。
lpLibFileName
參數指定的絕對路徑;可執行檔的啟動目錄;Windows 目錄;ROM DLL 檔案;OEM 指定的搜尋路徑。」
LoadLibrary
/LoadLibraryEx
不支援清單、LOAD_WITH_ALTERED_SEARCH_PATH
以及其他進階功能。
常見的使用案例包含一個應用程式程序 (例如:lua.exe
) 會載入二進制的 Lua 模組 (例如:foo.dll
)。lua.exe
和 foo.dll
本身也可能直接或間接地依賴其他 (非 Lua) 的程式庫/系統 DLL (例如:bar.dll
)。最簡單的檔案組織方式通常是將所有這些檔案都放在同一個目錄中,根據上述的搜尋規則,這大多時候「是」可行的。(不過,如果二進制的 Lua 模組 DLL 和程式庫/系統 DLL 的名稱碰巧發生衝突的話,就有可能造成混淆,這部分後面會再討論。)很多人 (例如:LuaForWindows?) 認為 DLL 會讓包含 EXE 的目錄變得雜亂,他們比較喜歡將 DLL 放置在一個不需要特別查看的子目錄中。你可以將兩種 DLL 放在同一個子目錄,也可以再細分成兩個子目錄。(許多腳本語言的 Linux 發行版會使用後者來避免腳本語言的程式庫散落在系統程式庫中。)不論如何,將這些檔案放置在不同的目錄中可能會讓情況變得複雜,因為你需要有一個機制才能讓檔案互相找到。解決方案有:
LoadLibraryEx
選項(例如:LOAD_WITH_ALTERED_SEARCH_PATH
)、[GetModuleFileName] 和 [GetFullPathName] 來強制使用絕對路徑或更特定的路徑。
與使用相對路徑的系統 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],它就絕對不會在目前的目錄中搜尋。)
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.dll
和 socket/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
變數供此變更使用。
abc.dll
」,在讓 Windows 掃描搜尋路徑時,會有明顯的效能開銷(> 2 個數量級)。(David Burgess 在 LuaList:2006-12/msg00049.html)
cyg
」前綴(例如 cyglua-5.1.dll
),類似於 Linux 共用函式庫上的「lib
」前綴。 [13][7].pyd
副檔名,而非 .dll
。詳見 (*.pyd 檔案與 DLL 是否相同?) 。「輸出檔案應命名為 spam.pyd
(在卸載模式)或 spam_d.pyd
(在除錯模式)。選擇 .pyd
副檔名是為了避免與系統函式庫 spam.dll
混淆,而您的模組可以是它的 Python 介面。」 [8]。Python 模組 DLL(例如 _sqlite3.pyd
)和 Python C 函式庫 DLL(例如 sqlite3.dll
)都儲存在「DLL」目錄中(當中只有大約二十幾個檔案),Python 原始程式碼函式庫(例如 sqlite3\dump.py
)儲存在「Lib」目錄中,而 python.exe
(和 pythonw.exe
)則在父目錄中。但是,Cygwin Python 2.6 沒有完全遵循這個規則(例如 _sqlite3.dll
和 cygsqlite3-0.dll
)。Python [dynload_win.c] 使用 LoadLibraryEx
搭配 LOAD_WITH_ALTERED_SEARCH_PATH
,而且路徑強制使用 GetFullPathName
變成絕對路徑。TIFF.dll
(Perl 二進制模組)和 libtiff-3_.dll
(C 函式庫)之類的名稱,而且這些名稱儲存在不同的目錄。Perl [win32.c] 使用 LoadLibraryEx
與 LOAD_WITH_ALTERED_SEARCH_PATH
,而路徑似乎被迫為絕對路徑(PerlDir_mapA
)。zlib.so
之類的二進制模組 DLL 名稱和像是 zlib1.dll
之類的系統 DLL 名稱。後者儲存在自己的目錄,而前者與 ruby.exe
和 rubyw.exe
儲存在同一個目錄。Ruby dl.h/dln.c 看起來只使用 LoadLibrary
(而非 LoadLibraryEx
)。共同的主題是,儘量讓指令碼語言二進制 C 模組和系統函式庫使用不同的 DLL 命名慣例。
foo.luad
」或「foo-lua.dll
」,而非「foo.dll
」,以避免與非 Lua DLL 產生衝突。LUA_CPATH
可以相應更新。LUA_LLE_FLAGS=LOAD_WITH_ALTERED_SEARCH_PATH
和相對路徑(例如 package.cpath=".\\?.dll"
)時 Lua 5.2.0-beta LoadLibraryEx
引發未定義的行為,或許應採取一些措施。建議的方法是讓路徑變成絕對路徑。這可以透過 GetFullPathName
來進行,儘管這可能導致多執行緒和共用函式庫的狀況產生毀損(為什麼是共用函式庫???)。 [5] 或者,可以從 package.cpath
中移除「.\\?.dll
」,但使用者可能仍會嘗試新增它。loadlib.c
findfile()
可能需要一個小的修補程式。(註腳:或許 LUA_PATH
也應該符合 .luac
檔案。另一方面,將 .luac
檔案重新命名為 .lua
可行,而且不會使路徑複雜化。)
搭配 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; }
LoadLibrary
和 5.2」- Scuri 建議使用 LOAD_WITH_ALTERED_SEARCH_PATH
LUA_LLE_FLAGS
LOAD_WITH_ALTERED_SEARCH_PATH
使用 luaconf.h
選項LOAD_WITH_ALTERED_SEARCH_PATH
LoadLibrary
」- 建議使用 LOAD_WITH_ALTERED_SEARCH_PATH
?.dll;
」之前使用 LUA_CDIR"\\?.dll;"