Сборник по задачам и примерам Assembler



 

Работа с консолью в среде Windows

Если ничто другое не помогает,
прочтите, наконец, инструкцию.
Прикладная Мерфология

В этом разделе будут рассмотрены средства для работы с консолью в среде операционной системы Windows. Как известно, Windows поддерживает работу двух типов приложений — оконных, в полной мере использующих все достоинства графического интерфейса, и консольных, работающих исключительно в текстовом режиме. Поэтому не следует путать понятие «консоли», используемое выше, с понятием «консольного приложения» Windows. В предшествующем материале под «консолью» подразумевались средства для ввода информации с клавиатуры и вывода ее на экран. Для однозначности изложения далее под термином «консоль» мы будем иметь в виду либо само консольное приложение, либо его видимую часть — окно консольного приложения.
Далее мы рассмотрим порядок ввода-вывода данных в консольное приложение для Windows, написанное на ассемблере. Организация ввода-вывода в оконном приложении Windows здесь рассматриваться не будет, так как в уроках 18 «Создание Windows-приложений на ассемблере» и 19 «Архитектура и программирование сопроцессора» учебника этот вопрос был рассмотрен очень подробно и полно. Что-либо существенное добавить к уже сказанному трудно. Данная книга рассматривается как практическое продолжение учебника, поэтому повторяться просто не имеет смысла. Что же касается организации работы с консольным приложением, то этот вопрос в учебнике был рассмотрен слабо — в контексте одной из задач урока 20 «ММХ-технология микропроцессоров Intel». Поэтому есть смысл рассмотреть его более систематично, попутно осветив проблемы ввода-вывода в консольном приложении Windows. Это тем более актуально, что при программировании на ассемблере необходимость написания консольного приложения возникает более часто, чем оконного. Причина проста — малыми затратами нам становятся доступны многие возможности API Win32.

Организация ввода-вывода в консольном приложении Windows

Язык ассемблера — язык системных программистов, исследователей принципов работы операционных систем, программ и аппаратных средств. Здесь не нужны красивые графические оболочки, а, наоборот, велика потребность в удобных средствах для работы с текстовой информацией. Операционная система Windows обеспечивает встроенную поддержку консолей, которые, по определению, являются интерфейсами ввода-вывода для приложений, работающих в текстовом режиме.
Консоль состоит из одного входного и нескольких экранных буферов. Входной буфер представляет собой очередь, каждая запись которой содержит информацию относительно отдельного входного события консоли. Экранный буфер — h двумерный массив, содержащий символы, выводимые в окно консоли, и данные [ об их цвете.
Очередь входного буфера содержит информацию о следующих событиях:

  • нажатии и отпускании клавиш;
  • манипуляциях мышью — движение, нажатие-отпускание кнопок;
  • изменение размера активного экранного буфера, состояние прокрутки.

Для поддержки работы консольных приложений API Win32 содержит более сорока функций, предназначенных для интеграции в среду Windows программ, работающих в текстовом режиме. Данные функции предоставляют два уровня доступа к консоли — высокий и низкий. Консольные функции ввода высокого уровня позволяют приложению извлечь данные, полученные при вводе с клавиатуры и сохраненные во входном буфере консоли. Консольные функции вывода высокого уровня позволяют приложению записать данные в устройства стандартного вывода или ошибки с тем, чтобы отобразить этот текст в экранном буфере консоли. Функции высокого уровня также поддерживают переназначение стандартных дескрипторов ввода-вывода и управление режимами работы консоли. Консольные функции низкого уровня позволяют приложениям получить детальную информацию о вводе с клавиатуры, событиях нажатия-отпускания кнопок мыши и о манипуляциях пользователя с окном консоли, а также осуществить больший контроль над выводом данных на экран.
Таким образом, API Win32 предоставляет два разных подхода для обеспечения ввода-вывода с консолью, выбор нужного зависит от гибкости и полноты контроля, которыми хочет обладать приложение для обеспечения своей работы с консолью. Функции высокого уровня обеспечивают простоту процесса ввода-вывода путем использования стандартных дескрипторов ввода-вывода, но при этом невозможен доступ к входному и экранным буферам консоли. Функции низкого уровня требуют учета большего количества деталей и объема кода, но это компенсируется большей гибкостью.
Высокоуровневый и низкоуровневый консольный ввод-вывод не являются взаимоисключающими, и приложение может использовать любую комбинацию этих функций.
С каждой консолью связаны две кодовые таблицы — по одной для ввода и вывода. Консоль использует входную кодовую таблицу для трансляции ввода с клавиатуры в соответствующее символьное значение. Аналогичным образом используется кодовая таблица вывода — для трансляции символьных значений, формируемых различными функциями вывода, в символ, отображаемый в окне консоли. Для работы с кодовыми таблицами приложение может использовать пары — функции SetConsoleCP и GetConsoleCP для входных кодовых таблиц и функции SetConsoleOutputCP и GetConsoieOutputCP для выходных кодовых таблиц. Идентификаторы кодовых таблиц, доступные на данном компьютере, сохраняются в системном реестре следующим ключом: <
HKEY_LOCAL_MACHINE\\SYSTEM\\
CurrentControlSet\\Control\\Nls\\CodePage

Минимальная программа консольного приложения

Минимальная программа консольного приложения на ассемблере выглядит так:

.model flat.STDCALL ;модель памяти flat.
i ncludeWindowConA.i nc
:Обьявление внешними используемых в данной программе функций Win32 (ASCII):
extrn AllocConsole:PROC
extrn SetConsoleTitleA:PROC
extrn ExitProcess:PROC
.data
TitleText db 'Минимальное консольное приложение Win32'.0
.code
start proc near :точка входа в программу:
:запрос консоли
call AllocConsole проверить успех запроса консоли
test eax.eax
jz exit :неудача :выведем заголовок окна консоли SetConsoleTitle:
push offset TitleText
call SetConsoleTitleA проверить успех вывода заголовка
test eax.eax
jz exit ;неудача
exit: :выход из приложения
:готовим вызов VOID ExitProcess(UINT uExitCode)
push 0
call ExitProcess start endp end start

Если убрать комментарии, то кода будет совсем немного. В нем представлены вызовы трех функций: AllocConsole, SetConsoleTitle, ExitProcess.
Первой функцией консольного приложения должна быть функция запроса консоли AllocConsole.

B00L AllocConsole(VOID);

Для вызова функции Al I ocConsol e не требуется никаких параметров. В случае успеха функция Al I ocConsol e возвращает ненулевое значение, при неудаче — нуль. Выделенная консоль представляет собой типичное для Windows окно. Процесс в конкретный момент времени может использовать одну консоль. Если ему нужно запустить еще одну консоль, то прежняя должна быть закрыта или освобождена с помощью функции FreeConsole.

B00L FreeConsole(VOID);

В случае успеха функция FreeConsol e возвращает ненулевое значение, при неудаче — нуль.
При завершении процесса выделенная процессу консоль освобождается автоматически. В нашем случае использован именно этот вариант закрытия консоли — функцией ExitProcess.

VOID ExitProcesstUINT uExitCode):

