【问题标题】:Catching LoadLibrary() errors gracefully优雅地捕获 LoadLibrary() 错误
【发布时间】:2010-11-20 20:43:39
【问题描述】:

我正在开发一款可在 Windows XP 和 Windows Vista 之间的所有 Windows 版本上运行的 C++ 软件。在我的代码中,我开发了一个链接到标准库(Qt library)的 DLL。部署我的软件后,用户在他的系统上没有完全相同的 Qt 构建但配置略有不同的情况并不少见。可能会禁用某些功能(因此他们的 Qt 构建不会导出相同的符号集),或者库甚至可能会以使库二进制文件与原始文件不兼容的方式进行更改。

有时,我正在通过 LoadLibrary() 调用加载我的 DLL。这会引入用户系统上的任何 Qt 库。如果幸运的话,他们的 Qt 版本与我在开发 DLL 时使用的版本兼容,因此 LoadLibrary() 成功。但是,根据他们对 Qt 构建所做的更改,LoadLibrary() 调用有时会失败并显示

  • "找不到指定的模块。";如果他们的 Qt 构建包含的 DLL 少于我的 Qt 构建,通常会发生这种情况。所以我的 DLL 尝试加载例如QtFoo.dll,但由于这个 dll 不是他们 Qt 构建的一部分,加载我的 DLL 失败。
  • “找不到指定的过程。”;如果他们更改 Qt 构建以禁用某些功能,这通常会发生这种情况,从而导致导出的符号减少。

我的问题是:如何优雅地捕捉这些错误?对,我只是使用 GetLastError() 然后打印上述两条消息中的任何一条。但是,如果我知道 which 模块无法找到,或者 which 过程丢失,它会更有用。我注意到,当在资源管理器中运行链接到丢失的 DLL 的应用程序时,资源管理器设法产生一个很好的“由于缺少所需的库 blah.dll,因此无法加载应用程序 foo”。是否有一些 API 可用于获取有关 LoadLibrary() 调用完全失败的原因的更多信息?

