У меня есть именно эта проблема в проекте, над которым я работаю - классы STL часто передаются в и из DLL. Проблема заключается не только в разных кучах памяти, но и в том, что у классов STL нет двоичного стандарта (ABI). Например, в отладочных сборках некоторые реализации STL добавляют дополнительную информацию об отладке к классам STL, например, sizeof(std::vector<T>)
(выпуск сборки)! = sizeof(std::vector<T>)
(сборка отладки). Ой! Нет надежды, что вы можете положиться на двоичную совместимость этих классов. Кроме того, если ваша DLL была скомпилирована в другом компиляторе с другой реализацией STL, которая использовала другие алгоритмы, у вас может быть другой двоичный формат в сборках релиза.
Я решил эту проблему, используя шаблонный класс с именем pod<T>
(POD обозначает простые старые данные, такие как символы и целые числа, которые обычно хорошо переносятся между библиотеками DLL). Работа этого класса состоит в том, чтобы упаковать его параметр шаблона в согласованный двоичный формат, а затем распаковать его на другом конце. Например, вместо функции в DLL, возвращающей std::vector<int>
, вы возвращаете pod<std::vector<int>>
. Есть специализация шаблона для pod<std::vector<T>>
, которая неправильно размещает буфер памяти и копирует элементы. Он также обеспечивает operator std::vector<T>()
, так что возвращаемое значение можно прозрачно сохранить обратно в std :: vector, создав новый вектор, скопировав в него свои сохраненные элементы и вернув его. Поскольку он всегда использует один и тот же двоичный формат, его можно безопасно скомпилировать в отдельные двоичные файлы и сохранить двоичную совместимость. Альтернативное имя для pod
может быть make_binary_compatible
.
Вот определение класса pod:
// All members are protected, because the class *must* be specialization
// for each type
template<typename T>
class pod {
protected:
pod();
pod(const T& value);
pod(const pod& copy); // no copy ctor in any pod
pod& operator=(const pod& assign);
T get() const;
operator T() const;
~pod();
};
Вот частичная специализация для pod<vector<T>>
- обратите внимание, частичная специализация используется, так что этот класс работает для любого типа T. Также обратите внимание, что он на самом деле хранит буфер памяти pod<T>
, а не просто T - если вектор содержал другой тип STL, такой как std :: string, мы хотели бы, чтобы он также был двоично-совместимым!
// Transmit vector as POD buffer
template<typename T>
class pod<std::vector<T> > {
protected:
pod(const pod<std::vector<T> >& copy); // no copy ctor
// For storing vector as plain old data buffer
typename std::vector<T>::size_type size;
pod<T>* elements;
void release()
{
if (elements) {
// Destruct every element, in case contained other cr::pod<T>s
pod<T>* ptr = elements;
pod<T>* end = elements + size;
for ( ; ptr != end; ++ptr)
ptr->~pod<T>();
// Deallocate memory
pod_free(elements);
elements = NULL;
}
}
void set_from(const std::vector<T>& value)
{
// Allocate buffer with room for pods of T
size = value.size();
if (size > 0) {
elements = reinterpret_cast<pod<T>*>(pod_malloc(sizeof(pod<T>) * size));
if (elements == NULL)
throw std::bad_alloc("out of memory");
}
else
elements = NULL;
// Placement new pods in to the buffer
pod<T>* ptr = elements;
pod<T>* end = elements + size;
std::vector<T>::const_iterator iter = value.begin();
for ( ; ptr != end; )
new (ptr++) pod<T>(*iter++);
}
public:
pod() : size(0), elements(NULL) {}
// Construct from vector<T>
pod(const std::vector<T>& value)
{
set_from(value);
}
pod<std::vector<T> >& operator=(const std::vector<T>& value)
{
release();
set_from(value);
return *this;
}
std::vector<T> get() const
{
std::vector<T> result;
result.reserve(size);
// Copy out the pods, using their operator T() to call get()
std::copy(elements, elements + size, std::back_inserter(result));
return result;
}
operator std::vector<T>() const
{
return get();
}
~pod()
{
release();
}
};
Обратите внимание, что используются функции выделения памяти: pod_malloc и pod_free - они просто malloc и бесплатны, но используют одну и ту же функцию для всех библиотек DLL. В моем случае все библиотеки DLL используют malloc и свободны от EXE-файла узла, поэтому все они используют одну и ту же кучу, что решает проблему с кучей памяти. (Как вы это выясните, зависит от вас.)
Также обратите внимание, что вам нужны специализации для pod<T*>
, pod<const T*>
и pod для всех основных типов (pod<int>
, pod<short>
и т. Д.), Чтобы они могли храниться в «векторе pod» и других модулях. контейнеры. Это должно быть достаточно просто, если вы понимаете приведенный выше пример.
Этот метод означает копирование всего объекта. Однако вы можете передавать ссылки на типы модулей, поскольку между двоичными файлами существует operator=
. Однако никакой реальной передачи по ссылке нет, поскольку единственный способ изменить тип модуля - это скопировать его обратно в исходный тип, изменить, а затем упаковать как модуль. Кроме того, копии, которые он создает, означают, что это не обязательно самый быстрый способ, но он работает .
Тем не менее, вы также можете специализировать свои собственные типы, что означает, что вы можете эффективно возвращать сложные типы, такие как std::map<MyClass, std::vector<std::string>>
, при условии, что есть специализация для pod<MyClass>
и частичные специализации для std::map<K, V>
, std::vector<T>
и std::basic_string<T>
(которую нужно написать только один раз).
Использование конечного результата выглядит следующим образом. Общий интерфейс определен:
class ICommonInterface {
public:
virtual pod<std::vector<std::string>> GetListOfStrings() const = 0;
};
DLL может реализовать это следующим образом:
pod<std::vector<std::string>> MyDllImplementation::GetListOfStrings() const
{
std::vector<std::string> ret;
// ...
// pod can construct itself from its template parameter
// so this works without any mention of pod
return ret;
}
И вызывающий, отдельный двоичный файл, может называть его так:
ICommonInterface* pCommonInterface = ...
// pod has an operator T(), so this works again without any mention of pod
std::vector<std::string> list_of_strings = pCommonInterface->GetListOfStrings();
Так что, как только он настроен, вы можете использовать его почти так, как если бы класс pod отсутствовал.