Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terminating thread on DLL unload

I'm trying to write a DLL plug-in for a third-party software.In the plug in i'm creating a thread in an initialization function which is called by the hosting program. However, there is no shutdown routine in which I could properly terminate the thread. I'm tried this code:

DLL_EXPORT void InitFunction() // is called by the host application
{
    myThread = std::move(std::thread{myThreadFunction});
}
bool WINAPI DllMain( HINSTANCE hDll, DWORD fdwReason, LPVOID lpvReserved )
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
    {
        DisableThreadLibraryCalls(hDll);
    }   break;
    case DLL_PROCESS_DETACH:
    {
        IsRunning.store(false); // tell the thread it's time to terminate;
        if(myThread.joinable())
            myThread.join();
    }break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    }
    return true;
}

I know this won't work, because windows would call DllMain upon thread termintation, but the DllMain is still running and therefore would deadlock. I tried using DisableThreadLibraryCalls() in different places in the Dll, but that didn't work either. So, How can I terminate the threads in my DLL properly?

like image 276
rashmatash Avatar asked Mar 06 '26 03:03

rashmatash


1 Answers

rashmatash.

I know this won't work, because windows would call DllMain upon thread termintation, but the DllMain is still running and therefore would deadlock.

This much would actually work. If you didn't do DisableThreadLibraryCalls, then all newborn threads would visit DllMain on DLL_THREAD_ATTACH, and all dying threads would visit the DLL on DLL_THREAD_DETACH. No thread would come to visit your DLL since you asked for DisableThreadLibraryCalls. So I don't see any deadlock with your code so far. The only visitor you'll get is the main thread, but going only through PROCESS_ATTACH/DETACH.

There are a few problems, though. The myThread variable. What is it? It is local to the DLL, right? It is either local, or imported from another DLL, no 3 ways about it. If it's local, then unloading the DLL is going to unmap both the code and the data pages of the DLL, resulting in undefined behaviour, probably access violations. So, I'm not confortable with that variable. Could still be okay.

What you might want to do is actually a thread management structure such as a list/vector/map. You'd actually want the threads to visit you, so you can push them to your container, you'd want to remove the ones that visit you as they die. You'd need to protect this container against contention. There was a time when DllMain access was serialized. You can then do something on DLL_PROCESS_DETACH with the remaining threads in the containe, those that are still hanging. One of them is your own from InitFunction.

However, this is still not right. You should allow all visits. You should take note of the very first thread that visits you, incidentally the same that runs PROCESS_ATTACH. Move your "sigkill" logic to DLL_THREAD_DETACH and only when the current visiting thread matches the recorded main. It's like saying that the main thread should call a ShutdownFunction as it exits. The DLL will process detach after every thread detaches from it.

This thread/library interlocking is a part of the CRT where a registry is held. We often have to replicate aspects of it, for reasons such as you described.

like image 140
Alexander Mihail Avatar answered Mar 07 '26 15:03

Alexander Mihail



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!