Window Desktop GUI 구조
2022년 8월 26일
ref #
https://blog.naver.com/tipsware/221211757379
https://woo-dev.tistory.com/121
https://blog.hexabrain.net/157
https://www.youtube.com/watch?v=Q47cpjahcYg&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=2
https://learn.microsoft.com/ko-kr/windows/win32/winmsg/about-messages-and-message-queues
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=21cchaos&logNo=70109275027
용어 #
핸들 #
창같은 커널오브젝트를 유저가 핸들링하기 위한 ID 값이라고 생각하면 된다.
32비트의 정수 멤버를 가진 구조체 포인터이며, 핸들마다 각 자료형(HWND, HPEN…)으로 typedef 되어있다. 자료형별로 다른 구조체이름으로 정해지기 때문에 형변환이 안되며 인자로 전달할때의 실수를 줄일 수 있다.
1#define DECLARE_HANDLE(name)
2 struct name##__{int unused;}; typedef struct name##__ *name
3DECLARE_HANDLE (HWND);
운영체제가 각각의 객체를 구분하기 위한 key값으로 사용한다. 같은 종류의 핸들끼리는 하나의 운영체제 안에서 중복된 값을 가질 수 없다. (다른종류의 핸들끼리는 중복될 수 있음)
Window Desktop GUI 구조 #
윈도우 데스크톱 프로그램은 위와 같이 구성되어 있다.
https://learn.microsoft.com/en-us/windows/win32/winmsg/about-windows
응용프로그램이 프로시저(WndProc)를 통해 실제로 텍스트나 그래픽 출력을 표시하는 부분은 client 영역이며, 나머지는 non-client 영역이다. non-client 영역은 대부분 시스템이 관리하며 CreateWindow 함수를 호출할 때 스타일을 지정할 수 있다. (스크롤바 포함여부, 최소화 최대화 닫기버튼 포함여부 등)
크롬 브라우저의 탭이나 visualstudio의 맘대로 만들어진 작업표시줄은 사실 비 클라이언트 영역을 전부 제거하고 클라이언트 영역에서 구현한 것이다.
CreateWindow 함수의 style에 WS_POPUP을 넣고 사이즈를 명시적으로 지정해보면 알 수 있다.
기본 WinMain 코드 #
윈도우즈 데스크톱 애플리케이션 프로젝트를 만들면 필요한 코드들이 만들어진다.
컴파일 후 실행해보면 아래와 같은 아주 기본적인 윈도우 프로그램이 실행된다.
전체적으로 보면 메세지가 들어올때까지 대기하다가 처리해주는 방식으로 진행되는데, 프로그램은 구현하기 나름이기 때문에 무조건 이 방식을 사용하는것은 아니다.
1/* Windows Desktop Application 의 HelloWorld */
2#include "framework.h" // 기본적인 windows.h, stdlib.h, malloc.h 등이 포함됨
3#include "WindowsProject1.h"
4
5#define MAX_LOADSTRING 100
6
7// 전역 변수:
8HINSTANCE hInst; // 현재 인스턴스입니다.
9WCHAR szTitle[MAX_LOADSTRING]; // 제목 표시줄 텍스트입니다.
10WCHAR szWindowClass[MAX_LOADSTRING]; // 기본 창 클래스 이름입니다.
11
12// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
13ATOM MyRegisterClass(HINSTANCE hInstance);
14BOOL InitInstance(HINSTANCE, int);
15LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
16INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
17
18int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
19 _In_opt_ HINSTANCE hPrevInstance,
20 _In_ LPWSTR lpCmdLine,
21 _In_ int nCmdShow)
22{
23 UNREFERENCED_PARAMETER(hPrevInstance); // 사용하지 않는다는 의미의 매크로
24 UNREFERENCED_PARAMETER(lpCmdLine); // 얘도
25
26 // TODO: 여기에 코드를 입력합니다.
27
28 // 전역 문자열을 초기화합니다.
29 LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
30 LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
31 MyRegisterClass(hInstance);
32
33 // 애플리케이션 초기화를 수행합니다:
34 if (!InitInstance (hInstance, nCmdShow))
35 return FALSE;
36
37 HACCEL hAccelTable = LoadAccelerators(hInstance,
38 MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
39
40 MSG msg;
41
42 // 기본 메시지 루프입니다:
43 while (GetMessage(&msg, nullptr, 0, 0))
44 {
45 if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
46 {
47 TranslateMessage(&msg);
48 DispatchMessage(&msg);
49 }
50 }
51
52 return (int) msg.wParam;
53}
매개변수 #
- SAL : 매개변수 앞에 In, Out 등의 키워드를 붙여 인자의 역할을 알기 쉽게 해준다.
함수로 전달되는 인자는 In 으로, 함수 내부에서 값을 저장해서 외부로 전달하는 인자는 Out 을 붙이고, _opt_는 선택적인자라는 뜻이다.
- hInstance : 메모리에 로딩된 프로세스(PE Header)의 시작주소. 요즘은 가상메모리때문에 같은 값이 들어올 수 있다. InitInstance() 가 실행되면 전역변수인 hInst에 세팅된다.
- hPrevInstance : 16비트 윈도우 시절 주 창과 통신하기 위한 값이였는데, 지금은 가상메모리로 인해 hInstance가 같은 값이 들어와서 의미가 없기때문에 NULL 값이 들어오고 사용하지 않는다는 의미로 main 시작에서 UNREFERENCED 매크로에 전달된다.
- lpCmdLine : 명령행 인자. argv같은 역할을 하지만, 인자들을 하나의 문자열로 묶어서 wchar_t* 가 전달된다.
- mCmdShow : 창모드, 전체화면 등을 의미하는 플래그. InitInstance() -> ShowWindows()로 전달됨
초기화 #
LoadStringW : 리소스파일의 스트링테이블에서 문자열을 가져와서 버퍼에 복사한다. 마지막인자(size)가 0이면, 복사가 아니라 읽기전용 문자열 포인터를 리턴한다.
리소스 파일도 rc파일 문법에 맞게 작성된 코드일뿐이고, 리소스뷰는 gui로 편하게 볼 수 있게 지원한다.
MyRegisterClass : 윈도우클래스에 대한 구조체 멤버들을 세팅하고 등록하는 함수. 아이콘, 메뉴, 커서 등의 리소스도 세팅하고 메세지처리 함수도 세팅한다.
프로세스에 윈도우는 0개일수도 있지만 여러개를 가질수도 있다. 윈도우마다 이 설정을 따로 해줘야한다.
1ATOM MyRegisterClass(HINSTANCE hInstance)
2{
3 WNDCLASSEXW wcex;
4
5 wcex.cbSize = sizeof(WNDCLASSEX);
6
7 wcex.style = CS_HREDRAW | CS_VREDRAW;
8 wcex.lpfnWndProc = WndProc; // 메세지 처리 콜백함수 포인터 저장
9 wcex.cbClsExtra = 0;
10 wcex.cbWndExtra = 0;
11 wcex.hInstance = hInstance;
12 wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
13 wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
14 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
15 wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
16 wcex.lpszClassName = szWindowClass; // 클래스의 key값 create할때 참조해서 생성함
17 wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
18
19 return RegisterClassExW(&wcex);
20}
InitInstance : 몇가지 초기화도 하고, 위에서 등록한 정보를 바탕으로 윈도우를 생성하는 함수
1BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
2{
3 hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
4
5 // 윈도우창 생성. 등록했던 클래스 key값 사용(문자열) 스타일 지정
6 HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
7 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
8 // 좌표 x, y 창의 너비, 높이
9
10 if (!hWnd)
11 return FALSE;
12
13 ShowWindow(hWnd, nCmdShow); // 창 상태에 맞게 그리기.
14 UpdateWindow(hWnd); // WM_PAINT를 즉시 보내 창을 업데이트한다.
15
16 return TRUE;
17}
ShowWindow에서도 WM_PAINT 메시지를 보내긴 하지만, 메시지큐가 비어있을때 보내게된다. 예전엔 컴퓨터가 느려서 화면을 띄우는게 늦었기 때문에 UpdateWindow를 바로 실행시켜 화면 업데이트를 빠르게 했다.
LoadAccelerators : 단축키 테이블을 세팅하는 함수. 이후 메시지루프에서 단축키 입력 여부를 확인한다.
1 HACCEL hAccelTable = LoadAccelerators(hInstance,
2 MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
리소스파일에서 설정할 단축키를 볼 수 있다.
키보드가 입력되면 메시지가 발생하고, 메시지루프에서 단축키가 입력됐는지 확인하고 입력됐다면 단축키에 맞는 메시지 (IDM_ABOUT)가 발생하면서 WndProc에서 메시지에 맞게 처리된다
메뉴 리소스도 확인해보면 같은 ID를 사용하고, 마찬가지로 메뉴에서도 버튼을 눌렀을때 IDM_ABOUT 메세지가 발생한다.
메세지루프 #
이제 프로세스가 무한루프를 돌면서 프로세스의 메세지 큐에서 메세지를 들어온 순서대로 가져와서 처리한다.
GetMessage : 메세지를 가져와서 msg 변수에 세팅. 메세지가 없다면 block 상태가 된다.
- 만약 msg가 WM_QUIT 이라면 false 를 반환하고 while문에서 빠져나가 프로그램이 정상종료된다. WM_QUIT이 발생했다는 것은 DestroyWindow 등 정상 종료를 할 준비가 된것.
TranslateAccelerator : 꺼낸 메시지를 확인해서 단축키가 완성되었다면 등록했던 프로시저(WndProc)를 직접 호출해서 WM_COMMAND와 Accelerator ID 파라미터를 담은 메세지를 보내고 1을 리턴한다.
TranslateMessage : 발생한 메세지가 WM_DOWN 이면서 문자키라면 WM_CHAR를 추가로 만들어 메세지큐에 덧붙이는 역할을 한다. 문자입력이 아니라면 아무일도 없다.
DispatchMessage : 읽은 메세지를 윈도우 프로시져(WndProc)로 전달하는 역할을 한다.
https://blog.naver.com/sheepst3/222858412022
나중에 좀 보면서 다시 옮기기.. 안예쁘게옮ㄱ져니다