DLLMain 기본 개념
BOOL __stdcall DllMain()
{
// DLL은 DllMain이 Entry Point가 된다.
return TURE;
}
-
반드시 필요하지는 않다.
-
DllMain이 호출되는 4가지
- DLL이 프로세스의 처음 매핑 될 때
- 프로세스에서 새로운 스레드가 생성 될 때
- 스레드가 종료 될 때
- DLL 이 프로세스에서 Unload 될 때
// Sample.cpp
#include <Windows.h>
#include <stdio.h>
BOOL __stdcall DllMain(HINSTANCE hinstDLL, // DLL의 모듈 핸들(DLL Load된 가상 주소)
DWORD fdwReason, // DllMain이 호출된 이유(아래 4가지 중 하나)
LPVOID lpvReserved) // DLL이 로드된 방식 (LoadLibrary()로 로드시 암시적 로딩)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
printf("DLL_PROCESS_ATTACH : %d\n", GetCurrentThreadId());
printf("DLL Address : %p\n", hinstDLL);
printf("%s Linking \n", lpvReserved ? "Implicit" : "Explicit");
break;
case DLL_PROCESS_DETACH: printf("DLL_PROCESS_DETACH : %d\n", GetCurrentThreadId()); break;
case DLL_THREAD_ATTACH: printf("DLL_THREAD_ATTACH : %d\n", GetCurrentThreadId()); break;
case DLL_THREAD_DETACH: printf("DLL_THREAD_DETACH : %d\n", GetCurrentThreadId()); break;
}
// return FALSE이면 DLL이 로드되지 않음.
return TRUE;
}
# 참고 DLL 커멘드로 빌드
$ cl sample.cpp /LD
#include <conio.h>
#include <Windows.h>
#include <stdio.h>
DWORD __stdcall threadMain(void* p)
{
printf("Start threadMain : %d\n", GetCurrentThreadId());
Sleep(5000);
printf("Finish threadMain : %d\n", GetCurrentThreadId());
return 0;
}
int main()
{
printf("PRIMARY THREAD ID : %d\n", GetCurrentThreadId());
_getch();
HMODULE hDll = LoadLibraryA("Sample.dll");
printf("ADDR : %p\n", hDll);
_getch();
HANDLE hThread = CreateThread(0, 0, threadMain, 0, 0, 0);
WaitForSingleObject(hThread, INFINITE);
_getch();
FreeLibrary(hDll);
}
PRIMARY THREAD ID : 10960
<< Enter >>
DLL_PROCESS_ATTACH : 10960 # PROCESS에서 DLL 로드시 호출
DLL Address : 79E10000
Explicit Linking
ADDR : 79E10000
<< Enter >>
DLL_THREAD_ATTACH : 19820 # Thread 생성시 호출
Start threadMain : 19820
Finish threadMain : 19820
DLL_THREAD_DETACH : 19820 # Thread 종료시 호출
<< Enter >>
DLL_PROCESS_DETACH : 10960 # Process 종료시 호출
그런데 Thread를 생성할때마다 DllMain이 호출되면 오버로드아닌가?
만약 dll을 100개 로드해놨는데 Thread하나 생성될때마다 DllMain이 매번 호출되어야 하나?
-> 프로세스별로 DllMain을 호출하지 Thread생성시 마다 호출하진 마라 명령 : DisableThreadLibraryClass(hinstDll)
// Sample.cpp
#include <Windows.h>
#include <stdio.h>
BOOL __stdcall DllMain(HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryClass(hinstDLL); // Process별로 DllMain을 불러라
printf("DLL_PROCESS_ATTACH : %d\n", GetCurrentThreadId());
printf("DLL Address : %p\n", hinstDLL);
printf("%s Linking \n", lpvReserved ? "Implicit" : "Explicit");
break;
case DLL_PROCESS_DETACH: printf("DLL_PROCESS_DETACH : %d\n", GetCurrentThreadId()); break;
case DLL_THREAD_ATTACH: printf("DLL_THREAD_ATTACH : %d\n", GetCurrentThreadId()); break;
case DLL_THREAD_DETACH: printf("DLL_THREAD_DETACH : %d\n", GetCurrentThreadId()); break;
}
return TRUE;
}
DllMain 활용
// buffer.cpp
#include <Windows.h>
#include <stdio.h>
char* pBuffer = 0; // 시작은 0으로 버퍼가 없는 상황
// Heap에 메모리를 할당하는 형태로 만들어 보자.
extern "C" __declspec(dllexport) void SetData(const char* s)
{
strcpy(pBuffer, s);
}
extern "C" __declspec(dllexport) void GetData(char* s)
{
strcpy(s, pBuffer);
}
BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
// HEAP에 메모리 할당
pBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 256);
DisableThreadLibraryClass(hinstDLL);
break;
case DLL_PROCESS_DETACH:
// HEAP 메모리 해지
HeapFree(GetProcessHeap(), 0, pBuffer);
break;
}
return TRUE;
}
멀티쓰레드 환경을 고려해보자.
// buffer2.cpp
#include <Windows.h>
#include <stdio.h>
__declspec(thread) char* pBuffer = 0; // thread 로컬 스토리지에 저장됨.
// 정적 TLS - Thread별로 할당해 달라는 뜻
extern "C" __declspec(dllexport) void SetData(const char* s)
{
printf("TID : %d, addr Buffer : %p\n", GetCurrentThreadId(), pBuffer);
strcpy(pBuffer, s);
}
extern "C" __declspec(dllexport) void GetData(char* s)
{
strcpy(s, pBuffer);
}
BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
pBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 256);
break;
case DLL_PROCESS_DETACH:
HeapFree(GetProcessHeap(), 0, pBuffer);
break;
// Thread가 생성될때마다 HEAP 새로 할당
case DLL_THREAD_ATTACH:
pBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 256);
break;
case DLL_THREAD_DETACH:
HeapFree(GetProcessHeap(), 0, pBuffer);
break;
}
return TRUE;
}
// useBuffer.cpp
#include <Windows.h>
#include <stdio.h>
#include <process.h>
#include <conio.h>
extern "C" __declspec(dllimport) void SetData(const char* s);
extern "C" __declspec(dllimport) void GetData(char* s);
#pragma comment(lib, "buffer2.lib")
UINT __stdcall foo(void* p)
{
char s[256];
SetData("Hello"); // Thread의 버퍼에 보관됨.
Sleep(1000);
GetData(s);
printf("foo : %s\n", s);
return 0;
}
int main()
{
char s[256];
SetData("AAA"); // DLL의 버퍼에 보관됨.
_beginthreadex(0, 0, foo, 0, 0, 0);
Sleep(300);
GetData(s);
printf("main : %s\n", s);
_getch();
}
main : AAA
foo : Hello
DllMain 주의사항
- DllMain은 Serialize된다.
- A라는 Thread가 DllMain 실행 중 B라는 Thread가 생성된다면?
- B Thread는 A Thread가 DllMain을 나갈때 까지 대기한다.
#include <Windows.h>
#include <stdio.h>
DWORD __stdcall foo(void* p)
{
printf("foo\n");
return 0;
}
BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
// 절대 이렇게 사용하면 안된다.
HANDLE hThread = CreateThread(0, 0, foo, 0, 0, 0);
WaitForSingleObject(hThread, INFINITE);
// 주 스레드가 새로운 스레드의 종료를 대기한다.
// foo보다 DllMain이 호출되며 무한대기(DeadLock)에 빠지게 된다.
// DllMain이 호출되는 이유는 스레드 생성시 DllMain호출
break;
}
return TRUE;
}