2010-02-15 61 views
19

雖然這似乎是一個非常普遍的問題,但我並沒有收穫太多的信息:如何在內存分配的DLL邊界之間創建一個安全的接口?跨DLL的內存/堆管理

這是很衆所周知,

// in DLL a 
DLLEXPORT MyObject* getObject() { return new MyObject(); } 
// in DLL b 
MyObject *o = getObject(); 
delete o; 

肯定會導致死機。 但是因爲像上面那樣的交互 - 正如我敢說 - 並不罕見,所以必須有一種方法來確保安全的內存分配。

當然,一個能提供

// in DLL a 
DLLEXPORT void deleteObject(MyObject* o) { delete o; } 

但也許有更好的方法(例如smart_ptr?)。我在閱讀關於使用自定義分配器處理STL容器時也是如此。

所以我詢問更多的是文章和/或文獻處理這一主題一般的指針。是否有特殊的謬論需要注意(異常處理?),並且這個問題僅限於DLL或者是否也是「共享對象」造成的?

+2

Linux中的共享庫也受到影響。 – sergiom 2010-02-15 13:36:00

回答

12

正如您所建議的那樣,您可以使用boost::shared_ptr來解決該問題。在構造函數中,您可以傳遞一個自定義清理函數,該函數可能是創建指針的dll的deleteObject-Method。例如:

boost::shared_ptr<MyObject> Instance(getObject(), deleteObject); 

如果您不需要C-界面爲你的DLL,你可以有getObject返回一個shared_ptr。

+2

這也是Effective C++中推薦的。 – 2010-02-15 14:15:43

9

超載operator newoperator delete等。人所有的DLL類和DLL中實現它們:

void* MyClass::operator new(size_t numb) { 
    return ::operator new(num_bytes); 
} 

void MyClass::operator delete(void* p) { 
    ::operator delete(p); 
} 
... 

這可以很容易地放置在DLL導出的所有類的公共基類。

這樣,分配和釋放完全在DLL堆上完成。老實說,我不確定它是否有任何嚴重的陷阱或可移植性問題 - 但它適用於我。

+0

這可能不是一個好主意:https://stackoverflow.com/questions/11846511/new-and-delete-operator-overloading-for-dll – jaba 2017-12-06 10:39:12

2

在某些情況下可能適用的另一種選擇是保持DLL內的所有分配和釋放,並防止對象穿越該邊界。您可以通過提供一個把手,這樣創造一個MyObject DLL的代碼中創建並返回一個簡單的手柄做到這一點(例如unsigned int),通過該客戶端的所有操作進行:

// Client code 
ObjHandle h=dllPtr->CreateObject(); 
dllPtr->DoOperation(h); 
dllPtr->DestroyObject(h); 

由於所有的分配發生在dll內部,你可以通過包裝在shared_ptr中來確保它被清理乾淨。這幾乎是John Lakos在Large Scale C++中提出的方法。

+0

謝謝你提醒我拿起閱讀/瀏覽Lakos!它的聲譽似乎被「兩個邁爾斯」所掩蓋,但這又是一個不同的話題。 – msi 2010-02-15 13:50:19

1

在一個「分層」體系結構(非常常見的場景)中,最深層的組件負責提供關於該問題的策略(可能會返回上述建議的shared_ptr<>或「調用者負責刪除此項」或「永不刪除這個,但是在完成時調用releaseFooObject()並且之後不訪問它們「或...),並且靠近用戶的組件負責遵循該策略。

雙向信息流使責任難以表徵。


被這個問題僅限於DLL或屬於「造成的」過UNIX共享對象?

其實它比這更糟糕:你可以像靜態鏈接庫一樣容易地遇到這個問題。在單個執行上下文中存在代碼邊界,從而有機會濫用或誤傳某些設施。

+0

其實在Unix上沒有這樣的問題... – Emiliano 2012-05-30 16:03:13

+1

@happy_emi:是的。有。問題是代碼界限而非操作系統的後果。 Unix有一些編碼傳統,使得它不太經常出現,但它仍然是可能的。 – dmckee 2012-05-30 16:52:34

+0

我明白了,謝謝。 – Emiliano 2012-05-30 17:10:51

5

你可能會說它「肯定會導致崩潰」。滑稽 - 「可能」意思與「肯定」完全相反。

現在,聲明大多是歷史。有一個非常簡單的解決方案:使用1個編譯器,1個編譯器設置,並鏈接到CRT的DLL形式。 (你可能會跳過後者)

有沒有特定的文章鏈接到,因爲這是一個沒有問題的時下。無論如何,你需要1個編譯器,1個設置規則。簡單的事情,如sizeof(std::string)依賴於它,否則你會有大規模的ODR違規。

0

我已經編寫了an article關於使用C++ 11的unique_ptr自定義刪除工具來通過DLL邊界(或Linux中的共享對象庫)傳遞對象。文章中描述的方法不會使用deleter「污染」unique_ptr簽名。

1

這是很衆所周知,

// in DLL a 
DLLEXPORT MyObject* getObject() { return new MyObject(); } 
// in DLL b 
MyObject *o = getObject(); 
delete o; 

肯定會導致死機。

上述是否具有明確的特徵取決於如何定義MyObject類型。

如果這個類有一個虛擬的析構函數,(而且那個析構函數沒有被內聯定義),那麼它就不會崩潰,並且會呈現定義良好的行爲。

通常列舉了爲什麼這種崩潰的原因是delete做兩件事情:

  • 調用析構函數
  • 可用內存(通過調用operator delete(void* ...)

一類非虛擬析構函數,它可以做到「內聯」這些事情,這導致DLL「b」中delete可能會嘗試從「a」堆==崩潰釋放內存的情況。

但是,如果MyObject析構函數是virtual然後調用「免費」的函數,編譯needs to determine the actual run-time class of the pointer之前,纔可以正確的指針傳遞給operator delete()

C++的任務,你必須通過完全一樣的作爲運營商新返回的運營商地址 刪除。當你使用新分配一個對象 ,編譯器暗中知道具體類型 對象(這是編譯器使用在正確的內存大小 傳遞給運營商新的,例如。)

然而,如果你的類有一個帶虛擬析構函數的基類 類,並且你的對象通過指向基類的 指針被刪除,編譯器不知道在調用位置的具體類型 ,因此不能計算正確的地址到 傳給operator delete()。爲什麼,你可能會問?因爲存在多個繼承,所以基類指針的地址可能是 ,與內存中對象的地址不同。

那麼,會發生什麼情況在 的情況是,當你刪除它有一個虛析構函數的對象, 編譯器調用所謂的刪除析構函數 而不是 呼叫通常順序正常的析構函數遵循通過運算符delete()來回收內存。

由於刪除析構函數是虛擬函數,在運行時 具體類型的執行將被調用,並且 該實現能夠計算正確的地址爲 對象在內存中的。該實現的功能是調用 常規析構函數,計算該對象的正確地址,然後 然後調用該地址上的operator delete()。

似乎GCC(來自鏈接的文章)和MSVC通過調用dtor以及「刪除析構函數」上下文中的「自由」函數來實現此目的。而且這個助手必須存在於你的DLL中,並且即使「a」和「b」具有不同的值,也總是使用正確的堆