簡體   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