【问题标题】:Can I organize classes in DLLs? [closed]我可以在 DLL 中组织类吗? [关闭]
【发布时间】:2016-04-19 10:32:17
【问题描述】:

我承认这个问题听起来很笼统。但毕竟,从 DLL 导出类是一个普遍而困难的话题,坦率地说,我目前在相当普遍的层面上感到困惑。

简短的问题:C++ 和 DLL 中的面向对象编程如何结合在一起?

长问题:阅读thisthis 后,我有点失望和困惑,因为我想知道如果DLL 边界不允许对象,那么面向对象编程如何与DLL 一起工作共享(假设两个 DLL 使用了不同的编译器或编译器版本)。导出类的唯一选项是这些(如herehere 所述):

  • 导出创建和删除方法(C 风格,悬空指针的危险,没有对象作为参数,丑陋)
  • 导出一个纯虚类和一个工厂函数,该函数创建从纯虚类派生的实际实现类的实例(需要继承,需要注意对象删除)

例如,我想将通用实用程序类放在一个 DLL 中,然后在其他 DLL 中的多个类中使用这些类,这些类本身也用于其他 DLL。我怎样才能做到这一点?这是组织课程的不明确方式吗?

额外问题:如果我导出一个具有实现指针的类,这是否等同于导出一个纯虚类和一个工厂函数?还是导出的成员函数必须是虚拟的?

编辑:如果重要的话,我在 Windows 7 上使用 Visual Studio 2010。迁移旧版 Visual Studio 让我对这个问题很敏感。

【问题讨论】:

  • 使用 DLL 是一个非常实用的解决方案,可以解决在进行小改动时必须等待很长时间才能编译和链接程序的问题。将它们用作一个库,任何编译器版本都应该可以使用任何编译设置轻松使用它们,嗯,不。二进制兼容性不是 C++ 功能,必须由您添加,并且您已经知道需要什么。
  • @Hans Passant:是否有替代 DLL 以可重用方式组织源代码的方法?标头实现可能吗?还有什么?

标签: c++ dll abi


【解决方案1】:

TL;DR:视情况而定。

简单案例

当两个 DLL(或一个 DLL 和一个可执行文件)使用相同的编译器和编译器版本构建时,它们使用相同风格的运行时(调试或发布),并且它们链接到运行时的动态版本, 你想做什么,就可以做什么。例如,跨 DLL 边界删除。

不太简单的情况

当一个 DLL 链接到调试运行时而另一个链接到发布运行时时,事情会变得更加复杂。这是因为调试运行时有不同的 STL 模板,用于调试目的。例如,您不能在发布 DLL 中操作从调试 DLL 分配的 std::vector,反之亦然。只要您将这些 STL 模板限制在正确的 DLL 中,一切就应该可以工作。显然,如果您自己公开不同的 ABI,例如通过在 #ifndef NDEBUG 块中声明一个成员,您会遇到问题。

您可能需要对 _CRT*defines 进行一些操作才能使其正常工作,具体取决于调试 DLL 使用的模板。

您还应该从同一个 DLL 创建和销毁对象。

可怕的(又名真实世界)案例

当编译器版本不匹配时,当至少一个 DLL 与静态运行时链接时。

您会遇到大量 ABI 问题。唯一安全的做法是通过extern "C"(广泛)依赖C ABI。如果您在运行时加载 DLL,这也可能是您想要做的。

在这一点上做一件好事:

  • 用 C++ 编写 DLL
  • 请勿使用 C++ ABI 公开 (dllexport) 任何内容。
  • 围绕您的 C++ 代码编写一个精简的 C 包装器。
  • 使用__declspec(dllexport) 公开此 C API
  • 围绕公开的 C API 编写仅包含标头的 C++ 包装器。

然后,客户端将使用您的仅标头包装器,因此对您的 DLL 代码的每次调用都将使用 C ABI 进行,该 ABI 非常稳定且不太可能中断。

【讨论】:

  • 简单案例现实吗?如果我有一整套套件想要从一个编译器版本迁移到另一个编译器版本怎么办?在这种情况下人们会怎么做?另外,您能否更详细地解释一下瘦 C 包装器和仅标头 C++ 包装器的外观,也许还有一个小例子?那将是非常友好的。
【解决方案2】:

我已经很久没有写one of the questions you linked了,但让我试着帮忙。

简短的问题:C++ 和 DLL 中的面向对象编程如何结合在一起?

这完全取决于您的对象所暴露的内容。如果您的对象返回纯 C 值,则应该没问题。如果您的对象返回包含纯 C 值的 POD 类,那么您也应该没问题。我试图在该问题/答案对中解决的令人头疼的问题几乎完全与 STL 相关。

为了回答自然的后续问题,STL 对 DLL 的处理非常糟糕。由于不同的打包、成员重新排序等,C++ 类具有固有的交叉编译器兼容性问题。STL 增加了一层潜在的不兼容性,因为当构建到调试 DLL 中时,可以添加额外的成员。

例如,我想将通用实用程序类放在一个 DLL 中,然后在其他 DLL 中的多个类中使用这些类,这些类本身也用于其他 DLL。我该怎么做?

在未能成功传递 STL 类型后,我最终将我的 C++ 类封装在一个层中,该层将它们与 C 对应物相互转换。在可能的情况下,我返回了基本数据类型。在我必须分配内存的地方(stringvector 等)我最终得到了 c 风格的创建/删除函数。我相信我还是暴露了一个纯虚拟接口来保护尽可能多的实现细节;我只是没有尝试直接跨 DLL 边界传递任何 STL 对象。

如果我导出一个具有实现指针的类,这是否等同于导出一个纯虚拟类和一个工厂函数?还是导出的成员函数必须是虚拟的?

如果我对问题的理解正确,您希望两者都做。

struct MyDLL
{
  virtual void DoSomething() = 0;
  virtual int AddSomething(int argument1, int argument2) = 0;
}

extern "C" __declspec(dllexport) MyDLL* GetMyDLL();

现在您的调用者可以调用 GetMyDLL 并获得指向您的类的指针,并且您可以在幕后安全地实现虚函数,而不必担心调用者看到您在做什么。

【讨论】:

  • 是否可以在 POD 结构中使用指向我的自定义类的指针?指针是 POD 类型,对吧?我的想法是让MyDLL 结构有一个指向内部类的指针,并让MyDLL 有委托给内部类的普通函数。
  • 我的问题旨在解决人们如何应对这个严重问题。我希望能够在我的 DLL 周围移动对象,但不能安全地做到这一点很痛苦。人们是否会忽略可能的问题或强迫他们的开发人员都使用相同的编译器,或者他们只是不共享除了 POD 和 int 之外的任何东西?也许我必须以同样的方式改变我的编程方式。
  • 只要您实际上没有跨 EXE/DLL 边界传递 C++ 类,我认为您应该没问题。我认为您的“指向自定义类的指针”的功能与我的答案中的 MyDLL/GetMyDLL 代码很相似吗?那应该足够安全。但是,如果您开始尝试公开指向 STL 对象的指针,请不要期望结果会很好。 EXE 的编译器可能对这些 STL 对象有不同的内存布局,导致指针指向您的 DLL 和 EXE 之间的不同事物。
  • 实际上,this question 中的 cmets 和答案表明,即使是 POD 类也可能无法完全安全地通过交叉编译器。如果可能的话,我建议将所有内容分解为固定大小的数据类型和/或 C ABI。
猜你喜欢
  • 1970-01-01
  • 2011-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多