Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create threads in dlls (c++)?

I know I cannot create threads from DllMain, but what is the correct approach for creating threads in a DLL?

I tried to create a class that wraps around it and declare a global instance of that class, but then WaitForSingleObject hangs in the destructor of the class, even though I'm certain that the thread has exited.

Here is my sample program to load the dll from an exe:

#include <Windows.h>
#include <iostream>

using namespace std;

int main(int argc, TCHAR* argv[])
{
    HMODULE module = LoadLibraryA("dll.dll");
    Sleep(500);
    if (module) FreeModule(module);
    else cout << "failed to load library...\n";
    return 0;
}

And here is the dll code:

#include <Windows.h>
#include <iostream>

using namespace std;

class X
{
    HANDLE thread = NULL;

    static DWORD WINAPI run_thread(LPVOID param)
    {
        cout << "dll.cpp: thread started\n";
        Sleep(1000);
        cout << "dll.cpp: thread is exiting\n";
        return 0;
    }

public:
    X()
    {
        DWORD tmp;
        thread = CreateThread(NULL, 0, run_thread, NULL, 0, &tmp);
        if (thread == NULL)
        {
            cout << "failed to create thread\n";
            throw 1;
        }
    }
    ~X()
    {
        if (thread != NULL)
        {
            cout << "joining thread...\n";
            DWORD ret = WaitForSingleObject(thread, INFINITE);
            cout << "result from waiting: " << ret << "\n";
            thread = NULL;
        }
    }
}x;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved)
{
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        cout << __FUNCTION__ << " reason DLL_PROCESS_ATTACH\n";
        break;
    case DLL_THREAD_ATTACH:
        cout << __FUNCTION__ << " reason DLL_THREAD_ATTACH\n"; 
        break;
    case DLL_THREAD_DETACH:
        cout << __FUNCTION__ << " reason DLL_THREAD_DETACH\n"; 
        break;
    case DLL_PROCESS_DETACH:
        cout << __FUNCTION__ << " reason DLL_PROCESS_DETACH\n"; 
        break;
    }

    return TRUE;
}

This is the program output:

DllMain reason DLL_PROCESS_ATTACH
DllMain reason DLL_THREAD_ATTACH
dll.cpp: thread started
DllMain reason DLL_PROCESS_DETACH
joining thread...
dll.cpp: thread is exiting
                            <- notice how WaitForSingleObject hangs,
                               even though the thread has exited

What is wrong with this approach?

If I happen to remove the Sleep(1000) from run_thread, so that the thread finishes before WaitForSingleObject is called, then it does not hang:

DllMain reason DLL_PROCESS_ATTACH
DllMain reason DLL_THREAD_ATTACH
dll.cpp: thread started
dll.cpp: thread is exiting
DllMain reason DLL_THREAD_DETACH
DllMain reason DLL_PROCESS_DETACH
joining thread...
result from waiting: 0

I don't understand the difference, and that is also not an option, since the purpose of the thread is to loop constantly while DLL_PROCESS_DETACH is not signaled.

like image 815
Alex Avatar asked Feb 15 '18 14:02

Alex


1 Answers

I know I cannot create threads from DllMain, but what is the correct approach for creating threads in a DLL?

After the main application loaded the dll (either implicitly or explicitly through LoadLibrary), it calls some function exported by your dll, which can do whatever it likes, including launching threads.

I tried to create a class that wraps around it and declare a global instance of that class

Initialization (destruction) of global objects happens inside DllMain as well; the "real" DllMain entrypoint (which is the one MSDN talks about) is taken by the CRT which uses it to initialize (destroy) globals & co. and then invoke your DllMain.

In this answer to a similar question (it's about LoadLibrary instead of CreateThread, but much of it apply to this case as well) you can find some more details.

What is wrong with this approach?

It's wrong that you are trying to do complex stuff inside DllMain, in particular stuff that is explicitly discouraged; ideally DllMain should just be empty or do extremely trivial work.

Most probably, in this specific case the thread being terminated is trying to call again DllMain for a DLL_THREAD_DETACH notification, but is being locked out either because there's an implicit mutex that prevents concurrent access to DllMain, or because Windows is explicitly forbidding a DLL_THREAD_DETACH notification from arriving after a DLL_PROCESS_DETACH.

This wouldn't surprise me - after all, as Hans Passant notes, calling FreeLibrary with code still running is obviously undefined behavior. It's not a chance that there's a specific function (FreeLibraryAndExitThread) to let a thread holding a reference to the library atomically free the library and kill itself.

like image 91
Matteo Italia Avatar answered Oct 29 '22 05:10

Matteo Italia