1. Windows API 소개

Windows API는 Microsoft 사의 Windows 운영체제에서 제공하는 자료 형식과 기능을 포함하는 시스템 라이브러리입니다.

Windows는 윈도우즈 운영체제를 의미하며 API는 Applicataion Program Interface의 약어로 응용 프로그램 인터페이스를 뜻합니다. 인터페이스가 특정 기능을 제공함을 약속하는 것이므로 API는 응용 프로그램을 작성할 때 사용하는 기능입니다. 따라서 Windows API는 응용 프로그램을 개발할 때 사용할 수 있게 Windows 운영체제에서 제공하는 라이브러리입니다.

운영체제에서 제공하는 라이브러리에는 기능과 함께 다양한 형식들을 제공하고 있습니다. 특히 운영체제에서 제공하는 기능을 시스템 호출이라고 부릅니다. 일반적으로 응용 프로그램을 작성할 때 운영체제에서 제공하는 기능을 호출하는 것이 정방향이며 이를 시스템 호출이라고 부릅니다.

그런데 윈도우즈 운영체제에서 개발하는 응용 프로그램은 GUI(Graphical User Interface) 형태로 작성할 때가 대부분입니다. GUI 형태의 응용 프로그램에서는 프로세스의 윈도우 창이나 컨트롤들에 마우스 클릭이나 키보드 누름 등의 사건이 발생하는 것에 따라 원하는 동작을 수행할 수 있어야 합니다. 하지만 모든 프로세스가 컴퓨터 시스템에서 발생하는 모든 사건을 모니터링한다면 시스템 성능은 떨어지겠죠.

윈도우즈 운영체제에서는 효과적인 시스템 사용을 위해 마우스 클릭이나 키보드 누름 등의 사건이 발생하는 것은 운영체제가 담당합니다. 대신 특정 사건이 발생하면 이 사건을 처리해야 할 프로세스(보다 정확히 얘기한다면 소유 스레드)에게 발생한 사건과 사건 처리에 필요한 정보를 메시지 형태로 전달합니다. 그리고 응용 프로세스에서는 자신의 메시지 큐에 도착한 메시지를 꺼내와서 프로그램에 작성한 방식으로 처리합니다. 이 때 윈도우 메시지를 꺼내와서 처리하는 함수를 윈도우 콜백 프로시저라고 말합니다. 여기에서 콜백은 응용 프로그램이 시스템의 기능을 호출하는 방향과 역방향으로 호출한다는 의미입니다.

[그림] 윈도우 메시지

1. Windows 커널의 핵심 모듈

윈도우즈 운영체제는 여러 개의 모듈로 구성하여 모듈 간의 상호 작용에 의해 동작하는 마이크로 커널입니다. Unix와 Linux는 단일 커널로 운영체제의 기능을 추가하거나 변경 등을 하려면 새로운 커널로 교체해야 합니다. 하지만 윈도우즈 운영체제는 해당 모듈을 추가하거나 삭제, 변경할 수 있습니다.

윈도우즈 운영체제를 구성하는 여러 모듈에서 가장 핵심적인 모듈은 User, GDI, Kernel입니다. User 모듈에서 제공하는 대표적인 것은 윈도우 개체이며 발급한 핸들은 시스템 전역에서 사용 가능합니다. GDI 모듈에서는 펜이나 브러쉬 등과 같이 그리기에 관한 개체들을 발급하는데 이들은 해당 응용 내에서만 사용 가능합니다. Kernel 모듈에서는 파일이나 프로세스, 쓰레드, IPC, 동기화에 관한 개체들을 발급하는데  권한이 있는 계정에게만 접근 가능한 개체를 발급하고 발급한 핸들은 해당 응용 내에서만 사용 가능합니다. 하지만 다른 응용에서도 권한을 갖고 있다면 별도의 핸들을 발급받아 사용할 수 있습니다.

이 책의 앞 부분에서는 User 모듈과 GDI 모듈에서 제공하는 자료 형식과 기능에 관해 다루고 있습니다. 그리고 뒷 부분에서 Kernel 모듈에 관해 다루고 있어요.

윈도우즈 API가 리눅스와 유닉스의 시스템 호출과 차이가 있는 부분은 User와 GDI 모듈 부분이며 Kernel 모듈은 제공하는 기능을 보면 큰 차이가 없습니다. 물론 자료 형식과 기능 이름까지 비슷한 것은 아닙니다.

