DLL Main 함수의 역할
DLL은 실행 파일처럼 WinMain이 꼭 있어야 한다는 강제가 없으며, 메시지도 처리하지 않기 때문에 WndProc도 불필요합니다.
함수들의 집합이기 때문에 함수에 대한 정의만 있으면 되지만, 원칙적으로 DLL도 반드시 엔트리 포인트를 가져야 합니다. 다만 C 런타임 라이브러리와 링크할 때는 C 런타임이 엔트리 포인트를 대신 제공해 주는데, 대부분의 경우 VC++를 사용하기 때문에 특별한 경우를 제외하고는 엔트리 포인트를 만들지 않아도 무방합니다.
그러나 DLL 내에 복잡한 전역변수가 있거나, 동적으로 메모리를 할당해서 사용해야 하는 경우 DLL Main 내에서 초기화와 종료 처리를 해주어야 합니다.
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpRes)
위 함수에서 첫번째 인자는 DLL의 인스턴스 핸들이며, 세 번째 인자는 사용되지 않습니다.
두 번째 인자에 따라 전역적인 초기화를 하거나 종료 처리를 하게 되며, 다음의 네 가지 값 중 하나를 가질 수 있습니다.
DLL_PROCESS_ATTACH | DLL이 프로세스의 주소 공간에 매핑될 때 호출됩니다. 묵시적 호출일 경우 프로세스가 시작될 때, 명시적 호출일 경우 LoadLibrary가 리턴되기 전에 이 값과 함께 DLL Main이 호출됩니다. 주로 메모리를 할당하거나 시스템 전역 핸들을 초기화하는 용도로 사용합니다. |
DLL_PROCESS_DETACH | DLL이 프로세스의 주소 공간에서 분리될 때 호출됩니다. 묵시적 호출일 경우 프로세스가 종료될 때, 명시적 호출일 경우 FreeLibrary 함수에 의해 이 값과 함께 DLL Main이 호출됩니다. 할당한 메모리를 해제하거나 시스템 전역 핸들을 릴리즈할 때 사용합니다. |
DLL_THREAD_ATTACH | DLL을 사용하는 클라이언트 프로세스에서 스레드를 생성할 때마다 이 값과 함께 DLL Main 함수가 호출됩니다. DLL 에서는 이 값을 받았을 때 스레드별 초기화를 수행합니다. 단, 이 값은 이미 존재하는 스레드에 대해서는 전달되지 않으며 스레드가 새로 생성될 때만 전달됩니다. 또한 최상위 스레드는 DLL_PROCESS_ATTACH가 대신 전달되므로 이 값이 전달될 필요가 없습니다. |
DLL_THREAD_DETACH | DLL을 사용하는 클라이언트 프로세스에서 스레드가 종료될 때마다 이 값과 함께 DLL Main 함수가 호출됩니다. DLL에서는 이 값을 받았을 때 스레드 별 종료 처리를 수행합니다. |
DLL Main 에서의 주의점
DLL Main은 LoadLibrary 등으로 DLL이 메모리로 불려지는 단계에서 호출되는 함수입니다. 이 단계는 자신 뿐만 아니라 다른 DLL이 정ㅇ확하게 초기화 되었는지 알 수 없는 단계이므로, 다른 DLL에 의존적인 함수를 호출하지 않는 것이 좋습니다. 또한, 의존성 루프를 생성할 수 있기 때문에 DLL Main 내에서는 LoadLibrary, LoadLibraryEx (혹은 이 함수를 호출하는 함수) 등으로 외부 DLL을 로드하지 말아야 합니다. 그리고 프로세스 종료 중에 엔트리 포인트에서 절대 FreeLibrary(혹은 이것을 호출하는 함수)를 호출해서도 안됩니다.
C++ 전역 객체에도 동일한 원리가 적용됩니다. C++ 전역 객체 또한 DLL Main과 같은 시점에 호출됩니다.
따라서 복잡한 일을 해야 하는 전역 객체가 있다면 포인터로 선언하고, Initialize 등의 함수를 노출시켜 해당 함수가 호출될 때에 객체를 생성하는 방법을 권장합니다.
다만, Kernel32.dll은 DLL Main이 호출될 때 프로세스 주소공간에 항상 로드되어 있기 때문에 Kernel32.dll 내부의 함수를 호출하는 것은 문제가 되지 않습니다. 따라서 다른 DLL을 로드하지 않는 Kernel32.dll 내의 함수들을 사용할 수 있는데, 예를 들어 크리티컬 섹션, 뮤텍스 등의 동기화 객체를 생성할 수 있습니다. 또한, deadlock이 발생할 수 있기 때문에 다른 스레드나 프로세스와 통신하지 않도록 주의해야 합니다.
DLL Main에서 수행하면 안되는 작업
- LoadLibrary, LoadLibraryEx 함수 호출 시 교착 상태에 빠질 수 있습니다.
- GetStringTypeA, GetStringTypeEx, GetStringTypeW 함수 호출 시 교착 상태에 빠질 수 있습니다.
- 다른 스레드와 동기화 시 교착 상태에 빠질 수 있습니다.
- CoInitializeEx를 사용한 COM 스레드 초기화 (특정한 상황에서 이 함수는 LoadLibraryEx 함수를 호출할 수 있습니다.)
- 레지스트리 관련 함수 (이러한 함수들은 Advapi32.dll에 구현되어 있는데, 호출 시점에서 이 DLL이 초기화되지 않았을 수 있습니다.)
- CreateProcess 함수 (이 함수는 다른 DLL을 로드합니다.)
- ExitThread 함수 (DLL Detach 시에 이 함수가 호출되면 교착 상태에 빠질 수 있습니다.)
- CreateThread 함수 (다른 스레드와 동기화하지 않은 경우 이 함수를 사용할 수는 있지만, 위험한 작업입니다.)
- Dynamic C Runtime (CRT) DLL에서 제공하는 메모리 관리 함수 (이 DLL이 초기화 되어있지 않을 수 있습니다.)
- user32.dll, gdi32.dll의 함수 호출 (이 DLL들이 초기화 되어있지 않을 수 있습니다.)
DLL Main에서 가능한 작업
- static 데이터는 컴파일 타임 시 초기화가 가능합니다.
- 뮤텍스, 크리티컬 섹션 등 동기화 객체의 사용이 가능합니다.
- 파일에 대하여 Open, Read, Write 작업이 가능합니다.
- Kernel32.dll의 함수 호출 (위에서 기술한 작업에 속하지 않는 경우에 한합니다.)
- 전역 포인터를 NULL로 초기화하고, 동적 멤버의 지연 초기화가 가능합니다.
자료 출처
'프로그래밍 > C++ & MFC' 카테고리의 다른 글
libcmt.lib 링크 충돌 문제 (0) | 2021.12.29 |
---|---|
CRT (C Run-time Libraries) (0) | 2021.07.12 |
함수 호출 규약 (0) | 2021.07.07 |