加载库

lua-users home
wiki

在 Windows 上,二进制模块作为 [DLL] 动态加载到 Lua 中。Windows 几乎总是通过 [LoadLibrary][LoadLibraryEx] Win32 API 函数加载 DLL。这些函数具有 Unicode (LoadLibraryW/LoadLibraryEx) 和 ANSI (LoadLibraryA/LoadLibraryExA) 变体,用于文件名参数。在 Lua 5.1 中,loadlib.c 调用 LoadLibraryA。在 Lua 5.2-beta 中,loadlib.c 调用 LoadLibraryExA,其第三个参数取自 LUA_LLE_FLAGS 定义(默认值为 0)。

Dll 路径规则

请参阅 [动态链接库搜索顺序],了解 Windows 搜索 DLL 的搜索路径顺序规则。这些规则很复杂,并且在不同版本的 Windows 之间往往会发生变化。(此外,Windows Embedded (CE) [1]/Mobile [2] 具有下面描述的特殊规则。)特别是...

首先,“如果字符串指定了完全限定路径,则该函数仅在该路径中搜索模块。” [3]。不会应用以下任何规则。此外,如果您使用 [DLL 重定向] 或清单(请参阅底部清单/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,那么它将与 Windows 系统目录中的 winmm.dll 具有相同的名称。如果您将 winmm.dll 放置在当前目录(我们假设它与 EXE 的目录不同)中,那么以下情况将发生

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

在“桌面应用程序的标准搜索顺序”下,如果“安全DllSearch模式”已启用,则这两个操作都会失败。如果提供给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 测试代码

以下测试 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;
}

链接

相关主题


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2011 年 12 月 15 日凌晨 2:40 GMT (差异)