加载库 |
|
LoadLibraryW/LoadLibraryEx
) 和 ANSI (LoadLibraryA/LoadLibraryExA
) 变体,用于文件名参数。在 Lua 5.1 中,loadlib.c 调用 LoadLibraryA
。在 Lua 5.2-beta 中,loadlib.c 调用 LoadLibraryExA
,其第三个参数取自 LUA_LLE_FLAGS
定义(默认值为 0)。
请参阅 [动态链接库搜索顺序],了解 Windows 搜索 DLL 的搜索路径顺序规则。这些规则很复杂,并且在不同版本的 Windows 之间往往会发生变化。(此外,Windows Embedded (CE) [1]/Mobile [2] 具有下面描述的特殊规则。)特别是...
首先,“如果字符串指定了完全限定路径,则该函数仅在该路径中搜索模块。” [3]。不会应用以下任何规则。此外,如果您使用 [DLL 重定向] 或清单(请参阅底部清单/WinSxS 链接),这些技术将在使用以下其他技术之前用于查找 DLL。
否则,接下来将应用以下两个规则
foo.dll
”,并且您稍后尝试使用绝对路径加载自己的“foo.dll
”,则两者都会加载。
kernel32.dll
”,并且您稍后尝试使用绝对路径加载自己的“kernel32.dll
”,则两者都会加载。
如果仍然没有找到匹配项,则会按以下所述执行标准或备用搜索顺序。
Dll
Search
Mode 启用”,[4](通常是默认行为),Windows 将在“应用程序加载的目录”(即 EXE 所在的位置)中搜索,然后是各种 Windows 系统目录,然后是当前目录,最后是PATH
环境变量中的目录。Windows 系统目录中的 DLL 有可能发生冲突,在 Windows 7 中,它们的数量接近 1500 个。
LoadLibraryEx
,“如果lpFileName
指定了相对路径,则整个相对路径将附加到 DLL 搜索路径中的每个标记。要从相对路径加载模块而不搜索任何其他路径,请使用 [GetFullPathName] 获取非相对路径,并使用非相对路径调用LoadLibraryEx
。如果模块作为数据文件加载,并且相对路径以.\
或..\
开头,则相对路径将被视为绝对路径。” [3](这是否也适用于LoadLibrary
?)由于 Lua 模块作为图像而不是数据文件加载(LOAD_LIBRARY_AS_DATAFILE
),因此package.cpath
的“.\?.dll
”将不会被LoadLibraryEx
视为绝对路径(如下所述),并且您需要使用类似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
的目录不同)。
LOAD_WITH_ALTERED_SEARCH_PATH
的LoadLibraryEx
,“如果使用此值,并且lpFileName
指定了相对路径,则行为未定义。” 以及“LOAD_WITH_ALTERED_SEARCH_PATH
不适用于相对路径。” [3] 这表明,如果 Lua 二进制模块 DLL 是通过./?.dll
而不是通过绝对路径定位的,则 Lua 5.2.0-beta LUA_LLE_FLAGS=LOAD_WITH_ALTERED_SEARCH_PATH
将无法正常工作。
.\\winmm.dll
”的LoadLibraryEx + LOAD_WITH_ALTERED_SEARCH_PATH
似乎优先从当前目录而不是从 Windows 7 的系统目录加载。
Windows Embedded (CE) [1] / Mobile [2] 具有以下特殊行为
Sample.cpl
执行 LoadLibrary
,操作系统不会加载 Sample.cpl
,而是再次加载 Sample.dll
。对于具有相同名称但位于不同目录中的模块,也存在类似的限制。例如,如果对 \\Windows\Sample.dll
调用 LoadLibrary
,然后对 \\MyDir\Sample.dll
调用 LoadLibrary
,则将简单地重新加载 \\Windows\Sample.dll
。"
.exe
启动目录;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,那么它将与 Windows 系统目录中的 winmm.dll 具有相同的名称。如果您将 winmm.dll
放置在当前目录(我们假设它与 EXE 的目录不同)中,那么以下情况将发生
package.loadlib('winmm.dll', 'luaopen_winmm') package.loadlib('.\\winmm.dll', 'luaopen_winmm')
在“桌面应用程序的标准搜索顺序”下,如果“安全Dll
Search
模式”已启用,则这两个操作都会失败。如果提供给
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
)都存储在“DLLs”目录中(该目录只有大约二十几个文件),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
可以相应地更新。
LoadLibraryEx
在 LUA_LLE_FLAGS=LOAD_WITH_ALTERED_SEARCH_PATH
和诸如 package.cpath=".\\?.dll"
之类的相对路径下触发未定义行为进行处理。建议的方法是将路径设为绝对路径。这可以通过 GetFullPathName
完成,尽管它可能会导致多线程和共享库情况下的损坏(为什么是共享库???)。 [5] 或者,可以从 package.cpath
中删除“.\\?.dll
”,但用户仍然可能会尝试添加它。
loadlib.c
findfile()
可能需要一个小补丁。
(脚注:也许 LUA_PATH
也应该匹配 .luac
文件。另一方面,将 .luac
文件重命名为 .lua
文件可以在不使路径复杂化的前提下工作。)
以下测试 DLL 与 package.loadlib
结合使用,可能有助于理解此行为。
//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
luaconf.h
选项用于 LOAD_WITH_ALTERED_SEARCH_PATH
LOAD_WITH_ALTERED_SEARCH_PATH
LoadLibrary
的建议" - 建议 LOAD_WITH_ALTERED_SEARCH_PATH
"?.dll;"
之前使用 LUA_CDIR"\\?.dll;"