Search code examples
c++windowsdlllog4cplus

How to terminate log4cplus in a DLL?


In our project we're using log4cplus indirectly: It is being used in a library that we statically link to, and that project is normally also compiled as a static lib and liked to from our executable. Everything here is Windows and Visual Studio based.

As we've been experiencing problem with application shutdown, I found that we must initialize log4cplus in our main() function, which solved the problem.

However, the application we're maintaining is - unfortunately - based on ACF (Advanced Component Framework). That means, the static lib (that links to the static lib that links to log4cplus) can be once more linked with a DLL that is then loaded by an application called Compositor during design time. (In Compositor, we can create the target application - which uses the static lib - in a high-level "component-based" fashion...). Now, the problem is that the Compositer would not close properly any more.

When it hangs after closing the main window, we can see the following callstack:

    ntdll.dll!NtWaitForAlertByThreadId() + 20 bytes Unknown
    ntdll.dll!RtlSleepConditionVariableSRW() + 265 bytes    Unknown
    KernelBase.dll!SleepConditionVariableSRW() + 45 bytes   Unknown
    msvcp140.dll!__crtSetThreadpoolWait() + 80 bytes    Unknown
    msvcp140.dll!_Cnd_timedwait() + 396 bytes   Unknown
    msvcp140.dll!_Cnd_timedwait() + 84 bytes    Unknown
    log4cplusUx64.dll!log4cplus::helpers::getFileInfo() + 3473 bytes    Unknown
    log4cplusUx64.dll!00007ff86917fefb()    Unknown
    ucrtbase.dll!_execute_onexit_table() + 342 bytes    Unknown
    ucrtbase.dll!_execute_onexit_table() + 123 bytes    Unknown
    ucrtbase.dll!_execute_onexit_table() + 52 bytes Unknown
    log4cplusUx64.dll!log4cplus::helpers::getFormattedTime() + 5056 bytes   Unknown
    log4cplusUx64.dll!log4cplus::helpers::getFormattedTime() + 5364 bytes   Unknown
    ntdll.dll!RtlAnsiStringToUnicodeString() + 663 bytes    Unknown
    ntdll.dll!LdrShutdownProcess() + 300 bytes  Unknown
    ntdll.dll!RtlExitUserProcess() + 173 bytes  Unknown
    kernel32.dll!ExitProcess() + 10 bytes   Unknown
    ucrtbase.dll!exit() + 468 bytes Unknown
    ucrtbase.dll!exit() + 127 bytes Unknown
>   Compositor.exe!__scrt_common_main_seh() Line 295    C++

In order to make Compositor close properly, I intorduced a DllMain function:

BOOL WINAPI DllMain(HINSTANCE, DWORD fdwReason, LPVOID)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        log4cplus::initialize();
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        log4cplus::threadCleanup();
        break;
    case DLL_PROCESS_DETACH:
        log4cplus::Logger::shutdown();
        log4cplus::deinitialize();
        break;
    }
    return TRUE;
}

Now, the application would no longer start but hang at the call to log4cplus::initialize():

    ntdll.dll!NtWaitForAlertByThreadId() + 20 bytes Unknown
    ntdll.dll!RtlSleepConditionVariableSRW() + 265 bytes    Unknown
    KernelBase.dll!SleepConditionVariableSRW() + 45 bytes   Unknown
    msvcp140.dll!__crtSetThreadpoolWait() + 80 bytes    Unknown
    msvcp140.dll!_Cnd_timedwait() + 396 bytes   Unknown
    msvcp140.dll!_Cnd_timedwait() + 84 bytes    Unknown
    log4cplusUx64.dll!00007ff8697360d0()    Unknown
    log4cplusUx64.dll!00007ff86973625f()    Unknown
    log4cplusUx64.dll!log4cplus::spi::FactoryRegistry<log4cplus::spi::LocaleFactory>::FactoryRegistry<log4cplus::spi::LocaleFactory>() + 1438 bytes Unknown
    log4cplusUx64.dll!log4cplus::initialize() + 194 bytes   Unknown
>   MePiaPck.arp!DllMain(HINSTANCE__ * __formal, unsigned long fdwReason, void * __formal) Line 46  C++

If I remove that call, startup is normal but the hanging behavior, i.e. Compositer not closing, remains, regardless of threadCleanup(), Logger::shutdown() and deinitialize() (I've tried all combinations).

How can I shutdown log4cplus in a DLL so that the application can terminate properly?


Solution

  • As it has been mentioned in @RbMm's comments, the reason why initializing log4cplus in DllMain does not work is that this function is executed inside the "loader lock" - cf. here or here.

    The solution is to find a function in the DLL that is executed from the loading application's main thread, and initialize log4cplus there.

    In case of ACF's Compositor, it is the package export function that is called first. Normally, it is wrapped as a macro in ACF packages, i.e. I_EXPORT_PACKAGE. Unrolling that macro allows for entering the following code:

    extern "C" I_FUNCTION_EXPORT icomp::CPackageStaticInfo* I_PACKAGE_EXPORT_FUNCTION()
    {
        static bool bFirstLoad = true;
        if (bFirstLoad)
        {
            log4cplus::initialize();
            log4cplus::deinitialize();
            bFirstLoad = false;
        }
        return &packageInfo;
    }
    

    Initializing and deinitializing log4cplus at the first call into the package enables the Compositor application to close and terminate normally again.