f12
를 눌러서 확인하면된다.__int64
(8바이트) 를 INT_PTR
로 재정의한 경우다.INT_PTR
이 __int64
이 자료형에 포인터를 붙여서 재정의 한것이 PINT_PTR
이다. typedef int MYINT
typedef int * PMYINT;
int
를 MYINT
로 재정의 한것이다.MYINT
가 int
니까 거기에 포인터 붙인걸 PMYINT
로 재정의 한것. MYINT* pA = &a;
PMYINT pA = &a;
typedef int MYINT, *PMYINT;
int
를 연달아 지은것. typedef int INT;
typedef int TIME;
TIME time;
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GAMECLIENT));
LoadAccelerators
함수는 단축키를 체크한다accelerator
테이블에 가면 있다.IDC_GAMECLIENT
IDM_ABOUT
에 저장이 되어 있다.accelerator
테이블도 직접적으로 접근하지 못하기 때문에 커널오브젝트인것이다.hAccelTable
에 단축키가 들어올때 핸들값을 저장한다. while (GetMessage(&msg, nullptr, 0, 0))
{
// 1
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
hAccelTable
)로 판단한다. // 1
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// 케이스 코드 많이 생략
// 2
case WM_COMMAND:
{
// 3
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
}
}
단축키가 눌렸다면 1번 함수에서 메세지 처리를 한다.
ALT + ?
를 눌르면 IDM_ABOUT
의 아이디 케이스로 들어가서 다이얼 로그가 생기는 것이다. 단축키가 발생하면 메세제는 WM_COMMAND
가 발생하고 해당 메세지의 부수적인 파라미터가 들어오는데 wParam
에 어떤 단축키인지 조합에 해당하는 아이디값인 IDM_ABOUT
의 값이 들어오고 저 아이디의 값인 케이스인 3번으로 가서 다이얼 로그 창을 만든다.
단축키가 발생하면 처리하는 함수가 있다 정도로 알고 가자.
MSG msg;
메세지 루프
메세지 정보를 받을 구조체 변수다.
typedef struct tagMSG {
// 1
HWND hwnd;
// 2
UINT message;
// 3
WPARAM wParam;
// 3
LPARAM lParam;
// 4
DWORD time;
// 5
POINT pt;
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
while (GetMessage(&msg, nullptr, 0, 0))
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
// 처리
TranslateMessage(&msg);
// 분석
DispatchMessage(&msg);
}
0x0100
== 256(10)WM_KEYDOWN
처럼 키가 눌렀다는 걸 알고 while (GetMessage(&msg, nullptr, 0, 0))
msg
에 구조체의 주소를 알려줘야 발생한 메세지의 정보들을 채워줄것이다.
특징
GetMessage
함수는 메세지 큐에서 정보를 꺼내고 큐를 비워버린다.while
문이 무한 반복으로 돌면서 메세지 큐가 반응하는지 체크한다.// while문 안에 있다.
DispatchMessage(&msg);
msg.hwnd
에 담긴 핸들값에 원도우 정보의 핸들값으로 그 함수의 주소를 호출한다.
DispatchMessage
함수안에서 함수 포인터로 프로시저를 각각의 프로시저를 호출한다.
함수 포인터의 기능을 사용해서 각각의 프로시저 함수에 접근한다.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
우리가 만든 원도우 메세지를 처리하는 프로시저함수다.
&msg
로 프로세스의 종류도 파악가능하니 각각의 메세지 큐에서 이 원도우는 어떤 방식으로 처리하겠다는 함수가 각각 존재해야 한다.함수의 주소를 알려줘서, 특정 상황(조건)이 맞으면 알려준 주소의 맞는 함수가 호출되는 구조
상황이 발생하면 딱 들어온다(콜백)
코드흐름은 내가 짠대로 함수를 호출하면 함수를 그대로 호출했다. but 콜백은 다름
// while문 안에 있다.
DispatchMessage(&msg);
// 프로시저함수
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
// 3
case WM_COMMAND:
// 4
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
// 5
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
About
일떄 작동하는 프로세스의 프로시저 함수// 메인 윈도우(WndProc)를 프로시저 함수다.
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석합니다:
switch (wmId)
{
// 1
case IDM_ABOUT:
// 2
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
// 아랫부분 생략
}
메인윈도우가 돌아가고있을떄 단축키가 발생하면 IDM_ABOUT
값이 1번 케이스의 DialogBox
라는 원도우 생성 함수다.
CreateWindowW
원도우랑 똑같은 거다.
DialogBox
는 정보를 갖출 클래스 정보가 없다.
IDD_ABOUTBOX
)값을 받아간다.이 간단한 원도우도 메세지가 발생하면 처리해야하는 함수인 프로시저가 필요하기 때문에 4번쨰 인자로 About
을 받았다.
프로시저 함수의 3번 케이스에 걸려서 사진 4번의 사진의 확인을 클릭하면 종료된다.
x
버튼은 IDCANCEL
것을 원도우가 기본적으로 정의해놨다.
4번의 조건이 발생해도 IDCANCEL``IDOK
이 발생하더라도 아무일도 안 일어나서 창이 종료되지않는다.
MyRegisterClass
함수 안에서 윈도우 정보를 만들때 호출함 함수의 주소를 등록해둠GetMessage
특징이 리턴조건이 메세지 큐에 메세지가 있어야 반환이 된는걸 활용해서 강제로 메세지 큐에 메세지를 넣어줄것이다. SetTimer(g_hWnd, 0, 50, nullptr );
g_hWnd
핸들값을 넣어준다.// WndProc프로시저 안에 있는 케이스
case WM_TIMER;
{
int a = 0;
}
break;
KillTimer(g_hWnd, 0);
id
값만 알려주었기 떄문에 메모리에서 삭제를 프로그래머가 직접 못해서 윈도우에서는 id
를 부여하니까 그 id에 맞는 삭제하는
함수도 알려준다.GetMessage
에 WM_QUIT
나왔다는거 자체가 종료한다는 것.// WndProc프로시저 안에 있는 케이스
case WM_DESTROY:
PostQuitMessage(0);
break;
PostQuitMessage
의 역할과 순서
PostQuitMessage
함수는 윈도우가 종료되기 전에 호출되며, 프로그램이 종료되기 전에 메시지 큐에 WM_QUIT
메시지를 넣습니다. 프로세스와 윈도우의 관계
PostQuitMessage
와 프로세스 종료
PostQuitMessage
는 "이 윈도우가 닫히면 프로세스를 종료할 것"이라는 의도를 나타내지만, 항상 즉각적인 프로세스 종료를 의미하지는 않습니다. WM_QUIT
메시지를 넣어 메시지 루프가 종료되면 프로세스도 종료될 수 있도록 합니다. 메시지 큐와 WM_QUIT
PostQuitMessage
는 메시지 큐에 WM_QUIT
메시지를 넣고, 이 메시지는 애플리케이션의 메시지 루프에 의해 처리됩니다. WM_QUIT
메시지를 받으면 메시지 루프가 종료되며, 이는 프로세스의 종료로 이어질 수 있지만, 애플리케이션의 전체적인 설계와 동작에 따라 달라집니다.요약
PostQuitMessage
는 윈도우가 파괴될 때 호출되어 WM_QUIT
메시지를 메시지 큐에 넣는 역할을 합니다. 이 메시지는 프로세스가 종료될 수 있도록 하는 신호이지만, 프로세스의 종료 여부는 애플리케이션의 설계에 따라 달라집니다.peek
: 엿보다.
GetMessage`의 문제
GetMessage
는 메시지 큐에 메시지가 있을 때만 반환하고, 큐에서 메시지를 꺼내어 처리합니다.
WM_QUIT
메시지가 큐에 있을 경우 false
를 반환하여 메시지 루프 종료를 알리며, 다른 메시지에 대해서는 true
를 반환하여 루프가 계속 실행되도록 합니다.
PeekMessage
로 문제 해결
메세지를 안꺼내올수도 있다. 확인을 하되 메세지 큐안에서 제거를 안할수 있다.
PeekMessage
를 반복문에 조건으로 사용해버리면 프로그램이 바로 종료되어 버린다.
한순간에라도 메세지 큐에 잠깐이라도 없는 순간이 있다면 프로그램을 꺼버리기 때문이다.
1초에 수십만번을 돌고 앵간한 메세지는 순식간에 처리되고 없는 상태로 존나 빠르게 돈다.
// 1
while (true)
{
// 2
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
// 4
if (msg.message == WM_QUIT)
break;
// 5
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// 3
else
{
// Game Logic
}
1번처럼 PeekMessage
기반으로 하면 while
문 조건을 true로 변경한다.
2번은 PeekMessage
에 메세지가 있다는 의미다.
메세지를 처리하게 해야한다.
1~3번인자는 GetMessage인자랑 같다.
마지막인자는 추가된다.
메세지를 엿보기만하고 메세지큐에서 처리를 안해버리고 같은 메세지를 계속 처리하게 된다.
GetMessage대용으로 쓸때에는 훔쳐보더라도 메세지가 있었으면 큐에서 뺴내야 된다.PM_REMOVE
PM_REMOVE
큐에서 제거하면서 메세지 구조체에 담아간다.
PM_NOREMOVE
는 0이니까 디폴트로 0을 넣어버리면 메세지 큐를 처리하지않는다.
3번은 메세지가 큐에 없을 때에는 게임 코드 실행
4번은 꺼내온 메세지가 WM_QUIT
이면 프로그램 종료
4번이 없다면 WM_QUIT
이 들어와도 처리만하기때문에 WM_QUIT
가 들어오면 반복문을 탈출하는 조건을 추가해줘야 한다.
5번은 꺼내온 메세지 처리한다.
#include "pch.h"
#include "framework.h"
#include "GameClient.h"
#include "Resource.h"
// 전역 변수:
HINSTANCE hInst; // 현재 인스턴스입니다.
HWND g_hWnd; // 메인 윈도우 핸들
wchar_t szTitle[100]; // 제목 표시줄 텍스트입니다.
wchar_t szWindowClass[100]; // 기본 창 클래스 이름입니다.
// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
// SAL : 주석 언어
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
// 윈도우 클래스 등록
MyRegisterClass(hInstance);
// 윈도우 생성
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
// CreateWindow 윈도우 생성
// 반환값은 윈도우 핸들(ID) 값,
// 커널 오브젝트 : OS 가 관리하는 오브젝트, 직접적인 접근이 불가능하고 ID 값을 통해서 제어함
g_hWnd = CreateWindowW(L"Window Class Key", L"My Game", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!g_hWnd)
return FALSE;
// 윈도우를 화면에 보여줄지 말지 설정
ShowWindow(g_hWnd, true);
UpdateWindow(g_hWnd);
// 리소스(단축키 테이블 핸들 얻기)
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GAMECLIENT));
// 메세지 루프
// 메세지 정보를 받을 구조체 변수
MSG msg;
//{
// // 1000 이 1초
// SetTimer(g_hWnd, 0, 50, nullptr);
// // GetMessage
// // 1. 리턴 조건 - 메세지 큐에서 메세지가 있으면 리턴
// // 2. WM_QUIT 이 발생하면 GetMessage 함수는 false 를 반환
// // WM_QUIT 이외 메세지면 GetMessage 함수는 true 를 반환
// while (GetMessage(&msg, nullptr, 0, 0))
// {
// if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
// {
// TranslateMessage(&msg);
// DispatchMessage(&msg);
// }
// }
// KillTimer(g_hWnd, 0);
//}
{
while (true)
{
// Peek 엿보다
// 메세지가 있던 없던, 리턴된다.
// 메세지가 있었으면 true, 메세지가 없었으면 false
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
// 꺼내온 메세지가 WM_QUIT 이면 프로그램 종료
if (msg.message == WM_QUIT)
break;
// 꺼내온 메세지 처리
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
// 메세지가 큐에 없을 때에는 게임 코드 실행
}
}
}
return (int) msg.wParam;
}
//
// 함수: MyRegisterClass()
//
// 용도: 창 클래스를 등록합니다.
//
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_GAMECLIENT));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
//wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_GAMECLIENT);
wcex.lpszClassName = L"Window Class Key";
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
// CallBack
// 함수의 주소를 알려줘서, 특정 상황(조건) 이 맞으면 알려준 함수가 호출되는 구조
// 각 윈도우들은 해당 윈도우에 메세지가 발생했을 때 처리를 해줄 프로시저함수를 등록해야한다.
// 메인 윈도우는 MyRegisterClass 함수 안에서 윈도우 정보를 만들때 호출함 함수의 주소를 등록해둠
// 도움말 윈도우는 다이얼로그 형태로서, DialogBox 함수를 호출할때 사용할
// 프로시저 함수의 주소를 입력으로 넣어줬다.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
ShowWindow(g_hWnd, false);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석합니다:
switch (wmId)
{
case IDM_ABOUT:
// 윈도우 생성 함수
//
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
EndPaint(hWnd, &ps);
}
break;
case WM_TIMER:
{
int a = 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
//EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
1차 24.01.18
2차 24.01.19
3차 24.01.22
4차 24.01.23
5차 24.01.24