在 DLL 卸载后使用 std::weak_ptr
Using std::weak_ptr after DLL unload
我在使用 std::weak_ptr
卸载另一个 DLL 中的对象时遇到问题。
这是简化的情况:有一个加载 2 个 DLL 的应用程序。 DLL 提供了一些继承公共接口的对象。应用程序保留了这些对象的一种缓存(std::vector
的 std::shared_ptr
s),以便 DLL 可以间接使用彼此的对象。
当 DLL 卸载时,它会从缓存中删除它的对象。
但是如果另一个 DLL 将 weak_ptr
保存到另一个 DLL 的对象,问题就来了,因为当调用 weak_ptr
的析构函数时,它会尝试删除控制块,这会导致AV
错误,因为所有者 DLL 已经卸载。
我虽然可以强制在应用程序中分配控制块,而不是 DLL,但我不知道该怎么做。我已经尝试在应用程序端创建一个 Allocator
并将其传递给 shared_ptr
构造函数,但控制块仍然保留一些指向 DLL 的 类 的指针,这再次导致 AV
错误。
似乎唯一可行的方法是在 DLL 中创建指向对象的原始指针,然后将它们传递给应用程序,然后应用程序将它们包装在 shared_ptr
中。但这会导致 enable_shared_from_this
.
出现一些问题
那么,我的问题是 - 最好的做法是什么?
我已经解决了这个问题。
一般来说,在 dll 的生命周期结束后,您不应保留指向 dll 中分配的数据的指针。
但是你可以解决这个具体问题。基本上,从程序中的原始指针创建中替换调用以创建 shared 和 shared ptr。最简单的方法可能是完全禁止共享 ptr,并编写一个包装 subclass 来进行重定向。
然后,制作一个“mysharedptr”dll。它提供了两个功能; dll safe make shared 和 dll safe shared ptr creation (from a ptr).
from ptr 很简单。 Header有一个template函数,调用了一个create void shared,传入ptr to deletion函数。创建该删除功能的 dll 必须持续足够长的时间;见下文。
然后它使用别名构造函数 return 与空指针控制块共享指向 T 的指针。
对于 make shared replacement,编写一个 dll 安全函数,使 shares 成为各种固定大小的缓冲区。就像二的幂。它还包含一个破坏内容的函数指针。现在在模板中,调用固定缓冲区 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 safe shared 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)中。所以你的 weak ptr 可以比 some_type dll.
与 make shared 支持类似,你有一个带胶水的 beader 文件,一个基于 void 的 dll 安全函数,确保控制块代码在一个安全的 dll 中,以及一些花哨的步法来获得指向析构函数的指针class 的个人 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 TB (1<<40
)。
dtor/delete 代码存在于该类型的 dll 中。它是通过您负责为要支持的每种类型编写的 dll_safe_destriy_for
函数获取的。
控制块代码位于不同的 dll 中;即,一个特殊的实现上面那些共享 ptr void 东西的东西。它需要比你的弱点活得更久。
我使用了这个的变体(在我的例子中,这是因为 DLL B 将来自 DLL A 的对象包装在共享指针中(在模板代码中,所以甚至不知道 shose classesit 是); DLL B 在 A 之前被卸载,B 创建的一些共享指针比它还长。Boom。上面的相同技巧将实际的共享指针创建移回 A。这种情况只需要两次技巧,因为我们需要 dtor住在A,控制块代码比A长寿。
(我不太理解亚当的建议;可能他的解决方案很棒...无论如何,这是我的想法:)
您混淆了不兼容的销毁方案。
当您卸载 DLL 时,指向 DLL 中函数的指针将变得无效。这意味着将这些指针放在 std::shared_ptr
中是没有意义的:即使没有人“使用”指向 DLL 中某个函数的指针,它也可能仍在通过其他函数使用。另外,决定何时卸载它可能比“目前没有人持有指向它的函数指针”更复杂。请注意:您使用什么代码来删除共享指针并不重要——这根本不是正确的抽象。
由于 std::shared_ptr
不在 table 之外,std::weak_ptr
也是如此 - 它仅指向由 std::shared_ptr
.
管理的指针
恕我直言,您真正需要的是一个用于处理 DLL 的库,它允许您分配不同类型的弱指针,跟踪 DLL 是否仍在加载。
我在使用 std::weak_ptr
卸载另一个 DLL 中的对象时遇到问题。
这是简化的情况:有一个加载 2 个 DLL 的应用程序。 DLL 提供了一些继承公共接口的对象。应用程序保留了这些对象的一种缓存(std::vector
的 std::shared_ptr
s),以便 DLL 可以间接使用彼此的对象。
当 DLL 卸载时,它会从缓存中删除它的对象。
但是如果另一个 DLL 将 weak_ptr
保存到另一个 DLL 的对象,问题就来了,因为当调用 weak_ptr
的析构函数时,它会尝试删除控制块,这会导致AV
错误,因为所有者 DLL 已经卸载。
我虽然可以强制在应用程序中分配控制块,而不是 DLL,但我不知道该怎么做。我已经尝试在应用程序端创建一个 Allocator
并将其传递给 shared_ptr
构造函数,但控制块仍然保留一些指向 DLL 的 类 的指针,这再次导致 AV
错误。
似乎唯一可行的方法是在 DLL 中创建指向对象的原始指针,然后将它们传递给应用程序,然后应用程序将它们包装在 shared_ptr
中。但这会导致 enable_shared_from_this
.
那么,我的问题是 - 最好的做法是什么?
我已经解决了这个问题。
一般来说,在 dll 的生命周期结束后,您不应保留指向 dll 中分配的数据的指针。
但是你可以解决这个具体问题。基本上,从程序中的原始指针创建中替换调用以创建 shared 和 shared ptr。最简单的方法可能是完全禁止共享 ptr,并编写一个包装 subclass 来进行重定向。
然后,制作一个“mysharedptr”dll。它提供了两个功能; dll safe make shared 和 dll safe shared ptr creation (from a ptr).
from ptr 很简单。 Header有一个template函数,调用了一个create void shared,传入ptr to deletion函数。创建该删除功能的 dll 必须持续足够长的时间;见下文。
然后它使用别名构造函数 return 与空指针控制块共享指向 T 的指针。
对于 make shared replacement,编写一个 dll 安全函数,使 shares 成为各种固定大小的缓冲区。就像二的幂。它还包含一个破坏内容的函数指针。现在在模板中,调用固定缓冲区 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 safe shared 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)中。所以你的 weak ptr 可以比 some_type dll.
与 make shared 支持类似,你有一个带胶水的 beader 文件,一个基于 void 的 dll 安全函数,确保控制块代码在一个安全的 dll 中,以及一些花哨的步法来获得指向析构函数的指针class 的个人 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 TB (1<<40
)。
dtor/delete 代码存在于该类型的 dll 中。它是通过您负责为要支持的每种类型编写的 dll_safe_destriy_for
函数获取的。
控制块代码位于不同的 dll 中;即,一个特殊的实现上面那些共享 ptr void 东西的东西。它需要比你的弱点活得更久。
我使用了这个的变体(在我的例子中,这是因为 DLL B 将来自 DLL A 的对象包装在共享指针中(在模板代码中,所以甚至不知道 shose classesit 是); DLL B 在 A 之前被卸载,B 创建的一些共享指针比它还长。Boom。上面的相同技巧将实际的共享指针创建移回 A。这种情况只需要两次技巧,因为我们需要 dtor住在A,控制块代码比A长寿。
(我不太理解亚当的建议;可能他的解决方案很棒...无论如何,这是我的想法:)
您混淆了不兼容的销毁方案。
当您卸载 DLL 时,指向 DLL 中函数的指针将变得无效。这意味着将这些指针放在 std::shared_ptr
中是没有意义的:即使没有人“使用”指向 DLL 中某个函数的指针,它也可能仍在通过其他函数使用。另外,决定何时卸载它可能比“目前没有人持有指向它的函数指针”更复杂。请注意:您使用什么代码来删除共享指针并不重要——这根本不是正确的抽象。
由于 std::shared_ptr
不在 table 之外,std::weak_ptr
也是如此 - 它仅指向由 std::shared_ptr
.
恕我直言,您真正需要的是一个用于处理 DLL 的库,它允许您分配不同类型的弱指针,跟踪 DLL 是否仍在加载。