DLL_PROCESS_ATTACH
DLL_PROCESS_DETACH
DLL_THREAD_ATTACH
DLL_THREAD_DETACH
Ostensibly, THREAD_ATTACH is where you initialize
any thread locals and THREAD_DETACH is where you clean them up.
However they do not work as they sound.
They are documented correctly however.
DLL_THREAD_ATTACH is only called for threads created after a dll is loaded.
DLL_THREAD_DETACH is only called for threads that exit while a dll is loaded.
That leaves DLL_THREAD_ATTACH not called for threads that exist
before the dll is loaded, and DLL_THREAD_DETACH not called for threads
that still exist when a dll is unloaded.
Therefore, if you have a thread local with a constructor, it can be used without being constructed.
If you have a thread local with a destructor, it might not be called.
Here is an example:
F:\1>type dll.cpp dll.h dll.def exe.cpp
dll.cpp
#include <windows.h>
#include <stdio.h>
int constructs;
int destroys;
__declspec(thread) bool constructed;
struct A
{
A() { constructed = true; printf(" constructed:%d on thread:%d \n", ++constructs, GetCurrentThreadId()); }
~A() { printf(" destroyed:%d on thread:%d \n", ++destroys, GetCurrentThreadId()); }
void F1() { printf(" called on thread:%d constructed:%d \n", GetCurrentThreadId(), (int)constructed); }
};
__declspec(thread) A a;
extern "C" void F1() { a.F1(); }
PCSTR ReasonString(ULONG Reason)
{
switch (Reason)
{
case DLL_PROCESS_ATTACH: return "DLL_PROCESS_ATTACH";
case DLL_PROCESS_DETACH: return "DLL_PROCESS_DETACH";
case DLL_THREAD_ATTACH: return "DLL_THREAD_ATTACH";
case DLL_THREAD_DETACH: return "DLL_THREAD_DETACH";
}
return "unknown";
}
BOOL __stdcall DllMain(HINSTANCE dll, ULONG reason, PVOID reserved)
{
printf(" DllMain dll:%p, reason:%s reserved:%d thread:%d \n", dll, ReasonString(reason), !!reserved, GetCurrentThreadId());
if (reason == DLL_PROCESS_DETACH)
printf(" unloading with %d leaked constructions \n", constructs - destroys);
return TRUE;
}
dll.h
extern "C" void F1();
dll.def
EXPORTS
F1
exe.cpp
#include <stdio.h>
#include <windows.h>
#include "dll.h"
decltype(&F1) pf1;
HANDLE thread[3];
ULONG threadid[3];
HANDLE event[3];
ULONG __stdcall Thread(PVOID p)
{
size_t i = (size_t)p;
// wait for dll/function to be available
while (!pf1)
Sleep(1);
pf1();
// Indicate this thread is done with the dll.
SetEvent(event[i]);
// keep doing more work on this thread -- at least pretend
if (i == 2)
{
printf(" not exiting thread:%d \n", GetCurrentThreadId());
Sleep(INFINITE);
}
printf(" exiting thread:%d \n", GetCurrentThreadId());
return 0;
}
int main()
{
size_t i = 0;
printf(" initial thread:%d \n", GetCurrentThreadId());
// create thread before loading dll (current thread as well)
event[i] = CreateEvent(0, 0, 0, 0);
thread[i] = CreateThread(0, 0, &Thread, (PVOID)i, 0, &threadid[i]);
printf(" created thread:%d \n", threadid[i]);
++i;
auto const dll = LoadLibrary("dll");
(PROC&)pf1 = GetProcAddress(dll, "F1");
pf1();
// create threads after loading dll
event[i] = CreateEvent(0, 0, 0, 0);
thread[i] = CreateThread(0, 0, &Thread, (PVOID)i, 0, &threadid[i]);
printf(" created thread:%d \n", threadid[i]);
++i;
event[i] = CreateEvent(0, 0, 0, 0);
thread[i] = CreateThread(0, 0, &Thread, (PVOID)i, 0, &threadid[i]);
printf(" created thread:%d \n", threadid[i]);
++i;
// Wait for all calls to the dll to be done.
WaitForMultipleObjects(3, event, TRUE, INFINITE);
// Wait for some of the threads to exit.
WaitForMultipleObjects(2, thread, TRUE, INFINITE);
FreeLibrary(dll);
}
F:\1>cl /LD /MD /Zi dll.cpp /link /def:dll.def
F:\1>cl /MD /Zi exe.cpp
F:\1>.\exe.exe
initial thread:80676
created thread:70020
constructed:1 on thread:80676
DllMain dll:00007FFBDEFD0000, reason:DLL_PROCESS_ATTACH reserved:0 thread:80676
called on thread:80676 constructed:1
created thread:66288
constructed:2 on thread:66288
DllMain dll:00007FFBDEFD0000, reason:DLL_THREAD_ATTACH reserved:0 thread:66288
created thread:49672
called on thread:70020 constructed:0 called on thread:66288 constructed:1
exiting thread:66288
exiting thread:70020
constructed:3 on thread:49672
DllMain dll:00007FFBDEFD0000, reason:DLL_THREAD_ATTACH reserved:0 thread:49672
called on thread:49672 constructed:1
not exiting thread:49672
destroyed:1 on thread:66288
DllMain dll:00007FFBDEFD0000, reason:DLL_THREAD_DETACH reserved:0 thread:66288
DllMain dll:00007FFBDEFD0000, reason:DLL_THREAD_DETACH reserved:0 thread:70020
destroyed:2 on thread:80676
DllMain dll:00007FFBDEFD0000, reason:DLL_PROCESS_DETACH reserved:0 thread:80676
unloading with 1 leaked constructions
- Jay