【问题讨论】:

    标签: c++ windows qt plugins loadlibrary


    【解决方案1】:

    在某些时候,我正在通过以下方式加载我的 DLL LoadLibrary() 调用。这拉进来 用户的任何 Qt 库 系统。

    不要这样做!您遇到的那种错误是幸运的,就像它很容易损坏内存和崩溃一样。 传送 Qt 应用程序的规范方式是传送 DLL 或静态链接。查看帮助文件中的 Qt 部署指南。

    后期编辑:

    在阅读了您的 cmets 之后,我仍然不建议您使用这种方法,因为即使加载 DLL,您也无法确定它们是否是二进制兼容的,这可能会导致难以跟踪的错误。

    尽管如此,我相信您可以拦截 LoadLibrary 调用并查看哪些调用失败。 MS Detours 库可用于此目的。另请参阅this Stackoverflow 问题。

    【讨论】:

    • 这不是一个简单的Qt应用程序;我正在编写的 DLL(实际上是执行 LoadLibrary() 调用的 DLL)被注入到 Qt 应用程序的进程中。因此,我需要链接与我要连接的进程完全相同的 Qt 库。情况并非总是如此,在这些情况下,我希望能够生成有用的错误消息。
    • 我仍然认为这不是正确的方法。由于您无法控制父应用程序附带的 Qt,因此您必须确保您的 DLL 可以与所有 Qt 构建一起使用。这意味着静态链接 Qt。就像我说的那样,仅仅因为您没有收到两条错误消息,并不意味着应用程序和您的 DLL 使用的是相同版本的 Qt。
    • 我无法静态链接 Qt,因为我需要使用与应用程序完全相同的 Qt 对象(例如 qApp)。我不能使用静态链接时获得的自己的副本。我知道这种方法不是最理想的——我没有选择它,这只是这里正在做的事情。考虑到这里的工作量,我相信这不会很快改变。我只想知道如何获得更好的错误消息,以防万一。
    【解决方案2】:

    要扩展 jeffamaphone 的答案,您可以在致电 LoadLibrary 之前尝试检索文件版本详细信息。您可以使用以下函数执行此操作:

    BOOL GetFileDetails(LPCTSTR lpszPath, LPDWORD lpMajorVersion, 
                        LPDWORD lpMinorVersion)
    {
        DWORD dwVersionHandle;
    
        DWORD dwVersionSize = GetFileVersionInfoSize((LPTSTR)lpszPath,
                                                     &dwVersionHandle);
        if (dwVersionSize == 0)
            return FALSE;
    
        LPBYTE lpVersion = new BYTE[dwVersionSize];
    
        if (!GetFileVersionInfo((LPTSTR)lpszPath, dwVersionHandle, 
                                dwVersionSize, lpVersion))
        {
            delete [] lpVersion;
            return FALSE;
        }
    
        VS_FIXEDFILEINFO *pVersionInfo = NULL;
        UINT nLength;
    
        if (!VerQueryValue(lpVersion, _T("\\"), (LPVOID *)&pVersionInfo, &nLength))
        {
            delete [] lpVersion;
            return FALSE;
        }
    
        *lpMajorVersion = pVersionInfo->dwFileVersionMS;
        *lpMinorVersion = pVersionInfo->dwFileVersionLS;
    
        return TRUE;
    }
    

    然后,您可以对照预期的版本检查主要/次要版本号。

    【讨论】:

    • 这是一个很好的功能,但不幸的是它不足以满足我的需求。用户倾向于使用不同的配置参数构建自己的 Qt 库,因此某些功能可能会被禁用但版本号不会改变。此外,他们可能以破坏二进制兼容性的方式修补了 Qt 库(例如,通过引入新的虚函数)。
    • 就我而言,这很好用。做LoadLibarary(),用GetModuleFileName()获取我刚刚加载的库的路径,然后调用你的函数。
    【解决方案3】:

    来自 ImageHLP.DLL 的 MapAndLoad 可能会有所帮助。它返回一个 LOADED_IMAGE 结构。

    【讨论】:

    • 有趣的想法。在我看来,我需要复制 LoadLibrary/GetProcAddress 功能;递归地遍历导入表并尝试自己解决每个符号。而且我什至不确定这是一个适当的复制。
    • 大概你只会在 LoadLibrary 失败后这样做。在晴天场景下不会影响性能。
    • 我想我会按照这条路线走,感谢您指出 MapAndLoad 函数!
    【解决方案4】:

    没有将调试器附加到您的进程,我认为您不能。发生这种情况时通常弹出的消息是由 LoadLibrary 内部生成的。 SetErrorMode 在很多应用程序中用于禁止这种形式的消息,我猜想在你的应用程序框架的某个地方,它调用SetErrorMode 来禁止操作系统消息。

    我见过的唯一一个生成自己的关于 dll 加载失败的详细消息的应用是 MS DevStudio - 它作为调试器附加,因此可以访问特殊的调试事件流。

    【讨论】:

      【解决方案5】:

      在调用 LoadLibrary() 之前,您能否更加主动并check the version 所需的 QT 二进制文件?然后,您可以警告您的用户,他们没有您的应用所需的版本,甚至可以为他们提供指向安装点的链接。

      【讨论】:

      • 在这种情况下我已经发出警告。但是,此代码仍然执行 LoadLibrary() 并且可能会失败。
      【解决方案6】:

      您也可以使用清单文件让 Windows 对此进行检查。该文件包含有关所用库版本要求的信息。更准确和完整的信息在msdn site

      看看this question about how to use LoadLibrary with a manifest file的答案。

      The Qt documentation简要提到了VS2005清单文件的使用;对于早期版本,您必须自己创建它。

      【讨论】:

      • 这甚至适用于较旧的编译器吗?我也需要支持 MSVC6。
      • 虽然我不是清单文件专家,但我知道它可以用作放置在可执行文件旁边的配置定义文件,Windows 库加载器将使用它来检查依赖的dll。
      猜你喜欢
      • 1970-01-01
      • 2018-01-01
      • 2023-03-13
      • 2012-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-15
      • 1970-01-01
      相关资源
      最近更新 更多