Window Desktop GUI 구조
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 되어있다. 자료형별로 다른 구조체이름으로 정해지기 때문에 형변환이 안되며 인자로 전달할때의 실수를 줄일 수 있다.
#define DECLARE_HANDLE(name)
struct name##__{int unused;}; typedef struct name##__ *name
DECLARE_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 코드
윈도우즈 데스크톱 애플리케이션 프로젝트를 만들면 필요한 코드들이 만들어진다.
컴파일 후 실행해보면 아래와 같은 아주 기본적인 윈도우 프로그램이 실행된다.
전체적으로 보면 메세지가 들어올때까지 대기하다가 처리해주는 방식으로 진행되는데, 프로그램은 구현하기 나름이기 때문에 무조건 이 방식을 사용하는것은 아니다.
/* Windows Desktop Application 의 HelloWorld */
#include "framework.h" // 기본적인 windows.h, stdlib.h, malloc.h 등이 포함됨
#include "WindowsProject1.h"
#define MAX_LOADSTRING 100
// 전역 변수:
HINSTANCE hInst; // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING]; // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING]; // 기본 창 클래스 이름입니다.
// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance); // 사용하지 않는다는 의미의 매크로
UNREFERENCED_PARAMETER(lpCmdLine); // 얘도
// TODO: 여기에 코드를 입력합니다.
// 전역 문자열을 초기화합니다.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 애플리케이션 초기화를 수행합니다:
if (!InitInstance (hInstance, nCmdShow))
return FALSE;
HACCEL hAccelTable = LoadAccelerators(hInstance,
MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
MSG msg;
// 기본 메시지 루프입니다:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
매개변수
- 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개일수도 있지만 여러개를 가질수도 있다. 윈도우마다 이 설정을 따로 해줘야한다.
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_WINDOWSPROJECT1));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
wcex.lpszClassName = szWindowClass; // 클래스의 key값 create할때 참조해서 생성함
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
InitInstance : 몇가지 초기화도 하고, 위에서 등록한 정보를 바탕으로 윈도우를 생성하는 함수
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
// 윈도우창 생성. 등록했던 클래스 key값 사용(문자열) 스타일 지정
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
// 좌표 x, y 창의 너비, 높이
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nCmdShow); // 창 상태에 맞게 그리기.
UpdateWindow(hWnd); // WM_PAINT를 즉시 보내 창을 업데이트한다.
return TRUE;
}
ShowWindow에서도 WM_PAINT 메시지를 보내긴 하지만, 메시지큐가 비어있을때 보내게된다. 예전엔 컴퓨터가 느려서 화면을 띄우는게 늦었기 때문에 UpdateWindow를 바로 실행시켜 화면 업데이트를 빠르게 했다.
LoadAccelerators : 단축키 테이블을 세팅하는 함수. 이후 메시지루프에서 단축키 입력 여부를 확인한다.
HACCEL hAccelTable = LoadAccelerators(hInstance,
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
나중에 좀 보면서 다시 옮기기..
안예쁘게옮ㄱ져니다
Comments