我在使用带有动态加载库的 std::shared_ptr 时遇到了问题。我只测试过这个
在 Visual Studio 2015 上,但在其他平台/编译器上可能存在相同的问题。这是一个提炼的例子
一个更大的程序,并不是为了说明好的 C++,而是一个重现问题的最小案例。
如果您将以下代码构建到库中,则为 Windows 上的 DLL
static int* local_allocate_memory( ) { return( new int( 10 ) ); }
extern "C" __declspec(dllexport) void* get_allocation_function( )
{
return( local_allocate_memory );
}
然后是一个测试程序,在构建时并没有链接到上面的库
#include <Windows.h>
void main( )
{
int* allocatedMemory = nullptr;
{
// The string here is wherever the result of the DLL library build went
HMODULE hModule = LoadLibraryEx( L"../Debug/DynamicLibrary.dll", nullptr, 0 );
// get_allocation_function must have "C" external linkage.
auto function = GetProcAddress( hModule, "get_allocation_function" );
// Get a pointer to a C++ function that will allocate some memory
auto allocation_function = function( );
auto allocater = reinterpret_cast<int*(*)( )>( allocation_function );
allocatedMemory = allocater( );
BOOL result = FreeLibrary( hModule );
}
delete allocatedMemory;
}
在上述程序中一切正常。这是为了说明这不是 CRT 问题或其他跨 DLL 内存分配问题。
现在,如果您将 local_allocate_memory 更改为
static std::shared_ptr<int> local_allocate_memory( )
{
return( std::make_shared<int>( 10 ) );
}
并在主程序中进行更改以匹配此
#include <Windows.h>
#include <memory>
void main( )
{
std::shared_ptr<int> allocatedMemory;
{
HMODULE hModule = LoadLibraryEx( L"../Debug/DynamicLibrary.dll", nullptr, 0 );
// get_allocation_function must have "C" external linkage.
auto function = GetProcAddress( hModule, "get_allocation_function" );
// Get a pointer to a C++ function that will allocate some memory
auto allocation_function = function( );
auto allocater = reinterpret_cast<std::shared_ptr<int>(*)( )>( allocation_function );
allocatedMemory = allocater( );
BOOL result = FreeLibrary( hModule );
}
allocatedMemory.reset( ); // Fails here
}
该程序将在 std::shared_ptr 重置时因内存访问冲突而失败。
这让我困惑了好一阵子。我相信问题在于 std::shared_ptr 中使用的虚拟类型删除。
Visual Studio 2015 使用名为 _Ref_count_base 的类型作为 std::shared_ptr 的控制 block 。当您执行 std::make_shared( 10 ) 之类的操作时,会创建 _Ref_count_base 的模板化子类,称为 _Ref_count_obj。
_Ref_count_obj 实现了各种虚拟基类方法,称为 _Destroy。作为一个模板类
这导致模板实例化。实例化的 vtable 指向编译器将这些实例化的位置
方法。对于上述动态加载的 DLL,这些 instations 存在于 DLL 中。
所有这一切的结果是,当您在它周围传递一个 std::shared 指针时,它具有指向其中可能存在几乎任何地方的函数的指针,具体取决于您的编译器/链接器如何处理模板实例化。所以在上面的例子中,当
dynamicall 加载的库被卸载,这些函数的 instations 也被卸载,导致函数指针悬空
在您的 vtable 中,当您尝试将它们作为 reset() 调用的一部分运行时,这会导致内存访问冲突。
所以毕竟这是我的问题。首先,我找不到任何对此问题的确认或任何讨论,
有没有?其次,是否有人对解决方法有任何好的建议。我用自定义分配器做了一些事情
但我想知道是否有一个简单的解决方案。注意不卸载共享库在我的现实世界中不是一个选项
程序。
这实际上不是一个更普遍的问题。通过 DLL 边界的 vtable 隐式导出模板实例化。例如,这可能会对使用动态加载库的依赖注入(inject)模式的程序产生潜在影响。
最佳答案
如果您希望以这种方式使用您的模块,请考虑让它获取对自身的引用以确保它不会被卸载。
如果您绝对需要在所有对象被销毁后卸载模块,请考虑维护所有此类对象的列表并在所有对象被销毁时释放库。您可以使用看门狗线程和FreeLibraryAndExitThread
或线程池看门狗和FreeLibraryWhenCallbackReturns
.小心比赛。
或者,考虑使用 COM,它具有对此类生命周期管理问题的内置支持(参见 DllCanUnloadNow
等)。
关于c++11 - 动态库加载、模板实例化和 std::shared_ptr,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32814246/