У меня есть эта точная проблема в проекте, над которым я работаю - классы STL передаются в библиотеки DLL и из них. Проблема заключается не только в разной памяти, но и в том, что классы STL не имеют двоичного стандарта (ABI). Например, в отладочных сборках некоторые реализации STL добавляют дополнительную информацию для отладки в классы STL, такие как sizeof(std::vector<T>)
(выпускная сборка)! = sizeof(std::vector<T>)
(сборка отладки). Ой! Нет никакой надежды, что вы можете положиться на двоичную совместимость этих классов. Кроме того, если ваша DLL была скомпилирована в другом компиляторе с некоторой другой реализацией STL, которая использовала другие алгоритмы, у вас может быть и другой бинарный формат в сборках релизов.
Способ, которым я решил эту проблему, - использовать класс шаблонов под названием pod<T>
(POD означает Plain Old Data, например, chars и ints, которые обычно передают между DLL). Задача этого класса - упаковать его параметр шаблона в согласованный двоичный формат, а затем распаковать его на другом конце. Например, вместо функции в DLL, возвращающей номер std::vector<int>
, вы возвращаете номер pod<std::vector<int>>
. Существует специальная спецификация шаблона для pod<std::vector<T>>
, которая заполняет буфер памяти и копирует элементы. Он также предоставляет operator std::vector<T>()
, так что возвращаемое значение может быть прозрачно сохранено обратно в std :: vector, построив новый вектор, скопировав в него сохраненные элементы и вернув его. Поскольку он всегда использует один и тот же двоичный формат, его можно безопасно скомпилировать для разделения двоичных файлов и сохранения бинарных данных. Альтернативным именем для pod
может быть make_binary_compatible
.
Вот определение класса стручка:
// 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 - это просто таНос и бесплатно, но с использованием той же функции между всеми библиотеками DLL. В моем случае все библиотеки DLL используют malloc и свободны от EXE-хоста, поэтому все они используют одну и ту же кучу, которая решает проблему памяти кучи. (Точно, как вы понять это до вас.)
Также обратите внимание, что вам нужны специализации для pod<T*>
, pod<const T*>
и стручок для всех основных типов (pod<int>
, pod<short>
и т.д.), так что они могут быть сохранены в " pod vector "и другие контейнеры для контейнеров. Они должны быть достаточно просты, чтобы писать, если вы понимаете приведенный выше пример.
Этот метод означает копирование всего объекта. Однако вы можете передавать ссылки на типы контейнеров, поскольку есть 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 не был там.
«Все, что можно было бы назвать новым/удалить» Я хотел сказать «все, что может вызвать удаление в моем коде на блоке памяти, который был выделен в dll». – SigTerm