본문 바로가기

Programming/WinAPI

[WinAPI]WinAPI프로젝트 생성

WinAPI(Application Programming interface)는 윈도우에서 사용되는 기능들의 집합을 WinAPI라고합니다

프로젝트 를 생성하는 방법부터 보여드리겠습니다(Visual Studio2019기준)

[C++] -> [Windows] ->[데스크톱] ->[Windows 데스크톱 마법사]

데스크톱 애플리케이션 선택후 확인

// Win32apiFirstProject.cpp : 애플리케이션에 대한 진입점을 정의합니다.
//

#include "framework.h"
#include "Win32apiFirstProject.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_WIN32APIFIRSTPROJECT, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화를 수행합니다:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32APIFIRSTPROJECT));

    MSG msg;

    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    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_WIN32APIFIRSTPROJECT));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WIN32APIFIRSTPROJECT);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   함수: InitInstance(HINSTANCE, int)
//
//   용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
//   주석:
//
//        이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
//        주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  용도: 주 창의 메시지를 처리합니다.
//
//  WM_COMMAND  - 애플리케이션 메뉴를 처리합니다.
//  WM_PAINT    - 주 창을 그립니다.
//  WM_DESTROY  - 종료 메시지를 게시하고 반환합니다.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    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_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;
}

위와같이 윈도우즈 창을 생성하는 기본코드가 제공됩니다

빌드해보면 아래와같이 빌드가 될것입니다

이제 우리는 위의 코드가 어떻게 동작하는지 알아볼것입니다

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)

우리가 다른 프로그래밍에서 main의 역활을 해주는 Windows의 main 함수입니다

Windows프로그램의 진입점 역할을 수행합니다

 

H : Handle을 의미합니다 고유식별 번호를 의미하며 이런 핸들은 운영체제가 부여해주고 중복되어서는 안됩니다

INSTANCE : 메모리에 실제로 올라간 객체를 의미하며,여기에서는 현재 구동 중인 프로그램을 의미합니다

HINSTANCE : 여러개의 프로그램이 실행된다 가정했을때 프로그램마다 운영체제가 식별하기위한 식별번호를 부여하게되는데 이것을 HINSTANCE라고합니다

    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 여기에 코드를 입력합니다.

    // 전역 문자열을 초기화합니다.
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WIN32APIFIRSTPROJECT, szWindowClass, MAX_LOADSTRING);

이부분은 별로 중요하지않은 부분이기에 넘어가겠습니다

 

MyRegisterClass(hInstance);

이부분은 정말 중요한데

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_WIN32APIFIRSTPROJECT));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WIN32APIFIRSTPROJECT);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

해당 부분은 윈도우 클래스를 등록하는 코드로

WNDCLASSEXW구조체를 wcex로 선언하고 해당 인스턴스에 값을 설정한다음 RegisterClassExw() 함수를 통해

클래스를 등록하는 코드입니다

 

각부분을 살펴보면

wcex.style          = CS_HREDRAW | CS_VREDRAW 윈도우의 수평, 수직값이 변화하면 다시그리는 설정입니다
wcex.lpfnWndProc    = WndProc 윈도우의 메세지 기반 처리를 위해 콜벡 함수 포인터를 저장합니다 특정 메시지가 발생하면 저장된 함수(WndProc())를 호출합니다
wcex.cbClsExtra     = 0;
wcex.cbWndExtra     = 0;
뒤에 Extra가 붙는 변수는 운영체제가 알아서 처리해주는부분입니다 신경쓰지않아도됩니다
wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32APIFIRSTPROJECT)); 어떤 아이콘을 사용할지 로드하여 설정합니다
wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW); 마우스 커서의 모양을 설정합니다
wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1); 윈도우의 배경색을 설정합니다
wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WIN32APIFIRSTPROJECT);  메뉴바를 설정합니다 nullptr을 넣으면 없어집니다
wcex.lpszClassName  = szWindowClass; 프로젝트의 이름이 들어갑니다
wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); 상태창의 작은 아이콘을 설정합니다
return RegisterClassExW(&wcex); 이후 생성될 윈도우를 설정된값에 마추어 등록합니다

 

 

    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }
(...)


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

 HWND는 생성된 윈도의 핸들입니다

Initinstance에서 윈도우 창을 만듭니다 이때 CreateWindowW()함수를 사용하게됩니다

이때 들어가는 인자로는

szWindowClass 클래스이름 szTitle 타이틀 이름이 인자로들어가며(유니코드 문자열을 넣어 변경가능)

WS_OVERLAPPEDWINDOW(WS는 Window Style입니다)는 윈도우의 모양을 지정합니다

CW_USERDEFFAULT,0,CWUSERDEFAULT는 x,y좌표와 가로 너비 세로너비를 지정합니다

다음은 부모 윈도우 상속관계를 꾸릴 수 있습니다 없으면 nullptr을 넣어줍니다

메뉴의 핸들 nullptr을 넣으면 MyRegisterClass에서 설정된값을 사용하겠다는의미입니다

 

그아래는 윈도우가 생성되지않았을때 예외처리를 한부분으로 생성되지않는다면 FALSE를 리턴합니다

 

정상적으로 생성된다면 화면에 보여줍니다

 

   	MSG msg;

	while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

Windows운영체제를 대화식 운영체제라고도 합니다

특정키가 입력되었을때 특정 키와같은 하드웨어에서 이벤트를 발생시킵니다 이때 운영체제가 이것을 감지하고

이벤트에 그에맞는 메시지를 발생시키고 이 메시지를 응용프로그램이 읽어서 메시지 처리 함수에서 처리합니다

위 코드의 GetMessage가 메시지를 읽어오는 함수입니다

메시지가 들어왔는지 반복적으로 검사합니다(메시지 큐에서 메시지를 읽어옵니다)

다른 인자들은 메시지를 어디서부터 어디까지 읽을 것인지 범위를 지정하는 인수입니다.

게임을 만들때는 이것을 사용하지않습니다 PeekMessage를 사용할 예정입니다

GetMessage는 읽어올 메시지가 없다면 블로킹 상태에 빠지며 다음메시지가 올때까지 대기합니다 게임에 적합하지않습니다

 

TranslateAccelerator는 윈도우 단축키 모음입니다 사용하지않을 예정입니다 격투 게임같은 경우 실시간 처리를 해줘야하기때문에 성능을 위해커멘드 큐를 구현하는게 좋습니다

typedef struct tagMSG {
    HWND        hwnd;
    UINT        message;	// 받은 메세지를 저장하는 공간 UINT = unsigned int
    WPARAM      wParam;		// 부가적인 정보(키보드 입력등) 저장하는 공간
    LPARAM      lParam;		// 부가적인 정보(키보드 입력등) 저장하는 공간
    DWORD       time;		// 메세지가 발생한 시간
    POINT       pt;		// 마우스의 좌표를 기록
#ifdef _MAC
    DWORD       lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

MSG는 얻은 메세지의 정보를 저장하는 구조체입니다

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    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_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

메세지가 들어오면 아래 함수를 호출하여 switch에서 message를 확인하여 기능을 실행시킵니다

 

함수 호출 규약

callback 함수: 사용하가 아닌 응용 프로그램이나 운영체제가 호출하는 함수입니다

내부로 들어가보면 __stdcall이 정의되어있습니다

 

함수 호출 규약은 함수 종료 시 stack의 반환을 caller(호출자) 혹은 callee(피호출자)중 누가 처리할 것인가에 대한 약속입니다

 

void CallerFunc()		// 호출자
{
	CalleeFunc()		// 피호출자
}

위의 코드를 보면 CallerFunc()함수가 호출자고 CalleeFunc() 함수가 피호출자입니다

 

__cdecl: C와 C++의 대표적인 규약입니다 가변 길이 인자 함수가 대표적입니다

호출자가 Stack을 정리합니다

ex)printf, scanf와 같은 함수

 

__stdcall: API의 대표적인 규약입니다.

피호출자가 stack을 정리합니다. 고정인자 함수가 대표적입니다