如何(可移植地)使用 C++ 类层次结构和动态链接库

Posted

技术标签:

【中文标题】如何(可移植地)使用 C++ 类层次结构和动态链接库【英文标题】:How to work (portably) with C++ class hierarchies & dynamic linked libraries 【发布时间】:2009-10-14 07:37:50 【问题描述】:

好的,所以我知道可移植性不是 C++ 的强项,但我必须让我的代码在 Mac 和 Windows 上运行。我想出了一个解决方案,但并不完美,我很想看看是否有人可以提出更好的解决方案。

我需要在几个 DLLs/bundle 中建立一个类层次结构——例如,我有一个抽象基类 BaseClass;我扫描一个给定的 DLL 目录,对于每个 DLL,我寻找工厂方法 BaseClass* CreateObject(); - 它返回一个“BaseClass”。我有一个“共享头文件”,它包含在“主可执行文件”和 DLL 中,它像这样声明 BaseClass

#ifdef _MAC
 #define DECLSPEC 
#else
 #ifdef COMPILING_DLL
 #define DECLSPEC __declspec(dllexport)
 #else
 #define DECLSPEC __declspec(dllimport)
 #endif
#endif

class DECLSPEC BaseClass
  [.. base "interface" declaration .. ]

然后,在我的 DLL 中,我通常会包含 BaseClass 声明,并声明我自己的“具体”类:

class MyDllClass:public BaseClass
[.. actual DLL class definition/implementation here goes here ...]

到目前为止,一切都很好。现在出于某种原因,我需要在我的主要可执行文件中区分两种不同类型的 BaseObject——比如我有一个 DescriptionClass 和一个 ActionClass,它们都是 BaseClass,但接口略有不同。我的第一个实现是简单地修改“共享标头”并添加:

class DECLSPEC DescriptionClass
  [.. base "Description interface" declaration .. ]


class DECLSPEC ActionClass
  [.. base "Action interface" declaration .. ]

那么我的 DLL 会变成:

class MyDllClass:public ActionClass /* or DescriptionClass, depending on case*/ 
[.. actual DLL class definition/implementation here goes here ...]

在我的主要可执行文件中,我会这样使用它:

BaseClass* obj = CreateObjectFromDLL("path_to_dll");
ActionClass* action_obj = dynamic_cast<ActionClass*>(obj);
if(action_obj)
   // Do the stuff that is relevant for Action objects

DescriptionClass* description_obj = dynamic_cast<ActionClass*>(obj);
if(description_obj)
   // Do the stuff that is relevant for Description objects

这就是问题所在:虽然它可以在带有 Visual Studio 的 Windows 上工作(可能是由于 MS declspec 扩展),但它在 Mac 上却失败了(现在不确定它是否在 Debug 上失败,但我确信它在发布时失败了) 使用 GCC 编译时。原因很简单,即使不是很明显:可执行文件和动态库是分开编译的。尽管它们都包含 BaseClass、ActionClass、DescriptionClass 的声明——这些类并不相同,它们只是二进制文件和 DLL 中存在的“相同副本”。所以实际上,我在 DLL 中创建的是一个 dll'BaseClass* ,它恰好与 main'Baseclass* 具有相同的内存布局,所以指针是兼容的,所以当我将指针从 DLL 传递给 EXE 时,它所有作品“按预期”。 OTOH,当我转到更复杂的类层次结构时,dll'ActionClass 和 main'ActionClass 的 vtables/RTTI 不再相同(尽管在源代码中它们是相同的),所以当我尝试转换(通过 dynamic_cast)一个 main 'BaseClass* to a main'ActionClass* 我得到一个空结果 -> 因为我的指针实际上指向一个 dll'BaseClass 对象/dll'ActionClass 对象,并且在 DLL 中我可以毫无问题地将“BaseClass*”转换为“ActionClass*” - 在主可执行文件中,我无法将 dll 的“BaseClass*”转换为“ActionClass*”,因为 DLL 和 Action Class 的“主可执行文件”版本之间存在细微的运行时差异。

我已经通过在 BaseClass 中声明一个虚拟方法(类似于 "bool isActionClass()" )来“修复”这个问题,所以现在我可以区分......但我对这个解决方案不太满意。

GCC 有什么东西吗?一些类似于“__declspec”的声明——一种保证在“主可执行文件”和“dll”中声明的同一个类将 100% 兼容的方法?

【问题讨论】:

【参考方案1】:

我实际上找到了我的问题的答案,因为我需要完全制定它,并且我做了更好的谷歌搜索:) 好像是

__attribute__((dllimport)) // import
__attribute__((dllexport)) // export

会尝试,我想我也会把问题留在这里,以防其他人偶然发现这个问题(并作为警告人们,包含在“主二进制文件”和 DLL 中的相同头文件通常会导致不同实际实现,取决于编译器选项 - 除非您在类上放置了正确的 dllimport/export 属性)。

【讨论】:

【参考方案2】:

只有在启用 RTTI 时才有效。您可能会在 MSVC++ 中自动启用它,但 gcc 需要特定的链接开关。详情请查看GCC FAQ。

【讨论】:

是的,但是已经启用了,我忘了说;但我确实说过动态转换在共享库中工作:) 好吧,我错过了。在这种情况下,您需要查找类似 dllexport 的定义,正如您已经提到的那样

以上是关于如何(可移植地)使用 C++ 类层次结构和动态链接库的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中可移植地计算 sha1 哈希?

静态库可移植性

可移植地识别非标准 C++?

C++:动态共享库中的虚函数产生段错误

如何简化导致链接方法调用的层次类结构?

c++继承层次结构实践