다이렉트 X 수업이라고는 했지만 간단하게 C++의 기초를 알아봤다.
그리고 강사님이 WINAPI를 조금 공부하고 넘어간다고 하셨는데
내가 유니티쪽 프로그래머를 하면서 WINAPI를 쓸 일이 있을까 라는 생각이 들긴 했지만..
C++을 사용하기도 하고 배워놓으면 나중에 윈도우 프로그램을 만들때 좀 도움이 되지 않을까 싶어서 정리 해보려고한다.
WINAPI
시작하기
비주얼 스튜디오를 켜서 데스크톱 마법사를 설정하고 데스크톱 어플리케이션을 설정해준다.
어플리케이션 종류는 콘솔이 아닌 데스크톱 어플리케이션으로 설정해주고,
공부를 위해 빈프로젝트로 설정해주었다.
#include<Windows.h>
Window.h를 include 시켜주면 준비는 끝난다.
WINAPI의 main
c,c++,c#에 main함수가 존재하는것처럼 WINAPI도 메인이 존재한다.
이 안에서
생소한것들 투성이다. 하나하나 알아보자
HINSTANCE
OS입장에서는 프로그램이 실행돼서 메모리에 올라온다면 인스턴스 이다.
실행이 되면 OS가 HINSTANCE에 정수값을 할당한다.
OS는 해당 프로그램을 이 정수값으로 프로그램을 판단하고 관리한다.
2번째 매개변수로 똑같이 HINSTANCE를 받는데, 이 2번째 매개변수는 어떤 프로그램안에서 어떤 프로그램이 실행 될 경우에 그 프로그램을 실행시킨 부모의 값이 들어간다.
예를 들어 1이라는 프로그램이 2를 실행시켰을 경우 2의 hPrev라고 적어놓은 저 변수값 안에는 1의 값이 들어간다는 것이다.
+ 추가로 마이크로소프트 도큐먼트에서 올려 놓은 설명도 가져왔다. (22년05월12일 수정인 것 같다.)
- hInstance 는 "인스턴스에 대한 핸들" 또는 "모듈에 대한 핸들"이라고 합니다. 운영 체제는 이 값을 사용하여 메모리에 로드될 때 EXE(실행 파일)를 식별합니다. 인스턴스 핸들은 특정 Windows 함수(예: 아이콘 또는 비트맵 로드)에 필요합니다.
- hPrevInstance 는 아무 의미가 없습니다. 16비트 Windows 사용되었지만 지금은 항상 0입니다.
지금 두번째 인스턴스는 사용 되지 않는 것 같다.
IpCmd, int nCmd
전달해야하는 정보들이있다면 이곳에 임의로 전달을 받는다고 한다.
cmd(명령프롬프트창)에서 명령을 실행할때 그곳에서 적은 옵션의 값들이 이곳에 전달이 된다고 한다.
마소 도큐먼트에서 발췌해왔는데 조금 다르다.
- pCmdLine 은 명령줄 인수를 유니코드 문자열로 포함합니다.
- nCmdShow 는 기본 애플리케이션 창을 최소화할지, 최대화할지 또는 정상적으로 표시할지를 나타내는 플래그입니다.
WNDCLASS
winapi에서는 WNDCLASS라는 구조체로 프로그램을 설정한다.
안의 값들을 하나하나 까보자
cbClsExtra, cbWndExtra
일종의 예약 영역으로 윈도우 내부적으로 사용하며 특수한 목적에 사용되는 여분의 공간입니다.
일반적으로 사용하지 않기에 값은 0으로 지정합니다.
hbrBackground
윈도우 배경 색상을 지정합니다.
색상은 GetStockObject 함수나 COLOR_WINDOW 시스템 색상으로 지정할 수 있습니다.
hIcon, hCursor
윈도우가 사용할 아이콘과 커서를 지정합니다.
LoadIcon, LoadCursor를 이용해 지정할 수 있습니다.
hInstance
윈도우 클래스를 등록하는 프로그램 번호입니다.
일반적으로 WinMain의 hInstance를 대입합니다.
IpfnWndProc
윈도우 메시지 처리 함수를 지정합니다.
WndProc으로 지정합니다.
+ 윈도우는 메세지를 통해 동작 하도록 되어있는데 , 예를들어 우리가 키보드를 누르고 마우스를 클릭하는것과 같은 이벤트들이 일어나면 OS가 모든 프로그램에게 메세지를 날린다.
ex -> OS : "키보드가 입력됐어!" -> 프로그램1 : "키보드가 입력됐네? 이걸 처리 해야지~"
ex -> OS : 키보드가 입력됐어! -> 프로그램2 : " 키보드 입력으로 난 할게 없음 ㅋㅋ"
그래서 IpfnWndProc에 어떤 함수가 해당 메세지들을 받을 것인지 지정을 해줘야 합니다.
IpszClassName
윈도우 클래스의 이름을 문자열로 정의합니다.
여기서 지정한 이름은 CreateWindow 함수에 전달되어 윈도우를 만듭니다.
변수처럼 이름뿐이므로 마음대로 작성할 수 있습니다.
IpszMenuName
이 프로그램이 사용할 메뉴를 지정합니다.
없을 경우 NULL로 지정하며 있을 경우 MAKEINTRESOURCE(메뉴 ID) 함수를 사용할 수 있습니다.
style
윈도우가 어떤 형태를 가질 것인지 지정하는 멤버입니다.
일반적으로 사용되는 것은 CS_HREDRAW, CS_VREDRAW가 있습니다.
CS_HREDRAW: 윈도우의 수평의 크기가 변할 경우 윈도우를 다시 그립니다.
값들이 너무 많다.. 하..
윈도우 창 띄우기
윈도우 창을 띄우기 위해 아래 코드와 같이 기본 값들을 입력 해주었다.
#include<Windows.h>
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR IpCmd, int nCmd)
{
WNDCLASS wc;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
//프로그램의 배경색, HBRUSH형이기 때문에 형 변화를 해줬다.
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.hCursor = nullptr;
wc.hIcon= nullptr;
//위에서 전달받은 hInst값을 넣어준다.
wc.hInstance = hInst;
wc.lpfnWndProc = nullptr;
//프로그램 이름
wc.lpszClassName = L"StudyAPI"; //WCHAR(유니코드)
wc.lpszMenuName = nullptr;
wc.style = NULL;
}
이렇게만 하면 윈도우창이 뜨지 않는다.
몇가지 과정을 더 거쳐야 한다.
#include<Windows.h>
//메세지 프로시저가 이 함수를 호춣함
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM Iparam)
{
switch (uMsg)
{
case WM_CLOSE:
//프로그램 종료됨
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, Iparam);
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR IpCmd, int nCmd)
{
WNDCLASS wc;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
//프로그램의 배경색, HBRUSH형이기 때문에 형 변화를 해줬다.
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.hCursor = nullptr;
wc.hIcon= nullptr;
//위에서 전달받은 hInst값을 넣어준다.
wc.hInstance = hInst;
wc.lpfnWndProc = MessageHandler;
//프로그램 이름
wc.lpszClassName = L"StudyAPI"; //WCHAR(유니코드)
wc.lpszMenuName = nullptr;
wc.style = NULL;
//윈도우 레지스터에 해당 정보를 등록
RegisterClass(&wc);
//윈도우창 생성 함수 // 윈도우생성을 하면 어떠한 값이 반환이 되는데, 그것을 윈도우 핸들에 넣어줘야함.
HWND hWnd = CreateWindow(wc.lpszClassName, L"First", WS_OVERLAPPEDWINDOW, 100, 100, 800, 600, NULL, NULL, hInst, NULL);
//해당 윈도우가 화면에 보이도록
ShowWindow(hWnd, nCmd);
//위의 내용들이 업데이트가 되도록 호출
UpdateWindow(hWnd);
//메세지값
MSG msg;
//종료 값이 들오기 전까지 돌려줌
while(GetMessage(&msg, NULL, 0, 0))
{
//메세지 처리 후
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//메세지의 wParam 파라미터 값
return msg.wParam;
}
1.윈도우 레지스터에 정보를 등록하고
2. 윈도우 핸들에 CreateWindow 함수로 윈도우 창을 생성하여 넘겨준다.
3. ShowWindow로 해당 윈도우가 화면에 보이도록 하고
4. 위의 내용들이 업데이트가 되도록 UpdateWindow 함수를 실행한다.
5. 메세지 값을 받아서 종료 값이 들어오기 전까지 while문이 계속 돌아간다.
6. 와일문이 종료되면 msg의 파라미터값을 리턴한다.
이 과정만 하면 윈도우창은 뜨지 않는다.
코드의 맨 위에있는 MessageHandler가 필요하다.
메세지 핸들러를 만들어서 전달되는 메세지값이 WM_CLOSE면 종료 되도록 했다.
그리고 ipfnWndProc에 해당 함수를 넘겨준다.
그럼 짜잔..
이렇게 윈도우창이 하나가 뜬다..
이 긴 코드에 First라는 이름만 적힌 창 하나가 뜨니 현타가 온다.
보내는 메세지도 한두개가 아니다 그만 알아보자..
한두번 만들어봐선 이렇게 많은 메세지들은 내 머리속에 넣을수가 없을거같다.
그림 그리기
창에 간단히 Hello라는 텍스트를 출력해보기로 한다.
//메세지 프로시저가 이 함수를 호춣함
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM Iparam)
{
//그림그리는 구조체
PAINTSTRUCT ps;
//그림 그리는 핸들러
HDC hdc;
switch (uMsg)
{
case WM_CLOSE:
//프로그램 종료됨
PostQuitMessage(0);
break;
case WM_PAINT:
//비긴페인트,엔드 페인트 사이에서 그림이 그려짐
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 100, 100, L"Hello", wcslen(L"Hello"));
EndPaint(hWnd,&ps);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, Iparam);
}
1. 아까 만들어놓은 메세지 핸들러에서 PAINTSTRUCT와 HDC의 변수를 선언한다.
2. case문에 WM_PAINT를 넣고 hdc에 BeginPaint값을 넣어준다.
3. TextOut 함수로 좌표값과 출력할 글자를 적어주고 wcslen으로 출력할 글자의 크기를 전달한다.
4. EndPaint로 페인트를 끝낸다.
짜잔~
안녕하지 못하지만 Hello가 출력됐다.
글자 입력하기
이제 Hello 대신에 내가 입력하는 글자를 출력해보자.
#include<Windows.h>
//입력값을 저장하는 전역변수 선언, wchar_t와 같다.
TCHAR str[256] = { 0 };
//입력받은 글자의 크기
int len = 0;
//메세지 프로시저가 이 함수를 호춣함
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM Iparam)
{
switch (uMsg)
{
case WM_PAINT:
//비긴페인트,엔드 페인트 사이에서 그림이 그려짐
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc,100, 100,str, len);
EndPaint(hWnd,&ps);
break;
//키보드를 누르면 발생하는 메세지 (wParam으로 들어옴)
case WM_CHAR:
str[len++] = (wchar_t)wParam;
//WM_Paint 메세지가 호출됨
InvalidateRect(hWnd, nullptr, false);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, Iparam);
}
1. 전역 변수로 str을 선언하고 , 입력한 글자의 길이를 받을 len변수도 선언한다.
2. 키보드를 누르면 발생하는 메세지 WM_CHAR를 case문에 넣어준다.
3. wParam으로 전달되는 입력값을 str에 하나씩 넣어준다.(len이 증가되며 차례대로 들어간다.)
4. WM_PAINT는 그냥 호출되는 메세지가 아니라 InvalidateRect로 wm_Paint메세지를 호출해준다.
5. TextOut 함수안에 str과 그 길이인 len을 넣어준다.
아주 잘 입력되는것을 볼 수 있다.
하지만 백스페이스를 누르면 지워지는게 아니라 ' | ' 과 같은 글자가 출력되는데,
백스페이스로 글자를 지우려면
case WM_CHAR:
if (wParam == 8)
{
len--;
}
else {
str[len++] = (wchar_t)wParam;
}
//WM_Paint 메세지가 호출됨
InvalidateRect(hWnd, nullptr, true);
break;
}
1. wParam에 백스페이스 키코드인 8이 들어오면 len을 빼주고, 아니라면 더해주고 wParam값을 넘긴다.
2. 그리고 InvalidateRect의 마지막 매개변수를 false에서 true로 바꿔준다. 바꿔주면 입력될때마다 그림을 새로그려서 잘 지워지게 된다.
마우스 클릭
이번에는 마우스를 클릭했을때 메세지를 띄워보자.
#include<Windows.h>
#include<string>
std::wstring Message = L"";
//메세지 프로시저가 이 함수를 호춣함
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM Iparam)
{
//그림그리는 구조체
PAINTSTRUCT ps;
//그림 그리는 핸들러
HDC hdc;
switch (uMsg)
{
case WM_LBUTTONDOWN:
Message = L"마우스 버튼 다운";
InvalidateRect(hWnd, nullptr, true);
break;
case WM_LBUTTONUP:
Message = L"마우스 버튼 업";
InvalidateRect(hWnd, nullptr, true);
break;
case WM_PAINT:
//비긴페인트,엔드 페인트 사이에서 그림이 그려짐
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc,100, 100,str, len);
TextOut(hdc, 100, 50, Message.c_str(),Message.length());
EndPaint(hWnd,&ps);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, Iparam);
}
1. string을 include 해주고 wstring Message 변수를 선언했다.
2. WM_LBUTTONDOWN과 UP이 메세지로 들어오면 Message 변수에 해당 문자를 넣어준다.
3.Invalidaterect로 wm_Paint를 출력하며 새로 그려준다.
4. TextOut 함수를 하나 더 만들어서 위에 그리는 좌표의 50밑에 그려준다.
잘 뜨는것을 볼 수 있다.
라인 그리기
이번엔 줄을 한번 그어 보도록 하자.
case WM_PAINT:
//비긴페인트,엔드 페인트 사이에서 그림이 그려짐
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc,100, 100,str, len);
TextOut(hdc, 100, 50, Message.c_str(),Message.length());
MoveToEx(hdc, 50, 300,NULL);
LineTo(hdc, 500, 300);
EndPaint(hWnd,&ps);
break;
WM_PAINT만 수정 해주면 된다.
MoveToEx 와 LineTo 는 세트다.
핸들러와 좌표값만 넘기면 끝이다.
선이 그려짐과 동시에 앞에 만들었던 다른 기능들이 다 잘 작동되는것을 볼 수 있다.
WINAPI 첫 수업을 다 정리해봤는데.. 양이 많은게 아니라
구조체나 변수의 이름들 하나하나가 직관적이지 않고 너무 생소해서 헷갈리고 이해가 안되는게 많았다.
나중에 간단하게 뭘 좀 만들어 봐야겠다.
힘겨웠던 Direct X로 가는 과정 3일차 끝 ㅠㅠ
'프로그래밍 공부 > C++ 프로그래밍' 카테고리의 다른 글
Direct X 4일차💻 <WINAPI> #2 (0) | 2022.06.19 |
---|---|
WINAPI 추가정리 (0) | 2022.06.19 |
Direct X 2일차💻 C++의 동적 배열 (0) | 2022.06.15 |
Direct X 수업 2일차💻 스마트 포인터 (0) | 2022.06.15 |
direct X 수업 1일차💻 C++의 클래스 / 그 외 (0) | 2022.06.15 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!