Функции ExitProcess передается код завершения процесса и всех завершаемых цепочек в этом процессе. Проанализировать этот код можно с помощью функ-
ций GetExitCodeProcess и GetExitCodeThread. В общем случае в различных ветвях кода может быть несколько точек выхода с вызовом ExitProcess. Задавая различные значения кода завершения, можно таким образом идентифицировать причину завершения процесса.
Окно консоли может иметь заголовок, для отображения которого предназначена функция SetConsoleTitle.

B00L SetConsolеTitle(LPCTSTR lpConsoleTitle) ;

Функция SetConsoleTitle имеет один параметр — указатель на строку с заголовком консоли, заканчивающуюся нулем.
Организация высокоуровневого консольного ввода-вывода
Для высокоуровневого ввода-вывода приложение может использовать файловые функции ReadFile и WriteFile, а также функции консольного ввода-вывода Read-Console и WriteConsole. Эти функции обеспечивают косвенный доступ к входному и экранным буферам пульта. Физически эти функции фильтруют записи входного буфера консоли так, чтобы возвратить ввод как поток символов, игнорируя все другие записи с расширенной информацией о мыши, клавиатуре и изменении размеров окна консоли. Отфильтрованный поток символов отображается в окне консоли начиная с текущей позиции курсора. Существуют два важных отличия в использовании пар функций ReadFile\WriteFile и ReadConsoleNWrite-Console.

  • Поддержка символов Unicode и ANSI. Консольные функции (ReadConso-le\WriteConsole) поддерживают эти наборы, а файловые (ReadFile\Write-File) — нет.
  • Функции файлового ввода-вывода могут использоваться как для обращения к файлам, так и к именованным каналам, устройствам, присоединенным к последовательному интерфейсу. Консольные функции ввода-вывода можно использовать только с тремя дескрипторами стандартного ввода-вывода (см. ниже описание функций ReadConsole\WriteConsole).

Из вышесказанного следует, что функции высокоуровневого ввода-вывода обеспечивают простой способ обмена (чтения-записи) потоков символов с консолью.

Операция чтения высокого уровня реализуется функцией ReadConsole, которая получает входные символы из буфера ввода консоли и сохраняет их в указанном буфере.

B00L ReadConsoleCHANDLE hConsolelnput. LPVOID ipBuffer. DWORD nNumberOfCharsToRead. LPDWORD lpNumberOfCharsRead. LPVOID lpReserved);

Параметры этой функции означают следующее:

  • hConsolelnput — дескриптор входного потока консоли;
  • IpBuffer — указатель на строку, в которую будет записана вводимая строка символов;
  • nNumberOfCharsToRead — размер буфера, указанного lpBuffer;
  • ipNumberOfCharsRead — количество действительно введенных символов;
  • lpReserved — этот параметр не используется, поэтому должен задаваться
    как NULL.

Операция записи высокого уровня реализуется функцией WriteConsole, которая извлекает символы из указанного буфера и записывает их в экранный буфер, начиная с текущей позиции курсора и продвигая ее по мере записи символов. B00L WriteConsoleCHANDLE hConsoleOutput. CONST VOID *lpBuffer.
DWORD nNumberOfCharsToWrite. LPDWORD
ipNumberOfCharsWritten. LPVOID lpReserved);
Параметры этой функции означают следующее:

  • hConsoleOutput — дескриптор выходного потока консоли;
  • lpBuffer — указатель на выводимую строку;
  • nNumberOfCharsToWrite — размер буфера, указанного IpBuffer;
  • IpNumberOfCharsWritten — количество действительно выведенных символов;
  • lpReserved — этот параметр не используется, поэтому должен задаваться
    как NULL.

Для своей работы эти и некоторые другие консольные функции требуют получения стандартных дескрипторов ввода-вывода. Значения этих дескрипторов присваиваются параметрам hConsolelnput и hConsoleOutput. По умолчанию стандартный дескриптор ввода связан с клавиатурой, стандартный дескриптор вывода—с экраном. Получить стандартный дескриптор ввода-вывода можно с помощью функции GetStdHandle.

HANDLE GetStdHand 1 e(DWORD nStdHandle):

На вход функции GetStdHandle должно быть подано одно из следующих значений:

  • STD_INPUT_HANDLE = -10 — дескриптор стандартного входного потока;
  • STD_OUTPUT_HANDLE = -11 — дескриптор стандартного выходного потока;
  • STD_ERROR_HANDLE - -12 — дескриптор стандартного потока ошибок.

Используя функции высокоуровневого ввода-вывода, приложение может управлять цветом текста и фона, с которыми должны отображаться символы, записываемые в экранный буфер. Приложение может изменять следующие свойства высокоуровневого консольного ввода-вывода:

  • эхо-контроль вводимых символов на экране из активного экранного буфера;
  • ввод строки, окончание операции чтения которой происходит при нажатии клавиши Enter;
  • автоматическая обработка некоторых символов, вводимых с клавиатуры:
    перевода каретки, нажатия клавиш Ctrl+C и т. д.;
  • автоматическая обработка некоторых символов, выводимых на экран: перевода строки и каретки, возврата на один символ и т. д.

Функция SetConsol eCursorPosition предназначена для указания позиции, с которой начинается выполнение операций чтения-записи в окно консоли. B00L SetConsoleCursorPosition(HANDLE hConsoleOutput. COORD dwCursorPosition); Параметрами этой функции являются стандартный дескриптор вывода hCon-[' soleOutput, полученный функцией GetStdHandle, и указатель на структуру COORD с координатами новой позиции курсора:

COORD struc x dw 0 у dw 0 ends

По умолчанию цветовое оформление окна консоли достаточно унылое — черный фон, белый текст. Внести разнообразие во внешний вид окна консоли поможет функция SetConsoleTextAttribute, с помощью которой можно изменить установки цвета по умолчанию для текста и фона.
B00L SetConsoleTextAttributetHANDLE hConsoleOutput. WORD wAttributes):
Первый параметр — без комментариев, второй определяет цвет текста и фона. Второй параметр формируется как логическое ИЛИ следующих значений:

  • FOREGROUND_BLUE=0001h - синий текст;
  • FOREGROUND_GREEN=0002h - зеленый текст;
  • FOREGROUND_RED=0004h — красный текст;
  • FOREGROUND_INTENSITY=0008h — текст повышенной яркости;
  • BACKGROUND_BLUE=0010h - голубой фон;
  • BACKGROUND_GREEN=0020h - зеленый фон;
  • BACKGROUND_RED=0040h - красный фон;
  • BACKGROUND_INTENSITY=0080h — фон повышенной яркости.

Для задания белого цвета складываются три компоненты, для задания черного — компоненты не задаются вовсе.

Пример программы ввода-вывода в консоль

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

:prg05_11.asm - программа ввода-вывода в консоль с изменением атрибутов выводимого текста
!
.data
.code
start proc near -.точка входа в программу:
..........
:получим стандартные дескрипторы ввода-вывода
push STD_OUTPUT_HANDLE
call GetStdHandle
movdOut.eax :dOut-fleCKpnnTop вывода консоли
push STD_INPUT_HANDLE
call GetStdHandle
mov din.eax idln-дескриптор ввода консоли :введем строку
.¦установим курсор в позицию (2,6)
mov con.хх.2
mov con.yy,6
push con
push dOut
call SetConsoleCursorPosition cmp eax. 0
jz exit ;если неуспех
push 0
push offset NumWri количество действительно введенных символов
push 80 :размер буфера TitleText для ввода
push offset TitleText
push din
call ReadConsoleA
cmp eax, 0
jz exit :если неуспех
:выведем введенную строку в заголовок окна консоли:
push offset TitleText
call SetConsoleTitleA проверить успех вывода заголовка
test eax.eax
jz exit ;неудача
:выведем строку в окно консоли с различных позиций и
с разными цветами установим курсор в позицию (2.5)
mov ecx.10 ;строку выведем 10 раз
mov bl.10000001b начальные атрибуты
ml: push ecx
inc con.xx
inc con.yy
push con
push dOut
call SetConsoleCursorPosition
cmp eax.O
jz exit :если неуспех ;определим атрибуты выводимых символов -
будем получать их циклически сдвигом - регистр
BL
хог еах.еах
rol Ы.1
mov al.bl
push eax
push dOut
call SetConsoleTextAttribute
cmp eax.O
jz exit ;если неуспех :вывести строку
push 0
push offset NumWri действительное количество выведенных на экран
push NumWri ;длина строки для вывода на экран
push offset TitleText :адрес строки для вывода на экран
push dOut
call WriteConsoleA
cmp eax.O
jz exit :если неуспех pop ecx
loop ml
exit: :выход из приложения

Каждый консольный процесс имеет свой собственный список функций-обработчиков, которые вызываются системой, когда происходят определенные собы тия, например при активном окне консоли пользователь нажимает комбинации клавиш Ctrl+C, Ctrl+Break или Ctrl+Close. При запуске консольного приложения список функций-обработчиков содержит только заданную по умолчанию функцию-обработчик, которая вызывает функцию ExitProcess. Консольный процесс может добавлять или удалять дополнительные функции-обработчики, вызывая функцию SetConsoleCtrlHandler.
B00L SetConsoleCtrlHandler(PHANDLER_ROUTINE HandlerRoutine. B00L Add): Данная функция имеет два параметра:

  • HandlerRoutine — указатель на определенную приложением функцию HandlerRoutine, которая должна быть добавлена или удалена;
  • Add — логическое значение, которое означает: 1 — функция должна быть
    добавлена, 0 — функцию необходимо удалить.

Функция HandlerRoutine — это определенная приложением функция обратного вызова. Консольный процесс использует эту функцию, чтобы обработать нажатия клавиш управления. На самом деле HandlerRoutine — идентификатор-заполнитель для определенного приложением имени функции. B00L WINAPI HandIerRoutine(DWORD dwCtrlType):
Параметр DwCtrlType определяет тип сигнала управления, получаемого обработчиком. Этот параметр может принимать одно из следующих значений:

  • CTRL_C_EVENT=O — сигнал, имитирующий нажатие клавиш Ctrl+C, может быть получен из двух источников: с клавиатуры или как сигнал, сгенерированный функцией GenerateConsoleCtrl Event;
  • CTRL_BREAK_EVENT=1 — сигнал имитирующий нажатие клавиш Ctrl+Break, может быть получен из двух источников: с клавиатуры или как сигнал, сгенерированный функцией GenerateConsoleCtrl Event;
  • CTRL_CL0SE_EVENT=2 — сигнал, который система посылает всем процессам, подключенным к данному консольному приложению, когда пользователь его закрывает (либо выбирая пункт Close в системном меню окна консоли, либо щелкая на кнопке завершения задачи в диалоговом окне Менеджера задач);
  • CTRL_LOGOFF_EVENT=5 — сигнал, который посылается всем консольным процессам, когда пользователь завершает работу в системе (этот сигнал не указывает, какой именно пользователь завершает работу);
  • CTRL_SHUTD0WN_EVENT=6 — сигнал, который система посылает всем консольным процессам при подготовке к выключению машины. Функция HandlerRoutine должна возвратить логическое значение: 1 — если она обрабатывает конкретный сигнал управления; 0 — для обработки полученного события будет использоваться другая функция-обработчик HandlerRoutine из списка функций-обработчиков для этого процесса (то есть включенная в этот список раньше данной функции).

Как уже было упомянуто, каждый консольный процесс может определить несколько функций HandlerRoutine, которые связываются в цепочку. Первоначально этот список содержит только заданную по умолчанию функцию обработчика, которая вызывает функцию ExitProcess и, как результат, приводит к завершению текущего консольного приложения. Консольный процесс добавляет или удаляет
Работа с консолью в среде Windows 233
дополнительные функции обработчика, вызывая функцию SetConsoleCtrl Handler, которая не затрагивает список функций-обработчиков для других процессов. Когда консольный процесс принимает любой из сигналов управления (см. выше), то вызывается последняя зарегистрированная функция-обработчик, если она не возвращает 1, то управление передается следующему (предыдущему) зарегистрированному обработчику и т. д., до тех пор пока один из обработчиков не возвратит 1. Если ни один из обработчиков этого не сделал, то вызывается обработчик, заданный по умолчанию.
Установка обработчиков для сигналов CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT и
CTRL_SHUTDOWN_EVENT дает процессу возможность выполнить специфичные для него
действия по корректному завершению приложения. Пользовательская функция
HandlerRoutine может быть вызвана для того, чтобы выполнить следующие действия:

  • вызвать функцию ExitProcess для завершения процесса;
  • возвратить 0 (ложь) — это означает, что завершение приложения должен
    выполнить обработчик, заданный по умолчанию;

В возвратить 1 — в этом случае никакие другие функции-обработчики не вызываются, а система отображает всплывающее диалоговое окно с запросом о необходимости завершения процесса; система также отображает диалоговое окно, если процесс не отвечает определенное время (5 секунд для CTRLCLOSEEVENT и 20 секунд для CTRLLOGOFFEVENT и CTRLSHUTDOWNEVENT); процесс может использовать функцию SetProcessShutdownParameters, чтобы запретить системе отображать последнее диалоговое окно, в этом случае система просто заканчивает процесс, когда HandlerRoutine возвращает истину или когда истекает определенный период времени. Ниже приведен пример пользовательского обработчика события — ввода комбинации Ctrl+C или Ctrl+Break. За основу взята предыдущая программа.

 

:prg05_12.asm - программа, демонстрирующая использование пользовательского обработчика события.
.data
Text_CTRL_C db "Нажаты CTRL+C"
Len_Text_CTRL=$-Text_CTRL_C
TextJREAK db "Нажаты CTRL+BREAK"
Len_BREAK=$-Text_BREAK
.code
CtrlHandler proc
arg @@dwCtrlType:DWORD
uses ebx.edi. esi ;эти регистры обязательно должны сохраняться
:анализируем тип сигнала управления
cmp @@dwCtrlType.CTRL_C_EVENT
je h_CTRL_C_EVENT
cmp (a@dwCtrlType.CTRL_BREAK_EVENT
je h_CTRL_BREAK_EVENT
jmp h_default
h_CTRL_C_EVENT: :при нажатии CTRL+C выводим сообщение: установим курсор

call SetConsoleCursorPosition :вывести строку Text_CTRL_C call WriteConsoleA
; возвращаем признак обработки
mov eax.l
jmp exit_CtrlHandler h_CTRL_BREAK_EVENT:
;при нажатии CTRL+BREAK выводим сообщение:
установим курсор
call SetConsoleCursorPosition : вывести строку
call WriteConsoleA
;возвращаем признак обработки
mov eax.l
jmp exit_CtrlHandler
h_default: mov eax.Offffffffh;возвращаем остальное не обрабатываем
exit_CtrlHandler: ret CtrlHandler endp start proc near ;точка входа в программу:
:работаем .........
:получим стандартные дескрипторы ввода-вывода
установим функцию-обработчик сигналов управления
push TRUE
push offset cs: CtrlHandler
call SetConsoleCtrlHandler
onp eax. 0
jz exit :если неуспех ;введем строку в буфер TitleText установим курсор в позицию (2.6)
call SetConsoleCursorPosition call ReadConsoleA
:выведем введенную строку в заголовок окна консоли: push offset TitleText call SetConsoleTitleA
:выведем строку в окно консоли с различных позиций и с разными цветами
mov ecx.10 :строку выведем 10 раз
mov bl.10000001b начальные атрибуты ml: push ecx установим курсор в позицию
call SetConsoleCursorPosition
определим атрибуты выводимых символов - будем получать их циклически сдвигом регистра BL хог еах.еах
rol Ы .1
mov al ,Ы
push eax
push d0ut
call SetConsoleTextAttribute . :вывести строку TitleText
call WriteConsoleA cmp eax.0
jz exit ;если неуспех pop ecx
loop ml

Относительно этой программы можно сделать два замечания. Первое касается функции Handl erRoutine, которая в нашей программе называется Ctrl Handler. Как упоминалось, эта функция является функцией обратного вызова. Ее вызов производится при возникновении определенных событий неявно — из системы Windows. По структуре и алгоритму работы она аналогична оконной функции, которую мы рассматривали в уроке 18 «Создание Windows-приложений на ассемблере» учебника. Поэтому за всеми подробностями отсылаем читателя к этому материалу. Второе замечание касается порядка отладки приложений, содержащих определяемые пользователем функции (процедуры) обратного вызова. Первое, что нужно сделать в процессе пошагового выполнения программы в отладчике, — выяснить адрес процедуры обратного вызова. В программе выше это можно сделать, выяснив, какое значение будет помещено в стек при выполнении команд:

..........
[установим функцию-обработчик сигналов управления
push TRUE
push offset cs: Ctrl Handler
call SetConsoleCtrlHandler
cmp eax. 0
jz exit [если неуспех
.........

После этого, сделав активным окно отладчика CPU (выбрав в меню команду
View CPU), необходимо установить указатель мыши в окно с командами процес-; сора и щелкнуть правой кнопкой мыши. В появившемся контекстном меню вы-
бер*етс пункт Goto... В результате этих действий отладчик отобразит диалоговое ¦ окно, в которое необходимо внести адрес программы-обработчика Ctrl Handler. ; В результате этого в верхней части окна команд отобразится первая команда [' процедуры Ctrl Handler. Установите на нее курсор и нажмите клавишу F4. Все, S программа начнет выполняться по своему алгоритму. При нажатии пользователем
управляющих комбинаций клавиш, допустимых функцией Handl erRoutine, управ-I ление будет передано этой функции, и вы сможете произвести ее отладку.

Организация низкоуровнего консольного ввода-вывода

I Низкий уровень консольного ввода-вывода по сравнению с высоким уровнем И бладает более широкими и гибкими возможностями. Низкоуровневые функции консольного ввода-вывода обеспечивают прямой доступ к входному и экранным буферам консоли, предоставляя приложению доступ к событиям мыши и клавиатуры, а также к информации об изменении размеров окна консоли. Функции низкоуровневого ввода-вывода позволяют приложению иметь доступ по чтению-записи к указанному числу последовательных символьных ячеек в экранном буфере или к прямоугольному блоку символьных ячеек в указанной позиции экранного буфера.
Обсудим возможности низкоуровневого ввода-вывода на примере работы с входным буфером (входной очередью) и буферами экрана. Отметим, для работы с ними существуют разные группы команд. Так, для работы с входным буфером используются функции низкоуровневого ввода-вывода — Wn'teConsolelnputXRead-Consolelnput. Группа функций для работы с буферами экрана будет конспективно рассмотрена в конце этого раздела.

Поддержка работы с мышью в консоли

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

B00L ReadConsoleInput(HANDLE hConsolelnput. PINPUT_RECORD lpBuffer, DWORD nLength. LPDWORD lpNumberOfEventsRead);

Параметры этой функции:

  • Consolelnput — стандартный дескриптор ввода, полученный функцией GetStdHandle;
  • lpBuffer — указатель на буфер, в который записывается информация о событии мыши, — эта область памяти имеет структуру, называемую INPUT_ RECORD, ее формат рассмотрен чуть ниже (необходимо заметить, что возможно групповое чтение информации из входного буфера, поэтому указатель ipBuffer может указывать на массив структур; информация о том, сколько событий будет читаться в этот массив структур, определяется параметром nLength);
  • nLength — размер во входных записях буфера, на который указывает указатель lpBuffer;
  • lpNumberOfEventsRead — определяет переменную, в которую записывается
    действительное число прочитанных записей входного буфера.

Запись входного буфера консоли имеет структуру, называемую INPUTRECORD. Ее описание на языке C++ выглядит так:

typedef struct _INPUT_RECORD { WORD EventType; union {
KEYJVENT_RECORD KeyEvent;
MOUSE_EVENT_RECORD MouseEvent;
WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
MENU_EVENT_RECORD MenuEvent:
FOCUSJVENT_RECORD FocusEvent;
} Event: } INPUT_RECORD;

В этой структуре первое поле EventType размером в слово содержит тип события, а второе поле Event является объединением различных структур. Поля какой из структур будут заполнены, определяется типом события, то есть первым полем, которое может принимать значения:

  • KEY_EVENT=0001h - поле Event содержит структуру KEYEVENTRECORD с информацией относительно события клавиатуры;
  • MOUSE_EVENT=0002h — ноле Event содержит структуру
    MOUSEEVENTRECORD с информацией относительно движения мыши или нажатия кнопки;
  • WINDOW_BUFFER_SIZE_EVENT-O004h - поле Event содержит структуру
    WINDOW_ BUFFER_SIZE_RECORD с информацией относительно нового размера экранного буфера;
  • MENU_EVENT=OOO8h — поле Event содержит структуру MENUEVENTRECORD (это событие используется внутри Windows и должно игнорироваться);
  • FOCUS_EVENT=0010h - поле Event содержит структуру FOCUSEVENTRECORD (это
    событие используется внутри Windows и должно игнорироваться).

Для обработки события мыши структура MOUSEEVENTRECORD выглядит так:

typedef struct _MOUSE_EVENT_RECORD {
COORD dwMousePosition;
DWORD dwButtonState;
DWORD dwControlKeyState:
DWORD dwEventFlags;
} MOUSE_EVENT_RECORD;

Исходя из вышесказанного структура INPUTRECORD для обработки событий мыши в программе на ассемблере должна выглядеть так:
INPUT_RECORD struc EventType dw 0 dwMousePosition struc x dw 0 у dw 0 ends
dwButtonState dw 0 dwControlKeyState dw 0 DwEventFlags dw 0 ends

Поле EventType для события мыши содержит значение MOUSE_EVENT=0002h, а поля структуры MOUSEEVENTRECORD соответственно означают следующее:
ш dwMousePosition — координаты мыши в окне консоли (в символьных координатах);
м dwButtonState — состояние кнопок мыши в момент возникновения события, при нажатии кнопок устанавливаются следующие биты (при одновременном нажатии устанавливается несколько соответствующих битов):

  • если установлен бит 0 ноля dwButtonState, то в момент наступления события была нажата левая кнопка мыши;
  • если установлен бит 1 поля dwButtonState, то в момент наступления события была нажата правая кнопка мыши;
  • если установлен бит 2 поля dwButtonState, то в момент наступления события была нажата средняя кнопка мыши, если она есть;
  • dwControlKeyState — поле описывает состояние управляющих клавиш клавиатуры в момент наступления события мыши (если одновременно нажато несколько клавиш, то значение в этом поле является результатом операции логического сложения ИЛИ перечисленных ниже значений):
  • • RIGHT_ALT_PRESSED=0001h - нажата правая клавиша Alt;
    • LEFT_ALT_PRESSED=0002h - нажата левая клавиша Alt;
    • RIGHT_CTRL_PRESSED=0004h — нажата правая клавиша
    Ctrl; LEFT_CTRL_PRESSED=OOO8h — нажата левая клавиша Ctrl;
    • SHIFT_PRESSED=OOlOh - нажата любая клавиша SHIFT;
    • NUMLOCK_ON=0020h - индикатор NumLock включен;
    • SCROLLLOCK_ON=0040h — индикатор ScrollLock включен;
    • CAPSLOCK_ON=0080h — индикатор CapsLock включен;
    ENHANCED_KEY=0100h — нажата клавиша расширенной клавиатуры (101 и 102 клавиши): Ins, Del, Home, End, Page Up, Page Down, «-, t, -», I, / или Enter;
    Ш dwEventFl ags — поле содержит одно из двух значений: »
    MOUSE_MOVED=0001h — перемещение мыши;
    • DOUBLE_CLICK=0002h — выполнен двойной щелчок мыши.

В ПРИМЕРе приведена демонстрационная программа обработки событий мыши (prg05_13. asm), которые отслеживаются следующим образом: нажатие левой кнопки приводит к выводу сообщения в позиции нажатия, нажатие правой кнопки приводит к завершению работы программы.
В заключение обращу внимание читателя на то, что API Win32 имеет функцию Mouse_Event, которая позволяет генерировать события, соответствующие реальным движениям мыши и щелчкам ее кнопок. Тем самым API Win32 предоставляет механизм для создания обучающих и демо-версий программ. Формат этой функции:
VOID mouse_event(DWORD dwFlags. DWORD dx. DWORD dy, DWORD dwData. DWORD dwExtralnfo)

Расширенная поддержка клавиатуры в консоли

Функции работы с текстом высокого уровня не дают других возможностей работы с клавиатурой, кроме как примитивного ввода текста. При разработке программ текстового режима часто требуется информация о состоянии управляющих клавиш, о факте удержания клавиши, что может свидетельствовать о желании пользователя повторить ввод некоторого символа или просто о желании получить тривиальный скан-код клавиши. Эти и другие события клавиатуры доступны программе посредством описанной выше функции ReadConsolelnput.
События клавиатуры генерируются при нажатии любой клавиши. Процесс их обработки аналогичен обработке событий мыши. В первую очередь заполняется
о нажатии некоторых управляющих клавиш. Для всех остальных клавиш просто фиксируется факт нажатия. При этом необходимо помнить, что однократному нажатию клавиши реально соответствуют два события — нажатие и отпускание клавиши. В связи с этим программа выводит два сообщения. На практике этого можно избежать, анализируя поле bKeyDown: bKeyDown=l, когда клавиша нажата; bKeyDown=0, когда клавиша отпущена. Выход из программы — при выполнении любых действий с мышью.

Окно консоли и экранный буфер

И в заключение обсуждения особенностей работы с консольными приложениями поясним, что представляет собой экранный буфер консоли и какие средства представляет API Win32 для работы с ним.
Для того чтобы читатель мог легко понять соотношение понятий «окно консоли» и «экранный буфер консоли», представьте себе офисный календарь, на котором текущее число отмечается квадратной рамкой, закрепленной на прозрачной целлофановой ленте и перемещающейся вдоль нее. Теперь представим, что содержимое листа календаря вне этой рамки невидимо, то есть доступно только через окошко, которое образует рамка. Для того чтобы увидеть содержимое всего листа календаря, необходимо двигать рамку. В контексте этой ассоциации — лист календаря — это экранный буфер, а площадь внутри рамки — окно консоли, то есть видимая часть экранного буфера.
Возможна поддержка нескольких экранных буферов, связанных с данной консолью, но только один из них может подвергаться отображению в окне консоли — его называют активным экранным буфером. Другие экранные буферы, если они были созданы, являются неактивными. Для создания экранного буфера используется функция CreateConsoleScreenBuffer. К неактивным экранным буферам можно обращаться для чтения и записи, но отображаться в окне консоли будет только активный экранный буфер (или его часть). Для того чтобы сделать экранный буфер активным, используется функция SetConsoleActiveScreenBuffer. Функция CreateConsoleScreenBuffer имеет показанный ниже формат.
HANDLE CreateConsoleScreenBuffer(DWORD dwDesiredAccess, DWORD dwShareMode,
CONST LPSECURITY_ATTRIBUTES ipSecurityAttributes. DWORD dwFlags. LPVOID lpScreenBufferData):

Параметры функции:

  • dwDesiredAccess — определяет желаемый тип доступа к экранному буферу консоли, этот параметр может быть либо одним из следующих значений либо их комбинацией:
  • GENERIC_READ=80000000h — запрашивается доступ по чтению к экранному буферу консоли для того, чтобы разрешить процессу прочитать данные из буфера;
  • GENERIC_WRITE=40000000h — запрашивается доступ для записи к экранному буферу консоли для того, чтобы разрешить процессу записать данные в буфер;
  • dwShareMode — определяет возможность разделения этого экранного буфера консоли; нулевое значение этого параметра указывает, что буфер не может
    быть разделен, ненулевое состояние этого буфера может быть одним из следующих значений или их комбинацией:
  • FILESHAREREAD — другие операции открытия могут быть выполнены для экранного буфера консоли с доступом для чтения;
    FILESHAREWRITE — другие операции открытия могут быть выполнены для экранного буфера консоли с доступом для записи;
  • IpSecurityAttributes — указатель на структуру SECURITY_ATTRIBUTES, которая определяет, может ли возвращаемый функцией CreateConsoleScreenBuffer дескриптор наследоваться дочерними процессами — если lpSecuri-tyAttributes=NULL, то дескриптор не может быть унаследован;
  • dwFlags — определяет тип создаваемого экранного буфера консоли, в настоящее время поддерживается только один такой тип — CONSOLE_TEXTMODE_ BUFFER=1;
    lpScreenBufferData — зарезервирован и должен быть равен NULL.

Функция CreateConsoleScreenBuffer формирует дескриптор созданного экранного буфера, который затем используется функциями для доступа к этому буферу.
Для того чтобы сделать буфер активным, используют функцию SetConsole-Acti veScreenBuf f er.
B00L SetConsoleActiveScreenBuffertHANDLE hConsoleOutput):
Функция имеет единственный параметр — hConsoleOutput — дескриптор экранного буфера, созданного функцией CreateConsoleScreenBuffer. Как уже было отмечено, консоль может иметь много экранных буферов. Функция SetConsoleActiveScreenBuffer определяет, какой из них будет отображен. Приложение может производить запись в неактивный экранный буфер и затем использовать функцию SetConsoleActiveScreenBuffer для отображения содержимого буфера. Чтение и запись в неактивный (и активный тоже) экранный буфер производится функциями низкоуровневого ввода-вывода — WriteConsoleOutputNWriteConsoleOutput-Character и ReadConsoleOutput\ReadConsoleOutputCharacter, которым при вызове передается дескриптор нужного экранного буфера, полученного предварительно функцией CreateConsol eScreenBuf fer.
Каждый из созданных экранных буферов поддерживает собственный текущий прямоугольник окна, определяемый координатами верхней левой и нижней правой символьных ячеек, которые будут отображены в окне консоли. Для определения видимого в окне консоли прямоугольника экранного буфера используется функция GetConsol eScreenBufferlnfo.
B00L GetConsoleScreenBufferInfo(HANDLE
hConsoleOutput. PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferlnfo);
Параметрами этой функции являются:

  • hConsoleOutput — дескриптор экранного буфера, созданного функцией CreateConsoleScreenBuffer; дескриптор должен иметь тип доступа GENERICREAD;
  • lpConsoleScreenBufferlnfo — указатель на структуру CONSOLE_SCREEN_BUFFER_ INFO, в которую помещается информация об экранном буфере.

Структура CONSOLESCREENBUFFERINFO имеет следующий вид:

typedef struct _CONSOLE_SCREEN_BUFFER_INFO
COORD dwSize: :размер экранного буфера в колонках и строках COORD dwCursorPosition: //координаты столбца и строки курсора в экранном буфере
WORD wAttributes: //цвет фона и текста, с которыми записываются
//и отображаются символы в экранном буфере функциями //WriteFile\WriteConsole и ReadFile\ReadConsole
SMALL_RECT srWindow; // определяет структуру
SMALL_RECT. которая содержит координаты // левого верхнего и нижнего правого углов экранного буфера, //видимого в окне консоли на экране дисплея COORD dwMaximumWindowSize; //определяет максимальный размер окна консоли
//с учетом текущего размера экранного буфера и шрифт } CONSOLE_SCREEN_BUFFER_INFO :

Для приложения интерес, в частности, может представлять параметр srWindow с координатами видимой части экранного буфера. Далее, руководствуясь действиями пользователя (выполняющего прокрутку окна или изменение его размера) по отношению к окну консоли, приложение может изменять значения в структуре SMALLRECT и передавать ее на вход функции SetConsoleWindowInfo, которая устанавливает текущий размер и позицию окна консоли относительно экранного буфера.
B00L SetConsoleWindowInf0ChANDLE hConsoleOutput.
B00L bAbsolute. CONST SMALL RECT *lpConsoleWindow);
Параметрами этой функции являются: .

  • hConsoleOutput — дескриптор экранного буфера, созданного функцией Create-Consol eScreenBuf fer; дескриптор должен иметь тип доступа GENERIC_WRITE;
  • bAbsol ute — определяет порядок использования координат в структуре, указанной параметром lpConsoleWindow; если bAbsol ute=l (истина), то координаты определяют новые левую верхнюю и нижнюю правую углы окна; если bAbsol ute=0 (ложь), то координаты — смещения относительно текущих координат углов окна;
  • lpConsoleWindow — указатель на структуру SMALL_RECT, в которую записывается информация о новых координатах экранного буфера.

Структура SMALL_RECT имеет следующий вид:

typedef struct _SMALL_RECT {
SHORT Left; //х-координата верхнего левого угла
SHORT Top: //у-координата верхнего левого угла
SHORT Right: //х-координата нижнего правого угла
SHORT Bottom: //у-координата нижнего правого угла } SMALL_RECT:

При работе с функцией SetConsoleWindowInfo следует иметь в виду, что она возвращает ошибку (нулевое значение), если координаты видимой части экранного буфера указывают за его действительные границы. Максимально допустимый раз мер окна для данной консоли можно получить с помощью функции GetConsole-ScreenBufferlnfo. Таким образом, обе эти функции можно использовать для листания экранного буфера.
Для закрытия экранного буфера используется функция CloseHandle, которой передается дескриптор закрываемого экранного буфера.
B00L CloseHandle(HANDLE hObject);
Для того чтобы завершить рассмотрение функций, предназначенных для поддержки консольного приложения, перечислим те из них, что остались «за кадром».

функция Назначение
FiConsoleOutputAttribute Устанавливает цвет текста и фона для указанного числа символьных ячеек, начинающихся по указанным координатам в экранном буфере
Fi П ConsoleOutputCharacter Запись символа в экранный буфер указанное число раз по указанным координатам
Fl ushConsolelnputBuffer Запись на диск входного буфера консоли. Все входные записи во входном буфере консоли до настоящего момента времени удаляются
GenerateConsoleCtrl Event Посылка сигнала, определенного этой функцией, совместно использующим консоль процессам
GetConsoleCursorlnfo Предоставление информации о размере и видимости курсора для указанного экранного буфера
GetConsoleMode Предоставление информации о текущем входном режиме входного буфера консоли или текущем режиме вывода экранного буфера консоли
GetConsoleTitie Извлечение строки из области заголовка для текущего окна консоли
GetLargestConsoleWi ndowSIze Возвращает размер самого большого возможного окна консоли, основанного на текущем шрифте и размере изображения
GetNumberOfConsolelnputEvents Возвращает число непрочитанных записей ввода во входном буфере пульта
GetNumberOfConsoleMouseButtons Возвращает число кнопок на мыши, используемых текущей консолью
PeekConsolelnput Чтение данных из входного буфера консоли без их удаления
Scrol1ConsoleScreenBuffer Перемещение блока данных в экранном буфере. Действие перемещения может быть ограничено путем определения отсекающего прямоугольника. Содержание экранного буфера вне отсекающего прямоугольника будет неизменным
SetConsoleCursorlnfo Установка размера и видимости курсора для указанного экранного буфера консоли
SetConsoleMode Установка режима входного буфера консоли или режима вывода экранного буфера консоли
SetConsoleScreenBufferS1ze Изменение размера указанного экранного буфера консоли
SetStdHandle Установка некоторого дескриптора как дескриптора стандартного ввода, стандартного вывода или устройства ошибки. Может использоваться при перенаправлении ввода-вывода

 

 
Назад Начало  



Книжный магазин