Win32 - API
[TOC]
윈도우 프로그래밍 구조
- WinMain
- 응용 프로그램 윈도우 생성
- 윈도우 프로그램의 진입점
- WndProc
- 메시지 처리
인스턴스(HINSTANCE)와 핸들(HWND)
- 운영체제가 부여하는 값으로써 응용 프로그램과 할당된 자원을 구분하는 방법
- 인스턴스와 핸들의 실체는 값이다.
- 인스턴스와 핸들의 값을 저장하기 위해 HINSTANCE, HWND 데이터형을 사용하고 이 데이터형은 결국 void* 형으로서 4바이트(혹은 8바이트)이다.
void* 형의 크기
sizeof(void*); // 4, x86
sizeof(void*); // 8, x64
인스턴스
- 응용 프로그램의 아이디값으로서 여러개 실행되어도 같은 값을 가진다.
- 다른 응용 프로그램과 중복되지 않는다.
- 거의 사용하지 않는다.
핸들
- 운영체제에서 할당한 자원의 아이디값으로서 할당된 자원별로 독립된 값을 가진다.
- 윈도우, 펜, 브러쉬 등
- HWN, HDC, HPEN, HBRUSH 등..
- 인스턴스와 다르게 같은 종류의 프로그램이라도 여러개 실행 시 다른 핸들 값을 갖는다.
- 많이 사용된다.
변수명에 붙이는 접두사
| 데이터형 | 접두사 |
|---|---|
| DWORD | dw |
| 문자열 | sz, s, str |
| 포인터 | p |
| 핸들 | h |
| 전역변수 | g_ |
| 윈도우 메시지 | msg |
| Count of bytes | cb |
- Coding Style Conventions
Window Class 구조체와 Window Procedure
- OS에서 발생한 메시지에 대해 처리를 하는 역할을 한다.
- 실행되는 프로그램은 Window 단위로 작업을 처리하고 Window에서 발생한 메시지를 처리하는 함수가 Procedure이다.
- 동일한 처리를 하는 Procedure는 여러 프로그램이 공유하여 자원을 아낄 필요가 있었고 이를 해결하기 위해 Window Class라는 구조체에 Procedure를 할당하고 이 Window Class를 등록하여 필요한 프로그램이 가져다 쓸 수 있게 만들었다.
- Procedure의 공유를 위해 Window Class 구조체를 만들었지만 Window를 생성하는 초기값으로 반드시 필요한 정보들도 함께 들어가게 되어었다.
WinMain()
역할
- 윈도우 구조체 등록, 생성
- 화면에 출력할 윈도우의 형태를 구조체에 세팅
- 세팅된 구조체를 기반으로 윈도우 생성
- 설정해야할 값(변수)이 많기 때문에 함수의 파라미터로 전달하지 않고 구조체를 만들어 전달
- 윈도우 생성과 출력
- 메시지 루프
- 메시지 체크
- 운영체제가 생성해서 저장한 메시지가 들어있는 메시지 큐에서 자신에게 해당하는 메시지가 있는지를 메시지 루프를 통해 확인하고 가져옴
- 메시지 루프를 돌지 않으면 운영체제는 생성한 메시지를 자동으로 전달하지 않음
- 메시지 체크
WinMain() 작성순서
Window Class 구조체 설정
Window의 프로시저, 모양, 클래스 이름 등이 설정된 Window Class를 생성한다.
// 1. Window Class 구조체 설정
// Window Class 구조체 생성
WNDCLASSEX wcex;
// WNDCLASSEX의 크기를 입력
// cb : count of bytes
wcex.cbSize = sizeof(WNDCLASSEX);
// 윈도우 출력 형태 지정. 가로, 세로 사이즈가 변하면 전체를 다시 그린다.
wcex.style = CS_HREDRAW | CS_VREDRAW;
// 메시지 처리에 사용될 함수
wcex.lpfnWndProc = WndProc;
// 동일한 'Window Class'를 사용하는 Window들이 공유할 수 있는 메모리 크기를 설정
// 40바이트까지 제한.
// SetClassWord, SetClassLong, GetClassWord, GetClassLong 함수를 사용하여 사용
// 보안에 취약하고 크기도 제한적이라서 거의 사용하지 않음.
wcex.cbClsExtra = 0;
// 이 'Window Class'를 사용하는 Window를 위한 개별 메모리를 추가. 동일한 'Window Class'를 사용해도 공유되지 않는다.
// 40바이트까지 제한
// SetWindowLong, GetWindowLong 함수를 사용하여 사용
// 거의 사용하지 않음.
wcex.cbWndExtra = 0;
// 윈도우 인스턴스
wcex.hInstance = hInstance;
// 기본 아이콘 지정, OS에서 미리 정의한 아이콘을 사용할 수 있다.
// IDI : ID for an icon
// 방법 1. 리소스의 이름을 문자열 포인터로 전달
wcex.hIcon = LoadIcon(NULL, "resourceName");
// 방법 2. Resource.h에 define된 리소스 아이디를 MAKEINTRESOURCE를 이용해 문자열 포인터 형식으로 변경
// 리소스 아이디로 변경된 문자열 포인터는 유효하지 않지만 OS는 이를 ID값으로 사용하여 문자열을 찾는다.(?)
wcex.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION)); // IDI_APPLICATION : 운영체제에 미리 정의된 기본 아이콘
// 기본 작은 아이콘 지정
wcex.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
// 기본 커서 지정, OS에서 미리 정의한 커서를 사용할 수 있다.
// IDC : ID for an cursor
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
// 윈도우의 배경 색을 지정
// 방법 1. CreateSolidBrush()로 브러시를 직접 생성하여 할당.
// 프로그램 종료 시 반드시 해제해야한다.
HBRUSH brushBg = CreateSolidBrush(RGB(0, 0, 0));
wcex.hbrBackground = brushBg;
// 방법 2. OS가 미리 설정한 상숫값 사용
// COLOR_WINDOW : 회색
// COLOR_WINDOW + 1 : 흰색
// COLOR_WINDOW + 2 : 어두운 회색
// COLOR_WINDOW + 3 : 검은색
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
// 방법 3. OS가 미리 만들어 놓은 Stock Resource를 가져다 사용
// OS가 사용빈도가 높은 자원을 미리 만들어 놓는데 이 자원을 Stock Resource 또는 Stock Object라 한다.
// 이 자원중 WHITE_BRUSH에 해당하는 자원의 핸들값을 얻어 배경색으로 지정한다.
// 반환값이 HGDIOBJ이므로 형변환하여 사용한다.
wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
// Window Class의 이름을 지정하고 이 이름으로 Window Class를 구분하기 때문에 다른 Window Class와 중복되지 않게 잘 지정한다.
// 대소문자를 구분하지 않는다.
LPCTSTR strClassName = TEXT("Win32");
wcex.lpszClassName = strClassName;
// 메뉴 이름
wcex.lpszMenuName = NULL;
Window Class 구조체 등록
// 2. Window Class 구조체 등록
RegisterClassEx(&wcex);
Window 생성
// 3. Window 생성
HWND hWnd = CreateWindow(
strClassName, // LPCSTR lpClassName : Window Class 이름
TEXT("Hello World"), // LPCSTR lpWindowName : 윈도우 타이틀 이름
WS_OVERLAPPEDWINDOW, // DWORD dwStyle : 윈도우 스타일, dw(DWORD)
CW_USEDEFAULT, 0, // int X, int Y : 윈도우 x, y 좌표, CW(Create Window), CW_USEDEFAULT : 운영체제가 정한 기본값을 사용한다.
CW_USEDEFAULT, 0, // int nWidth, int nHeight : 윈도우 가로, 세로 크기(클라이언트 영역 포함)
nullptr, // HWND hWndParent : 부모 윈도우 핸들
nullptr, // HMENU hMenu : 메뉴 핸들
hInstance, // HINSTANCE hInstance : 응용 프로그램 인스턴스
nullptr // LPVOID lpParam : 생성 윈도우 정보, 여유분으로 사용하지 않는다.
);
윈도우 출력
// 4. 윈도우 출력
// 방법 1. 프로그램 실행 시 넘어온 ShowWindow Commands를 사용한다.
ShowWindow(hWnd, nCmdShow);
// 방법 2. 미리 정의된 ShowWindow Commands를 사용한다.
ShowWindow(hWnd, SW_SHOW);
// WM_PAINT 메시지를 발생시켜 윈도우를 그린다.
UpdateWindow(hWnd);
메시지 루프
- GetMessage()
- 메시지 큐에 있는 메시지를 가져온다.
- WM_QUIT 이 발생하면 FALSE를 리턴하고 그 외에는 TRUE를 리턴한다.
- TranslateMessage()
- 가져온 메시지가 문자키 입력일 경우 TranslateMessage() 함수에서 WM_CHAR를 만들어 메시지 큐에 덧붙인다.
- DispatchMessage()
- 메시지를 WndProc에 전달하기 위해 DispatchMessage를 호출한다.
- DispatchMessage 함수 내부에서 WndProc 함수를 호출한다.
- WndProc() 함수가 종료될 때까지 대기
// GetMessage() : 가져올 메시지가 없으면 대기한다.(반환하지 않는다)
// 게임에서는 대기하지 않고 결과를 바로 리턴하는 PeekMessage()를 사용한다.
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
메시지 처리 과정
- 하드웨어
- (키보드, 마우스 등)이벤트 발생
- 이벤트 감지
- 운영체제
- 메시지 전송
- 메시지 큐에 저장
- 어플리케이션
- WinMain 메시지 큐에서 메시지 확인하고 가져옴
- TranslateMessage를 통해 문자입력을 가공하고 DispatchMessage로 WndProc 함수를 호출
WndProc()
역할
- 메시지가 발생한 경우 어떻게 처리할지를 정의
- switch문으로 각 메시지별 처리를 한다.
- 처리되지 않은 메시지는 DefWndProc()을 호출하여 처리한다.
- false(0)을 리턴하면 메시지를 제대로 처리하지 않았음을 나타낸다.
- 윈도우 생성중(CreateWindow()함수 내부)에 false를 리턴하면 윈도우가 생성되지 않기 때문에 처리하는 메시지가 없어도 DefWindowProc를 통해 0이 아닌 값을 리턴하도록 해야한다. 윈도우 생성 후에는 메시지가 제대로 처리되지 않았음을 나타낼뿐 프로그램이 종료되지는 않는다.
- WM_CREATE 에서 return 0; 해도 윈도우는 생성된다.
- 다른 메시지가 DefWindowProc를 통해 처리되지 않을 때 윈도우가 생성되지 않는다.
- 메시지
- 양의 정수값
메시지 종류
- WM_xxx
- 윈도우 메시지
- WM_PAINT
- 최초 UpdateWindow() 함수에 의해 발생
- 윈도우의 일부 영역을 새로 출력할 때 운영체제에서 발생
- WM_DESTROY
- 윈도우가 화면에 사라진 후에 보내는 메시지
- 메모리에서 제거되기 직전에 보냄
- PostQuitMessage(0)을 호출하여 WM_QUIT 메시지를 발생시킨다.
- WM_QUIT
- CB_xxx
- LM_xxx
- PBM_xxx
wParam, lParam
- 메시지의 부가정보를 담고 있다.
- 16비트 OS에서 wParam은 WORD 즉, 2바이트이고 lParam은 LONG 즉, 4바이트였다.
- 32비트 OS에서는 wParam, lParam 둘 다 4바이트이다.
- 따라서 w와 l은 WORD와 LONG을 나타내는 접두어였지만 이제는 단순한 이름일뿐이다.
리소스(Resource)
- 리소스 편집기를 활용하여 커서, 메뉴, 아이콘, 문자열, 비트맵, 다이얼로그, 엑셀레이터 등을 추가/편집
- 리소스를 윈도우 기본으로 설정 시 인스턴스는 NULL로 설정
- 활용빈도 순서
- 메뉴, 다이얼로그, 비트맵
- 커서, 아이콘
- 엑셀레이터
- 문자열
- 리소스 아이디
- resource.h 헤더 파일에 정의
- 리소스 파일
- 리소스 아이디로 실제 값을 찾을 때 실제 값이 들어있는 파일
- 확장자 : *.rc
- VS 내에서 스크립트 언어로 작성됨
- VS 프로젝트에서 ‘리소스 추가’를 통해 추가 가능
아이콘 리소스
- 크기
- 16, 32, 48 픽셀
- 색상
- 4, 8, 32 비트
- 기본 4비트, 32x32
- 아이콘 아이디 접두사
- IDI_ : ID Icon
- 편집 창에서 청녹색 픽셀은 실제 출력시 투명하게 처리된다.
커서 리소스
- 커서 아이디 접두사
- IDC_ : ID Cursor
- 핫 스폿(hot spot) 설정
- 커서가 윈도우 영역 안에 있는지를 판단하는 좌표, 마우스 포인터의 중심
- 설정하기
- 아이콘 에디터 > 핫 스폿 설정 도구
- 핫 스폿으로 지정할 픽셀을 클릭
- 핫 스폿 확인하기
- 커서 아이콘 우클릭 > 속성 > Hot spot
- 커서 설정
- 마우스 이동 시 발생하는 WM_MOUSEMOVE, WM_SETCURSOR 메시지에서 SetCursor()로 설정
- LoadCursor를 이용해 얻은 커서의 핸들을 SetCursor에 넘김
- 마우스 이동 시 발생하는 WM_MOUSEMOVE, WM_SETCURSOR 메시지에서 SetCursor()로 설정
switch (message)
{
case WM_SETCURSOR:
HCURSOR hCursor;
hCursor = LoadCursor(g_hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
SetCursor(hCursor);
break;
...
}
메뉴 리소스
- 메뉴 아이디 접두사
- IDR_ : ID Resource(?)
- 왜 얘만 ID Resource 일까?
- 윈도우 상단바의 메뉴를 구성
- 각 메뉴별 아이디가 부여되고 이 값을 WM_COMMAND 메시지에서 처리
- wParam의 아래 2바이트가 메뉴의 아이디값
- 이 값으로 어떤 메뉴가 눌렸는지 판단
- wParam의 위 2바이트가 이벤트값
- 이 값으로는 뭐하나?
switch (message)
{
case WM_COMMAND:
{
WORD menuId = LOWORD(wParam);
WORD menuEvent = HIWORD(wParam);
switch(menuId)
{
case 00:
break;
...
}
}
}
문자열 리소스
- 문자열 아이디 접두사
- resource.h 파일에 문자열 아이디와 캡션(내용)을 저장
- LoadString()을 통해 리소스파일에 저장된 문자열을 읽어 파라미터로 넘긴 버퍼에 저장
TCHAR szWindowName[100];
LoadString(hInstance, IDS_TITLE, szWindowName, 100);
윈도우즈의 구성
- Kernel
- 메모리를 관리하고 프로그램을 실행
- USER
- 유저 인터페이스와 윈도우를 관리
- GDI
- 화면 처리와 그래픽을 담당
에러 해결
빌드 시 “_main 외부 기호(참조 위치: “int __cdecl invoke_main(void)” (?invoke_main@@YAHXZ) 함수)에서 확인하지 못했습니다.” 뜨는 경우
- 프로젝트 설정 > 링커 > 시스템 > 하위 시스템 > 설정 안함
- 콘솔 프로그래밍 시 콘솔창이 바로 꺼지지 않게 ‘콘솔’로 설정한 경우 Win32 프로그래밍에서는 문제가 된다.
리소스 뷰에서 “다른 편집기에서 열려 있습니다” 뜨는 경우
- 원인
- 리소스를 편집하면 VS가 자동으로 resource.h 파일에 해당 리소스의 아이디 등을 기록하는데 열려 있거나 다른 파일에서 사용중이면 충돌할 수 있기 때문에 resource.h 파일은 동시에 편집할 수 없다.
- 해결
- resource.h 파일이 열려있으면 닫아주면 된다.
프로그램 실행 시 마우스 커서가 모래시계로 나오는 경우
- 원인
- 모르겠다.
- 해결
- 마우스 이동 시 발생하는 WM_SETCURSOR 메시지에서 SetCursor를 해주는 것으로 일단 해결
- 이렇게 해결하면 클라이언트 영역뿐만 아니라 윈도우 전 영역에 걸쳐 내가 지정한 커서가 나오게 된다.
- 마우스 이동 시 발생하는 WM_SETCURSOR 메시지에서 SetCursor를 해주는 것으로 일단 해결
메뉴 생성 후 IDR_MENU1 매크로를 찾을 수 없다는 VS의 경고. 컴파일, 실행은 잘됨
- 원인
- 모르겠다.
- 해결
- 다음날 하니 잘 됨…
Run-Time Check Failure #2 - Stack around the variable ‘변수 이름’ was corrupted. 에러
- 원인
- 스택에 저장된 변수의 포인터를 이용하여 변수의 크기보다 더 큰 값을 저장한 경우 스택이 깨져서 발생되는 버그
- 해결
- 데이터 형을 정확히 지정해준다.
- 사례
- _stprintf_s 사용 시 버퍼의 길이를 실제 길이 보다 더 큰 값을 준경우
영어 단어
- work out
- 되다. 운동하다. 만들어내다. 해결하다. 이끌어내다.
- caption
- 표제, 자막, 타이틀
- stock
- 재고, 저장, 비축, 주식
- indirect
- 간접적인
참고
- 나우캠퍼스 - Win32 API 윈도우 프로그래밍
- Window Styles
- Window Class Styles
- Coding Style Conventions
- LoadIconA function
- LoadCursorA function
- UCRT alphabetical function reference
- Window Class에 대하여~
- 링크 에러 해결
- 리소스 뷰 “다른 편집기에서 열려 있습니다’ 에러 해결
- 핵심 API로 배우는 윈도우 프로그래밍
- API 입문 강좌
- 비트맵 직접제어(DIB Section)
- API 비트맵 DIB에 대해서
- GetAsyncKeyState 함수