성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 자식 윈도우를 생성하는 방법</h1> <p> 지난 글에 이어,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 윈도우를 직접 띄우는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13287'>https://www.sysnet.pe.kr/2/0/13287</a> </pre> <br /> 대화창 내의 컨트롤까지 생성하는 것을 다뤄보겠습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > BEGIN <span style='color: blue; font-weight: bold'>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</span> END </pre> <br /> 관련 내용은 시리즈의 3번째 글에서 잘 정리하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > The dialog manager, part 3: Creating the controls ; <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20050331-00/?p=36003'>https://devblogs.microsoft.com/oldnewthing/20050331-00/?p=36003</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 이에 대한 구조체 정의도 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/dlgbox/dlgtemplateex'>DLGTEMPLATEEX</a>와 마찬가지로 문서에만 있고 표준 Win32 헤더에는 없습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DLGITEMTEMPLATEEX structure ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/dlgbox/dlgitemtemplateex'>https://learn.microsoft.com/en-us/windows/win32/dlgbox/dlgitemtemplateex</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > typedef struct { DWORD helpID; DWORD exStyle; DWORD style; short x; short y; short cx; short cy; DWORD id; sz_Or_Ord windowClass; sz_Or_Ord title; WORD extraCount; } DLGITEMTEMPLATEEX; </pre> <br /> 문서의 구조체에서는 마지막에 extraCount로 맺고 있지만, 실제로는 extraCount 값이 0이 아닌 경우 이후의 데이터를 더 읽어들여 CreateWindowEx의 마지막 인자에 전달해야 하는 것으로 나옵니다. (하지만, 실제로는 거의 쓰이는 경우가 없다고 합니다.)<br /> <br /> 이 구조체 역시 중간에 sz_Or_Ord로 대표되는 가변 크기의 필드 때문에 읽어내는 것이 좀 복잡하므로 그 코드를 여기에 싣는 것은 생략합니다. (첨부 파일에는 모든 코드가 있습니다.)<br /> <br /> 암튼, 그래서 그 코드를 이용해 다음과 같이 읽어냈다고 가정합니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > BYTE* pNext = (BYTE*)lpv + forward; // DLGTEMPLATEEX을 읽은 후 DLGITEMTEMPLATEEX의 첫 번째 위치까지 이동 vector<DLGITEMTEMPLATEEX*> items; // 읽어낸 DLGITEMTEMPLATEEX를 보관할 vector list for (int i = 0; i < pDialog->cDlgItems; i++) { size_t itemSize = 0; DLGITEMTEMPLATEEX* item = DLGITEMTEMPLATEEX::ReadItem(pNext, itemSize); pNext += itemSize; items.push_back(item); } pDialog->_controls = items; </pre> <br /> 정상적으로 2진 데이터로부터 DLGITEMTEMPLATEEX를 컨트롤 수만큼 읽었다면 가장 먼저 처리해야 할 것은 x, y, cx, cy 필드에 대한 DLU 단위를 pixel로 처리하는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DLGITEMTEMPLATEEX* control = *it; int x = DLUtoPixelX(control->x); // <a target='tab' href='https://www.sysnet.pe.kr/2/0/13288'>DLU를 pixel로 변환</a> int y = DLUtoPixelY(control->y); int cx = DLUtoPixelX(control->cx); int cy = DLUtoPixelY(control->cy); </pre> <br /> 그다음, windowClass 필드에 지정한 값(ordinal 또는 문자열)을 읽어야 하는데요, ordinal로 저장된 것이라도 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13286#class_ordinal'>미리 정의된 숫자</a>들은 그에 해당하는 className으로 바꿔야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > LPCWSTR GetText(sz_Or_Ord* pOrdinal, BOOL resolveClassName) { WCHAR* pText = nullptr; if (pOrdinal != nullptr && pOrdinal->HasValue()) { if (pOrdinal->ordinal != 0) { if (resolveClassName == TRUE) { switch (pOrdinal->ordinal) { case 0x0080: return L"Button"; case 0x0081: return L"Edit"; case 0x0082: return L"Static"; case 0x0083: return L"List box"; case 0x0084: return L"Scroll bar"; case 0x0085: return L"Combo box"; } } else { pText = MAKEINTRESOURCE(pOrdinal->ordinal); } } else { pText = pOrdinal->name; } } return pText; } </pre> <br /> 따라서 className과 title을 각각 다음과 같이 처리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > LPCWSTR className = GetText(control->windowClass, TRUE); LPCWSTR title = GetText(control->title); </pre> <br /> 그런데, 여기서 한 가지 유의할 점이 ICON 형식의 static 컨트롤에 대한 처리입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ICON IDR_MAINFRAME,IDC_STATIC,14,14,21,20 </pre> <br /> 저렇게 rc 파일에서 <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20250210-00/?p=110854'>ICON으로 정의</a>되면 그것은 DLGITEMTEMPLATEEX의 style에 SS_ICON 필드가 설정됩니다. 또한 "title"에 해당하는 값은 윈도우 타이틀 문자열을 의미하지 않고 Icon 자원의 ordinal을 가리킵니다. 따라서 특별히 SS_ICON을 위해 다음과 같은 후처리를 필요로 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if ((control->style & SS_ICON) == SS_ICON) { control->_hIcon = LoadIcon(this->_hInstance, title); // 여기서의 title은 IDR_MAINFRAME title = nullptr; // title이 문자열인 경우 유니코드 + 소문자 사용 시 주의 ("<a target='tab' href='https://learn.microsoft.com/en-us/answers/questions/2155202/issue-with-win32-api-loading-of-pe-resources-conta'>Issue with Win32 API Loading of PE Resources Containing Lowercase Letters</a>") // non-ASCII 문자열인 경우, 대문자로 변환해 검색하지 않음. } // 초기 윈도우 3.x 시절에는 progman.exe 파일에 MS-DOS 시절의 프로그램을 위한 아이콘이 들어 있었습니다. // What were the MS-DOS programs that Windows used the progman.exe stock icons for? // ; <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20250506-00/?p=111149'>https://devblogs.microsoft.com/oldnewthing/20250506-00/?p=111149</a> // 하지만 점차로 지원하는 아이콘이 늘어나면서 별도로 분리해야 할 필요가 생겨 리소스 전용의 "moricons.dll" 파일이 추가됩니다. // 재미있게도 progman.exe는 이제 파일 탐색기로 대체되면서 삭제되었지만, // moricons.dll은 (현재 64비트 윈도우가 MS-DOS 환경을 지원하지 않는데도) 살아남아 "C:\Windows\System32" 디렉터리에 존재하고 있습니다. // 그리고 해당 아이콘들은 비주얼 스튜디오 등의 프로그램으로 DLL을 로드해 확인하는 것도 가능하지만, 아래의 글에 잘 정리가 돼 있으니 참고하세요. ^^ // What were the MS-DOS programs that the moricons.dll icons were intended for? // ; <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20250507-00/?p=111157'>https://devblogs.microsoft.com/oldnewthing/20250507-00/?p=111157</a> </pre> <br /> 자, 끝입니다. ^^ 이제 DLGITEMTEMPLATEEX 정보에 기반해 윈도우를 생성하고 아이콘 설정, helpID 처리, font 설정만 하면 마무리가 됩니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HWND hwndChild = CreateWindowEx( control->exStyle <span style='color: blue; font-weight: bold'>| WS_EX_NOPARENTNOTIFY</span>, // 대화창 컨트롤의 경우 className, title, control->style, x, y, cx, cy, this->_hDlg, reinterpret_cast<HMENU>(control->id), this->_hInstance, control->rgbExtra); if (hwndChild == nullptr) { return FALSE; } if (control->_hIcon != nullptr) { SendMessage(hwndChild, STM_SETICON, (WPARAM)(HICON)(control->_hIcon), 0L); } if (control->helpID != 0) { SetWindowContextHelpId(hwndChild, control->helpID); } if (this->_hFont != nullptr) { ::SendMessageW(hwndChild, WM_SETFONT, (WPARAM)this->_hFont, FALSE); } </pre> <br /> 간단하죠. ^^ 위의 과정을 설명하면서 Raymond Chen은 대화창 생성이 실패하는 사례에 대해 설명합니다. 가령 해당 class name의 윈도우 클래스 등록이 이뤄지지 않았다면 위에서 대화창의 자식 컨트롤을 생성하는 과정에 실패해 대화창 생성이 안 된다는 것입니다. (Win32 common control을 사용했는데 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrl-initcommoncontrolsex'>InitCommonControlsEx</a> 호출을 잊은 경우!)<br /> <br /> 그런데, 자식 컨트롤 생성에 실패해도 이를 무시하는 옵션으로 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/dlgbox/dialog-box-styles'>DS_NOFAILCREATE</a>가 있다고 합니다. ^^ <br /> <br /> 자... 어찌어찌해서 대화창 구성이 완료되었다면 이제서야 비로소 WM_INITDIALOG를 보낼 수 있게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > BOOL ignoreFailCreate = ((this->style & DS_NOFAILCREATE) == DS_NOFAILCREATE); if (CreateChildControls(ignoreFailCreate) == FALSE) { DestroyWindow(this->_hDlg); this->_hDlg = nullptr; return FALSE; } ::SetWindowLongPtr(this->_hDlg, DWLP_DLGPROC, (LPARAM)this->_dlgProc); ::SendMessageW(this->_hDlg, WM_SETFONT, (WPARAM)this->_hFont, FALSE); HDC hDC = GetDC(this->_hDlg); TEXTMETRIC tm = { 0 }; GetTextMetrics(hDC, &tm); // WM_INITDIALOG를 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13284'>Dialog Procedure</a>에 전송해서 초기화를 마무리 HWND hwndDefaultFocus = GetNextDlgTabItem(this->_hDlg, NULL, FALSE); if (SendMessage(this->_hDlg, WM_INITDIALOG, (WPARAM)hwndDefaultFocus, 0)) { // <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20040802-00/?p=38283'>https://devblogs.microsoft.com/oldnewthing/20040802-00/?p=38283</a> SendMessage(this->_hDlg, WM_NEXTDLGCTL, (WPARAM)hwndDefaultFocus, TRUE); } ::ShowWindow(this->_hDlg, SW_SHOW); </pre> <br /> 물론, 이 외에도 여러 가지 자잘한 작업들이 있는데, 일단 위와 같은 정도만 구현해도 제법 구색이 갖춰집니다. (실행해 보면 Win32 API로 수행한 대화창과 완전히 동일한 모습으로 생성되는 것을 확인할 수 있습니다.)<br /> <br /> (첨부 파일은 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13287'>지난 글의 예제 코드</a>를 <a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2034&boardid=331301885'>이번 글의 변경 사항을 적용해 수정한 버전</a>입니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 잠시 뒷이야기를 다뤄볼까요? ^^<br /> <br /> 지금까지 리소스 파일을 읽어 대화창을 구성하는 것에 집중하느라 미처 하지 못한 작업이 있습니다. 바로 자원의 해제입니다.<br /> <br /> 만약 Modal 유형의 대화창을 처리하는 것이었다면, 위의 작업을 마무리한 후 대화창이 닫히면 자원 해제를 할 수 있습니다. 하지만 Modeless 유형이라면 어떨까요? 대화창을 닫는 작업을 Dialog Procedure 내에서 WM_DESTROY 메시지로 전송(DestroyWindow)할 텐데 그에 대한 처리를 할 수 있는 것은 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defdlgprocw'>DefDlgProc</a> 함수가 됩니다.<br /> <br /> 그러니까, 우리에게 자원 해제를 할 기회가 주어지지 않는 것입니다. 이게 가능하려면 전에 설명한 것처럼,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Win32 C/C++ - Dialog Procedure를 재정의하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13285'>https://www.sysnet.pe.kr/2/0/13285</a> </pre> <br /> DefDlgProc과 같은 Window Procedure를 지정한 우리의 대화창 class를 가져야 합니다. 다시 말해, Window 개발자들은 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13287#modeless_dlg'>CreateDialog</a>와 같은 Win32 API를 이 글에서 구현한 방식만으로 구현하는 것에 한계를 알고 있었던 것입니다. <br /> <br /> 아마도 그런 이유로 인해 #32770 window class가 등록되지 않았을까... 하는 예상을 해봅니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 또 한 가지 설명해야 할 것은, Visual Studio가 생성한 Windows Application의 기본 프로젝트에 있는 RC 파일의 About 대화상자에 지정한 아이콘의 리소스 ID가 실은 없는 자원을 나타낸다는 점입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > BEGIN ICON <span style='color: blue; font-weight: bold'>IDR_MAINFRAME</span>,IDC_STATIC,14,14,21,20 ...[생략]... END </pre> <br /> 저 IDR_MAINFRAME에 해당하는 ICON 리소스가 없기 때문에 실제로 제가 만든 함수든, 윈도우가 제공하는 CreateDialog든, 실행하게 되면 아이콘 영역이 비어 있습니다.<br /> <br /> <img alt='dialog_manager_part2_1.png' src='/SysWebRes/bbs/dialog_manager_part2_1.png' /><br /> <br /> 마이크로소프트가 사용자에게 의도적으로 재정의하라고 저렇게 한 것인지, 아니면 버그인지는 알 수 없으나 암튼 기본 생성한 RC 파일에는 다음의 2개 아이콘이 정의돼 있으므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > IDI_PROJECT1 ICON "Project1.ico" IDI_SMALL ICON "small.ico" </pre> <br /> 둘 중의 아무거나 하나를 지정해서 사용하시면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > BEGIN ICON <span style='color: blue; font-weight: bold'>IDI_PROJECT1</span>,IDC_STATIC,14,14,21,20 ...[생략]... END </pre> <br /> 그럼 우리가 만든 대화창 소스코드로도 아래와 같이 정상적으로 ^^ 아이콘이 static 컨트롤 영역에 나타납니다.<br /> <br /> <img alt='dialog_manager_part2_2.png' src='/SysWebRes/bbs/dialog_manager_part2_2.png' /><br /> <br /> (2025-04-24 업데이트)<br /> <br /> 아래의 문서를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > What resource ID should I give my application’s main icon? ; <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20250423-00/?p=111106'>https://devblogs.microsoft.com/oldnewthing/20250423-00/?p=111106</a> </pre> <br /> IDI_APPLICATION 상수가 있다고 하는데요, 아마도 이것이 (본문의 경우처럼) 근래에는 "IDI_[프로젝트명]"으로 바뀐 것 같습니다. (또한 그 상수값은 107입니다.) 따라서 "default icon"으로 나타내기 위해서는 이후의 아이콘에 대한 상수값을 "ID_[프로젝트명]"보다 크게 잡아야 합니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2131
(왼쪽의 숫자를 입력해야 합니다.)