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)
13515정성태1/6/20242621닷넷: 2195. async 메서드 내에서 C# 7의 discard 구문 활용 사례 [1]
13514정성태1/5/20242307개발 환경 구성: 702. IIS - AppPool의 "Disable Overlapped Recycle" 옵션
13513정성태1/5/20242224닷넷: 2194. C# - WebActivatorEx / System.Web의 PreApplicationStartMethod 특성
13512정성태1/4/20242176개발 환경 구성: 701. IIS - w3wp.exe 프로세스의 ASP.NET 런타임을 항상 Warmup 모드로 유지하는 preload Enabled 설정
13511정성태1/4/20242198닷넷: 2193. C# - ASP.NET Web Application + OpenAPI(Swashbuckle) 스펙 제공
13510정성태1/3/20242120닷넷: 2192. C# - 특정 실행 파일이 있는지 확인하는 방법 (Linux)
13509정성태1/3/20242169오류 유형: 887. .NET Core 2 이하의 프로젝트에서 System.Runtime.CompilerServices.Unsafe doesn't support netcoreapp2.0.
13508정성태1/3/20242221오류 유형: 886. ORA-28000: the account is locked
13507정성태1/2/20242867닷넷: 2191. C# - IPGlobalProperties를 이용해 netstat처럼 사용 중인 Socket 목록 구하는 방법파일 다운로드1
13506정성태12/29/20232461닷넷: 2190. C# - 닷넷 코어/5+에서 달라지는 System.Text.Encoding 지원
13505정성태12/27/20232990닷넷: 2189. C# - WebSocket 클라이언트를 닷넷으로 구현하는 예제 (System.Net.WebSockets)파일 다운로드1
13504정성태12/27/20232579닷넷: 2188. C# - ASP.NET Core SignalR로 구현하는 채팅 서비스 예제파일 다운로드1
13503정성태12/27/20232443Linux: 67. WSL 환경 + mlocate(locate) 도구의 /mnt 디렉터리 검색 문제
13502정성태12/26/20232564닷넷: 2187. C# - 다른 프로세스의 환경변수 읽는 예제파일 다운로드1
13501정성태12/25/20232325개발 환경 구성: 700. WSL + uwsgi - IPv6로 바인딩하는 방법
13500정성태12/24/20232416디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
13498정성태12/23/20233102닷넷: 2186. 한국투자증권 KIS Developers OpenAPI의 C# 래퍼 버전 - eFriendOpenAPI NuGet 패키지
13497정성태12/22/20232496오류 유형: 885. Visual Studiio - error : Could not connect to the remote system. Please verify your connection settings, and that your machine is on the network and reachable.
13496정성태12/21/20232490Linux: 66. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (gdb)
13495정성태12/20/20232417Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20232591Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20232721닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20232394개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
13491정성태12/19/20232269Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20232399개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20232178개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...