Q:

Visual C++: Linking a DLL from another DLL using a relative path

I have the following file structure
C:\Application\application.exe
C:\Application\plugins\myplugin\myplugin.dll
C:\Application\plugins\myplugin\libs\utils.dll

Here application.exe loads myplugin.dll dynamically via LoadLibrary. Note that I have no control over application.exe as I am developing the plugin only.
What I want is to make myplugin.dll load libs\utils.dll via a relative path (ideally using static linking). That is, I don't want to be dependent on the location of application.exe. I currently add C:\Application\plugins\myplugin\libs to the PATH environment variable when installing myplugin, but environment variables are not an ideal solution and I want to avoid doing that.
I was hoping I could use assemblies and config files to specify the relative path libs\utils.dll in myplugin.dll. And I tried this, but to no avail. I then saw someone mentioning here on StackOverflow that config files only work for applications (i.e. executables). But as I said above, I have no control over application.exe. Is there a solution to this seemingly simple problem which I believe on Unix systems can be solved via rpaths?

A:

You cannot statically link to a DLL path at all, relative or absolute.  The PE imports table only contains filenames.  That is why a DLL search path exists to locate DLLs.
If you want to control where utils.dll is loaded from, you have to load it dynamically.  myplugin.dll can retrieve its own path using GetModuleFileName(), using the module handle that is passed in to its DllMain() entry point.  It can then remove the filename from the path, append the relative path to the path, and then load the DLL when needed (not inside of DllMain() itself, or else a deadlock/crash may occur).
There are two ways you can handle this:

load everything dynamically yourself:
#include <windows.h> 
#include <shlwapi.h> 

#pragma comment(lib, "shlwapi.lib")

HINSTANCE hThisDLL = NULL;
HMODULE hUtils = NULL;

typedef ReturnType __CallingConv (*DllFuncType)(Params);
DllFuncType DllFunc = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    if (fdwReason == DLL_PROCESS_ATTACH)
    {
        hThisDLL = hinstDLL;
        ...
    }

    return TRUE;
}

...

ReturnType CallDllFunc(Params)
{
    if (!hUtils)
    {
        TCHAR szUtilsFileName[MAX_PATH] = {0};
        GetModuleFileName(hThisDLL, szUtilsFileName, MAX_PATH);

        if (!PathRemoveFileSpec(szUtilsFileName))
        {
            // do something...
            return ...;
        }

        if (!PathAppend(szUtilsFileName, TEXT("libs\\utils.dll")))
        {
            // do something...
            return ...;
        }

        hUtils = LoadLibrary(szUtilsFileName);
        if (!hUtils)
        {
            // do something...
            return ...;
        }
    }

    if (!DllFunc)
    {
        DllFunc = (DllFuncType) GetProcAddress(hUtils, "DllFuncName");
        if (!DllFunc)
        {
            // do something...
            return ...;
        }
    }

    return DllFunc(Params);
}

static link to everything like you normally would, but then utilize your compiler's delay load feature (if supported) so you can specify the DLL's filename dynamically at runtime, but still statically link to the DLL function itself (the delay-load mechanism will call GetProcAddress() for you).
#include <windows.h> 
#include <shlwapi.h> 
#include <delayimp.h>

#pragma comment(lib, "Delayimp.lib")
#pragma comment(lib, "shlwapi.lib")

HINSTANCE hThisDLL = NULL;

FARPROC WINAPI DelayLoadHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
    if ((dliNotify == dliNotePreLoadLibrary) &&
        (strcmpi(pdli->szDll, "utils.dll") == 0))
    {
        TCHAR szUtilsFileName[MAX_PATH] = {0};
        GetModuleFileName(hThisDLL, szUtilsFileName, MAX_PATH);

        if (!PathRemoveFileSpec(szUtilsFileName))
        {
            // do something...
            return NULL;
        }

        if (!PathAppend(szUtilsFileName, TEXT("libs\\utils.dll")))
        {
            // do something...
            return NULL;
        }

        HMODULE hUtils = LoadLibrary(szUtilsFileName);
        return reinterpret_cast<FARPROC>(hUtils);
    }

    return NULL;
}

PfnDliHook __pfnDliNotifyHook2 = DelayLoadHook;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    if (fdwReason == DLL_PROCESS_ATTACH)
    {
        hThisDLL = hinstDLL;
        ...
    }

    return TRUE;
}

...

ReturnType CallDllFunc(Params)
{
    return DllFuncName(Params);
}

