【问题标题】:Returning C++ objects from Windows DLL从 Windows DLL 返回 C++ 对象
【发布时间】:2011-02-06 09:20:27
【问题描述】:

由于 Microsoft 在其非 DLL 版本的运行时中实现堆的方式,从 DLL 返回 C++ 对象可能会导致问题:

// dll.h
DLL_EXPORT std::string somefunc();

和:

// app.c - not part of DLL but in the main executable
void doit()
{
    std::string str(somefunc());
}

如果 DLL 和 EXE 都是使用多线程 DLL 运行时库构建的,上述代码运行良好。

但是如果 DLL 和 EXE 是在没有 DLL 运行时库(单线程或多线程版本)的情况下构建的,则上面的代码会失败(使用调试运行时,由于断言 _CrtIsValidHeapPointer(pUserData) 失败,代码会立即中止;使用非调试运行时,堆会损坏,程序最终会在其他地方失败)。

两个问题:

  1. 除了要求所有代码都使用 DLL 运行时之外,还有其他方法可以解决这个问题吗?
  2. 对于将库分发给第三方的人,您如何处理?你不在你的 API 中使用 C++ 对象吗?您是否要求您的库的用户使用 DLL 运行时?还有什么?

【问题讨论】:

    标签: c++ windows dll


    【解决方案1】:

    除了要求所有代码都使用 DLL 运行时之外,还有其他方法可以解决这个问题吗?

    我不知道。

    对于将库分发给第三方的人,您如何处理?你不在你的 API 中使用 C++ 对象吗?您是否要求您的库的用户使用 DLL 运行时?还有什么?

    过去我分发了一个带 dll 的 SDK,但它是基于 COM 的。使用 COM 会自动为您完成所有参数和 IPC 的编组。用户也可以通过这种方式与任何语言集成。

    【讨论】:

    • 感谢关于 COM 的建议。不幸的是,我们还需要各种 Posix 操作系统的库版本,所以我需要避免使用 MS 特定技术。
    【解决方案2】:

    您的代码有两个潜在问题:您解决了第一个问题 - CRT 运行时。这里还有另一个问题:std::string 可能会在 VC++ 版本之间发生变化。事实上,它在过去确实发生了变化。

    安全的处理方法是只导出 C 基本类型。并从 DLL 导出创建和释放函数。不是导出一个 std::string,而是导出一个指针。

    __declspec(export)  void* createObject()
    {
         std::string* p = __impl_createObject();
         return (void*)p;
     }
    
    __declspec(export)  void releasePSTRING(void* pObj)
    {   
         delete ((std::string*)(pObj));
    }
    

    【讨论】:

      【解决方案3】:

      有一种方法可以解决这个问题,但这有点不重要。与库的大多数其他部分一样,std::string 不直接使用new 分配内存——而是使用分配器(默认情况下为std::allocator<char>)。

      您可以提供自己的分配器,该分配器使用您自己的堆分配例程,这些例程对于 DLL 和可执行文件是通用的,例如通过使用 HeapAlloc 来获取内存,并从那里子分配块。

      【讨论】:

      • @Jerry - 接受您的回答,因为您提供了可行的替代解决方案。
      • 有点晚了,但值得添加到 Jerry 的回答中:自定义分配器可以捕获当前翻译单元的分配函数 operator new 和 operator delete,从而将分配器“绑定”到原始翻译单元动态链接库。请参阅我已成功使用多年的modulebound_allocator。而不是std::string/std::wstring,我在整个项目中使用typedef std::basic_string<char, std::char_traits<char>, modulebound_allocator<char[]>> mystring
      【解决方案4】:

      如果您有要分发的 DLL,并且不想将调用者绑定到 C-Runtime 的特定版本,请执行以下任一操作:

      我。将 DLL 链接到 C-Runtime 库的静态版本。在 Visual Studio 项目属性页面中,选择配置属性 -> C/C++ -> 代码生成选项卡。这些是用于选择“运行库”的选项。选择“多线程”或“多线程调试”而不是 DLL 版本。 (等效的命令行是 /MT 或 /MTd)

      这种方法有几个不同的缺点:

      一个。如果 Microsoft 曾经发布过 CRT 的安全补丁,那么在您重新编译和重新编译二进制文件之前,您的交付组件可能会受到攻击。

      b.由 DLL 中的“malloc”或“new”分配的堆指针不能被 EXE 或其他二进制文件“释放”或“删除”。否则你会崩溃的。 fopen 创建的 FILE 句柄也是如此。您不能在 DLL 中调用 fopen 并期望 EXE 能够关闭它。再次,如果你这样做,就会崩溃。您将需要构建 DLL 的接口以适应所有这些问题。对于初学者来说,将实例返回到 std::string 的函数可能会成为一个问题。提供由您的 DLL 导出的函数,以根据需要处理资源的释放。

      其他选项:

      二。交付时没有 c-runtime 依赖。这有点难。您首先必须从代码中删除对 CRT 的所有调用,提供一些存根函数以使 DLL 链接,并指定“无默认库”链接选项。可以的。

      三。可以使用 COM 接口指针从 DLL 中干净地导出 C++ 类。您仍然需要解决上述 1a 中的问题,但 ATL 类是消除 COM 开销的好方法。

      【讨论】:

        【解决方案5】:

        这里的简单事实是,除了微软的实现,C++ 不是 ABI。您不能在任何平台上从动态模块导出 C++ 对象,并期望它们与不同的编译器或语言一起使用。

        从 Dlls 导出 c++ 类在很大程度上是毫无意义的练习 - 因为名称修改,以及 c++ 中缺乏对动态加载类的支持 - dll 必须静态加载 - 所以你失去了将项目拆分为的最大好处dll - 仅根据需要加载功能的能力。

        【讨论】:

        • “仅根据需要加载功能的能力”确实是一个好处。但是将代码放入 DLL 中(即使它们一直都被加载)可以简化产品维护和支持,因此它是更广泛使用的模式。
        • 怎么样?严重地?在 C++ 开发的上下文中,Dll 的使用受到特别限制:- 以一流的可以继承的方式从 Dll 中实际导出 C++ 类要求 Dll 始终静态链接,并且所有 Dll(和 exe)总是一起重建以响应任何类定义的变化。您添加的只是故障模式:将纯 C++ 实现分解为 Dll 没有任何好处,但会引入现在需要管理的版本不匹配的可能性。
        猜你喜欢
        • 2011-04-12
        • 2011-02-05
        • 2021-08-09
        • 2015-10-26
        • 2012-12-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多