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