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




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/28/2024]

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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1780정성태10/15/201424205오류 유형: 249. The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID
1779정성태10/15/201419744오류 유형: 248. Active Directory에서 OU가 지워지지 않는 경우
1778정성태10/10/201418191오류 유형: 247. The Netlogon service could not create server share C:\Windows\SYSVOL\sysvol\[도메인명]\SCRIPTS.
1777정성태10/10/201421313오류 유형: 246. The processing of Group Policy failed. Windows attempted to read the file \\[도메인]\sysvol\[도메인]\Policies\{...GUID...}\gpt.ini
1776정성태10/10/201418329오류 유형: 245. 이벤트 로그 - Name resolution for the name _ldap._tcp.dc._msdcs.[도메인명]. timed out after none of the configured DNS servers responded.
1775정성태10/9/201419448오류 유형: 244. Visual Studio 디버깅 (2) - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1774정성태10/9/201426645개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
1773정성태10/8/201429795.NET Framework: 471. 웹 브라우저로 다운로드가 되는 파일을 왜 C# 코드로 하면 안되는 걸까요? [1]
1772정성태10/3/201418604.NET Framework: 470. C# 3.0의 기본 인자(default parameter)가 .NET 1.1/2.0에서도 실행될까? [3]
1771정성태10/2/201428106개발 환경 구성: 245. 실행된 프로세스(EXE)의 명령행 인자를 확인하고 싶다면 - Sysmon [4]
1770정성태10/2/201421712개발 환경 구성: 244. 매크로 정의를 이용해 파일 하나로 C++과 C#에서 공유하는 방법 [1]파일 다운로드1
1769정성태10/1/201424132개발 환경 구성: 243. Scala 개발 환경 구성(JVM, 닷넷) [1]
1768정성태10/1/201419557개발 환경 구성: 242. 배치 파일에서 Thread.Sleep 효과를 주는 방법 [5]
1767정성태10/1/201424640VS.NET IDE: 94. Visual Studio 2012/2013에서의 매크로 구현 - Visual Commander [2]
1766정성태10/1/201422507개발 환경 구성: 241. 책 "프로그래밍 클로저: Lisp"을 읽고 나서. [1]
1765정성태9/30/201426060.NET Framework: 469. Unity3d에서 transform을 변수에 할당해 사용하는 특별한 이유가 있을까요?
1764정성태9/30/201422304오류 유형: 243. 파일 삭제가 안 되는 경우 - The action can't be comleted because the file is open in System
1763정성태9/30/201423876.NET Framework: 468. PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법파일 다운로드1
1762정성태9/30/201424555.NET Framework: 467. 닷넷에서 EIP/RIP 레지스터 값을 구하는 방법 [1]파일 다운로드1
1761정성태9/29/201421602.NET Framework: 466. 윈도우 운영체제의 보안 그룹 이름 및 설명 문자열을 바꾸는 방법파일 다운로드1
1760정성태9/28/201419881.NET Framework: 465. ICorProfilerInfo::GetILToNativeMapping 메서드가 0x80131358을 반환하는 경우
1759정성태9/27/201430993개발 환경 구성: 240. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법파일 다운로드1
1758정성태9/23/201437894개발 환경 구성: 239. 원격 데스크톱 접속(RDP)을 기존의 콘솔 모드처럼 사용하는 방법 [1]
1757정성태9/23/201418428오류 유형: 242. Lync로 모임 참여 시 소리만 들리지 않는 경우 - 두 번째 이야기
1756정성태9/23/201427459기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434234오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...