이 책에서는 GUI 기반의 응용 프로그램을 작성할 때 필요한 User와 GDI 모듈을 우선적으로 소개할게요. 이를 통해 MFC나 .NET 기반의 Windows Forms, WPF 등의 기술을 사용하여 작성한 프로그램이 어떠한 원리로 동작하는지 파악할 수 있을 것입니다.

Kernel 모듈 부분은 시스템에 관한 이해와 기본적인 시스템 프로그래밍에 필요한 기술을 다루는 것으로 별개의 기술처럼 느껴질 수 있습니다.

[표] Windows 커널의 주요 모듈
[그림] Windows API 개체 종류에 따른 접근 권한

2. 자료 형식

Win32 API에서는 다양한 시스템 형식을 제공하고 있습니다. 여러분이 C언어나 C++언어와 Win32 API를 이용한다면 프로그래밍 언어에서 제공하는 형식과 Win32 API에서 제공하는 형식을 모두 사용할 수 있습니다. 그리고 이들 형식 사이에는 공통적인 부분이 있어 상호 호환성을 제공하는 형식들이 있습니다.

C언어나 C++언어로 Win32 API를 사용할 때 포함해야 할 여러가지 헤더파일이 있는데 개발 편의를 위해 기본적인 Win32 API를 이용할 때 windows.h 파일만 포함해서 사용할 수 있게 여러 헤더 파일을 windows.h 파일에 포함하고 있습니다. 그리고 windows.h 파일에 포함하는 헤더 파일 중에 대부분의 형식은 windef.h 파일에 정의하고 있습니다.

다음은 windef.h 파일에서 일부 형식을 정의하고 있는 구문을 발췌(일부 수정)한 것입니다.

#define CONST               const
typedef unsigned char     BYTE;
typedef BYTE *               LPBYTE;
typedef unsigned short    WORD;
typedef unsigned short    USHORT;
typedef WORD *             LPWORD;
typedef int                    INT;
typedef int *                  LPINT;
typedef unsigned int        UINT;
typedef unsigned long     ULONG;
typedef long *               LPLONG;
typedef unsigned long     DWORD;
typedef DWORD *           LPDWORD;
typedef void *                LPVOID;
typedef CONST void *      LPCVOID;
typedef int                     BOOL;
typedef BOOL  *              LPBOOL;
typedef float                   FLOAT;
typedef UINT_PTR            WPARAM;
typedef LONG_PTR           LPARAM;
typedef LONG_PTR           LRESULT;
 
#define FALSE                 0
#define TRUE                  1

대부분의 형식은 C언어의 형식 이름을 대문자로 표기한 것과 같습니다. 그리고 포인터 형식은 LP가 붙고 상수형에는 C가 붙는 규칙성을 확인할 수 있습니다. BOOL 형식은 C++언어의 bool 형식이 아닌 int 형식이므로 주의하시기 바랍니다.

이 외에도 간단한 여러가지 자료형을 제공하고 있습니다. 이들 중 자주 사용하는 형식은 다음과 같아요.

typedef struct tagRECT
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;
 
