dimanche 27 septembre 2015

Dynamic library loading, template instantiaton and std::shared_ptr

I have run into a problem using std::shared_ptr with a dynamically loaded library. I have only tested this on Visual Studio 2015 but the same problem may exist on other platforms/compilers. This is a distilled example from a much larger program and is not meant to illustrate good C++ but rather a minimal case to reproduce the issue.

If you have the following code that builds into a library, a DLL on Windows

static int* local_allocate_memory(  ) { return( new int( 10 ) ); }

extern "C" __declspec(dllexport) void* get_allocation_function( )
{
    return( local_allocate_memory );
}

Then there is a test program, which does not link to the above library at build time

#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;
}

Everything works fine in the above program. This is to illustrate this is not a problem with the CRT or other cross DLL memory allocation issues.

Now if you change the local_allocate_memory to be

static std::shared_ptr<int> local_allocate_memory(  )
{
    return( std::make_shared<int>( 10 ) );
}

and make the change to match this in the main program

#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
}

The program will fail with on the std::shared_ptr reset with a memory access violation. This had me baffled for quite a while. I believe the problem lies in the virtual type erasure used in std::shared_ptr.

Visual Studio 2015 uses a type called _Ref_count_base as the control block os a std::shared_ptr. When you do something like std::make_shared( 10 ) a templated subclass of _Ref_count_base is created called _Ref_count_obj. _Ref_count_obj implements various virtual base class methods called things like _Destroy. Being a templated class this leads to template instantion. The vtable of the instantion points to where the compiler puts the instation of these methods. In the case of the dynamically loaded DLL above these instations live in the DLL.

The upshot of all this is that when you pass a std::shared pointer around it has pointer to functions in it that may live almost anywhere depending on how your compiler/linker handle template instantiations. So in the above example when the dynamicall loaded library is unloaded so are the instations of these functions which leads to dangling function pointers in your vtable which leads to a memory access violation when you try to run them as part of the reset( ) call.

So after all that what is my question. Well first I couldn't find any confirmation of this issue or any discussion of it, is there any? Second does anyone have any good suggestions for a work around. I have done some stuff with custom allocaters but I'm wondering if there is a simple solution. Note not unloading the shared library is not an option in my real world program.

Aucun commentaire:

Enregistrer un commentaire