c++11 - 动态库加载、模板实例化和 std::shared_ptr

标签 c++11 visual-c++

我在使用带有动态加载库的 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/

相关文章:

c++ - 使用智能指针编写安全的复制构造函数

c++ - 与 c++ 中的删除功能混淆

c++ - 在C++14模式下用clang for libstdc++编译regex程序报错

C++更改错误数组的值

visual-c++ - vtable 中重载方法的顺序(在 win32 上)

c++ - 使用 std::map 时只读成员的错误递减

C++:有没有办法创建模板化命名空间?

c++ - 注册表中的 Internet 设置 - 如何代理自动登录?

c++ - 用于函数调用的带有可变参数模板的模板类型推导

c++ - 类型转换自定义 C++ 类