1

由于 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 是否安全?

4

2 回答 2

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 运行时库。所以不要newdelete你的 MyStruct 对象来自客户端代码;相反,在 DLL 中提供函数来为您执行此操作。

远离界面中的标准库容器。如果 DLL 和客户端没有链接到同一个标准库,它们将无法工作。

不要害怕虚函数。

注意:所有这些问题在理论上也存在于其他平台上,但在 Linux 和 Mac OS X 的实践中似乎不太相关。

于 2013-06-07T09:07:55.837 回答
1

DLL 边界和 vtable 都没有任何特殊之处。发布单元边界是。

如果您在一个可执行文件中混合使用不同编译器、不同编译器版本或有时不同编译器选项编译的对象,则很有可能在程序的不同部分中获得不兼容的对象布局。如果您使用 DLL 或静态链接,并且使用任何类型的对象,无论是否使用 vtable,都可能发生这种情况。

因此,如果您的可执行文件及其所有 DLL 作为一个整体被编译和发布,您就无需担心。如果可执行文件和 DLL 是不同的产品,由独立组织分别发布,则需要非常小心。明智的做法是只使用跨发布单元边界的 C 兼容数据结构,并且永远不要释放分配在不同单元中的内存。

于 2013-06-07T09:21:22.217 回答