【问题标题】:Get DLL path at runtime在运行时获取 DLL 路径
【发布时间】:2023-03-08 07:36:01
【问题描述】:

我想从其代码中获取 dll 的 目录(或文件)路径。 (不是程序的.exe文件路径)

我尝试了一些我发现的方法:
GetCurrentDir - 获取当前目录路径。
GetModuleFileName - 获取可执行文件的路径。

那么我怎样才能找出代码在哪个 dll 中呢?
我正在寻找类似于 C# 的Assembly.GetExecutingAssembly

【问题讨论】:

    标签: c++ dll


    【解决方案1】:

    您可以使用GetModuleHandleEx 函数并获取DLL 中静态函数的句柄。你会发现更多信息here

    之后你可以使用GetModuleFileName从你刚刚获得的句柄中获取路径。有关该电话的更多信息是here

    一个完整的例子:

    char path[MAX_PATH];
    HMODULE hm = NULL;
    
    if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 
            GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
            (LPCSTR) &functionInThisDll, &hm) == 0)
    {
        int ret = GetLastError();
        fprintf(stderr, "GetModuleHandle failed, error = %d\n", ret);
        // Return or however you want to handle an error.
    }
    if (GetModuleFileName(hm, path, sizeof(path)) == 0)
    {
        int ret = GetLastError();
        fprintf(stderr, "GetModuleFileName failed, error = %d\n", ret);
        // Return or however you want to handle an error.
    }
    
    // The path variable should now contain the full filepath for this DLL.
    

    【讨论】:

    • 这项技术对我来说非常有效。使用 __ImageBase 接受的答案导致我的 dll 在使用 VC11 初始化期间崩溃。只是在引用 __ImageBase 的代码中链接导致一些 CRT 或 ATL 初始化代码崩溃,并出现 0xC0000005。
    • 有趣的是,lpModuleName 存在 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS 标志可能不仅是本地函数。它也可能是一个(本地)静态变量的地址。
    • +1;很好的答案(只是利用它);优于公认的。
    • PathRemoveFileSpec 也是你的朋友。
    • 在 Windows 10 64 位上的 win32 dll 上为我工作,但我也使用 wchar_t 而不是 char 和 wchar_t full_file_path[MAX_PATH];并转换为 (LPCWSTR)&functionInThisDll 而不是 (LPCSTR) &functionInThisDll
    【解决方案2】:
    EXTERN_C IMAGE_DOS_HEADER __ImageBase;
    

    ....

    TCHAR   DllPath[MAX_PATH] = {0};
    GetModuleFileName((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath));
    

    【讨论】:

    • 关于__ImageBase 变量未来兼容性的任何cmets?
    • __ ImageBase 是 IMAGE_DOS_HEADER 类型的变量,这首先出现在 PE(可移植可执行格式)中。它是一个windows结构,只在windows下可用。在我看来是可以安全使用的,并且将来不会改变。另一种方法是 GetModuleHandle,但需要 dll 名称。
    • 我刚刚读到了这个全局变量。我知道 PE 标头,但不知道这一点。谢谢!
    • 在MS示例代码中使用,应该是安全的。例如,请参阅msdn.microsoft.com/en-us/library/windows/desktop/…
    • 为什么不直接使用 DllMain 中的内容而不是 __ImageBase?
    【解决方案3】:

    GetModuleFileName() 在 DLL 代码中可以正常工作。请确保不要将第一个参数设置为NULL,因为这将获取调用进程的文件名。您需要指定 DLL 的实际模块实例。您可以在 DLL 的 DllEntryPoint() 函数中将其作为输入参数,只需将其保存到某个变量中以供以后需要时使用。

    【讨论】:

    • GetModuleHandle 将为您提供与 DllEntryMain 的输入参数相同的句柄。
    • 这似乎是最简单的答案,而且它也有效。 Agnel 意味着 DllMain() 顺便说一句,而不是 DllEntryMain()。
    【解决方案4】:

    试试GetModuleFileName函数。

    【讨论】:

      【解决方案5】:

      对于 Delphi 用户:

      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
      

      如果你的 Delphi 没有 SysUtils.GetModuleName,它被声明为:

      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;
      

      【讨论】:

        【解决方案6】:

        假设您实现了以下 dll 入口点:(通常是 dllmain.cpp)

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

        你可以这样做:

        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 将包含当前 dll 代码加载的路径。在这种情况下,hModule 由加载 dll 的进程传递。

        【讨论】:

          【解决方案7】:

          这是最高投票答案的 Unicode 修订版:

          CStringW thisDllDirPath()
          {
              CStringW thisPath = L"";
              WCHAR path[MAX_PATH];
              HMODULE hm;
              if( GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 
                                      GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                                      (LPWSTR) &thisDllDirPath, &hm ) )
              {
                  GetModuleFileNameW( hm, path, MAX_PATH );
                  PathRemoveFileSpecW( path );
                  thisPath = CStringW( path );
                  if( !thisPath.IsEmpty() && 
                      thisPath.GetAt( thisPath.GetLength()-1 ) != '\\' ) 
                      thisPath += L"\\";
              }
              else if( _DEBUG ) std::wcout << L"GetModuleHandle Error: " << GetLastError() << std::endl;
              
              if( _DEBUG ) std::wcout << L"thisDllDirPath: [" << CStringW::PCXSTR( thisPath ) << L"]" << std::endl;       
              return thisPath;
          }
          

          【讨论】:

            【解决方案8】:

            我想实现类似的东西,除了想将类似的功能放入一个 .dll 中 - 但是你不能使用 __ImageBase,因为它特定于函数所在的那个 .dll。我什至尝试过使用方法覆盖

            GetDllPath( HMODULE hDll = (HMODULE) __ImageBase)
            

            但这对我们也不起作用。 (出于某种原因,之后返回应用程序路径。)

            然后我想通了 - 为什么我不使用 VirtualQuery,并使用函数指针并从那里获取 HMODULE。但是再次 - 如何获取调用者的函数指针?

            现在它回到调用堆栈确定 - 我不会用所有肮脏的细节来打扰你,只需点击引用链接的链接即可。

            这是整个代码快照:

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

            【讨论】:

            • 在容易出错的堆栈跟踪函数旁边,您在 GetDllPath() 中返回“路径”,它是本地数组,因此在返回时被销毁。这会崩溃。只需将 GetModuleFileName() 与 DllMain() 中传递的 DLL 句柄(如上所述)一起使用,只需 3 行代码即可。
            • 仅当您想将 GetDllPath 放入共享 dll 并从其他 dll 使用它时才使用我的代码示例。更简单的方法是分别为每个 dll 编写 GetDllPath,但是您将拥有相同函数的多个副本,或者多个 dll 中包含相同的源代码 - 这当然也是一种解决方案。
            • 但是'path'是在栈上创建的,一旦GetDllPath()返回这个变量就会消失。所以使用字符串是必然会崩溃的吧?
            • 函数本身会为调用者创建字符串的副本。理论上不应该崩溃。
            • 我看到'wchar_t path',所以它在堆栈上。然后是“返回路径”,它返回一个指向堆栈项的指针。但实际上,我将隐式 CStringW 视为返回类型,这可能会生成副本。很抱歉造成混乱。
            【解决方案9】:

            恕我直言,Remy Lebau 的答案是最好的,但缺乏像所有其他答案一样呈现 DLL 的目录。我引用了最初的问题:“我想从它的代码中获取一个 dll 的目录(或文件)路径。 (不是程序的 .exe 文件路径)。”

            正如 Remy 和 Jean-Marc Volle 所指出的,通常包含在 dllmain.cpp 中的 DLL 入口函数DllMain 提供了 DLL 的句柄。这个句柄通常是必要的,所以它会被保存在一个全局变量hMod 中。我还添加了 std::wstring 类型的变量来存储 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;
            }
            

            这些全局变量可以在 DLL 中使用。

            【讨论】:

              【解决方案10】:
              HMODULE hmod = GetCurrentModule();
              TCHAR szPath[MAX_PATH + 1] = 0;
              DWORD dwLen = GetModuleFileHName(hmod, szPath, MAX_PATH);
              

              【讨论】:

              • 这将为您提供加载 dll 的 exe 路径,而不是 dll 本身。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-08-11
              • 1970-01-01
              • 1970-01-01
              • 2019-11-14
              • 2020-04-16
              • 1970-01-01
              相关资源
              最近更新 更多