使用共享库发布支持不同ABI的最佳实践是什么?

时间:2018-05-20 03:42:56

标签: c++ visual-c++ dll shared-libraries abi

我相信MS会在MSVC的每个主要版本中打破他们的C ++ ABI。我不确定他们的小发行版。也就是说,似乎如果你向公众发布你的dll的二进制版本,你需要发布几个版本 - 一个版本用于你希望支持的每个主要版本的MSVC。如果您在分发库后发布了MSVC的新的次要版本,如果他们的应用程序是使用新版本的MSVC构建的,那么人们可以安全地使用您的库吗?

Wikipedia显示了MSVC版本的表格 https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#cite_note-43

从_MSC_VER可以看出,Visual Studio 2015和Visual Studio 2017对于编译器具有相同的主要版本19。所以用它构建的DLL Visual Studio 2015应该与使用Visual Studio 2017构建的应用程序一起使用,对吗?

1 个答案:

答案 0 :(得分:2)

在编译器版本中发生变化的主要因素是C / C ++运行时。因此,例如,在您的API中传递streamFILE *可能会造成麻烦,因此请勿这样做。同样,不要在您的应用程序中free内存在DLL中分配,也不要在应用程序中删除在DLL中实例化的对象。反之亦然。

可能会改变的其他事情是对象中成员变量的/对齐/总大小的顺序,不同版本的编译器使用的名称修改方案,或者vtable中的vtable的布局object(也许是那些包含该对象的vtable的位置,特别是在使用多个或虚拟继承时)。

虽然在隧道的尽头有一些亮光。如果您准备将要在API中导出的C ++类包装成类似于COM object的内容,那么您可以确保自己不会遇到所有这些问题。这是因为微软已经有效地承诺不会改变这种对象的vtable布局,因为如果他们这样做,COM就会破坏。

这确实对如何使用这种“COM-like”对象施加了一些限制,但我会在一分钟内解决这个问题。好消息是,你可以避免大部分繁重的工作,实现一个完整的COM对象涉及樱桃挑选最好的位。举例来说,您可以执行以下操作。

首先,一个通用的公共抽象类,它允许我们为std :: unique_ptr和std :: shared_ptr提供自定义删除器:

// Generic public class
class GenericPublicClass
{
public:
    // pseudo-destructor
    virtual void Destroy () = 0;

protected:
    // Protected, virtual destructor
    virtual ~GenericPublicClass () { }
};

// Custom deleter for std::unique_ptr and std::shared_ptr
typedef void (* GPCDeleterFP) (GenericPublicClass *);

void GPCDeleter (GenericPublicClass *obj)
{
    obj->Destroy ();
};

现在由DLL导出的类(MyPublicClass)的公共头文件:

// Demo public class - interface
class MyPublicClass;

extern "C" MyPublicClass *MyPublicClass_Create (int initial_x);

class MyPublicClass : public GenericPublicClass
{
public:
    virtual int Get_x () = 0;
    // ...

private:
    friend MyPublicClass *MyPublicClass_Create (int initial_x);
    friend class MyPublicClassImplementation;

    MyPublicClass () { }
    ~MyPublicClass () = 0 { }
};

接下来,MyPublicClass的实现,它是DLL专用的:

#include "stdio.h"

// Demo public class - implementation
class MyPublicClassImplementation : public MyPublicClass
{

public:

// Constructor
MyPublicClassImplementation (int initial_x)
{
    m_x = initial_x;
}

// Destructor
~MyPublicClassImplementation ()
{
    printf ("Destructor called\n");
    // ...
}

// MyPublicClass pseudo-destructor
void Destroy () override
{
    delete this;
}

// MyPublicClass public methods
int Get_x () override
{
    return m_x;
}

// ...

protected:
    // ...

private:
    int m_x;
    // ...
};

最后,一个简单的测试程序:

#include "stdio.h"
#include <memory>

int main ()
{
    std::unique_ptr <MyPublicClass, GPCDeleterFP> p1 (MyPublicClass_Create (42), GPCDeleter);
    int x1 = p1->Get_x ();
    printf ("%d\n", x1);
    std::shared_ptr <MyPublicClass> p2 (MyPublicClass_Create (84), GPCDeleter);
    int x2= p2->Get_x ();
    printf ("%d\n", x2);
}

输出:

42
84
Destructor called
Destructor called

注意事项:

  • MyPublicClass的构造函数和析构函数声明为private,因为它们对DLL的用户是禁止的。这可确保new和delete使用相同版本的运行时库(即DLL使用的版本)。
  • MyPublicClass的对象是通过工厂函数Create_MyPublicClass创建的。这被声明为extern "C"以避免名称错位问题。
  • MyPublicClass的所有公共方法都被声明为virtual,以避免名称错位问题。当然,MyPublicClassImplementation可以做任何事情。
  • MyPublicClass没有数据成员。它可以有(如果它们被声明为私有)但它不需要。

这样做的代价是:

  • 你可能需要做很多包装。
  • 使用DLL的应用程序无法从DLL导出的类派生。
  • 通过进行所有方法调用virtual以及将它们转发到底层实现(如果这就是你最终要做的那样),会有一些(次要的)性能损失。对我来说,这是我最不担心的事情。
  • 您不能将这些对象放在堆栈上。

好的一面:

  • 您可以在几乎任何您喜欢的方式中更改未来版本中的实现。
  • 如果这些编译器声称支持COM,您可以混合和匹配编译器供应商。你的DLL的用户可能会喜欢这个。

只有你可以判断这种方法是否值得付出努力。 LMK。

编辑:在清除一些荆棘时我想到了这一点,并意识到需要使用std :: unique_ptr和std :: shared_ptr才有用。它也可以通过使公共类抽象(如COM)然后在DLL中的派生类中实现所有功能来改进,因为这在实现类时为您提供了更大的灵活性。因此,我重新编写了上面的代码以包含这些更改并更改了一些内容的名称以使意图更清晰。希望它可以帮到某人。