繁体   English   中英

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

[英]returning std::string/std::list from dll

简短的问题。

我只是得到了一个我应该与之接口的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)的任何内容)。

我是对还是不对?

要记住的主要事情是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之间传输良好)。 此类的工作是将其模板参数打包为一致的二进制格式,然后在另一端解包。 例如,代替返回std::vector<int>的DLL中的函数,返回pod<std::vector<int>> pod<std::vector<T>>有一个模板专门化,它对内存缓冲区进行malloc并复制元素。 它还提供了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的所有基本类型( pod<int>pod<short>等)的专业化,以便它们可以存储在“pod矢量”中“和其他豆荚容器。 如果你理解上面的例子,这些应该足够直接写。

此方法确实意味着复制整个对象。 但是,您可以传递对pod类型的引用,因为有一个operator=在二进制文件之间是安全的。 但是,没有真正的传递参考,因为更改pod类型的唯一方法是将其复制回原始类型,更改它,然后重新打包为pod。 此外,它创建的副本意味着它不一定是最快的方式,但它的工作原理

但是,你也可以pod-specialize你自己的类型,这意味着你可以有效地返回复杂的类型,如std::map<MyClass, std::vector<std::string>>pod<MyClass>和partial的特化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类不存在一样。

我不确定“任何可以调用new / delete的东西” - 这可以通过仔细使用具有适当分配器/删除器功能的共享指针等效物来管理。

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

当我需要这种功能时,我经常使用跨边界的虚拟接口类。 然后,您可以为std::stringlist等提供包装器,以便您通过接口安全地使用它们。 然后,您可以使用您的实现或使用shared_ptr来控制分配等。

说完这一切之后,我在DLL接口中使用的一件事就是shared_ptr ,因为它太有用了。 我还没有遇到任何问题,但一切都是用相同的工具链构建的。 我正在等待这个咬我,因为毫无疑问它会。 请参阅前一个问题: 在dll-interfaces中使用shared_ptr

对于std::string您可以使用c_str返回。 在更复杂的东西的情况下,选项可以是类似的

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

然后(假设你想使用std :: list),你可以使用一个接口

class List
    {
    public:
        virtual void processItems(ContainerValueProcessor&& proc)=0;
    };

请注意,List现在可以由任何容器实现。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM