Возможности консольных приложений для Windows

В настоящее время, когда у разработчиков программного обеспечения появляется всё больше возможностей для сознания приложений со сложным графическим интерфейсом, консольные приложения по-прежнему прочно удерживают свои позиции, даже в такой, казалось бы «чуждой» им среде, как современная операционная система Windows. Это объясняется в первую очередь простотой их исполнения и некоторыми специфическими особенностями консольных приложений, которые делают их в ряде случаев более подходящими для решения задачи, чем приложения с графическим интерфейсом. Один минус: выглядят такие приложения очень уныло и однообразно, как безликая серая масса букв и цифр на чёрном фоне окна консоли. Но не всё так безнадёжно, как может показаться на первый взгляд. В этой статье я попытаюсь дать несколько полезных рецептов расширения функциональности консольных приложений и придания их внешнему виду большей выразительности. И так: начнём с заголовка окна консоли. При запуске программы в заголовке окна отображается полное имя файла, с которого было запущено приложение. Вместо имени файла, в заголовке окна можно указать любой другой текст: название приложения, например. Это делается с помощью функции SetConsoleTitle.

              ::SetConsoleTitle(_T("Пример программы на C++")); 

Далее рассмотрим проблему с выводом на консоль текста кириллицы. Практически каждому программисту когда-нибудь приходилось с этим сталкиваться. По сложившейся традиции, для вывода текста на консоль в большинстве примеров на C++ используются функции стандартной библиотеки, такие как printf или puts, которые работают с текстом в кодировке OEM, что соответствует кодовой странице 866 для русского языка. То же самое происходит при использовании потока вывода cout. Но проблема в том, что большинство текстовых редакторов для Windows работают с текстом в кодировке ANSI, что для русского языка соответствует кодовой странице 1251. Как решить эту проблему? Проще всего использовать функции, которые записывают текст непосредственно в буфер консоли, а не в стандартный поток. Для этого нужно в программный код включить заголовочный файл CONIO.H и, вместо функций printf и puts, вызывать аналогичные функции _cprintf и _cputs. Так они выглядят в коде на Visual C++:

    _cputts(_T("Всем привет от меня!\r\n"));
    _tcprintf(_T("%s\r\n"), _T("Благодарю за внимание."));

Несложно написать и собственную процедуру вывода текста на консоль с использованием системной функции WriteConsole. Вот пример такой процедуры:

BOOL PrintText(LPCTSTR szText)
{
      static HANDLE hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE);
      //
      DWORD dw(0);
      return ::WriteConsole(hConsole, szText, ::lstrlen(szText), &dw, NULL);
}

А вот так она используется:

PrintText(_T(«Всем пока…\r\n»));

Как вы уже наверно заметили, для перевода строки необходимо указывать последовательность из двух символов: “\r\n”, в отличие от стандартного потока, где указывается лишь один символ ‘\n’. Но бывает и так, что нужно вывести текст именно через стандартный поток, чтобы его можно было перенаправить в текстовый файл или в другое консольное приложение. Для этого текст нужно перевести в другую кодировку с помощью функции CharToOem. Или использовать функцию WideCharToMultiByte с параметром CP_OEMCP, если ваш текст в кодировке UNICODE.

Другой способ изменить кодировку символов выводимого в консоль текста - это установка нужной локали. Вот пример того, как это можно сделать:

#include <locale.h>
 
class CLocale
{
public:
	CLocale(LPCTSTR szLocale)
	{
		_tsetlocale(LC_ALL, szLocale);
	}
} _locale(_T(".1251"));

В этом примере конструктор глобального объекта вызывает функцию setlocale.

Размер буфера консоли по умолчанию равен 80х300, т.е. 300 строк по 80 символов каждая. Задать буферу консоли другой размер можно при помощи функции SetConsoleScreenBufferSize.

Атрибутами текста консоли являются: цвет символов и цвет заднего фона. Код атрибута соответствует числовому значению в диапазоне от 0x00 до 0x7F. Так выглядит таблица с числовыми значениями атрибутов текста в шестнадцатеричном коде:

Функция SetConsoleTextAttribute задаёт выводимому тексту указанный атрибут. Для задания всему окну консоли указанных атрибутов применяется функция FillConsoleOutputAttribute. Позиционирование текста (т.е. установка курсора на позицию с заданными координатами) производится с помощью функции SetConsoleCursorPosition.

Полный код моего примера в Visual C++ 2005 выглядит так:

#include "stdafx.h" 
#include <conio.h> 
 
BOOL PrintText(LPCTSTR szText); 
BOOL SetConsoleAttrib(WORD wAttrib); 
BOOL SetCurrentPos(SHORT x, SHORT y); 
BOOL SetConsoleSize(SHORT x, SHORT y);
 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
      ::SetConsoleTitle(_T("Пример программы на C++")); 
      // 
      SetConsoleSize(160, 80); 
      SetConsoleAttrib(0x6F); 
      // 
      _cputts(_T("Всем привет от меня!\r\n")); 
      _tcprintf(_T("%s\r\n"), _T("Благодарю за внимание.")); 
      // 
      SetCurrentPos(25, 10); 
      PrintText(_T("Всем пока...\r\n")); 
      // 
      _gettch(); // ожидание нажатия клавиши... 
      return 0; 
} 
 
static HANDLE _ConsoleOut = ::GetStdHandle(STD_OUTPUT_HANDLE); 
 
BOOL SetConsoleAttrib(WORD wAttrib) 
{ 
      ::SetConsoleTextAttribute(_ConsoleOut, wAttrib); 
      CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; 
      ::GetConsoleScreenBufferInfo(_ConsoleOut, &csbi); 
      DWORD dw(0); 
      COORD cr = {0, 0}; 
      return ::FillConsoleOutputAttribute(_ConsoleOut, 
            wAttrib, csbi.dwSize.X * csbi.dwSize.Y, cr, &dw); 
} 
 
BOOL PrintText(LPCTSTR szText) 
{ 
      DWORD dw(0); 
      return ::WriteConsole(_ConsoleOut, szText, ::lstrlen(szText), &dw, NULL); 
} 
 
BOOL SetCurrentPos(SHORT x, SHORT y) 
{ 
      COORD pos = {x, y}; 
      return ::SetConsoleCursorPosition(_ConsoleOut, pos); 
} 
 
BOOL SetConsoleSize(SHORT x, SHORT y) 
{ 
      COORD size = {x, y}; 
      return ::SetConsoleScreenBufferSize(_ConsoleOut, size); 
} 

Приведённый пример был создан в среде Visual C++ 2005 как проект Win32 Console Application.

В заключение хочу отметить, что возможности консольного интерфейса Windows отнюдь не исчерпываются теми функциями, которые были упомянуты в этой статье. Не было рассказано про получение и обработку сообщений от мыши, об использовании в консольных приложениях возможностей графического интерфейса Windows. Но об этом речь пойдёт в дальнейших публикациях…