由于c ++中未定义的vtable布局,跨越DLL边界传递类是一个坏主意, 但是,如果我显式设置调用约定,并避免虚函数和继承呢?
换句话说,我可以安全地通过DLL将指针传递给以下结构吗?
struct MyStruct {
int a;
int b;
WINAPI MyStruct(int a, int b)
: a(a), b(b)
{}
void WINAPI SetA(int a) {
this->a = a;
}
};
使用带有不同编译器版本等的DLL链接是否安全?
答案 0 :(得分:2)
您的代码实际上等同于以下C代码:
struct MyStruct {
int a;
int b;
};
void WINAPI InitMyStruct(struct MyStruct* p, int a, int b)
{
p->a = a; p->b = b;
}
void WINAPI MyStruct_SetA(struct MyStruct* p, int a)
{
p->a = a;
}
避免虚拟功能基本上没有给你带来任何好处;它仍然依赖于编译器,这些“等效”C函数将被调用(“名称修改”),因此您需要使用兼容的编译器。本千年的所有MSVC版本在这方面都是相互兼容的。本千年的所有GCC版本都是相互兼容的。只是不要混淆两者(链路时间错误会发生)。
还有其他问题来源:
确保您的打包/对齐设置匹配(但它们也需要对普通C接口执行此操作)。
如果在一个DLL中使用“new”而在另一个DLL中使用“delete”,则除非使用完全相同的编译器版本并使用DLL运行时库,否则可能会遇到麻烦。所以不要从客户端代码new
或delete
MyStruct对象;相反,在DLL中提供函数来为您完成。
远离界面中的标准库容器。如果DLL和客户端未链接到同一标准库,它们将无法工作。
不要害怕虚拟功能。
注意:所有这些问题在理论上也存在于其他平台上,但在Linux和Mac OS X的实践中似乎有点不太相关。
答案 1 :(得分:1)
DLL边界和vtable都不是特殊的。发布单位边界是。
如果将使用不同编译器编译的对象,不同的编译器版本或有时不同的编译器选项混合在一个可执行文件中,则很可能在程序的不同部分中获得不兼容的对象布局。如果您使用DLL或静态链接,以及任何类型的对象,无论是否使用vtable,都可能发生这种情况。
因此,如果您的可执行文件及其所有DLL都是作为一个整体编译和发布的,那么您无需担心。如果可执行文件和DLL是由独立组织单独发布的不同产品,则需要非常小心。明智的做法是只在发布单元边界上使用C兼容的数据结构,并且永远不要释放在不同单元中分配的内存。