简短的问题.
我只是得到了一个我应该与之接口的DLL.Dll使用来自msvcr90D.dll的crt(注意D),并返回std :: strings,std :: lists和boost :: shared_ptr.操作员new/delete不会在任何地方超载.
我假设crt mixup(发布版本中的msvcr90.dll,或者如果其中一个组件使用较新的crt重建等)最终会导致问题,并且应该重写dll以避免返回任何可能调用new/delete的内容(即任何可以在我的代码中调用删除在dll中分配的内存块(可能使用不同的crt)的任何内容).
我是对还是不对?
Dou*_* T. 12
要记住的主要事情是dll包含代码而不是内存.分配的内存属于进程(1).在进程中实例化对象时,可以调用构造函数代码.在该对象的生命周期中,您将调用其他代码片段(方法)来处理该对象的内存.然后,当对象消失时,将调用析构函数代码.
STL模板未显式从dll导出.代码静态链接到每个dll.因此,当在a.dll中创建std :: string并传递给b.dll时,每个dll将有两个不同的string :: copy方法实例.在a.dll中调用的副本调用a.dll的复制方法...如果我们在b.dll中使用s并调用copy,则将调用b.dll中的复制方法.
这就是为什么在西蒙的回答中他说:
除非您始终可以保证您的整个二进制文件都使用相同的工具链构建,否则将会发生不好的事情.
因为如果由于某种原因,字符串s的副本在a.dll和b.dll之间有所不同,会发生奇怪的事情.更糟糕的是,如果字符串本身在a.dll和b.dll之间是不同的,并且一个中的析构函数知道要清除另一个忽略的额外内存......你可能很难追踪内存泄漏.可能更糟糕的是... a.dll可能是针对完全不同版本的STL(即STLPort)构建的,而b.dll是使用Microsoft的STL实现构建的.
那你该怎么办?在我们工作的地方,我们严格控制工具链并为每个dll构建设置.因此,当我们开发内部dll时,我们可以自由地转移STL模板.我们仍然遇到问题,在极少数情况下会因为某人没有正确设置他们的项目而突然出现.然而,我们发现STL的便利性值得偶然出现问题.
为了让dll暴露给第三方,这完全是另一个故事.除非您想严格要求客户端的特定构建设置,否则您将希望避免导出STL模板.我不建议严格强制您的客户具有特定的构建设置......他们可能有另一个第三方工具,希望您使用完全相反的构建设置.
(1)是的我知道静态和本地在dll加载/卸载时被实例化/删除.
我正在研究的项目中存在这个确切的问题 -  STL类很多都是从DLL传输的.问题不仅在于不同的内存堆 - 实际上STL类没有二进制标准(ABI).例如,在调试版本中,一些STL实现向STL类添加额外的调试信息,例如sizeof(std::vector<T>)(发布版本)!= sizeof(std::vector<T>)(调试版本).哎哟! 没有希望你可以依赖这些类的二进制兼容性.此外,如果您的DLL是在使用其他算法的其他STL实现的不同编译器中编译的,那么您在发布版本中也可能有不同的二进制格式.
我解决这个问题的方法是使用一个名为的模板类pod<T>(POD代表普通旧数据,如字符和整数,通常在DLL之间传输良好).此类的工作是将其模板参数打包为一致的二进制格式,然后在另一端解包.例如,代替返回a的DLL中的函数std::vector<int>,返回a pod<std::vector<int>>.有一个模板专门化pod<std::vector<T>>,它mallocs一个内存缓冲区并复制元素.它还提供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和free,但在所有DLL之间使用相同的函数.在我的例子中,所有DLL都使用malloc并从主机EXE中释放,因此它们都使用相同的堆,这解决了堆内存问题.(你究竟如何解决这个问题取决于你.)
另外请注意,你需要的专业化pod<T*>,pod<const T*>以及荚所有基本类型(pod<int>,pod<short>等),使它们可以被存储在一个"吊舱向量"和其他荚容器.如果你理解上面的例子,这些应该足够直接写.
此方法确实意味着复制整个对象.但是,您可以传递对pod类型的引用,因为operator=二进制文件之间存在安全性.但是,没有真正的传递参考,因为更改pod类型的唯一方法是将其复制回原始类型,更改它,然后重新打包为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类不存在一样.
| 归档时间: | 
 | 
| 查看次数: | 6479 次 | 
| 最近记录: |