Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 21개 있습니다.)
Windows: 226. Win32 C/C++ - Dialog에서 값을 반환하는 방법
; https://www.sysnet.pe.kr/2/0/13284

Windows: 227. Win32 C/C++ - Dialog Procedure를 재정의하는 방법
; https://www.sysnet.pe.kr/2/0/13285

Windows: 228. Win32 - 리소스에 포함된 대화창 Template의 2진 코드 해석 방법
; https://www.sysnet.pe.kr/2/0/13286

Windows: 229. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 윈도우를 직접 띄우는 방법
; https://www.sysnet.pe.kr/2/0/13287

Windows: 230. Win32 - 대화창의 DLU 단위를 pixel로 변경하는 방법
; https://www.sysnet.pe.kr/2/0/13288

Windows: 231. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 자식 윈도우를 생성하는 방법
; https://www.sysnet.pe.kr/2/0/13289

Windows: 232. C/C++ - 일반 창에도 사용 가능한 IsDialogMessage
; https://www.sysnet.pe.kr/2/0/13292

Windows: 233.  Win32 - modeless 대화창을 modal처럼 동작하게 만드는 방법
; https://www.sysnet.pe.kr/2/0/13295

Windows: 234. IsDialogMessage와 협업하는 WM_GETDLGCODE Win32 메시지
; https://www.sysnet.pe.kr/2/0/13296

Windows: 235. Win32 - Code Modal과 UI Modal
; https://www.sysnet.pe.kr/2/0/13297

Windows: 237. Win32 - 모든 메시지 루프를 탈출하는 WM_QUIT 메시지
; https://www.sysnet.pe.kr/2/0/13299

Windows: 238. Win32 - Modal UI 창에 올바른 Owner(HWND)를 설정해야 하는 이유
; https://www.sysnet.pe.kr/2/0/13300

Windows: 242. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (쉬운 버전)
; https://www.sysnet.pe.kr/2/0/13305

Windows: 243. Win32 - 윈도우(cbWndExtra) 및 윈도우 클래스(cbClsExtra) 저장소 사용 방법
; https://www.sysnet.pe.kr/2/0/13306

Windows: 244. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (개선된 버전)
; https://www.sysnet.pe.kr/2/0/13312

Windows: 245. Win32 - 시간 만료를 갖는 컨텍스트 메뉴와 윈도우 메시지의 영역별 정의
; https://www.sysnet.pe.kr/2/0/13315

Windows: 246. Win32 C/C++ - 직접 띄운 대화창 템플릿을 위한 Modal 메시지 루프 생성
; https://www.sysnet.pe.kr/2/0/13329

Windows: 247. Win32 C/C++ - CS_GLOBALCLASS 설명
; https://www.sysnet.pe.kr/2/0/13330

Windows: 248. Win32 C/C++ - 대화창을 위한 메시지 루프 사용자 정의
; https://www.sysnet.pe.kr/2/0/13332

Windows: 249. Win32 C/C++ - 대화창 템플릿을 런타임에 코딩해서 사용
; https://www.sysnet.pe.kr/2/0/13333

Windows: 250. Win32 C/C++ - Modal 메시지 루프 내에서 SetWindowsHookEx를 이용한 Thread 메시지 처리 방법
; https://www.sysnet.pe.kr/2/0/13334




Win32 - 윈도우(cbWndExtra) 및 윈도우 클래스(cbClsExtra) 저장소 사용 방법

Window를 하나 생성하려면, 그 윈도우가 속한 "클래스"를 지정해야 하는데요, 일례로 신규 Windows Application 프로젝트를 생성하면 다음과 같은 식으로 기본 코드에 그 과정이 포함됩니다.

WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    // ...[생략]...

    LoadStringW(hInstance, IDC_MODALOWNER, szWindowClass, MAX_LOADSTRING);

    // ...[생략]...
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MODALOWNER));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_MODALOWNER);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

그리고, 위의 코드에서 cbClsExtra, cbWndExtra 필드가 바로 오늘 다룰 내용의 주제입니다. ^^




이름에서 의미하듯이 cbClsExtra는 윈도우 클래스 단위로 확보되는 메모리 크기이고, cbWndExtra는 윈도우 단위로 확보되는 메모리 크기입니다. 일반적인 OOP 개념에서 각각 클래스 필드, 인스턴스 필드에 대응하는 것으로 이해해도 무방합니다.

따라서, cbClsExtra에 지정하는 메모리 크기는 윈도우 클래스 레벨이므로 그 클래스로 생성되는 모든 윈도우에서 접근하는 공통 저장소 역할을 합니다. 반면, cbWndExtra의 경우라면 개별 윈도우 단위로 유지할 저장소를 제공합니다.

간단하게 예를 들어볼까요? ^^ cbClsExtra와 cbWndExtra에 대해 다음과 같이 포인터 크기만큼의 데이터를 확보해 보겠습니다.

wcex.cbClsExtra = sizeof(LONG_PTR);
wcex.cbWndExtra = sizeof(LONG_PTR);

