Win32 C/C++ - CS_GLOBALCLASS 설명
보통은 윈도우 응용 프로그램을 만들 때 RegisterClass는 기본 코드 그대로 사용하기 마련입니다.
BOOL InitApp(HINSTANCE hInstance)
{
WNDCLASS wc;
wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
wc.lpfnWndProc = WLWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(WLDLGPROC);
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = TEXT("WLDialog");
if (!RegisterClass(&wc)) return FALSE;
return TRUE;
}
oldnewthing 글에서는,
When should I use CS_GLOBALCLASS?
; https://devblogs.microsoft.com/oldnewthing/20230310-00/?p=107926
About Window Classes
; https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-classes
위의 코드 중 style 필드에 설정할 수 있는 CS_GLOBALCLASS에 대해 자세하게 설명하고 있습니다.
RegisterClass 호출 시 "lpszClassName" 필드에 클래스를 식별할 수 있는 이름을 전달하는데요, 실제로는 또 다른 식별자가 하나 더 있습니다. 바로 "hInstance" 필드입니다.
wc.hInstance = hInstance;
wc.lpszClassName = TEXT("WLDialog");
즉, 해당 클래스를 HINSTANCE+CLASSNAME 쌍으로 식별하게 되는 것입니다. 그래서
CreateWindow 호출 시,
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
클래스 이름뿐만 아니라 HINSTANCE 값도 함께 넘기는 것입니다. 실제로 (기본 생성된 C/C++ Window 프로젝트에서) CreateWindow를 호출하는 코드를 다음과 같이 바꿔보면,
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HINSTANCE hInst2 = (HINSTANCE)::GetModuleHandle(L"kernel32.dll");
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInst2, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
hWnd 값은 0이 나오고, 이때의 오류 코드는 "0x0000057F (Cannot find window class.)"가 됩니다. 그런데, 일반적으로 윈도우가 미리 제공하는 Button과 같은 경우에는 HINSTANCE를 그 Button을 구현한 모듈이 아닌 현재의 모듈을 전달해도 잘 동작합니다.
HWND hwndChild =
CreateWindow(
TEXT("button"), /* Class Name */
TEXT("Button &1"), /* Title */
WS_CHILD | WS_VISIBLE | WS_TABSTOP |
BS_DEFPUSHBUTTON | BS_TEXT, /* Style */
0, 0, 100, 100, /* Position and size */
hWnd, /* Parent */
(HMENU)100, /* Child ID */
hInst, /* Instance */
0); /* No special parameters */
왜냐하면, 바로 저 Button 클래스는 RegisterClass를 호출했을 때 style에 CS_GLOBALCLASS 옵션을 줬기 때문입니다. 즉, 이 옵션으로 설정된 클래스 이름은 그것을 식별하기 위한 조건에서 HINSTANCE가 무시되는 것입니다.
사실, 대부분의 경우에 CS_GLOBALCLASS 옵션을 쓸 필요는 없습니다. 혹시나 자신만의 윈도우 클래스를 만들어 DLL로 분리했을 때에도 그 DLL의 HINSTANCE(Module Handle) 값을 구해 넘겨주면 그만입니다.
하지만, 유일하게 신경 써야 할 때가 있는데요, 바로 대화창의 Template으로 특정 윈도우를 등록하는 경우입니다.
이전에 설명했던,
Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 자식 윈도우를 생성하는 방법
; https://www.sysnet.pe.kr/2/0/13289
글에서 아래와 같은 형식의 리소스 템플릿을 읽어들여 윈도우를 직접 생성하고 있는데요,
BEGIN
ICON IDR_MAINFRAME,IDC_STATIC,14,14,21,20
LTEXT "Project1, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX
LTEXT "Copyright (c) 2023",IDC_STATIC,42,26,114,8
DEFPUSHBUTTON "OK",IDOK,113,41,50,14,WS_GROUP
END
보시면 저 정보에 HINSTANCE에 대한 필드가 없습니다. 어쩌면 기본 컨트롤에 대해서는 고정적으로 user32.dll의 모듈 핸들을 넘겨주도록 처리했어도 되었을 것입니다, 하지만 그런 경우 저런 식으로 기본 컨트롤만으로 구성된 예제는 상관없지만 이후 나온
(Comctl32.dll에 구현된) Common Controls나, 한때 유행했던 Internet Explorer ActiveX의 경우에는 CS_GLOBALCLASS 옵션이 없었다면 대화창의 template으로 처리할 수 없어, 아마도 불편하게 WM_INITDIALOG에서 코드로 생성해야만 했을 것입니다.
이런 거 보면... 정말 Win32 시스템을 설계한 사람들이 공을 참 많이 들였구나... 하는 생각이 듭니다. ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]