动态库加载,模板实例化和std :: shared_ptr

时间:2015-09-28 00:29:16

标签: 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的类型作为控制块os std :: shared_ptr。当您执行类似std :: make_shared(10)的操作时,会创建一个名为_Ref_count_obj的_Ref_count_base的模板化子类。 _Ref_count_obj实现了各种称为_Destroy之类的虚拟基类方法。作为一个模板类 这会导致模板实例化。 instantion的vtable指向编译器放置这些实例的位置 方法。在上面动态加载的DLL的情况下,这些实例存在于DLL中。

所有这一切的结果是,当你传递一个std :: shared指针时,它有指向它的函数的指针,它几乎可以存在,取决于你的编译器/链接器如何处理模板实例化。所以在上面的例子中 动态加载的库被卸载,因此这些函数的实例导致悬空函数指针 在您的vtable中,当您尝试在reset()调用中运行它们时会导致内存访问冲突。

所以那就是我的问题。首先,我无法找到对此问题的任何确认或对此进行任何讨论, 有没有?第二,任何人都有任何好的建议来解决问题。我用自定义分配器做了一些事情 但我想知道是否有一个简单的解决方案。注意,在我的现实世界中,不能卸载共享库不是一个选项 程序

这不是一个更普遍的问题。通过vtables跨DLL边界隐式导出模板实例化。例如,这可能会对使用动态加载库的依赖注入模式的程序产生潜在影响。

1 个答案:

答案 0 :(得分:1)

如果您希望以这种方式使用您的模块,请考虑让它获取对自身的引用,以确保它不会被卸载。

如果绝对需要在销毁所有对象后卸载模块,请考虑维护所有此类对象的列表,并在销毁所有对象时释放库。您可以使用监视程序线程和FreeLibraryAndExitThread或线程池监视程序和FreeLibraryWhenCallbackReturns。小心比赛。

或者,考虑使用COM,它内置了对此类生命周期管理问题的支持(请参阅DllCanUnloadNow等)。