使用 boost::signals2 和卸载 DLL 时发生访问冲突

Access violation when using boost::signals2 and unloading DLLs

我正在尝试解决这个问题。

假设,您有一个使用 boost::signals2 进行对象间通信的代码。让我们称他们为 "colorscales"。这些色阶的代码通常与使用它们的代码位于同一个 DLL 中。我们称它为 main.dll

但有时来自其他 DLL 的代码需要使用这些对象,这就是问题开始的地方。

基本上,应用程序非常大,加载了大部分 DLL 以完成一些工作,然后将它们卸载。包含色标代码的 DLL 不是这种情况,它在应用程序正常运行时不需要卸载。

因此,当其中一个 DLL 被加载(我们称之为 tools.dll)并且一些代码运行时,它可能想要使用这些色标对象并与它们通信,所以我连接到这些对象的信号提供。

问题是 boost 非常懒惰而且很聪明,当你 disconnect() 插槽时,它实际上并没有删除 connection 和与之相关的东西(像 boost::bind 对象等)。它只是设置一个标志,表明此 connection 现在已断开连接,并在稍后清理它(实际上,当您连接新插槽时它会清理这些对象中的 2 个,而当您从版本 1.57 开始调用信号时会清理其中的 1 个)。您可能已经知道这是怎么回事了。

因此,当您不需要更多工具时,您可以断开这些信号,然后卸载应用程序 tools.dll

然后在稍后阶段,一些代码从 main.dll 开始执行,这会导致调用其中一个色标信号。 boost::signals2 去调用它,但在它尝试清理一个断开连接的插槽之前。这是发生访问冲突的地方,因为内部连接有一个 shared_state 对象或类似的东西,它试图以线程安全的方式清理自身。但它面临的问题是,它尝试调用的代码已经不存在,因为 DLL 已卸载,因此抛出访问冲突异常。

我试图通过在卸载 DLL 之前调用带有一些虚拟参数的信号以及连接然后断开更多插槽来解决这个问题(这是一个愚蠢的想法,因为它没有解决问题,但只是乘以它)一些预定义的次数(比所有插槽多 2 或 3 倍)。

它成功了,或者我认为是这样,因为现在它不会立即崩溃,而是在您下次加载相同的内容时崩溃 tools.dll。我仍然需要弄清楚它崩溃的位置和原因,但它在 boost.

中的其他地方

所以,我想问一下,我有哪些修复方法? 我的想法是

嗯,修复后好像找到了崩溃的原因

所以,基本上,当您使用上述解决方法(多次使用虚拟参数调用信号)时,会发生什么,它所做的是替换从 [=12= 创建的 _shared_state 对象] 来自 main.dll 的代码由来自 tools.dllboost 代码创建的另一个 _shared_state 对象创建。此对象在内部维护指向引用计数器(派生自 boost::detail::sp_counter_base 的类型)的指针。

然后 tools.dll 卸载,对象仍然存在,但它的虚拟 table 指向不再存在的代码。让我们看看引用计数器的virtual table 来了解是怎么回事。

    [0] 0x000007fed8a42fe5 tools.dll!boost::detail::sp_counted_impl_p<...>::`vector deleting destructor'(unsigned int)
    [1] 0x000007fed8a4181b tools.dll!boost::detail::sp_counted_impl_p<...>::dispose(void)
    [2] 0x000007fed8a4458e tools.dll!boost::detail::sp_counted_base::destroy(void)
    [3] 0x000007fed8a43c42 SegyTools.dll!boost::detail::sp_counted_impl_p<...>::get_deleter(class type_info const &)
    [4] 0x000007fed8a42da6 tools.dll!boost::detail::sp_counted_impl_p<...>::get_untyped_deleter(void)

如您所见,所有这些方法都与引用计数器的处理有关,因此在您第二次尝试执行相同的技巧之前不会出现问题。因此,断开所有信号以尝试从 tools.dll 中删除所有代码的技巧不会按预期工作,下次您尝试执行此技巧时,会出现 Access Violation