(Win32 : WindowsProgramming-26) DLL Entry Point - DLLMain

Posted by : at

Category : win32   WindowsProgramming


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;
}

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links