std::weak_ptr卸載后,我在使用另一個 DLL 中的物件時遇到問題。
這是簡化的情況:有一個加載 2 個 DLL 的應用程式。DLL 提供了一些繼承通用介面的物件。應用程式保留了這些物件的一種快取(a std::vectorof std::shared_ptrs),以便 DLL 可以間接地使用其他物件。
當 DLL 卸載時,它會從快取中洗掉它的物件。
但是如果另一個 DLL 保留weak_ptr另一個 DLL 的物件,問題就會出現,因為當weak_ptr呼叫thar 的解構式時,它會嘗試洗掉控制塊,這會導致AV錯誤,因為所有者 DLL 已經卸載。
雖然我可能會強制在應用程式中分配控制塊,而不是 DLL,但我不知道該怎么做。我嘗試Allocator在應用程式端創建一個并將其傳遞給shared_ptr建構式,但控制塊仍然保留一些指向 DLL 類的指標,這再次導致AV錯誤。
似乎唯一可行的方法是創建指向 DLL 中物件的原始指標,然后將它們傳遞給應用程式,然后應用程式將它們包裝在shared_ptr. 但這會導致enable_shared_from_this.
所以,我的問題是 - 最好的做法是什么?
uj5u.com熱心網友回復:
我已經解決了這個問題。
通常,您不應該在 dll 的生命周期之后保留指向分配在 dll 中的資料的指標。
但是你可以解決這個特定的問題。基本上,從程式中的原始指標創建中替換對 make shared 和 shared ptr 的呼叫。最簡單的方法可能是完全禁止共享 ptr,并撰寫一個執行重定向的包裝子類。
然后,制作一個“mysharedptr”dll。它提供了兩個功能;dll 安全創建共享和 dll 安全共享 ptr 創建(來自 ptr)。
from ptr 很容易。Header 有一個模板函式,它呼叫一個 create void shared 并將 ptr 傳遞給洗掉函式。創建洗掉函式的 dll 必須持續足夠長的時間;見下文。
然后它使用別名建構式將一個共享的 ptr 回傳給帶有 void 指標控制塊的 T。
對于 make shared 替換,撰寫一個 dll 安全函式,使共享各種固定大小的緩沖區。就像二的冪一樣。它還包含一個破壞內容的函式指標。現在在模板中,呼叫固定緩沖區 make shared,緩沖區中的放置構造,然后安裝一個指向銷毀函式的指標(按此順序)。
最后,為了使洗掉器和銷毀函式 dll 安全,請使用 ADL 和標記調度。
using cleanup=void(*)(void*);
template<class T>struct tag_t{};
cleanup dll_safe_delete_for(tag_t<Bob>);
cleanup dll_safe_destroy_for(tag_t<Bob>);
由 dll 匯出的這兩個函式型別來自并位于所述型別的命名空間中。dll 安全共享 ptr 通過標記調度找到它們。
std::shared_ptr<void> safe_void_shared( void*, void(*)(void*) ); // export from "safe shared ptr util" dll
// API for dll safe shared ptrs:
template<class T>
std::shared_ptr<T> safe_shared( T* t ) {
auto pvoid = safe_void_shared( t, dll_safe_delete_for(tag_t<T>{}) );
return std::shared_ptr<T>( std::move(pvoid), t ); // aliasing ctor
}
然后在安全的共享 ptr dll 中:
std::shared_ptr<void> safe_void_shared( void* ptr, void(*dtor)(void*) ){
return {ptr, dtor};
}
要使型別起作用,他們需要這樣做:
namespace FooNS{
struct some_type {/*blah*/};
cleanup dll_safe_delete_for(tag_t<some_type>);// exported from dll
}
// in cpp in dll for some_type
cleanup FooNS::dll_safe_delete_for(tag_t<some_type>){
return [](void* pvoid){if(pvoid) delete static_cast<some_type*>(pvoid);};
}
并做了。用戶只是:
auto ptr=safe_shared<FooNS::some_type>( pSomeType );
dtor 代碼位于 some_type dll 中,而控制塊代碼位于 dll 安全共享 dll(不同的 dll)中。所以你的弱 ptr 可以比 some_type dll 活得更久。
類似地,對于 make 共享支持,您有一個帶膠水的 beader 檔案,一個基于 void 的 dll 安全函式,確保控制塊代碼在一個安全的 dll 中,以及一些從類的個人 dll 中獲取指向解構式的指標的花哨步驟。
// exported from dll
// creates an approx bytes sized buffer using make_shared, then emplaces and installs dtor and reutns pointer at object
std::shared_ptr<void> emplace_shared_ptr( std::size_t bytes, std::function<void*(void*)> ctor, void(* dtor)(void*) );
template<class T, class...Args>
std::shared_ptr<T> make_dll_safe_shared( Args&&...args ){
auto pvoid = emplace_shared_ptr(
sizeof(T),
[&](void* here){ return ::new(here) T(std::forward<Args>(args)...); },
dll_safe_destroy_for(tag_t<T>{})
);
return std::shared_ptr<T>(std::move(pvoid), static_cast<T*>(pvoid.get()) );
}
現在 emplace void 有點棘手。
template<std::size_t sz>
struct buffer{
std::array<char, sz> data;
void(*dtor)(void*)=nullptr;
~buffer(){ if (dtor) dtor(data.data()) }
};
trmplate<std::size_t...mags>
std::shared_ptr<void> emplace_shared_ptr_impl( std::index_sequence<Is...>, std::size_t magnitude, std::function<void*(void*)> ctor, void(* dtor)(void*) ){
using factory=std::shared_ptr<void>(*)( std::function<void*(void*)>, void(*)(void*) );
static factory factories[]={
[](std::function<void*(void*)> ctor, void(*dtor)(void*))->std::shared_ptr<void>{
auto pbuff=std::make_shared<buffer<1<<mags>();
void* pvoid=ctor(pbuff->data.data());
if(!pvoid)return{};
pbuff->dtor=dtor;
return std::shared_ptr<void>( std::move(pbudd), pvoid );
}...
};
return factories[magnitude](ctor, dtor);
}
std::shared_ptr<void> emplace_shared_ptr( std::size_t bytes, std::function<void*(void*)> ctor, void(* dtor)(void*) ){
return emplace_shared_ptr_impl(std::make_index_sequence<40>{}, pow_of_2_at_least_as_big_as(bytes), ctor, dtor);
}
很多錯別字,效率調整和錯誤檢查。但就是這樣。生成代碼 ( 1<<40) 的最大物件大小為 1 TB 。
dtor/delete 代碼位于該型別的 dll 中。它是通過dll_safe_destriy_for您負責為要支持的每種型別撰寫的函式獲取的。
The control block code lives in a different dll; namely, a special one that implements those shared ptr void stuff above. It needs to outlive your weak ptrs.
I have used a variation of this (in my case, it was because DLL B was wrapping objects from DLL A in shared ptrs (in template code, so didn't even know shose classesit was); DLL B was unloaded before A, and some shared ptrs that B made outlived it. Boom. The same trick above moved the actual shared ptr creation back into A. This case just has to the trick twice, as we need the dtor to live in A, and the control block code to outlive A.
uj5u.com熱心網友回復:
(我不太明白亞當的建議;他的解決方案可能很棒……無論如何,這就是我的想法:)
您正在混淆不兼容的銷毀方案。
當您卸載 DLL 時,指向 DLL 中函式的指標將變為無效。這意味著將這些指標放在std::shared_ptr: 中是毫無意義的:即使沒有人“使用”指向 DLL 中某個函式的指標,它也可能通過其他函式被使用。另外,決定何時卸載它可能比“目前沒有人持有指向它的函式的指標”更復雜。并且注意:您為共享指標洗掉器使用什么代碼并不重要 - 它根本不是正確的抽象。
由于std::shared_ptr不在表中,因此是std::weak_ptr- 它僅指向由std::shared_ptr.
恕我直言,您真正需要的是一個用于處理 DLL 的庫,它允許您發出一種不同型別的弱指標,用于跟蹤 DLL 是否仍在加載。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/368081.html