typedef struct tagPOINT
{
    LONG  x;
    LONG  y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;
 
typedef struct tagSIZE
{
    LONG        cx;
    LONG        cy;
} SIZE, *PSIZE, *LPSIZE;

그리고 색상을 표현하는 COLORREF 형식을 제공하고 있으며 값을 설정할 때 RGB 매크로를 이용합니다.

특히 Win32 API에서는 응용 프로그램에서 Win32 API 개체를 사용하길 원할 때 실제 개체를 반환하지 않고 해당 개체를 제어하여 사용할 수 있는 핸들을 사용할 수 있게 하고 있습니다. Win32 API에서 제공하는 개체를 사용하는 핸들 형식은 H로 시작하며 종류에 따라 핸들 형식명이 다릅니다.

typedef void *                 HANDLE; (winnt.h 파일에 정의)
#define DECLARE_HANDLE(name) struct name##__{int unused;}; typedef struct name##__ *name
DECLARE_HANDLE            (HWND); 
DECLARE_HANDLE            (HDC);

HANDLE은 파일 개체, 프로세스 개체 등 많은 Win32 API 개체를 제어할 때 사용하는 형식이며 HWND는 윈도우 개체를 제어할 때 HDC는 그리기 작업에 필요한 정보 개체인 DC(Device Context)를 설정하고 그리기 작업을 할 때 사용합니다. 이 외에도 제어할 개체의 종류에 따라 다양한 핸들 형식을 정의하고 있는데 보시는 것처럼 실제 개체 형식이 무엇인지는 알 수 없게 정의하였습니다. 윈도우즈 응용 프로그래밍에서 개발자는 이러한 핸들을 어떻게 사용하는지만 신경쓰지 실제 개체를 위해 어떠한 내부 자료를 사용하는지 알 필요는 없습니다.

3. 첫 번째 윈도우즈 프로그램 만들기

이제 첫 번째 윈도우즈 프로그램을 작성해 봅시다.

개발 도구에서 [Windows 응용 프로그램]을 선택하시고 마법사의 응용 프로그램 설정에서 빈 프로젝트를 체크하여 프로젝트를 생성하세요.

여기에서는 프로그램을 시작하면 메시지 창이 뜨고 확인을 누르면 메시지 창이 닫히면서 응용 프로그램이 끝나는 아주 작은 프로그램입니다. 이 프로그램을 통해 윈도우즈 프로그램의 진입점과 함수 호출 규약 및 포함해야 할 파일 등을 간략하게 살펴볼 거예요.

다음처럼 코드를 작성하세요.

//첫 번째 프로그램 - 메시지 창 띄우기
#include <Windows.h>//윈도우즈 API의 제공 형식과 기능을 사용하기 위해 포함

INT APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPrev, LPSTR cmd, INT nShow)
{
    MessageBox(0,TEXT("첫 번재 윈도우즈 프로그램입니다."), TEXT("메시지 창 띄위기"),MB_OK);
    return 0;
}
[그림] 메시지 창 띄위기 실행화면

이제 코드를 하나 하나 살펴보기로 하죠.

#include <Windows.h>//윈도우즈 API의 제공 형식과 기능을 사용하기 위해 포함

윈도우즈 API를 이용하여 프로그래밍할 때 대부분의 형식이나 기능은 “Windows.h” 파일을 포함하면 사용할 수 있습니다. 특별한 형식이나 기능이 아니라면 다른 헤더 파일을 포함할 필요가 없습니다.

INT APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPrev, LPSTR cmd, INT nShow)

윈도우즈 응용 프로그램의 진입점은 WinMain 입니다. 반환 형식은 INT이며 함수 호출 규약은 APIENTRY를 따릅니다. 함수의 리턴 형식과 함수명 사이에 함수 호출 규약을 표현할 수 있으며 표현하지 않으면 CPP 언어의 디폴트 함수 호출 규약인 __cdecl을 컴파일러가 자동 추가합니다. 윈도우즈 API를 사용하지 않고 특정 프로그래밍 언어로 프로그램을 작성할 때는 언어의 디폴트 호출 규약에 맞게 동작하게 만들기 때문에 개발자는 함수 호출 규약을 표현하지 않아도 컴파일러가 자동으로 작성해 주었습니다.

하지만 윈도우즈 API로 작성할 때 API에서 미리 약속한 형태의 함수를 정의할 때는 약속한 함수 호출 규약에 맞게 작성해야 합니다. APIENTRY는 __stdcall 함수 호출 규약을 표현한 매크로입니다. 앞으로 함수 호출 규약을 표기할 때 CALLBACK 혹은 WINAPI를 표현할 때가 있는데 이들도 __stdcall 함수 호출 규약을 표현한 매크로이며 똑같은 표현입니다.

코드를 분석할 때 가독성있게 작성하기 위해 진입점은 APIENTRY로 표현하고 콜백 프로시저는 CALLBACK, 기타 윈도우즈 API에서 약속한 함수 포인터 형식은 WINAPI로 표현합니다. 참고로 윈도우즈 API에서는 표준 함수 호출 규약으로 PASCAL 방식을 따르고 있습니다.

#define CALLBACK    __stdcall
#define WINAPI      __stdcall
#define WINAPIV     __cdecl
#define APIENTRY    WINAPI
#define APIPRIVATE  __stdcall
#define PASCAL      __stdcall

