본문 바로가기

Application Programming Interface/Windows API

Windows API로 콘솔(터미널) 입/출력하기

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Windows API로 콘솔(터미널) 입/출력하기

본 포스팅에서는 Windows API로 콘솔(터미널)을 띄운 후 이 콘솔로부터 입력 및 출력을 수행하는 과정에 대해 다룹니다. Windows API 콘솔 입출력은 디버그를 위한 문자열 출력 및 GUI에 영향을 주지 않는 각종 테스트 등에 활용될 수 있습니다.

1 단계. 창 띄우기

콘솔 창을 띄우기에 앞서 우선, Windows API로 다음과 같이 빈 창을 띄우겠습니다.

/* winmain.c */
#include <Windows.h>

TCHAR g_szClassName[] = TEXT("ConsoleWindow");
TCHAR g_szWindowName[] = TEXT("Console Window Example");

HINSTANCE g_hInstance = NULL;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    WNDCLASS wndClass;
    MSG msg;
    HWND hWnd;
    
    g_hInstance = hInstance;
    
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndClass.hInstance = hInstance;
    wndClass.lpfnWndProc = WndProc;
    wndClass.lpszClassName = g_szClassName;
    wndClass.lpszMenuName = (LPCTSTR)NULL;
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    
    RegisterClass(&wndClass);
    
    hWnd = CreateWindow(g_szClassName, g_szWindowName,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        (HWND)NULL, (HMENU)NULL, hInstance, (LPVOID)NULL);
    
    ShowWindow(hWnd, nShowCmd);
    
    while (GetMessage(&msg, (HWND)NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = 0L;
    
    switch (uMessage)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    }
    
    return lResult;
}

 

 

2 단계. 콘솔창 띄우기/닫기

콘솔창을 띄우는 Windows API와 그 원형은 다음과 같습니다.

BOOL WINAPI AllocConsole(void);

 

별도의 매개변수를 필요하지 않으며 그냥 호출만 하면 됩니다. 성공하면 TRUE, 실패하면 FALSE를 반환합니다. 함수 호출이 실패할 경우 GetLastError 함수를 호출하여 사유를 확인할 수 있습니다.

여기에서는 창을 생성하면서 동시에 콘솔창을 띄우도록 WM_CREATE 메시지를 처리할 때 이 함수를 호출해보겠습니다.

/* ... 중략 ... */
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = 0L;
    
    switch (uMessage)
    {
    case WM_CREATE:
        AllocConsole();
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    }
    
    return lResult;
}

 

 

콘솔창을 닫는 Windows API와 그 원형은 다음과 같습니다.

BOOL WINAPI FreeConsole(void);

 

역시 별도의 매개변수를 필요하지 않으며 그냥 호출만 하면 됩니다. 성공하면 TRUE, 실패하면 FALSE를 반환합니다. 함수 호출이 실패할 경우 GetLastError 함수를 호출하여 사유를 확인할 수 있습니다.

여기에서는 창을 닫을 때 콘솔창도 같이 닫히도록 WM_CLOSE 메시지를 처리할 때 이 함수를 호출해보겠습니다.

/* ...  중략 ... */
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = 0L;
    
    switch (uMessage)
    {
    case WM_CLOSE:
        FreeConsole();
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    case WM_CREATE:
        AllocConsole();
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    }
    
    return lResult;
}

 

MSDN(https://docs.microsoft.com/en-us/windows/console/allocconsole)에 따르면, 1개 프로세스는 1개 콘솔만을 가질 수 있으며 만일 해당 프로세스가 이미 콘솔창을 띄운 상태에서 재차 AllocConsole를 실행할 경우 이 때는 FALSE를 반환한다고 되어 있습니다. 그럼에도 불구하고 하나의 GUI 프로그램이 여러 콘솔을 거느려야 할 필요가 생길 수 있는데 이와 관련한 소스 코드는 나중에 설명합니다.

 

3 단계. 콘솔창의 제목 가져오기/설정하기

콘솔창의 제목을 가져오거나 설정하는 Windows API와 그 원형은 다음과 같습니다.

BOOL WINAPI SetConsoleTitle(IN LPCTSTR lpConsoleTitle);
DWORD WINAPI GetConsoleTitle(OUT LPTSTR lpConsoleTitle, IN DWORD nSize);

 

SetConsoleTitle 함수는 콘솔창의 제목을 바꾸는 함수입니다. lpConsoleTitle 매개변수는 콘솔창의 새 제목을 받습니다. 성공하면 TRUE를 반환하고 실패하면 FALSE를 반환합니다. 함수 호출에 실패할 경우 GetLastError 함수로 그 사유를 확인할 수 있습니다.

콘솔창을 생성하는 과정에서 그 제목을 바꿔보도록 하겠습니다.

/* ...  중략 ... */
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = 0L;
    
    switch (uMessage)
    {
    case WM_CLOSE:
        FreeConsole();
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    case WM_CREATE:
        AllocConsole();
        SetConsoleTitle(TEXT("테스트용 콘솔"));
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    }
    
    return lResult;
}

 

 

