I have this exact problem in the project I'm working on - the STL classes are passed to and from the DLLs. The problem is not only in different memory, but also in the fact that STL classes do not have a binary standard (ABI). For example, in debug builds, some STL implementations add additional debugging information to STL classes, such as sizeof(std::vector<T>) (release build)! = sizeof(std::vector<T>) (debug build). Oh! There is no hope that you can rely on the binary compatibility of these classes. Also, if your DLL was compiled in another compiler with some other STL implementation that used different algorithms, you may have a different binary format in versions.
The way I solved this problem is to use a template class called pod<T> (POD means Plain Old Data, such as chars and ints, which are usually passed between DLLs). The task of this class is to pack its template parameter into a consistent binary format, and then unpack it at the other end. For example, instead of the function in the DLL that returns std::vector<int> , you return pod<std::vector<int>> . It specializes in a template for pod<std::vector<T>> , which expands the memory buffer and copies elements. It also provides an operator std::vector<T>() , so the return value can be transparently saved back to std :: vector by building a new vector, copying the saved elements into it and returning it. Since it always uses the same binary format, it can be safely compiled to split binary files and save binary data. An alternate name for pod might be make_binary_compatible .
Here is the pod class definition:
// 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(); };
Here's the partial specialization for pod<vector<T>> - pay attention to the partial specialization, so this class works for any type T. Also note: it actually stores the pod<T> memory buffer, not just T - if the vector contained another type of STL, such as std :: string, we would like this to be binary compatible too!
// 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(); } };
Please note that the memory allocation functions used - pod_malloc and pod_free - are just malloc and free, but using the same function between all the DLLs. In my case, all the DLLs use malloc and are free from the EXE host, so they all use the same heap, which solves the heap memory problem. (Exactly how you understand this is up to you.)
Also note that you need specializations for pod<T*> , pod<const T*> and pod for all main types ( pod<int> , pod<short> , etc.) so that they can be saved in the "vector container" and the other block containers. They should be simple enough to write if you understand the example above.
This method means copying the entire object. However, you can pass references to pod types, since there is operator= , which is safe between binary files. However, there is no real transfer by reference, since the only way to change the type of an element is to copy it back to the original type, change it, and then repack it as a container. In addition, the created copies mean that this is not necessarily the fastest way, but it works.
However, you can also specialize your own types, which means you can effectively return complex types such as std::map<MyClass, std::vector<std::string>> , providing specialization for pod<MyClass> and partial specializations for std::map<K, V> , std::vector<T> and std::basic_string<T> (which you only need to write once).
Using the final result is as follows. The common interface is defined:
class ICommonInterface { public: virtual pod<std::vector<std::string>> GetListOfStrings() const = 0; };
A DLL can implement this as such:
pod<std::vector<std::string>> MyDllImplementation::GetListOfStrings() const { std::vector<std::string> ret;
And the caller, a separate binary, may call it the following:
ICommonInterface* pCommonInterface = ...
So, once it is configured, you can use it almost as if the pod class were not there.