Home > OS >  shared_ptr instances managed by a singleton cache across DLL boundaries generates runtime error R602
shared_ptr instances managed by a singleton cache across DLL boundaries generates runtime error R602

Time:12-29

I encounter a OOP conceptual limitation when implementing a singleton in C 11/14 which caches a set of shared_ptr instances created from a third party DLL (external constraint with a poor design).

//DLL1 data source session (third library)
class SessionFactory final
{
public:
    static std::shared_ptr<ISession> CreateSession(const std::string& pi_dataSourceId);
};
//DLL2 client
class SessionCache final
{
public:
    static SessionCache& Get()
    {
        static SessionCache s_instance;
        return s_instance;
    }

    void clean() {m_sessions.clear();}

    std::shared_ptr<ISession> getSession(const std::string& pi_dataSourceId)
    {
        auto im = m_sessions.find(pi_dataSourceId);
        if (im == m_sessions.end())
        {
             auto l_session = SessionFactory::CreateSession(pi_dataSourceId);
             m_sessions.insert(pi_dataSourceId, l_session)
             return l_session;
        }
        else return im->second;
    }

private:
    std::map<std::string, std::shared_ptr<ISession>> m_sessions;
};

//optional explicit singleton content cleaning
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch(fdwReason) 
    { 
        case DLL_PROCESS_DETACH:
            SessionCache::Get().clean(); //=> runtime error R6025 pure virtual function call (cached ISession are no more referenced and their virtual dtor are called leading to runtime error R6025)
            break;
    }
    return TRUE;
}

When I want to clean the cache when the client DLL is unloaded (explicitly from DllMain procedure or implicitly by the static instance destructor called), unfortunately I suffer a "runtime error R6025 pure virtual function call" system exception because DLL1 has already been unloaded when DLL2 is unloading.

As ISession's destructor implementation belongs to DLL1, it is no more reachable at this step.

A tactical workaround has been to adapt the "static" singletion allocation with a dynamic allocation but the cached ISession destructors are no more called.

  1. Does anybody know how I could elegantly address this technical problem?
  2. Did anybody encounter this similar technical limitation under Unix like systems?

CodePudding user response:

unfortunately I suffer a "runtime error R6025 pure virtual function call" system exception because DLL1 has already been unloaded when DLL2 is unloading.

Then you need to ensure that DLL1 can't be unloaded until DLL2 is done using it. Either by making DLL1 be a static dependency of DLL2, or by having DLL2 manually increment/decrement DLL1's reference count at runtime via LoadLibrary()/FreeLibrary():

The system maintains a per-process reference count on all loaded modules. Calling LoadLibrary increments the reference count. Calling the FreeLibrary or FreeLibraryAndExitThread function decrements the reference count. The system unloads a module when its reference count reaches zero or when the process terminates (regardless of the reference count).

//DLL2 client
HMODULE hDLL1 = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch(fdwReason) 
    { 
        case DLL_PROCESS_ATTACH:
            hDLL1 = LoadLibrary(TEXT("dll1"));
            if (!hDLL1) return FALSE;
            break;
        case DLL_PROCESS_DETACH:
            SessionCache::Get().clean();
            FreeLibrary(hDLL1);
            break;
    }
    return TRUE;
}

Or:

//DLL2 client
class SessionCache final
{
public:
    SessionCache()
    {
        hDLL1 = LoadLibrary(TEXT("dll1"));
        if (!hDLL1) throw ...;
    }

    ~SessionCache()
    {
        FreeLibrary(hDLL1);
    }

    ...

private:
    HMODULE hDll1;
    ...
};

CodePudding user response:

One way to solve this problem would be to remove the call to the clean() method from the DllMain procedure and instead define a method that explicitly clears the SessionCache instance when the client DLL is unloaded.

To do this, you could, for example, define an Unload method in the SessionCache class that calls the clean() method and then clears the SessionCache instance itself. You could also define a function in the client DLL that calls the unload method when the DLL is unloaded.

Here is an example of a possible implementation:

//DLL2 client
class SessionCache final
{
public:
    static SessionCache& Get()
    {
        static SessionCache s_instance;
        return s_instance;
    }

    void clean() {m_sessions.clear();}
    void Unload()
    {
        clean();
        delete this;
    }

    std::shared_ptr<ISession> getSession(const std::string& pi_dataSourceId)
    {
        auto im = m_sessions.find(pi_dataSourceId);
        if (im == m_sessions.end())
        {
             auto l_session = SessionFactory::CreateSession(pi_dataSourceId);
             m_sessions.insert(pi_dataSourceId, l_session)
             return l_session;
        }
        else return im->second;
    }

private:
    std::map<std::string, std::shared_ptr<ISession>> m_sessions;
};

extern "C" __declspec(dllexport) void UnloadSessionCache()
{
    SessionCache::Get().Unload();
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch(fdwReason) 
    { 
        case DLL_PROCESS_DETACH:
            UnloadSessionCache();
            break;
    }
    return TRUE;
}

This solution has the advantage that the SessionCache instance is only deleted when the DLL is unloaded and not already when the process is terminated. This way, all objects managed by std::shared_ptr can be cleanly cleaned up before the DLL is unloaded.

Note: Instead of delete this, you could also use a std::unique_ptr to delete the SessionCache instance.

CodePudding user response:

It is not possible to call the delete operator on a static object, because static objects are not dynamically allocated in memory. Static objects are created at compile time and exist for the lifetime of the program, so there is no need to delete them.

Therefore, calling delete this; on a static object would be a syntax error, because this is a pointer to the current object, and static objects do not have a valid address in dynamic memory.

In general, it is not a good idea to use the delete operator on static objects, because doing so can lead to undefined behavior and memory leaks. Instead, you should rely on the compiler to automatically clean up static objects when the program terminates.

I hope this helps clarify the issue.

  • Related