C++ 从dll返回std::string/std::list

C++ 从dll返回std::string/std::list,c++,winapi,dll,C++,Winapi,Dll,简短的问题 我刚刚得到了一个应该与之接口的dll。 Dll使用msvcr90D.Dll中的crt(注意D),并返回std::strings、std::list和boost::shared\u ptr。运算符new/delete未在任何位置重载 我假设crt混合(发布版本中的msvcr90.dll,或者如果其中一个组件是用较新的crt重建的,等等)最终必然会导致问题,并且应该重写dll以避免返回任何可能调用new/delete的内容(即在分配的内存块上的代码中可能调用delete的内容)(可能使用

简短的问题

我刚刚得到了一个应该与之接口的dll。 Dll使用msvcr90D.Dll中的crt(注意D),并返回std::strings、std::list和boost::shared\u ptr。运算符new/delete未在任何位置重载

我假设crt混合(发布版本中的msvcr90.dll,或者如果其中一个组件是用较新的crt重建的,等等)最终必然会导致问题,并且应该重写dll以避免返回任何可能调用new/delete的内容(即在分配的内存块上的代码中可能调用delete的内容)(可能使用不同的crt)在dll中)


我说得对吗?

我不确定“任何可以调用new/delete的东西”——这可以通过小心地使用具有适当分配器/deleter函数的共享指针等价物来管理

但是,一般来说,我不会跨越DLL边界传递模板-模板类的实现最终在接口的两侧,这意味着您可以使用不同的实现。除非您始终能够保证您的整个二进制文件集都是使用相同的工具链构建的,否则会发生不好的事情

当我需要这类功能时,我经常使用虚拟接口类跨越边界。然后,您可以为
std::string
list
等提供包装,允许您通过接口安全地使用它们。然后,您可以使用实现或使用
共享的\u ptr
来控制分配等


说了这么多,我在DLL接口中使用的一件事是共享的,因为它太有用了。我还没有遇到任何问题,但所有的东西都是用相同的工具链构建的。我在等待它咬我,毫无疑问它会咬我。请看前面的问题:

我在一个项目中遇到了这个问题-STL类与DLL之间的传输非常频繁。问题不仅仅在于不同的内存堆——实际上STL类没有二进制标准(ABI)。例如,在调试版本中,一些STL实现向STL类添加额外的调试信息,例如
sizeof(std::vector)
(发布版本)!=
sizeof(std::vector)
(调试版本)。哎哟!这些类的二进制兼容性是不可能的。此外,如果您的DLL是在不同的编译器中编译的,并且使用了其他算法的其他STL实现,那么在发布版本中也可能有不同的二进制格式

我解决这个问题的方法是使用一个名为
pod
的模板类(pod代表普通的旧数据,如chars和int,它们通常在DLL之间传输良好)。此类的工作是将其模板参数打包为一致的二进制格式,然后在另一端将其解包。例如,与DLL中返回
std::vector
的函数不同,您返回的是
pod
pod
有一个模板专用化,它将malloc作为内存缓冲区并复制它还提供了
操作符std::vector()
,这样通过构造一个新的向量,将其存储的元素复制到其中并返回,返回值可以透明地存储回std::vector中。因为它总是使用相同的二进制格式,所以可以安全地编译成单独的二进制文件并保持二进制兼容。
pod
的另一个名称可以be
使二进制兼容

以下是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();
};
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 MyDllImplementation::GetListOfStrings()常量
{
std::载体ret;
// ...
//pod可以从其模板参数构造自身
//所以这在没有提到pod的情况下有效
返回ret;
}
调用方是一个单独的二进制文件,可以这样调用它:

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();
ICommonInterface*pCommonInterface=。。。
//pod有一个操作符T(),因此它可以在不提及pod的情况下再次工作
std::vector list_of_strings=pCommonInterface->GetListOfStrings();

因此,一旦设置好,您就可以像pod类不存在一样使用它。

要记住的主要一点是DLL包含代码而不是内存。分配的内存属于进程(1)。当您在流程中实例化一个对象时,您将调用构造函数代码。在该对象的生存期内,您将调用其他代码(方法)来处理该对象的内存。然后,当对象离开时,将调用析构函数代码

STL模板不是从dll显式导出的。代码是静态链接到每个dll中的。因此,当std::string s在a.dll中创建并传递到b.dll时,每个dll将有两个不同的string::copy方法实例。a.dll中调用的copy调用a.dll的copy方法…如果我们在b.dll中使用s并调用copy,则copy将调用b.dll中的方法

这就是为什么西蒙在回答时说:

除非你能,否则坏事就会发生 始终保证您的整套 所有的二进制文件都是用相同的 工具链

因为如果由于某种原因,字符串的副本在a.dll和b.dll之间不同,就会发生奇怪的事情。更糟糕的是,如果字符串本身在a.dll和b.dll之间不同,并且其中一个中的析构函数知道清除另一个忽略的额外内存,则可能很难跟踪内存泄漏。可能更糟糕的是,可能是生成了a.dll而b.dll是使用Microsoft的STL实现构建的

那你该怎么办呢?在我们工作的地方,我们对工具链和每个dll的构建设置都有严格的控制。因此,当我们开发内部dll时,我们可以自由地传递STL模板。我们仍然存在一些问题,因为有人没有正确地设置他们的项目。然而,我们发现STL的便利不值得他偶然发现了一个问题

Fo
class ContainerValueProcessor
    {
    public:
         virtual void operator()(const trivial_type& value)=0;
    };
class List
    {
    public:
        virtual void processItems(ContainerValueProcessor&& proc)=0;
    };