Question: Is DllMain invoked at each FreeLibrary?
Answer: No. DllMain is invoked on first load of the module (i.e. when it's loaded into memory), on last free (i.e. when it's unloaded from memory), and when a thread is created or destroyed.
Question: Even with DLL_THREAD_DETACH?
Answer: FreeLibrary and DLL_THREAD_DETACH are not connected in any way. Speaking of FreeLibrary - only the FreeLibrary which decrements the reference counter to 0 invokes DllMain with DLL_PROCESS_DETACH; this reference counter is increased with each load of the module (both dynamic and [delayed] import table based), and decreased with each unload.
Question: Prove it.
Answer: Here's a simple experiment:
File: test_dll.c
// DllMain behavior test, gynvael.coldwind//vx, 2013
#include <windows.h>
#include <stdio.h>
#define UNUSED (void)
__declspec(dllexport) BOOL WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
) {
UNUSED hinstDLL; UNUSED lpvReserved;
const char *reason_to_string[] = {
"DLL_PROCESS_DETACH",
"DLL_PROCESS_ATTACH",
"DLL_THREAD_ATTACH",
"DLL_THREAD_DETACH"
};
printf("DllMain called; fdwReason=%s, threadID=%x\n",
reason_to_string[fdwReason],
(unsigned)GetCurrentThreadId());
return TRUE;
}
File: test.c
// DllMain behavior test, gynvael.coldwind//vx, 2013
#include <windows.h>
#include <stdio.h>
// Poor mans flags. On x86 access is atomic anyway, so no synchronisation
// is required.
int end_the_thread;
int thread_ready;
#define UNUSED (void)
// Thread that does nothing.
DWORD WINAPI MyThread(LPVOID param) {
UNUSED param;
thread_ready = 1;
while(!end_the_thread) Sleep(0);
return 0;
}
// Main function.
int main(void) {
HANDLE hLib[5], hTh;
int i;
puts("-- LoadLibrary phase");
for(i = 0; i < 5; i++) {
printf("LoadLibrary call #%i\n", i);
hLib[i] = LoadLibrary("test_dll.dll");
printf("hLib[%i] is %x\n", i, (unsigned)hLib[i]);
// Note: hLib will be always the same, since it's the address
// of the module loaded in memory, and the module is loaded
// only once (the first time); then just a reference counter
// is incremented.
}
puts("-- CreateThread phase");
hTh = CreateThread(NULL, 0, MyThread, 0, 0, 0);
while(!thread_ready) Sleep(0);
puts("-- Calling FreeLibrary once");
printf("FreeLibrary call #%i\n", 0);
FreeLibrary(hLib[0]); // Expect no DllMain calls.
// Let's sleep here to give the thread some time if it would call
// the DllMain (which it won't).
Sleep(500);
puts("-- Thread Ends phase");
end_the_thread = 1;
WaitForSingleObject(hTh, INFINITE);
puts("-- FreeLibrary phase");
for(i = 1; i < 5; i++) {
printf("FreeLibrary call #%i\n", i);
FreeLibrary(hLib[i]);
}
return 0;
}
Console output:
>gcc test_dll.c -c -Wall -Wextra
>dllwrap test_dll.o -o test_dll.dll
dllwrap: no export definition file provided.
Creating one, but that may not be what you want
>gcc test.c -Wall -Wextra
>a
-- LoadLibrary phase
LoadLibrary call #0
DllMain called; fdwReason=DLL_PROCESS_ATTACH, threadID=1ed8
hLib[0] is 6ad40000
LoadLibrary call #1
hLib[1] is 6ad40000
LoadLibrary call #2
hLib[2] is 6ad40000
LoadLibrary call #3
hLib[3] is 6ad40000
LoadLibrary call #4
hLib[4] is 6ad40000
-- CreateThread phase
DllMain called; fdwReason=DLL_THREAD_ATTACH, threadID=1e80
-- Calling FreeLibrary once
FreeLibrary call #0
-- Thread Ends phase
DllMain called; fdwReason=DLL_THREAD_DETACH, threadID=1e80
-- FreeLibrary phase
FreeLibrary call #1
FreeLibrary call #2
FreeLibrary call #3
FreeLibrary call #4
DllMain called; fdwReason=DLL_PROCESS_DETACH, threadID=1ed8
>
Even though FreeLibrary was called 5 times, DllMain was called only 4 times:
1. At first load (LoadLibrary phase, call #0)
2. When a thread was created (CreateThread phase)
3. When it exited (Thread Ends phase)
4. And when the last FreeLibrary was called (FreeLibrary phase, call #4)