Какова лучшая практика для поддержки различных ABI с выпуском совместно используемой библиотеки? - PullRequest
0 голосов
/ 20 мая 2018

Я считаю, что MS ломает свой C ++ ABI с каждым основным выпуском MSVC.Я не уверен насчет их небольших релизов.Тем не менее, кажется, что если вы публикуете бинарную сборку вашего dll для публики, вам нужно будет выпустить несколько сборок - по одной сборке для каждого основного выпуска MSVC, который вы хотите поддерживать.Если после распространения вашей библиотеки выйдет новый дополнительный выпуск MSVC, смогут ли люди безопасно использовать вашу библиотеку, если их приложение построено с новой версией MSVC?

Википедия показывает таблицу версий 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 голосов
/ 20 мая 2018

Главное, что меняется в разных версиях компилятора, - это среда выполнения C / C ++.Так, например, передача stream или FILE * через ваш API может вызвать проблемы, поэтому не делайте этого.Точно так же, не free память в вашем приложении, которая была выделена в вашей DLL, и не удаляйте в приложении объект, который был создан в DLL.Или наоборот.

Другие вещи, которые могут измениться, - это порядок / выравнивание / общий размер переменных-членов в объекте, схема искажения имени, используемая различными версиями компилятора, или макетвиртуальных таблиц в объекте (и, возможно, расположение этих виртуальных таблиц внутри объекта, особенно при использовании множественного или виртуального наследования).

Хотя в конце туннеля есть некоторый свет.Если вы готовы обернуть класс C ++, который вы хотите экспортировать через API, во что-то похожее на COM-объект , тогда вы можете застраховать себя от всех этих проблем.Это потому, что Microsoft фактически пообещала не изменять макет vtable для такого объекта, потому что, если они это сделают, COM сломается.

Это накладывает некоторые ограничения на то, как такие «COM-подобные» объекты могут использоватьсяно я подойду к этому через минуту.Хорошей новостью является то, что вы можете избежать большей части тяжелой работы, которую требует полномасштабный 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 ();
};

Теперь открытый файл заголовка для класса (MyPublicClass), который нужно экспортировать с помощью DLL:

// 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, поскольку это дает вам большую гибкость при реализации класса.Поэтому я переработал приведенный выше код, чтобы включить эти изменения, и изменил названия нескольких вещей, чтобы сделать намерение более ясным.Надеюсь, это кому-нибудь поможет.

...