我在我正在处理的项目中有这个确切的问题 - STL类传递给DLL和很多。问题不仅仅是不同的内存堆 - 实际上,STL类没有二进制标准(ABI)。例如,在调试版本中,一些STL实现会向STL类添加额外的调试信息,例如sizeof(std::vector<T>)
(发布版本)!= sizeof(std::vector<T>)
(调试版本)。哎哟!没有希望你可以依赖这些类的二进制兼容性。此外,如果您的DLL是在其他一些使用其他算法的其他STL实现的编译器中编译的,那么您也可以在发布版本中使用不同的二进制格式。
我解决这个问题的方法是使用一个名为pod<T>
(POD代表简单的旧数据,如chars和ints,它们通常在DLL之间转换)的模板类。此类的工作是将其模板参数打包为一致的二进制格式,然后在另一端将其解包。例如,代替返回std::vector<int>
的DLL中的函数,您将返回pod<std::vector<int>>
。 pod<std::vector<T>>
有一个模板专门化,它将malloc一个内存缓冲区并复制这些元素。它还提供了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 - 如果vector包含另一个类似std :: string的STL类型,我们希望它也是二进制兼容的!
// 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和free,但使用的所有DLL之间相同的功能。在我的情况下,所有DLL使用malloc并从主机EXE中释放,所以它们都使用相同的堆,这解决了堆内存问题。 (究竟如何算出这个是到你。)
另外请注意,你需要专业化的pod<T*>
,pod<const T*>
,荚所有基本类型(pod<int>
,pod<short>
等),使它们可以被存储在一个“荚载体“和其他荚容器。如果你了解上面的例子,这些应该足够简单。
此方法的意思是复制整个对象。但是,您可以传递对pod类型的引用,因为在二进制文件之间有一个安全的operator=
。然而,没有真正的传递引用,因为更改pod类型的唯一方法是将其复制回原始类型,然后将其更改,然后重新打包为pod。此外,它创建的副本意味着它不一定是最快的方式,但它作品。
但是,你也可以荚专注自己的类型,这意味着你可以有效地返回复杂的类型,比如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 2010-08-25 13:47:22