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;
}
위 소스를 실행하면 다음과 같이 마우스의 클릭에 반응하여 콘솔창에 문자열을 출력하는 것을 확인 가능합니다.
'Application Programming Interface > Windows API' 카테고리의 다른 글
| Win32에서 IDispatch 인터페이스를 활용한 객체 사용 방법 (0) | 2018.03.01 |
|---|---|
| Win32 C++에서 Microsoft Excel 파일 다루는 방법 (0) | 2018.02.25 |
| RAW형 데이터의 프린터 출력을 위한 Windows API 호출 과정 (1) | 2017.07.17 |
| Windows NT 4.0 DDK 문서 - IMEAPPS.DOC [Part 2] (0) | 2014.10.02 |
| Windows NT 4.0 DDK 문서 - IMEAPPS.DOC [Part 1] (2) | 2014.10.02 |