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

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



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

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 Установка некоторого дескриптора как дескриптора стандартного ввода, стандартного вывода или устройства ошибки. Может использоваться при перенаправлении ввода-вывода
     
     



    Содержание раздела