이후 클래스 저장소를 접근하고 싶다면 GetClassLongPtr / SetClassLongPtr API를 사용하면 됩니다. 이름에서 벌써 "Class"가 포함돼 있습니다.

반면 윈도우 레벨의 저장소를 접근하고 싶다면 (짐작할 수 있겠지만 "Window" 이름이 들어간) GetWindowLongPtr / SetWindowLongPtr API를 사용하면 됩니다.

이것을 이용해 다음과 같이 코딩해 보면,

// 예를 들기 위해 윈도우 2개 생성
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

HWND hWnd2 = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

// 윈도우 저장소 테스트
SetWindowLongPtr(hWnd, 0, 501); // hWnd 윈도우 공간에 501 기록
SetWindowLongPtr(hWnd2, 0, 601); // hWnd2 윈도우 공간에 602 기록

LONG_PTR v1 = GetWindowLongPtr(hWnd, 0); // v1 == 501
LONG_PTR v2 = GetWindowLongPtr(hWnd2, 0); // v2 == 601

// 클래스 저장소 테스트
SetClassLongPtr(hWnd, 0, 502); // 윈도우 클래스 공간에 502 기록
SetClassLongPtr(hWnd2, 0, 602); // 윈도우 클래스 공간에 602 기록

LONG_PTR v3 = ::GetClassLongPtr(hWnd, 0);  // v3 == 602
LONG_PTR v4 = ::GetClassLongPtr(hWnd2, 0); // v4 == 602

cbClsExtra와 cbWndExtra 공간의 차이점을 확연히 알 수 있습니다. 윈도우가 다른 경우 cbWndExtra 공간도 달라지는 반면, 해당 윈도우들이 같은 클래스를 지정했다면 동일한 cbClsExtra 공간을 접근한 결과입니다.




위에서 cbWndExtra의 경우 포인터 공간을 확보하고 있는데요, 사실 Window Manager는 기본적으로 윈도우 생성 시 포인터 크기만큼의 공간을 사용자를 위해 1개 마련해 두고 있기 때문에 위와 같이 코딩할 필요가 없습니다.

해당 영역은 GWLP_USERDATA로 접근할 수 있는데요, 따라서 다음과 같이 코딩할 수 있습니다.

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    // ...[생략]...
    wcex.cbWndExtra = 0;
    // ...[생략]...

    return RegisterClassExW(&wcex);
}

...[생략]...
{
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   HWND hWnd2 = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   SetWindowLongPtr(hWnd, GWLP_USERDATA, 500);
   SetWindowLongPtr(hWnd2, GWLP_USERDATA, 600);

   LONG_PTR v1 = GetWindowLongPtr(hWnd, GWLP_USERDATA); // v1 == 500
   LONG_PTR v2 = GetWindowLongPtr(hWnd2, GWLP_USERDATA); // v2 == 600

   return TRUE;
}

참고로, 웹상의 블로그 내용 중에는 cbWndExtra로 추가한 공간을 접근할 때 GWLP_USERDATA를 사용해야 한다고 설명하는 글이 있는데 올바르지 않습니다. GWLP_USERDATA는 기본적으로 생성된 공간이고, cbWndExtra로 추가한 공간은 (바이트 크기의) 0을 기준으로 GetWindowLongPtr, SetWindowLongPtr로 접근해야 합니다.

예를 들어, cbWndExtra에 2개 포인터만큼의 크기를 확보했다면,

wcex.cbWndExtra = sizeof(LONG_PTR) * 2;

해당 바이트의 크기만큼 offest을 지정해 다음과 같이 값을 다룰 수 있습니다.

SetWindowLongPtr(hWnd, 0, 501);
SetWindowLongPtr(hWnd2, 0, 601);
SetWindowLongPtr(hWnd, sizeof(LONG_PTR), 502);
SetWindowLongPtr(hWnd2, sizeof(LONG_PTR), 602);

LONG_PTR v1 = GetWindowLongPtr(hWnd, 0); // v1 == 501
LONG_PTR v2 = GetWindowLongPtr(hWnd2, 0); // v2 == 601
LONG_PTR v3 = GetWindowLongPtr(hWnd, sizeof(LONG_PTR)); // v3 == 502
LONG_PTR v4 = GetWindowLongPtr(hWnd2, sizeof(LONG_PTR)); // v4 == 602

마지막으로, 윈도우 단위로 저장하는 공간에서는 이 외에도 (문서에 나오듯이) GWL_EXSTYLE, GWLP_HINSTANCE, GWLP_ID, GWL_STYLE, GWLP_WNDPROC 값이 있습니다.

그중에서, GWLP_HINSTANCE는 Window를 생성할 때 전달했던 HINSTANCE 값인데요,

HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

대개의 경우, hInstance 값을 (편의상) 전역 변수에 저장해 다루곤 하는데 사실 윈도우 핸들(HWND)로부터 이 값을 구할 수 있기 때문에,

HINSTANCE hInst = (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);

꼭 필요한 것은 아닙니다.




우리가 알고 있는 MFC 프레임워크는 GWLP_USERDATA 또는 cbWndExtra를 잘 활용한 하나의 예라고 보시면 됩니다. 이에 대해서는 마이크로소프트의 공식 문서에서 더 잘 설명하고 있으니 참고하시면 됩니다.

