Search code examples
c++cwinapidll

LoadLibraryEx cannot find DLL or its dependencies


I am currently writing a program which loads a DLL (a.dll), which relies on another DLL (b.dll).

When using the LoadLibrary function, loading a.dll succeeds; so long as b.dll is in the same directory as the executable (no matter where a.dll is located). However, for my purposes, b.dll cannot be in the same directory as the executable. As such, I resorted to LoadLibraryEx, which has a flags parameter to give more specific parameters on loading DLLs. However, none of them seem to do anything. No matter what I have tried for the past few hours, all calls to LoadLibraryEx fail and return the error code 126.

HANDLE handle;

// this works if b.dll is in the same
// directory as the executable file
handle = LoadLibrary("a.dll");
FreeLibrary(handle);

// this also works, since using a value
// of zero for the flags makes it behave
// the same as LoadLibrary
handle = LoadLibraryEx("a.dll", NULL, 0);
FreeLibrary(handle);

// this does not work, even if b.dll is
// in the same directory as the executable
// file
handle = LoadLibraryEx("a.dll", NULL,
    LOAD_LIBRARY_SEARCH_APPLICATION_DIR);

// this does not work, even if b.dll is
// in the same directory as a.dll
handle = LoadLibraryEx("a.dll", NULL,
    LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);

// this does not work, even if b.dll is
// in one of the directores specified by
// a sucessful call to AddDllDirectory
DLL_DIRECTORY_COOKIE cookie =
    AddDllDirectory(L"C:\\Fully\\Qualified\\Path");
handle = LoadLibraryEx("a.dll", NULL,
    LOAD_LIBRARY_SEARCH_USER_DIRS);
RemoveDllDirectory(cookie);

Solution

  • The way LoadLibraryEx locates DLLs also applies to the DLL being loaded, not just its dependencies (this should have been obvious to me in hindsight, but that's where coding until 1:30AM gets you). If we have the following file structure, for example:

    C:/
    ├─ Program Files/
    │  ├─ My Program/
    │  │  ├─ addons/
    │  │  │  ├─ a.dll
    │  │  ├─ depend/
    │  │  │  ├─ b.dll
    │  │  ├─ program.exe
    

    And program.exe runs the following:

    DLL_DIRECTORY_COOKIE cookie =
        AddDllDirectory(L"C:\\Program Files\\My Program\\depend");
    HANDLE handle = LoadLibraryEx("addons\\a.dll", NULL,
        LOAD_LIBRARY_SEARCH_USER_DIRS);
    

    Windows will attempt to open a.dll at C:\\Program Files\\My Program\\depend\\addons\\a.dll, which fails, since that file does not exist. In this situation, this can be fixed in one of two ways.

    1. By using an absolute path instead of a relative one.
    // Since an absolute path is used, Windows does not need to search
    // for a.dll (and thus, successfully opens it). Afterwards, it will
    // search for, and successfully find, b.dll in the previously given
    // depend folder.
    DLL_DIRECTORY_COOKIE cookie =
        AddDllDirectory(L"C:\\Program Files\\My Program\\depend");
    HANDLE handle =
        LoadLibraryEx("C:\\Program Files\\My Program\\addons\\a.dll",
        NULL, LOAD_LIBRARY_SEARCH_USER_DIRS);
    
    FreeLibrary(handle);
    RemoveDllDirectory(cookie);
    
    1. Or, by adding an an additional DLL directory (in this case, C:\\Program Files\\My Program).
    // Here, since the program folder has also been added as a
    // directory to search through, a.dll will be successfully
    // found in the addons folder.
    DLL_DIRECTORY_COOKIE programCookie =
        AddDllDirectory(L"C:\\Program Files\\My Program\\");
    DLL_DIRECTORY_COOKIE dependCookie =
        AddDllDirectory(L"C:\\Program Files\\My Program\\depend");
    HANDLE handle =
        LoadLibraryEx("addons\\a.dll",
        NULL, LOAD_LIBRARY_SEARCH_USER_DIRS);
    
    FreeLibrary(handle);
    RemoveDllDirectory(programCookie);
    RemoveDllDirectory(dependCookie);
    

    Furthermore, make sure to use wide character strings (e.g., L"path") when supplying paths to AddDllDirectory! When I didn't use them, LoadLibraryEx would sometimes look in the wrong folder. For example, C:\\Fully\\Qualified\\Path would become something like C:\\Fully\\重武器家伙\\Path — at least according to Process Monitor. On that note, make sure you always use the correct string encoding for whatever function you are calling.


    Special thanks to @273K for mentioning the SysInternals Process Monitor to me in the comments. Both it and the Dynamic-Link Library Security article from Microsoft were instrumental in solving this issue.