Windows 如果将 DLL 移动到不同位置,DLL 函数行为会有所不同

Windows DLL function behaviour is different if DLL is moved to different location

我正在尝试在 CI 机器上调试 Unreal 中一些非常不透明的 DLL 问题(有关详细信息,请参阅 Unreal: Diagnosing why Windows cannot load a DLL)。 glu32.dll 似乎是 Unreal 进程崩溃的 DLL,并且由于 Windows 服务器不包含普通 Windows 10 包含的所有与图形相关的 DLL,因此建议我从我的 machine/Microsoft 可再发行组件上传某些 DLL,以确保虚幻构建过程可以 运行.

出于理智考虑,我编写了一个小实用程序来测试我机器上的 glu32.dll 是否可以动态加载并可以正确调用其函数。我计划很快 运行 在麻烦的 CI 机器上运行这个可执行文件,看看会发生什么。

程序代码如下:

#include <windows.h> 
#include <iostream>
#include <GL/gl.h>

extern "C"
{
    typedef const GLubyte* (__stdcall *ErrorStringFunc)(GLenum error);
}

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        std::cerr << "Usage: GLU32Loader.exe <path to glu32.dll>" << std::endl;
        return 1;
    }

    const char* path = argv[1];
    std::cout << "Attempting to load: " << path << std::endl;

    HMODULE dllHandle = LoadLibraryA(path);

    if (!dllHandle)
    {
        std::cerr << "Could not load " << path << std::endl;
        return 1;
    }

    std::cout << "Successfully loaded DLL: 0x" << dllHandle << std::endl;

    const char* funcName = "gluErrorString";

    std::cout << "Looking up function: " << funcName << std::endl;
    ErrorStringFunc func = reinterpret_cast<ErrorStringFunc>(GetProcAddress(dllHandle, funcName));

    if (func)
    {
        std::cout << "Successfully loaded function: 0x" << func << std::endl;

        const GLubyte* str = (*func)(100902);
        std::cout << "Error string for value 100902: \"" << str << "\" (0x" << static_cast<const void*>(str) << ")" << std::endl;
    }
    else
    {
        std::cerr << "Failed to load function " << funcName << std::endl;
    }

    FreeLibrary(dllHandle);

    return 0;
}

当我 运行 可执行文件并将其指向 System32 文件夹中的 glu32.dll 时,我得到预期的输出:

> GLU32Loader.exe "C:\Windows\System32\glu32.dll"
Attempting to load: C:\Windows\System32\glu32.dll
Successfully loaded DLL: 0x00007FFC7A350000
Looking up function: gluErrorString
Successfully loaded function: 0x00007FFC7A35C650
Error string for value 100902: "out of memory" (0x000001E5757F51D0)

但是,如果我将 DLL 复制到我的桌面并再次 运行 程序,虽然 DLL 和函数看起来已加载,但函数返回的字符串为空:

> GLU32Loader.exe "C:\Users\Jonathan\Desktop\glu32.dll"
Attempting to load: C:\Users\Jonathan\Desktop\glu32.dll
Successfully loaded DLL: 0x00007FFC8DDB0000
Looking up function: gluErrorString
Successfully loaded function: 0x00007FFC8DDBC650
Error string for value 100902: "" (0x0000025C5236E520)

为什么会这样?它是完全相同的 DLL,只是在不同的文件夹中,我认为它引用的任何其他依赖 DLL 应该仍然可用,因为它们都在 System32 中。是否有一些我不熟悉的神秘 属性 of Windows DLL 可能会导致这种情况发生?

编辑:我现在才从您的日志中看到您没有重命名该文件。然后我不确定它会是什么。无论如何我都会留下这个解释,因为如果重命名该文件也会发生这种情况,所以也许它对其他人有帮助...


在我看来,您似乎重命名了 DLL 文件(不仅从另一个位置加载它,而且还使用另一个文件名)。

glu32.dll不喜欢重命名,因为有些地方使用了像GetModuleHandle("glu32.dll")这样的代码,而不是将DllMain收到的hInstance存入全局变量并使用该句柄(应该 已经完成,但不幸的是,这不是 Microsoft 所做的)。如果重命名 DLL,此调用将 return NULL。不幸的是,在那种情况下 glu32 中也没有太多错误处理。

错误字符串存储在某种全局数组中,但它们是从字符串 table 资源中延迟加载的。第一次调用 gluErrorString 时,使用 LoadString 加载错误字符串,它采用 DLL 的 hInstance。对于重命名的 DLL,这将是伪造的 NULL 句柄,调用 LoadString(NULL, ...) 将 return 0,指示错误。通常数字 returned 是字符串的长度。 glu32 不以任何特殊方式处理零的情况,只是将零字符复制到数组中,最后 return 很高兴地给你一个空字符串。

这是一个例子,说明为什么不应乱用系统 DLL。

与许多 Microsoft DLL 一样,有问题的 DLL 使用 MUI(多语言用户界面)。

如果你查看它的资源,除了 MUI 类型的资源,它没有其他资源,指向包含相应 .mui 文件的文件夹,该文件包含它的实际(国际化)资源。

所以,如果你还想复制它,至少也要复制对应的.mui文件:

  • System32\glu32.dll<my_files>\glu32.dll
  • System32\en-US\glu32.dll.mui<my_files>\en-US\glu32.dll.mui

根据默认区域设置,en-US 部分在您的系统上可能有所不同。