c++winapidllclr

How to create a working thread inside a dll?


I'd like to write a C++ CLR DLL which creates a thread that does some stuff on his own. The DLL is loaded and maintained by a smoke "classic" C++ win application.

Here is the DLL code:

#include <windows.h>
#include "MyOutputWnd.h"
#pragma unmanaged
#pragma comment(lib, "user32.lib")
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>

using namespace System;
using namespace System::Drawing;
using namespace System::Windows::Forms;

#include <stdio.h>

extern void ShowForm(void);
HANDLE hThread = NULL;

::BOOL WINAPI DllWork(__in::HMODULE hModule) {
    MessageBoxA(NULL, "DllWork thread", "Msg title", MB_OK | MB_ICONQUESTION);
    //ShowForm();
    return true;
}

DWORD WINAPI CreateMessageBox(LPVOID lpParam) {
    MessageBoxA(NULL, (char*)lpParam, "The thread", MB_OK);
    return 0;
}

::BOOL WINAPI DllMain(
    __in::HMODULE hinstDLL,  // handle to DLL module
    __in::DWORD fdwReason,     // reason for calling function
    __in __reserved::LPVOID lpvReserved)  // reserved
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:

        /*char buffer[100];
        sprintf_s(buffer, "DllMain%s\n", "I can inject things");
        OutputDebugStringA(buffer);*/


        if  ((hThread = CreateThread(NULL, 0, &CreateMessageBox, "Hello World", 0, NULL)) == NULL) {
            MessageBoxA(NULL, "CreateThread failed!", "Msg title", MB_OK | MB_ICONQUESTION);
            return FALSE;
        }

        MessageBoxA(NULL, "CreateThread OK!", "Msg title", MB_OK | MB_ICONQUESTION);

        break;

    case DLL_THREAD_ATTACH:
        // Do thread-specific initialization.
        break;

    case DLL_THREAD_DETACH:
        // Do thread-specific cleanup.
        break;

    case DLL_PROCESS_DETACH:

        if (lpvReserved != nullptr)
        {
            if (::CloseHandle(hThread) == FALSE) {
                MessageBoxA(NULL, "CloseHandle(hThread) failed!", "Msg title", MB_OK | MB_ICONQUESTION);
            }
            else
            {
                MessageBoxA(NULL, "CloseHandle(hThread) OK!", "Msg title", MB_OK | MB_ICONQUESTION);
            }
            break; // do not do cleanup if process termination scenario
        }

        // Perform any necessary cleanup.
        break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

and the C++ application code running the library as simple as:

#include <windows.h>
#include <iostream>
#include <Objbase.h>
#include <atlcomcli.h>
#include <atlbase.h>
#include <atlcom.h>
#include <initguid.h>
#include <comdef.h>
typedef int(__stdcall* f_funci)();

int main()
{
    HINSTANCE hGetProcIDDLL = LoadLibrary(L"D:/Projects/Project3/Debug/Project3.dll");  

    if (!hGetProcIDDLL) {
        std::cout << "could not load the dynamic library" << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

The DLL is loaded successfully, the thread is reported to be properly created, but for some reason the code inside it doesn't seems to be executed because the DllWork thread's Message Box isn't created/spawned.

When I turn the DLL into an executable itself (hence running the CLR application on its own), the DllWork thread Message Box does appear.

What can be the reason for the in-thread Message Box not working in case of the DLL scenario?


Solution

  • It's not safe to call MessageBox from DllMain. See Dynamic-Link Library Best Practices.

    Relevant excerpt:

    You should never perform the following tasks from within DllMain:

    [...]

    • Call functions in User32.dll or Gdi32.dll. Some functions load another DLL, which may not be initialized.

    (Note that MessageBox is a User32.dll function.)

    Instead, use your debugger to determine if something is being called by using a breakpoint or OutputDebugString.

    There are some other best practices you are violating - things you should never perform in DllMain:

    In DllMain, never...

    • Call CreateThread. Creating a thread can work if you do not synchronize with other threads, but it is risky.
    • Use managed code.

    So overall, don't do what you are doing in DllMain. Let your library load normally doing as little work as possible, then, once your library is loaded, call a user-defined entry point function (you can dllexport a function with a name of your choice, e.g.: InitMyLibrary) to do whatever work you want your library to do such as creating threads. Or create a late initialization function by doing something similar to this at the beginning of the relevant API call if (!initialized) { Init(); initialized = true; }