【问题标题】:C++: allow program to run even if 3rd party DLL is missing?C++:即使缺少第 3 方 DLL,也允许程序运行?
【发布时间】:2018-02-13 00:58:34
【问题描述】:

我有一个链接到一些第 3 方动态库的 c++ 应用程序。我的一些主要类从这些库继承,并对这些库进行函数调用等。我的应用程序在理论上不包含这些库的情况下工作(即,如果我手动剥离与这些库有关的所有代码和引用,它会仍然有效),它只会在功能上受到更多限制。如果我可以做一个类比,想象一下我创建了一个 Windows 记事本克隆,并包含一个允许用户在文档中嵌入图片和视频的 3rd 方库。

当我分发我的应用程序时,我的客户可能没有安装这些库。有没有办法让我的程序检测所需的 DLL 库是否存在,如果没有安装则简单地忽略所有相关代码?

目前,如果我在未安装 3rd 方库的情况下运行我的应用程序,它会显示与缺少 DLL 和崩溃相关的错误。一个明显的解决方案是简单地发布我的应用程序的两个版本......一个没有外部依赖项,一个有,但我想避免像那样独立管理两个产品。

【问题讨论】:

标签: c++ dll dependencies


【解决方案1】:

有这样的选项“延迟加载dll”

  1. 对于 dll xxx.dll,您将链接器配置为使用“延迟加载”
  2. 在您从 dll 调用任何函数之前,它不会被加载,即使 dll 不存在,您的应用程序也会成功启动
  3. 您使用 LoadLibrary 检查 xxx.dll 是否可用。
  4. 如果 LoadLibrary 失败 - 您使用 xxx.dll 禁用模块
  5. 如果 LoadLibrary 成功 - 您卸载它(您不需要动态加载 - 它仅用于测试 dll 的存在)并使用库,就像它定期链接一样 - 无需使用任何 xxx 修改代码。 dll 相关功能

【讨论】:

  • 延迟加载非常简单,如果 dll 名称固定,则非常有用。但是一些第 3 方库更改了它们的 dll 名称,例如,它包含(愚蠢地)一个版本号。在这种情况下,请参阅下面韦恩的回答。不幸的是 GetProcAddress 很乏味。
【解决方案2】:

在此处查看此答案: Dynamically load a function from a DLL

  1. 你基本上是用LoadLibrary来加载DLL的,结果会是 加载时是否为 null。

  2. 然后使用 GetProcAddress 获取函数。

【讨论】:

    【解决方案3】:

    如果你想分发你的应用程序的二进制文件,你可以使用动态加载(看看here

    但是如果您可以选择在客户端系统上构建您的应用程序,您可以使用 CMake 并在它找不到您想要的库时设置编译标志。然后你的代码可以根据这个标志的值来运行(即这样你就可以知道这个库是否存在于你的代码中)

    【讨论】:

      【解决方案4】:

      加载动态库或注入进程或...可以在此之后使用:

          if (ul_reason_for_call == DLL_PROCESS_ATTACH)
      {
          inj_hModule = hModule;
          DisableThreadLibraryCalls(hModule); // Disable DllMain calls for DLL_THREAD_*
      
          HANDLE hThreadProc = CreateThread(nullptr, NULL, LPTHREAD_START_ROUTINE(ThreadProc), hModule, NULL, nullptr);
          CloseHandle(hThreadProc);
          Beep(2000, 500);
      }
      
      if (ul_reason_for_call == DLL_PROCESS_DETACH)
      {
          Beep(2000, 500);
          FreeLibraryAndExitThread(hModule, 0);
          // exit this thread
          ExitThread(0);
      }
      

      【讨论】:

        【解决方案5】:

        好吧,您应该修改您的软件架构,并按照 Martin Fowler 的介绍使用 Plugin Design Pattern
        您不希望这种情况下的动态链接,尽管它非常相似。

        关于 C++ 中的实现,这基本上意味着您引入了一个 先抽象接口

         struct Foo {
             virtual void Bar() = 0;
             virtual ~Foo() {}
         };
        

        并在运行时查找与共享库一起提供的合适实现1

        共享库实现的导出接口需要与你的抽象接口声明相匹配。

        您可以使用Factory 进行查找、加载共享库并创建实现的实例。

        Here 提供了更多关于动态加载和绑定插件所涉及的操作系统功能的信息。


        1)请注意,如果您主动加载共享库,则不需要扩展名为 .dll.so

        【讨论】:

          【解决方案6】:

          我的建议是仔细拆分依赖于该 DLL 的代码。为项目的那部分设计一个干净的接口,可以轻松地动态加载(LoadLibrary),并且在您与程序的主要部分交互的任何类上只有纯虚拟接口(无 DLL 导出)。理想情况下,它有一个纯函数接口,因为它们是最稳定的。

          现在,那个 DLL 可以像正常一样依赖于第 3 方 DLL。您的主代码通过手动加载来调用您的“包装”DLL,并优雅地处理加载失败。

          这比使用不是为此设计的 DLL 更容易,您可以通过省略包装 DLL 来进行实验,而无需安装/卸载第 3 方 DLL。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-10-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-03-21
            • 2023-01-26
            相关资源
            最近更新 更多