我可以在DLL中组织类吗?

时间:2016-04-19 10:32:18

标签: c++ dll abi

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

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

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

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

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

加分问题:如果我导出一个具有指向实现的指针的类,这相当于导出纯虚拟类和工厂函数吗?或者导出的成员函数必须是虚拟的吗?

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

2 个答案:

答案 0 :(得分:3)

TL; DR :这取决于。

简单案例

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

不太简单的案例

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

根据调试DLL使用的模板,您可能需要使用_CRT*定义强制执行此操作,具体取决于调试DLL使用的模板。

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

The Terrible(a.k.a。真实世界)案例

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

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

此时要做的好事:

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

然后客户端将使用仅包头的包装器,因此每次调用DLL代码都将使用C ABI进行,这非常稳定,不会破坏。

答案 1 :(得分:1)

自从我写one of the questions you linked以来已经很长时间了,但让我试着帮忙。

  

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

这完全取决于您的对象所暴露的内容。如果你的对象返回普通C值,你应该没问题。如果您的对象返回包含plain-C值的POD类,那么您也应该没问题。我试图在那个问题/答案对中尝试解决的头痛几乎完全与STL有关。

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

  

例如,我想将常用的实用程序类放在一个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并指向您的班级指针,您可以安全地在幕后实施虚拟功能,而无需担心来电者看到您正在做什么。