그렇지만 윈도우즈 API에 인자로 전달하는 목적과 진입점이 아닌 개발자가 작성하는 함수의 함수 호출 규약은 언어의 디폴트 규약을 따라도 문제가 없기 때문에 함수 호출 규약을 표시하지 않아도 문제가 없습니다.

INT APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPrev, LPSTR cmd, INT nShow)

이번에는 WinMain의 입력 매개 변수를 살펴봅시다.

첫 번째 인자 hIns와 두 번째 인자 hPrev는 HINSTANCE 형식으로 실행 모듈 자체에 부여한 핸들입니다. 프로세스(실행하는 프로그램)에서는 윈도우 클래스를 등록하거나 윈도우 인스턴스를 생성할 때 자신이 어떠한 모듈인지 운영체제에 알려주어야 하는데 이 때 첫 번째 인자인 hIns를 사용합니다. 두 번째 인자는 이전 16비트 API와 호환을 위해 남겨둔 부분으로 현재 아무런 의미도 없습니다.

세 번째 인자는 명령라인에서 전달한 문자열을 받는 부분이며 네 번째 인자는 윈도우 창을 띄울 때 전체 화면으로 띄울 것인지 중간에 띄울 것인지 등에 관해 사용자가 제어판에 설정한 값입니다. 되도록 메인 창을 띄울 때는 WinMain에서 전달받은 값으로 시각화할 것을 권하고 있습니다. 이에 관한 사항은 두 번째 프로그램을 작성하면서 설명하기로 할게요.

MessageBox(0,TEXT("첫 번재 윈도우즈 프로그램입니다."), TEXT("메시지 창 띄위기"),MB_OK);

이 부분은 메시지 창을 띄우는 부분입니다.

실제 MessageBox는 다음처럼 뒤에 W와 A가 붙는 두 개의 함수를 제공합니다. 뒤에 W가 붙는 함수는 유니코드 문자 집합을 사용하는 함수이고 A가 붙는 함수는 멀타 바이트 문자 집합을 사용하는 함수입니다. 유니코드 문자 집합은 하나의 문자를 2바이트 형식인 WCHAR를 사용하며 멀티 바이트 문자 집합은 하나의 문자를 1바이트 형식인 CHAR를 사용합니다. MessageBox는 매크로 함수로 프로젝트에 설정한 문자 집합에 맞게 함수로 전개합니다.

int WINAPI MessageBoxW(HWND hWnd, LPCWSTR lpText,LPCWSTR lpCaption, UINT uType);
int WINAPI MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

MessageBox 함수의 첫 번째 인자인 hWnd는 윈도우 핸들 형식이며 메시지 창을 띄울 때 기준으로 삼을 창의 핸들입니다. 만약 유효한 핸들을 전달하면 전달한 윈도우의 중앙에 메시지 창을 띄워줍니다. 0을 주면 디폴트 위치에 창을 띄웁니다.

두 번째 전달 인자는 메시지 창에 출력할 메시지입니다. 그리고 세 번째 전달 인자는 메시지 창 캡션에 출력할 캡션 명입니다.

네 번째 전달 인자는 메시지 창에 자식 버튼을 어떠한 것을 띄울 것인지를 결정하는 매크로 상수입니다. 다음은 MessageBox의 네 번째 인자로 사용할 수 있는 매크로 상수 중 일부입니다.

#define MB_OK                       0x00000000L
#define MB_OKCANCEL                 0x00000001L
#define MB_ABORTRETRYIGNORE         0x00000002L
#define MB_YESNOCANCEL              0x00000003L
#define MB_YESNO                    0x00000004L
#define MB_RETRYCANCEL              0x00000005L

다음은 네 번째 인자에 따라 띄워지는 메시지 창의 형태들입니다.

[그림] 네 번째 인자에 따른 메시지 창 모습

그리고 MessageBox 함수의 반환 값은 어느 버튼을 눌렀는지를 의미합니다. 다음은 메시지 버튼을 눌렀을 때 비교할 때 사용할 매크로 상수입니다.

#define IDOK               1
#define IDCANCEL          2
#define IDABORT           3
#define IDRETRY            4
#define IDIGNORE          5
#define IDYES              6
#define IDNO              7
#define IDCLOSE           8

이렇게 첫 번째 윈도우즈 응용 프로그램을 작성해 보았고 코드를 살펴보았습니다.