Get DLL path at runtime

Viewed 84467

I want to get a dll's directory (or file) path from within its code. (not the program's .exe file path)

I've tried a few methods I've found:
GetCurrentDir - gets the current directory path.
GetModuleFileName - gets the executable's path.

So how can i find out in which dll the code is in ?
I'm looking for something similar to C#'s Assembly.GetExecutingAssembly

11 Answers

For Delphi users:

SysUtils.GetModuleName(hInstance);              //Works; hInstance is a special global variable
SysUtils.GetModuleName(0);                      //Fails; returns the name of the host exe process
SysUtils.GetModuleName(GetModuleFilename(nil)); //Fails; returns the name of the host exe process

In case your Delphi doesn't have SysUtils.GetModuleName, it is declared as:

function GetModuleName(Module: HMODULE): string;
var
   modName: array[0..32767] of Char; //MAX_PATH is for a single filename; paths can be up to 32767 in NTFS - or longer.
begin
   {
      Retrieves the fully qualified path for the file that contains the specified module. 
      The module must have been loaded by the current process.
   }
   SetString(Result, modName, GetModuleFileName(Module, modName, Length(modName)));
end;

Provided you implemented the following dll entry point: (usually dllmain.cpp)

BOOL APIENTRY DllMain( HMODULE hModule,
                   DWORD  ul_reason_for_call,
                   LPVOID lpReserved
                 )

You can simply do:

