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

Viewed 205

I'm attempting to debug some very opaque issues with DLLs in Unreal on a CI machine (see Unreal: Diagnosing why Windows cannot load a DLL for more information). glu32.dll seems to be the DLL at which the Unreal process falls over, and as Windows Server doesn't contain all the graphics-related DLLs that normal Windows 10 does, I was recommended to upload certain DLLs from my machine/Microsoft redistributables in order to make sure the Unreal build process could run.

For sanity purposes, I've written a small utility program to test whether glu32.dll on my machine can be dynamically loaded and can have its functions called correctly. I'm planning to run this executable on the troublesome CI machine soon to see what happens.

The code for the program is below:

#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;
}

When I run the executable and point it to glu32.dll in the System32 folder, I get expected output:

> 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)

However, if I copy the DLL to my desktop and run the program again, although the DLL and function appear to be loaded, the string returned from the function is empty:

> 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)

Why would this be? It's exactly the same DLL, just in a different folder, and I would have thought that any other dependent DLLs that it references should still be available because they're all in System32. Is there some mystical property of Windows DLLs that I'm not familiar with that might cause this to happen?

2 Answers

This is an example of why one shall not mess around with system DLLs.

The DLL in question, like many Microsoft DLLs, uses MUI (Multilingual User Interface).

If you look at its resources, it has no resources except a MUI type resource, pointing to a folder containing the corresponding .mui file, which contains its actual (internationalized) resources.

So, if you still want to copy it, at least also copy the corresponding .mui file:

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

The en-US part may be different on your system depending on the default locale.

EDIT: I saw only now from your log that you didn't rename the file. Then I'm not sure what it can be. I'll leave this explanation up anyway because it would also be what happens if one were to rename that file, so maybe it is helpful to someone else...


It seems to me as if you renamed the DLL file (not just loaded it from another location but with another filename as well).

glu32.dll doesn't like to be renamed, because in some places code like GetModuleHandle("glu32.dll") is used instead of saving the hInstance received in DllMain into a global variable and using that handle (which is what should have been done, but unfortunately it isn't what Microsoft did). If you rename the DLL, this call will return NULL. Now unfortunately there also isn't much error handling going on in glu32 in that case.

The error strings are stored in global arrays of some sort, but they are lazy-loaded there from string table resources. The first time you call gluErrorString, the error strings are loaded using LoadString which takes the hInstance of the DLL. With the renamed DLL, this will be the bogus NULL handle, and calling LoadString(NULL, ...) will return 0, indicating an error. Normally the number returned is the length of the string. glu32 doesn't handle the zero case in any special way and just copies the zero characters to the array and happily returns an empty string to you at the end.

Related