GetConsoleTitle는 콘솔창의 제목을 가져옵니다. lpConsoleTitle 매개변수는 콘솔창의 제목이 복사될 문자열 버퍼이고, nSize 매개변수는 콘솔창의 제목을 복사해올 버퍼의 최대 크기(단위: 문자수)입니다. 복사가 완료되면 복사한 문자 수가 반환됩니다. 복사 실패 시 FALSE가 반환되며 GetLastError 함수로 그 사유를 확인할 수 있습니다.

창을 닫는 시점에서 콘솔창의 제목을 가져오는 함수를 실행해보겠습니다.

/* ... 중략 ...*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = 0L;
    TCHAR szConsoleTitle[256];
    
    switch (uMessage)
    {
    case WM_CLOSE:
        FreeConsole();
        GetConsoleTitle(szConsoleTitle, sizeof(szConsoleTitle) / sizeof(TCHAR));
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    case WM_CREATE:
        AllocConsole();
        SetConsoleTitle(TEXT("테스트용 콘솔"));
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    }
    
    return lResult;
}

 

Windows Vista 부터는 상기 함수를 통해 콘솔창의 제목을 변경해도 본래의 제목을 보관하는 기능이 생겼습니다. 콘솔창이 생성될 당시의 본래 제목을 가져오는 함수는 GetConsoleOriginalTitle입니다.

DWORD WINAPI GetConsoleOriginalTitle(OUT LPTSTR lpConsoleTitle, IN DWORD nSize);

 

4 단계. 코드페이지 설정하기

요즘은 전부 유니코드로 통일되었지만 그렇지 않은 환경을 지원하기 위해 콘솔 입출력의 코드페이지를 가져오거나 설정할 수 있습니다.

입력 스트림과 입력 버퍼에 대한 코드 페이지의 확인 및 변경은 다음과 같습니다. 콘솔로 받은 키보드의 키 값은 코드 페이지에 해당하는 문자로 변환되고 이 문자가 다시 유니코드로 변환되어 GUI 프로그램 측에서 확인 가능합니다.

UINT WINAPI GetConsoleCP(void);
BOOL WINAPI SetConsoleCP(IN UINT wCodePageID);

 

출력 스트림과 출력 버퍼에 대한 코드 페이지의 확인 및 변경은 다음과 같습니다.

UINT WINAPI GetConsoleOutputCP(void);
BOOL WINAPI SetConsoleOutputCP(IN UINT wCodePageID);

 

미국 영어의 코드페이지는 437이고, 한국어의 코드 페이지는 949입니다. 그 외 컴퓨터에 설치된 다른 언어에 대한 코드페이지를 확인하기 위해서는 EnumSystemCodePages 함수를 호출하고, 여기서 반환된 코드 페이지 중 하나를 선택하기 위해서 유효성 검사를 한번 더 시행할 경우 IsValidCodePage 함수를 사용합니다.

 

5 단계. 텍스트를 출력하기

앞서 띄운 콘솔창을 통해 printf 함수를 사용하여 텍스트를 출력해보겠습니다.

/* winmain.c */
#pragma warning(disable: 4996) // 표준이 아닌 MS 자체 함수로서 _s로 끝나는 함수를 쓸 것을 강제하는 것을 비활성화합니다.
#include <Windows.h>
#include <stdio.h> // freopen, printf 등을 사용
#include <locale.h> // locale 적용
#include <tchar.h> // _tfreopen, _tprintf를 사용
 
/* ... 중략 ...*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = 0L;
    TCHAR szConsoleTitle[256];
    
    switch (uMessage)
    {
    case WM_CLOSE:
        FreeConsole();
        GetConsoleTitle(szConsoleTitle, sizeof(szConsoleTitle) / sizeof(TCHAR));
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    case WM_CREATE:
        AllocConsole();
        SetConsoleTitle(TEXT("테스트용 콘솔"));
        /* AllocConsole 함수를 호출하여 콘솔창을 띄웠다면 freopen 함수로 기본 입출력 위치를 지정해야 합니다. */
        _tfreopen(_T("CONOUT$"), _T("w"), stdout);
        _tfreopen(_T("CONIN$"), _T("r"), stdin);
        _tfreopen(_T("CONERR$"), _T("w"), stderr);
        /* setlocale 함수로 기본 입출력에 대한 로케일을 설정합니다. */
        _tsetlocale(LC_ALL, _T(""));
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    /* 이후부터는 보통의 콘솔처럼 printf, puts 등의 함수를 호출 가능합니다. */
    case WM_LBUTTONDOWN:
        _tprintf(_T("WM_LBUTTONDOWN\n"));
        break;
    case WM_LBUTTONUP:
        _tprintf(_T("WM_LBUTTONUP\n"));
        break;
    case WM_MBUTTONDOWN:
        _tprintf(_T("WM_MBUTTONDOWN\n"));
        break;
    case WM_MBUTTONUP:
        _tprintf(_T("WM_MBUTTONUP\n"));
        break;
    case WM_RBUTTONDOWN:
        _tprintf(_T("WM_RBUTTONDOWN\n"));
        break;
    case WM_RBUTTONUP:
        _tprintf(_T("WM_RBUTTONUP\n"));
        break;
    default:
        lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
        break;
    }
    
    return lResult;
}

 

위 소스를 실행하면 다음과 같이 마우스의 클릭에 반응하여 콘솔창에 문자열을 출력하는 것을 확인 가능합니다.