Window Desktop GUI 구조

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 구조 #

b41e46a7-9422-4640-a149-a96f16d1f646

윈도우 데스크톱 프로그램은 위와 같이 구성되어 있다.

https://learn.microsoft.com/en-us/windows/win32/winmsg/about-windows

응용프로그램이 프로시저(WndProc)를 통해 실제로 텍스트나 그래픽 출력을 표시하는 부분은 client 영역이며, 나머지는 non-client 영역이다. non-client 영역은 대부분 시스템이 관리하며 CreateWindow 함수를 호출할 때 스타일을 지정할 수 있다. (스크롤바 포함여부, 최소화 최대화 닫기버튼 포함여부 등)

크롬 브라우저의 탭이나 visualstudio의 맘대로 만들어진 작업표시줄은 사실 비 클라이언트 영역을 전부 제거하고 클라이언트 영역에서 구현한 것이다.

CreateWindow 함수의 style에 WS_POPUP을 넣고 사이즈를 명시적으로 지정해보면 알 수 있다.


기본 WinMain 코드 #

윈도우즈 데스크톱 애플리케이션 프로젝트를 만들면 필요한 코드들이 만들어진다.

컴파일 후 실행해보면 아래와 같은 아주 기본적인 윈도우 프로그램이 실행된다.

a571af6a-eb11-422d-8ed3-c956f158cc55

전체적으로 보면 메세지가 들어올때까지 대기하다가 처리해주는 방식으로 진행되는데, 프로그램은 구현하기 나름이기 때문에 무조건 이 방식을 사용하는것은 아니다.

 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로 편하게 볼 수 있게 지원한다.

1d0020c5-4637-4aac-ad15-3908f2ae2894


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));

리소스파일에서 설정할 단축키를 볼 수 있다.

e3ef727c-1c09-400c-8a88-75ede6139b54

키보드가 입력되면 메시지가 발생하고, 메시지루프에서 단축키가 입력됐는지 확인하고 입력됐다면 단축키에 맞는 메시지 (IDM_ABOUT)가 발생하면서 WndProc에서 메시지에 맞게 처리된다

176bde19-e7ec-46b0-a6ad-00532b8e1aa6

메뉴 리소스도 확인해보면 같은 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 powered by Disqus