switch (ul_reason_for_call)
{ 
case DLL_PROCESS_ATTACH:
{
    TCHAR dllFilePath[512 + 1] = { 0 };
    GetModuleFileNameA(hModule, dllFilePath, 512)
}
    break;
case DLL_THREAD_ATTACH: break;
...

dllFilePath will then contain the path to where the current dll code was loaded. In this case hModule is passed by the process loading the dll.

I wanted to achieve something similar, except wanted to make similar function into one .dll - but then you cannot use __ImageBase, since it's specific to that .dll where function is located. I've even tried to override using approach

GetDllPath( HMODULE hDll = (HMODULE) __ImageBase)

But that did not work our either. (For some reason returns application path after that.)

Then I've figured out - why I don't use VirtualQuery, and use function pointer and get HMODULE from there. But again - how to get function pointer of caller ?

And now it gets back to call stack determination - I won't bother you with all dirty details, just follow links of referred links.

Here is whole code snapshot:

//
//  Originated from: https://sourceforge.net/projects/diagnostic/
//
//  Similar to windows API function, captures N frames of current call stack.
//  Unlike windows API function, works with managed and native functions.
//
int CaptureStackBackTrace2( 
    int FramesToSkip,                   //[in] frames to skip, 0 - capture everything.
    int nFrames,                        //[in] frames to capture.
    PVOID* BackTrace                    //[out] filled callstack with total size nFrames - FramesToSkip
)
{
#ifdef _WIN64
    CONTEXT ContextRecord;
    RtlCaptureContext(&ContextRecord);

    UINT iFrame;
    for (iFrame = 0; iFrame < (UINT)nFrames; iFrame++)
    {
        DWORD64 ImageBase;
        PRUNTIME_FUNCTION pFunctionEntry = RtlLookupFunctionEntry(ContextRecord.Rip, &ImageBase, NULL);

        if (pFunctionEntry == NULL)
        {
            if (iFrame != -1)
                iFrame--;           // Eat last as it's not valid.
            break;
        }

        PVOID HandlerData;
        DWORD64 EstablisherFrame;
        RtlVirtualUnwind(0 /*UNW_FLAG_NHANDLER*/,
            ImageBase,
            ContextRecord.Rip,
            pFunctionEntry,
            &ContextRecord,
            &HandlerData,
            &EstablisherFrame,
            NULL);

        if(FramesToSkip > (int)iFrame)
            continue;

        BackTrace[iFrame - FramesToSkip] = (PVOID)ContextRecord.Rip;
    }
#else
    //
    //  This approach was taken from StackInfoManager.cpp / FillStackInfo
    //  http://www.codeproject.com/Articles/11221/Easy-Detection-of-Memory-Leaks
    //  - slightly simplified the function itself.
    //
    int regEBP;
    __asm mov regEBP, ebp;

    long *pFrame = (long*)regEBP;               // pointer to current function frame
    void* pNextInstruction;
    int iFrame = 0;

    //
    // Using __try/_catch is faster than using ReadProcessMemory or VirtualProtect.
    // We return whatever frames we have collected so far after exception was encountered.
    //
    __try {
        for (; iFrame < nFrames; iFrame++)
        {
            pNextInstruction = (void*)(*(pFrame + 1));

            if (!pNextInstruction)     // Last frame
                break;

            if (FramesToSkip > iFrame)
                continue;

            BackTrace[iFrame - FramesToSkip] = pNextInstruction;
            pFrame = (long*)(*pFrame);
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }

#endif //_WIN64
    iFrame -= FramesToSkip;
    if(iFrame < 0)
        iFrame = 0;

    return iFrame;
} //CaptureStackBackTrace2



//
//  Gets .dll full path or only directory.
//
CStringW GetDllPath( bool bPathOnly /* = false */ )
{
    void* pfunc = &GetDllPath;
    wchar_t path[MAX_PATH] = { 0 };
    MEMORY_BASIC_INFORMATION info;
    HMODULE hdll;

    CaptureStackBackTrace2(1, 2, &pfunc);

    // Get the base address of the module that holds the current function
    VirtualQuery(pfunc, &info, sizeof(MEMORY_BASIC_INFORMATION));

    // MEMORY_BASIC_INFORMATION::AllocationBase corresponds to HMODULE
    hdll = (HMODULE)info.AllocationBase;

    // Get the dll filename
    if ( !GetModuleFileName( hdll, path, MAX_PATH ) )
        return L"";

    if ( bPathOnly )
    {
        wchar_t* p = wcsrchr( path, '\\' );
        if ( p )
            *p = 0;
    }

    return path;
} //GetDllPath

Imho, Remy Lebau’s answer is the best, but lacks like all other answers to render the directory of the DLL. I quote the original question: “I want to get a dll's directory (or file) path from within its code. (not the program's .exe file path).”

As Remy and Jean-Marc Volle pointed out, the DLL entry function DllMain usually contained in dllmain.cpp provides the handle to the DLL. This handle is often necessary, so it will be saved in a global variable hMod. I also add variables of type std::wstring to store the fully qualified name and the parent path of the DLL.

HMODULE hMod;
std::wstring PathAndName;
std::wstring OnlyPath;
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
  switch (ul_reason_for_call)
  {
    case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH:
       case DLL_PROCESS_DETACH:
     break;
  }
  hMod = hModule;
  const int BUFSIZE = 4096;
  wchar_t buffer[BUFSIZE];
  if (::GetModuleFileNameW(hMod, buffer, BUFSIZE - 1) <= 0)
  {
    return TRUE;
  }

  PathAndName = buffer;

  size_t found = PathAndName.find_last_of(L"/\\");
  OnlyPath = PathAndName.substr(0, found);

  return TRUE;
}

These global variables can be used inside the DLL.

I'm (in my case) comping those who revealed the easy DllMain method. The job was to find what is the machine bitness of the current thread running inside of a dll. To be absolutely sure I wanted also the name of running dll and exe. Name of caller is found by NULL parameter:

char exePat[MAX_PATH] = { 0 }; GetModuleFileName(NULL, exePat, MAX_PATH);

Using the hModule of DllMain feels most complete and trustable way to find dll name and machine bitness. Code was found so that to obtain the "machine" only module is needed. Here is the part which brings up the name too for debugging purpose.

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  reason,LPVOID lpReserved ) {

     char  path[MAX_PATH] = { 0 };
     GetModuleFileNameA(hModule, path, MAX_PATH);
     PIMAGE_DOS_HEADER startPoint = (PIMAGE_DOS_HEADER)hModule;
     PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)startPoint + startPoint->e_lfanew);
     PIMAGE_FILE_HEADER fileHeader = &pNtHeaders->FileHeader;
     short machine = fileHeader->Machine;
     ////0x8664 is 64bit  0x14c is 32bit.
     int stop = 0;
     return TRUE;
 }
Related