Managing Application State
; https://learn.microsoft.com/en-us/windows/win32/learnwin32/managing-application-state-

CreateWindowEx를 이용해 상태 정보를 마지막 인자 lpParam에 전달하고, WM_CREATE 메시지에 전달된 CREATESTRUCT로부터,

typedef struct tagCREATESTRUCTA {
  LPVOID    lpCreateParams; // CreateWindowEx에 전달한 lParam 값
  HINSTANCE hInstance;
  HMENU     hMenu;
  HWND      hwndParent;
  int       cy;
  int       cx;
  int       y;
  int       x;
  LONG      style;
  LPCSTR    lpszName;
  LPCSTR    lpszClass;
  DWORD     dwExStyle;
} CREATESTRUCTA, *LPCREATESTRUCTA;

lParam에 전달했던 상태 정보를 lpCreateParams 필드로 구해 그것을 윈도우 저장소에 보관함으로써,

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    StateInfo *pState;
    if (uMsg == WM_CREATE)
    {
        CREATESTRUCT *pCreate = reinterpret_cast(lParam);
        pState = reinterpret_cast(pCreate->lpCreateParams);
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
    }
    else
    {
        pState = GetAppState(hwnd);
    }

    // ...[생략]...
}

inline StateInfo* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo *pState = reinterpret_cast(ptr);
    return pState;
}

이후의 처리를 OOP 방식으로 매끄럽게 연결하는 것을 볼 수 있습니다. ^^




이번 글은, 아래의 글에 대한 장황한 버전이었습니다. ^^

The bonus window bytes at GWLP_USERDATA
; https://devblogs.microsoft.com/oldnewthing/20050303-00/?p=36293




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/4/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...
NoWriterDateCnt.TitleFile(s)
13261정성태2/15/20234537Windows: 224. Visual Studio - 영문 폰트가 Fullwidth Latin Character로 바뀌는 문제
13260정성태2/14/20234328오류 유형: 847. ilasm.exe 컴파일 오류 - error : syntax error at token '-' in ... -inf
13259정성태2/14/20234491.NET Framework: 2095. C# - .NET5부터 도입된 CollectionsMarshal
13258정성태2/13/20234334오류 유형: 846. .NET Framework 4.8 Developer Pack 설치 실패 - 0x81f40001
13257정성태2/13/20234400.NET Framework: 2094. C# - Job에 Process 포함하는 방법 [1]파일 다운로드1
13256정성태2/10/20235249개발 환경 구성: 665. WSL 2의 네트워크 통신 방법 - 두 번째 이야기
13255정성태2/10/20234583오류 유형: 845. gihub - windows2022 이미지에서 .NET Framework 4.5.2 미만의 프로젝트에 대한 빌드 오류
13254정성태2/10/20234512Windows: 223. (WMI 쿼리를 위한) PowerShell 문자열 escape 처리
13253정성태2/9/20235263Windows: 222. C# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법파일 다운로드1
13252정성태2/9/20234085오류 유형: 844. ssh로 명령어 수행 시 멈춤 현상
13251정성태2/8/20234535스크립트: 44. 파이썬의 3가지 스레드 ID
13250정성태2/8/20236381오류 유형: 843. System.InvalidOperationException - Unable to configure HTTPS endpoint
13249정성태2/7/20235212오류 유형: 842. 리눅스 - You must wait longer to change your password
13248정성태2/7/20234244오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
13247정성태2/7/20235126VS.NET IDE: 180. Visual Studio - 닷넷 소스 코드 디버깅 중 "Decompile source code"가 동작하는 않는 문제
13246정성태2/6/20234293개발 환경 구성: 664. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 - 두 번째 이야기
13245정성태2/6/20234867.NET Framework: 2093. C# - PEM 파일을 이용한 RSA 개인키/공개키 설정 방법파일 다운로드1
13244정성태2/5/20234196VS.NET IDE: 179. Visual Studio - External Tools에 Shell 내장 명령어 등록
13243정성태2/5/20235036디버깅 기술: 190. windbg - Win32 API 호출 시점에 BP 거는 방법 [1]
13242정성태2/4/20234487디버깅 기술: 189. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.UnauthorizedAccessException
13241정성태2/3/20233955디버깅 기술: 188. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.IO.FileNotFoundException
13240정성태2/1/20234113디버깅 기술: 187. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.Web.HttpException
13239정성태2/1/20233787디버깅 기술: 186. C# - CacheDependency의 숨겨진 예외 - System.Web.HttpException
13238정성태1/31/20235883.NET Framework: 2092. IIS 웹 사이트를 TLS 1.2 또는 TLS 1.3 프로토콜로만 운영하는 방법
13237정성태1/30/20235560.NET Framework: 2091. C# - 웹 사이트가 어떤 버전의 TLS/SSL을 지원하는지 확인하는 방법
13236정성태1/29/20235127개발 환경 구성: 663. openssl을 이용해 인트라넷 IIS 사이트의 SSL 인증서 생성
1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...