GetSafeHwnd()와 GetSafeHandle()의 역할 및 차이점
MFC(Microsoft Foundation Classes) 프로그래밍에서 객체의 실제 윈도우 핸들이나 GDI 핸들을 안전하게 가져오기 위해 GetSafeHwnd()와 GetSafeHandle() 메서드를 자주 사용합니다. 이 두 메서드는 대상 객체의 종류에 따라 명확히 구분됩니다.
1. GetSafeHwnd(): 윈도우 객체 전용
GetSafeHwnd()는 CWnd 파생 클래스에서 사용되며, 해당 윈도우 객체의 안전한 핸들(HWND)을 반환합니다. 이 핸들을 통해 Win32 API를 호출하여 윈도우를 직접 제어할 수 있습니다.
2. GetSafeHandle(): GDI 객체 전용
GetSafeHandle()는 CGdiObject 파생 클래스(예: CBitmap, CFont, CPen 등)에서 사용되며, 해당 GDI 객체의 핸들을 반환합니다.
안전한 포인터 및 핸들 검증 패턴
포인터를 통해 핸들에 접근할 때는 반드시 포인터 자체의 유효성과 객체가 실제 핸들을 보유하고 있는지 확인해야 합니다. 이를 위해 다음과 같은 방어적 코드를 작성하는 것이 권장됩니다.
// pTargetWindow은 CWnd 포인터라고 가정
if (pTargetWindow != nullptr && ::IsWindow(pTargetWindow->GetSafeHwnd()))
{
// 윈도우 핸들이 유효할 때만 API 호출 또는 작업 수행
::SetWindowText(pTargetWindow->GetSafeHwnd(), L"Updated Title");
}
핸들(Handle)과 포인터(Pointer)의 근본적인 차이
초보 개발자들은 핸들을 단순한 포인터로 오해하기 쉽지만, 윈도우 시스템 내부에서 두 개념은 엄격히 구분됩니다.
1. 메모리 접근 권한과 안전성
포인터는 메모리의 특정 주소를 직접 가리키므로, 개발자가 해당 메모리를 임의로 읽고 쓸 수 있습니다. 이는 유연성을 제공하지만, 잘못된 포인터 연산은 메모리 손상 및 프로그램 크래시를 유발합니다. 반면, 핸들은 윈도우 커널이 관리하는 객체 테이블의 인덱스 역할을 합니다. 개발자는 핸들을 통해 객체의 내부 메모리에 직접 접근할 수 없으며, 오직 윈도우가 제공하는 API 함수를 통해서만 객체를 조작할 수 있습니다. 이로 인해 시스템 안정성이 보장됩니다.
2. 내부 구조와 시스템 자원 관리
Win32 환경에서 핸들은 기본적으로 void*로 정의된 불투명 포인터(Opaque Pointer)입니다. (32비트 시스템에서는 4바이트, 64비트 시스템에서는 8바이트 크기를 가집니다.)
핸들이 가리키는 대상은 스레드, 프로세스, 윈도우 등 시스템과 밀접하게 연관된 복잡한 커널 구조체입니다. 예를 들어, 스레드가 예기치 않게 종료되면 운영체제는 핸들을 통해 해당 스레드가 점유하던 CPU 시간과 메모리 자원을 자동으로 회수합니다.
포인터가 가리키는 사용자 정의 구조체의 경우, 메모리 할당과 해제를 전적으로 개발자가 책임져야 하는 것과는 대조적입니다. (단, CString이나 스마트 포인터처럼 RAII 패턴으로 래핑된 클래스는 예외입니다.)
3. 식별자(ID)와의 차이점 및 프로세스 간 공유
핸들을 단순한 리소스 ID(예: IDR_MAINFRAME)로 착각해서는 안 됩니다. 리소스 ID는 디스크에 있는 리소스를 식별할 수 있지만, 핸들은 메모리에 로드된 특정 객체 인스턴스를 유일하게 지칭하며 시스템은 해당 객체의 크기와 상태를 파악하고 있습니다.
또한, 윈도우 핸들(HWND)은 시스템 전역 테이블에 등록되어 있으므로 다른 프로세스에서도 동일한 핸들 값을 사용하여 윈도우에 메시지를 보낼 수 있습니다. 반면, 파일이나 스레드 같은 커널 객체 핸들은 프로세스 종속적이므로, 프로세스 간에 공유하려면 DuplicateHandle API를 통해 명시적으로 복제해야 합니다.
윈도우 핸들(HWND)을 획득하는 주요 Win32 API
MFC 객체와 무관하게, 시스템에 존재하는 윈도우의 핸들을 찾아야 할 때 다음과 같은 Win32 API를 사용합니다.
1. 이름과 클래스로 찾기
HWND hMainWnd = ::FindWindow(L"MainWndClass", L"My Application");
HWND hChildWnd = ::FindWindowEx(
hMainWnd, // 부모 윈도우 핸들
NULL, // 이전 자식 윈도우 핸들
L"Button", // 자식 윈도우 클래스 이름
L"OK" // 자식 윈도우 텍스트
);
2. 마우스 커서 위치로 찾기
POINT cursorPos;
::GetCursorPos(&cursorPos);
HWND hHoverWnd = ::WindowFromPoint(cursorPos);
3. 열거(Enumeration)를 통한 찾기
// 최상위 윈도우 열거
::EnumWindows([](HWND hWnd, LPARAM lParam) -> BOOL {
// 각 윈도우에 대한 작업 수행
return TRUE; // 열거 계속 진행
}, 0);
// 특정 부모의 자식 윈도우 열거
::EnumChildWindows(hMainWnd, [](HWND hWnd, LPARAM lParam) -> BOOL {
// 자식 윈도우 처리
return TRUE;
}, 0);
MFC에서 핸들과 포인터 간의 변환
MFC는 내부 맵(Handle Map)을 유지하여 윈도우 핸들(HWND)과 MFC 객체 포인터(CWnd*)를 서로 연결합니다.
1. 포인터에서 핸들 추출
CWnd 파생 객체의 포인터가 주어졌을 때, GetSafeHwnd()를 사용하여 기본 핸들을 얻습니다.
CWnd* pActiveView = GetActiveView();
HWND hViewHandle = pActiveView ? pActiveView->GetSafeHwnd() : NULL;
2. 핸들에서 포인터 복원
Win32 API 콜백 등에서 HWND만 전달받은 경우, FromHandle 정적 메서드를 사용하여 임시 또는 영구 MFC 포인터를 얻을 수 있습니다.
HWND hExternalWnd = /* 외부에서 전달받은 핸들 */;
CWnd* pTempWnd = CWnd::FromHandle(hExternalWnd);
if (pTempWnd != nullptr)
{
pTempWnd->SetWindowText(L"Text Updated via Handle");
}
이러한 변환 메커니즘은 CBitmap, CPen, CFont 등 CGdiObject 파생 클래스에서도 FromHandle과 GetSafeHandle을 통해 동일하게 제공됩니다.