关于C#:获取LoadLibrary无法加载DLL的原因

Get reason that LoadLibrary cannot load DLL

在 Linux 和 Mac 上,当使用 dlopen() 加载链接到另一个库的共享库时,如果由于缺少符号而链接失败,您可以使用 dlerror() 获取缺少符号的名称。它说类似

1
dlopen failed: cannot locate symbol"foo"

在 Windows 上,当使用 LoadLibrary() 加载缺少符号的 DLL 时,您只能从 GetLastError() 获取错误代码,对于此类问题,该错误代码始终为 127。我如何确定哪个符号是缺少,或者来自 LoadLibrary() 的更详细的错误消息解释了函数失败的原因?


我想出了一个使用 MSYS2 终端的方法。其他方法可能适用于 GUI 软件。
一个主要的警告是,这不能在纯 C/C 中完成并为最终用户发布。它仅供开发人员使用,但总比没有好。

通过下载 Windows SDK 并取消选中除调试工具之外的所有内容来安装 Windows 调试工具。
我可能是错的,但似乎安装此软件会在 Windows 内核中安装一个挂钩,以允许 LoadLibrary() 将详细信息写入标准错误。

以管理员身份打开MSYS2 Mingw64终端并运行

1
'/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/gflags.exe' -i main.exe +sls

这会将以下内容打印到终端以确认注册表已更改。

1
2
Current Registry Settings for main.exe executable are: 00000002
    sls - Show Loader Snaps

如果您需要撤消,请使用 -sls 而不是 +sls,因为我相信更改会发生在全局 Windows 中所有名为 main.exe 的程序中,而不仅仅是您的文件。

然后运行 ??main.exe 应该将调试信息打印到 stderr,但由于我正在调试 -mwindows 应用程序,它对我不起作用。

但由于某种原因,使用 MSYS2 的 gdb 运行二进制文件允许将调试信息打印到 stderr。
使用 MSYS2 安装 mingw-w64-x86_64-gdb 并运行 gdb ./main.exe 并键入 runr
搜索类似于以下内容的部分。

1
2
3
4
5
6
7
8
warning: 1ec8:43a0 @ 764081125 - LdrpNameToOrdinal - WARNING: Procedure"foo" could not be located in DLL at base 0x000000006FC40000.
warning: 1ec8:43a0 @ 764081125 - LdrpReportError - ERROR: Locating export"foo" for DLL"C:\\whatever\\plugin.dll" failed with status: 0xc0000139.
warning: 1ec8:43a0 @ 764081125 - LdrpGenericExceptionFilter - ERROR: Function LdrpSnapModule raised exception 0xc0000139
    Exception record: .exr 00000000050BE5F0
    Context record: .cxr 00000000050BE100
warning: 1ec8:43a0 @ 764081125 - LdrpProcessWork - ERROR: Unable to load DLL:"C:\\whatever\\plugin.dll", Parent Module:"(null)", Status: 0xc0000139
warning: 1ec8:43a0 @ 764081171 - LdrpLoadDllInternal - RETURN: Status: 0xc0000139
warning: 1ec8:43a0 @ 764081171 - LdrLoadDll - RETURN: Status: 0xc0000139

太棒了!它说 Procedure"foo" could not be located in DLL 所以我们有我们缺少的符号,就像在 POSIX/UNIX 的 dlopen().


虽然 Remy Lebeau 的回答在技术上是正确的,但在 Windows 平台上仍然可以通过 GetLastError() 确定丢失的符号。要了解究竟缺少什么,了解术语至关重要。

符号:

When a DLL is compiled, it's functions are referenced by symbols.
These symbols directly relate to the functions name (the symbols are
represented by visible and readable strings), its return type, and
it's parameters. The symbols can actually be read directly through a
text editor although difficult to find in large DLLs.DLL Symbols - C++ Forum

缺少符号意味着无法找到其中的函数。如果在使用 GetProcAddress() 之前发生此错误,则可能由于缺少先决条件而无法加载任意数量的函数。这意味着您尝试加载的库可能还需要第一个无法加载的库。这些依赖级别可能会持续到未知数量的层,但 GetLastError() 可以确定的唯一答案是缺少符号。一种这样的方法是使用 Dependency Walker 来确定第一个库所需的缺失库。一旦所有需要的库都可用并且可以被该库找到(可以是它自己的蠕虫罐),该库就可以通过 LoadLibrary() 加载。