Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Text111

.pdf
Скачиваний:
49
Добавлен:
06.02.2018
Размер:
1.16 Mб
Скачать

BOOL ReadConsoleOutputAttribute(HANDLE hConsOut,

WORD* attribs, DWORD len, COORD pos, DWORD*

actlen);

Вторая из перечисленных функций является обратной к функции с именем WriteConsoleOutput, которая в свою очередь предназначена для размещения на экране информации из двухмерного массива элементов, состоящих из пары (символ, его атрибут). Практически все параметры этих функций совпадают по использованным здесь обозначениям, по их типам и назначению. Вся разница между этими функциями в том, что одна из них переносит информацию из указанного в них прямоугольника экрана в двухмерный массив данных (функция ReadConsoleOutput), а другая осуществляет перенос информации в противоположном направлении: из двухмерного массива элементов – в указанный прямоугольник экрана. В обоих случаях параметр rect определяет используемый в описании прямоугольник экрана; параметр cells – массив элементов (ячеек для пар символатрибут); параметр dwBufferSize – как размеры массива, а dwBufferCoord – позицию (индекс строки и столбца) того элемента массива, начиная с которого используется массив в конкретном вызове рассматриваемых функций.

Функция ReadConsoleOutputCharacter выполняет чтение только символов в буфер программы с именем buffer (или по адресу, который дает указатель buffer – в зависимости как конкретно определено это имя), а функция ReadConsoleOutputAttribute выполняет чтение только атрибутов в массив слов, указанный параметром attribs. Число читаемых при этом символов или атрибутов задается параметром len, чтение выполняется с начальной позиции экрана, задаваемой параметром с именем pos, а действительное число прочитанных символов или атрибутов возвращается с помощью параметра actlen.

Воперационной системе Unix отсутствуют стандартные средства чтения символов с экрана. Это объясняется тем, что основные принципы работы с Unix формировались еще в то время, когда основным средством вывода информации для отдельного пользователя интерактивной ОС были телетайпы, осуществляющие вывод на бумажную ленту, считывать информацию с которой было невозможно по техническим причинам. Как показывает опыт работы в этой ОС, без средств чтения информации с экрана вполне можно обойтись без снижения потребительских возможностей операционной системы и прикладных программ.

Вто же время современные версии Unix, в частности Linux, ориентируясь на возможности современных электронных устройств вывода информации пользователю, предлагают дополнительные виртуальные устройства. Одним из таких устройств является "виртуальный экран консоли", обозначаемый в файловой системе Unix как vcs (сокращение от virtual consoe screen). В связи с замечательными особенностями файловой системы Unix устройства в ней рассматриваются как файлы, только специализированные. Это позволяет использовать операцию откры-

тия файлов для доступа к устройствам. Заметим, что специальные файлы устройств стандартным образом размещаются в каталоге /dev, поэтому полное наименование устройства виртуального экрана консоли следует задавать в виде /dev/ vcs.

Практически в настоящее время имеется целый набор виртуальных экранов консолей на весь их возможный набор. Когда желательно иметь непосредственный доступ к виртуальному экрану для текущей консоли, с которой работает программа или оператор, рекомендуется использовать виртуальное устройство, обозначаемое как vcs. Это виртуальное устройство, рассматриваемое как файл, отображает содержимое видеопамяти для экрана данной виртуальной консоли. Каждой позиции экрана в этом файле соответствует один байт. Поэтому при использовании типового разрешения экрана в 25 строк по 80 символов виртуальный экран консоли vcs хранится как файл из 2000 байтов (25x80).

Кроме виртуального экрана консоли vcs, можно использовать расширенное виртуальное устройство, обозначаемое как vcsa, которое позволяет считывать из видеопамяти не только коды символов, но и их атрибуты. На каждую позицию экрана в этой файле имеет два байта: один хранит код самого символа, а второй (следующий) код атрибутов этого символа. Кроме того, в файле vcsa - в его начале размещаются четыре служебных байта, хранящие, соответственно, число строк экрана, число столбцов в нем, а далее номер столбца и номер строки, в которых находится курсор экрана. Поэтому при использовании типового разрешения экрана в 25 строк по 80 символов, виртуальный экран консоли vcsa хранит как файл 4004 байтов (25x80x2+4).

Следующая программа, приведенная листингом 4.4.1, демонстрирует сохранение текущего содержимого экрана в файле с именем myscreen.

#include <stdio.h> #include <fcntl.h> void main()

{int screena, fhandle; int rc;

char buffer[256];

screena=open("/dev/vcs",O_RDONLY);

if (screena= =-1) {printf("Error open virtual screen\n"); exit(0);} fhandle=open("myscreen",O_WRONLY|O_CREAT, 0764); while ((rc=read(screena,buffer,256))!=0)

{write(fhandle, buffer,rc);} close(screena);

close(fhandle);

}

Листинг 4.4.1. Чтение из виртуального устройства экрана

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

Следует только заметить, что для непривилегированных пользователей Linux устройство vcs может оказаться недоступным по чтению, если об этом не побеспокоиться на уровне общесистемных установок. (При недоступном устройстве возвращается ошибка открытия файла.) Чтобы сделать это устройство доступным по чтению любым пользователям следует в режиме привилегированного пользователя (обычно пользователя с именем root) выполнить команду

chmod o+r /dev/vcs

Проверить результат можно с помощью команды Unix, которую следует задать в виде

ls -l /dev/vcs

Она должна выдать сообщение вида

crw----r-- 1 root tty номер, номер дата /dev/vcs

(Вместо дефисов здесь могут быть (хотя и маловероятно) какие-то другие символы.)

Аналогичное действие следует выполнить и над устройством vcsa, если предполагается использовать последнее.

5. СИСТЕМНЫЕ ФУНКЦИИ ВВОДА ДЛЯ КОНСОЛЬНЫХ УСТРОЙСТВ

5.1. Системные функции ввода текстовых строк

Как ни странно, ввод текстовых строк организуется для пользователей системных функций проще чем ввод отдельных символов. Поэтому с ввода текстовых строк и начнем.

В операционных системах MS Windows для ввода текстовых строк с клавиатуры в консольном режиме предназначена функция ReadConsole с прототипом

BOOL ReadConsole(HANDLE hConsInput, VOID* buffer,

DWORD len, DWORD *actlen, VOID* reserved);

Первый параметр этой функции задает хэндл буфера ввода для консоли. Он в простейших (и большинстве) случаях получается в результате вызова функции GetStdHandle с аргументом STD_INPUT_HANDLE. Параметр buffer задает буфер для ввода текстовой строки, а параметр len определяет максимальный размер вводимой строки (обычно этот параметр совпадает с длиной буфера buffer). Параметр

actlen используется для получения числа – сколько символов было введено в результате выполнения функции. (В число символов входит и завершающий символ '\n'.) Последний параметр вызова функции зарезервирован разработчиками и должен браться равным NULL.

Заметим, что рассмотренная функция дает возможность ввода с консоли только в том случае, когда стандартный ввод не переназначен. Если же это условие нарушено, то данная функция возвращает значение FALSE, свидетельствующее об ошибке. При необходимости в Windows осуществлять ввод только с консоли, даже когда стандарный ввод переназначен, следует открыть для ввода файл, обозначаемый специальным именем CON (применив функцию CreateFile) и осуществлять ввод из этого файла.

Воперационной системе Unix для ввода текста с консоли, если стандартный ввод не переназначается, можно использовать стандартные функции gets и scanf. Следует заметить, что использование функции gets настойчиво не рекомендуется профессионалами. Дело в том, что она может вернуть текст большей длины, чем предусмотрено программистом в ее вызове. Такая ситуация приводит к катастрофическим последствиям. Единственным эффективным решением указанной проблемы является отказ от применения данной функции. Вместо нее допустимо использовать более общую функцию стандартной библиотеки Си с прототипом

char* fgets(char *s, int size, FILE *stream),

где в качестве последнего параметра можно использовать символическую константу stdin, обозначающую поток стандартного ввода. (В этой функции явно указывается максимальный размер size текста, который может быть введен при ее вызове.) В то же время рассмотренная возможность не дает принципиального решения проблемы ввода с консоли, поскольку стандартный ввод может переназначаться в момент вызова программы в Unix.

ВUnix для специализированного ввода именно с консоли следует использовать непосредственно устройство консоли как специальный файл. С этой целью нужно открыть доступ к виртуальной консоли, имеющей файловое имя /dev/tty. (В Unix обычно параллельно функционирует ряд виртуальных консолей, обозначаемых именами /dev/tty1, /dev/tty2, /dev/tty3 и т.д., но обозначение /dev/tty задает текущую управляющую консоль для исполняемой программы.) Заметим, что в Unix по историческим причинам принято называть консоль словом терминал, которое

вобщем случае обозначает нечто гораздо более общее, в частности, удаленное средство доступа к операционной системе. В данном изложении для единообразия

врассмотрении средств различных ОС будет использоваться термин консоль как приближенное наименование терминала в Unix.

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

hcons=open("/dev/tty",O_RDONLY),

где переменная hcons должна быть предварительно описана как целочисленная. После такого открытия можно использовать функцию read(hcons, buffer, сколько) для чтения текста непосредственно с клавиатуры (даже если стандартный ввод переназначен).

5.2. Событийно-управляемый ввод

Поведение современных программных систем, активно взаимодействующих с пользователем, определяется не столько последовательностью операторов в исходной программе, сколько внешними воздействиями на компьютер. Такими воздействиями являются нажатия клавиш клавиатуры, перемещение мыши и нажатие ее кнопок, а также более сложные действия, реализуемые обычно также с помощью мыши. Примером такого сложного действия является перетаскивание какого-то графического объекта на экране с помощью мыши (dragging).

Поведение же программ, написанных в классических традициях, определяет сам программист, он при написании таких программ сам решает, что на очередном шаге выполнения будет требовать и ожидать программа: данных от клавиатуры или информации от мыши.

Естественно первый из перечисленных подходов отвечает реальным событиям в реальном мире, порядок которых никому, в том числе программисту, не известен. Поэтому именно он и реализуется в современных программах. Такая реализация потребовала отказа от безоговорочного следования за идеей алгоритма (как последовательности действий предварительно и строго описанной). По существу она оказалась возможной на основе сложного аппаратно-программного механизма, встроенного во все современные компьютеры и называемого механизмом прерываний.

Этот механизм позволяет немедленно реагировать на внешние сигналы путем запуска специально подготовленных программ – обработчиков прерываний, причем выполнявшаяся в момент прерывания программа временно приостанавливается с сохранением всей информации, необходимой для будущего продолжения. После завершения обработки прерываний прерванная программа возобновляет свое выполнение с прерванного места.

Разработка программ обработчиков прерываний очень не простая задача и поэтому ее выполняют разработчики операционных систем. Остальным предоставляется неявно пользоваться этим механизмом. Его использование в прикладных программах основывается на специализированных структурах данных о внешних по отношению к программе событиях.

Само событие, в частности движение мыши, это явление материальной реальности и, чтобы сделать его доступным для программ, внутренние компоненты ОС строят специализированные описания событий, обычно называемые сообщениями

(message) или для выразительности просто событиями (event). Для нас, как программных пользователей операционной системы, существенно, что какой-то ее компонент подготавливает такие специализированные структуры данных для программ, способных их использовать.

Для текстовой консоли Windows, по замыслу разработчиков, теоретически возможны сообщения от нажатия клавиши клавиатуры, от мыши, сообщения об изменении размера окна, о переходе активности к текущему окну или о потере такой активности. Свойство активности визуально отражается изменением цвета заголовка окна и содержательно состоит в том, что только активное окно получает данные от клавиатуры.

В Windows программа для текстового окна может запросить сообщение путем вызова системной функции ReadConsoleInput. Эта функция имеет прототип

BOOL ReadConsoleInput(HANDLE hConsInput,

INPUT_RECORD* buffer, DWORD len, DWORD* actlen). Кроме хэндла для указания консольного буфера ввода (в частности хэндла

стандартного файла ввода) эта функция содержит адрес буфера, который представляет собой в общем случае массив записей типа INPUT_RECORD для размещения некоторого числа записей сообщений ввода. Размер массива выбирается программистом. Размер этого массива записей задается при вызове в параметре len. В простейших случаях массив buffer состоит из единственного элемента – для размещения единственного очередного сообщения, и параметр len берется поэтому равным 1. В общем случае, когда при вызове функции задается len, не равное 1, следует в программе после обращения к ReadConsoleInput проверять, сколько записей о вводе было действительно получено (с помощью параметра actlen), и принимать соответствующие действия с учетом этого фактического значения. Заметим, что функция ReadConsoleInput возвращает управление в вызвавшую ее программу только после появления сообщения о вводе. До этого момента вычислительный процесс выполнения программы, содержащей такую функцию, приостановлен (блокирован).

Сообщения как структуры данных типа INPUT_RECORD, получаемые от этой функции, имеют довольно сложное строение. Это строение описывается в заголовочном файле следующим образом:

typedef struct _INPUT_RECORD { WORD EventType;

union {

KEY_EVENT_RECORD KeyEvent; MOUSE_EVENT_RECORD MouseEvent; WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; MENU_EVENT_RECORD MenuEvent; FOCUS_EVENT_RECORD FocusEvent;

}Event;

}INPUT_RECORD, *PINPUT_RECORD;

Ключевым полем в этой записи является тип события EventType, его значения задаются предопределенными константами, описанными как

// EventType flags:

 

#define KEY_EVENT

0x0001 // Event contains key event record

#define MOUSE_EVENT

0x0002 // Event contains mouse event record

#define WINDOW_BUFFER_SIZE_EVENT 0x0004

// Event contains window change event record #define MENU_EVENT 0x0008 // Event contains menu event record #define FOCUS_EVENT 0x0010 // event contains focus change

Это ключевое поле EventType своим значением определяет более детальное строение сообщения. Если его значение равно KEY_EVENT, то на самом деле в этой универсальной структуре вся остальная часть (обобщенно называемая Event) есть структура типа KEY_EVENT_RECORD. Если же значение типа равно MOUSE_EVENT, то остальная часть есть в действительности структура типа MOUSE_EVENT_RECORD. (Остальные типы сообщений и их структуры в данном изложении рассматриваться не будут.)

Поэтому типовое использование системной функции ReadConsoleInput может быть описано наиболее характерной схемой вида

. . .

ReadConsoleInput( hInput, &inpbuf, 1, &actlen); if (inpbuf. EventType = = KEY_EVENT) {обработка события от клавиатуры,

структура которого представляется в программе обозначением ipnbuf.Event.KeyEvent }

if (inpbuf. EventType = = MOUSE_EVENT) {обработка события от мыши,

структура которого представляется в программе обозначением ipnbuf.Event.MouseEvent }

.. . ,

вкоторой предполагается, что использованные информационные объекты данных где-то раньше описаны как

HANDLE hInput; INPUT_RECORD inpbuf; DWORD actlen;

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

Конкретные внутренние поля типов данных для сообщений от клавиатуры и мыши будут рассмотрены несколько дальше.

5.3.Системные функции ввода с клавиатуры

Воперационной системе Windows ввод символов даже для консоли организован довольно сложно. Эта организация ввода символов была предварительно рассмотрена в предыдущем разделе. Теперь рассмотрим более детально строение экземпляров структуры типа KEY_EVENT_RECORD. Именно эти структуры данных оказываются в сообщении типа INPUT_RECORD, когда ключевое поле EventType в ней имеет значение KEY_EVENT.

Структура данных KEY_EVENT_RECORD для записи ввода с клавиатуры описана в заголовочном файле как

typedef struct _KEY_EVENT_RECORD { BOOL bKeyDown;

WORD wRepeatCount; WORD wVirtualKeyCode; WORD wVirtualScanCode; union {

WCHAR UnicodeChar; CHAR AsciiChar;

}uChar;

DWORD dwControlKeyState;

} KEY_EVENT_RECORD, *PKEY_EVENT_RECORD;

Вней содержатся поля как собственно ASCII-кода символа (который обычно

инужен при вводе с клавиатуры), так и дополнительная информация. Поле bKeyDown определяет, нажата клавиша (значения TRUE) или отпущена (значение FALSE). Заметим, что для каждого события нажатия или отпускания клавиши формируется свое отдельное сообщение. Поле wRepeatCount дает число многократных сигналов от клавиши, формируемых в автоматическом режиме при длительном удержании клавиши (для последовательности таких событий ОС формирует только одно сообщение – для экономии). Поле wVirtualScanCode дает сканкод нажатой клавиши, а поле wVirtualKeyCode определяет специальный код для управляющих клавиш, принятый в системах Windows. Наконец, поле dwControlKeyState комбинацией бит информирует получателя сообщения о текущем – на момент формирования сообщения - состоянии нажатий на специальные клавиши. Коды клавиш для этого поля описаны следующими определениями

// ControlKeyState flags

#define RIGHT_ALT_PRESSED 0x0001 // the right alt key is pressed.

#define LEFT_ALT_PRESSED 0x0002 // the left alt key is pressed.

#define RIGHT_CTRL_PRESSED 0x0004 // the right ctrl key is pressed.

#define LEFT_CTRL_PRESSED

0x0008 // the left ctrl key is pressed.

#define SHIFT_PRESSED

0x0010

// the shift key is pressed.

#define NUMLOCK_ON

0x0020

// the numlock light is on.

#define SCROLLLOCK_ON

0x0040

// the scrolllock light is on.

#define CAPSLOCK_ON

0x0080

// the capslock light is on.

#define ENHANCED_KEY

0x0100

// the key is enhanced.

В операционной системе Unix нет специальных средств, отличных от стандарта языка Си, для ввода символов с консоли, а предполагается использовать стандартные библиотеки языка Си. Поэтому для ввода с консоли следует использовать стандартные функции getchar и getch.

Дополнительные возможности в Windows предоставляются путем выборочного задания типов событий, которые будут далее поступать при вызове функции ReadConsoleInput. Этот выбор задается функцией SetConsoleMode с прототипом

BOOL SetConsoleMode(HANDLE hConsHandle, DWORD mode);

Параметр mode задает в ней новый режим управления вводом (или выводом, если в качестве первого параметра hConsHandle задан хэндл буфера консольного вывода). Возможные режимы для буфера ввода с консоли задаются константами

#define ENABLE_PROCESSED_INPUT

0x0001

#define ENABLE_LINE_INPUT

0x0002

#define ENABLE_ECHO_INPUT

0x0004

#define ENABLE_WINDOW_INPUT

0x0008

#define ENABLE_MOUSE_INPUT

0x0010

Если с помощью функции SetConsoleMode задать в качестве режима значение константы ENABLE_MOUSE_INPUT, а другие константы при этом задании режима не использовать, то по запросу событий будут поступать только сообщения от мыши (даже если пользователь и пытается нажимать на клавиши клавиатуры при активном окне текущего приложения). Если же с помощью данной функции установить только режим ENABLE_PROCESSED_INPUT, то будут поступать сообщения лишь от клавиатуры и т.п.

Отключив режим ENABLE_ECHO_INPUT, можно отказаться от использования эха символов, но делать это следует временно – с последующим восстановлением стандартного режима. Поэтому вначале целесообразно запомнить предыдущее значение режима для буфера ввода консоли, для этого действия предназначена функция

BOOL GetConsoleMode(HANDLE hConsHandle, DWORD* pmode),

в которой возвращаемое значение текущего режима передается через второй параметр ее вызова.

Как уже пояснялось выше, в Unix для доступа непосредственно к клавиатуре следует выполнить открытие специализированного виртуального устройства /dev/tty. В общем случае универсальная функция read() в варианте использования, читающем по одному символу, может обеспечить посимвольный ввод. Но при этом для программиста возникает проблема, обусловленная тем, что вводимые символы поступают в программу только после нажатия на клавишу Enter, когда используется обычный (стандартный) режим работы консоли. (Информация о нажатии клавиш клавиатуры оказывается недоступен программе до воздействия на клавишу Enter.)

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

int tcgetattr(int htty, struct termios *tsave),

int tcsetattr(int htty, int action, struct termios *tnew), где параметр htty задает хэндл консоли.

Функция tcgetattr() позволяет получить детальную информацию о текущих режимах консоли, а функция tcsetattr() – установить новые режимы. Получение текущих режимов перед установкой новых настоятельно рекомендуется для восстановления стандартных режимов работы консоли перед выходом из программы (иначе нестандартный режим может существенно нарушить работу последующих программ на данной консоли). Обе функции требуют использования заголовочного файла с именем termios.h. Среди возможных значений параметра action функции tcsetattr() чаще всего используется задаваемое символической константой TCSAFLUSH (параметр задает, когда и как будут проведены изменения режима консоли).

Структура типа termios содержит множество полей, подробную информацию о которых можно получить в технической документации. С целью настройки режима для одиночного ввода символов с клавиатуры – требуется изменить поля с именами c_lflag и c_cc. Второе из них представляет собой массив, в элементах которого с индексами, задаваемыми константами VMIN и VTIME, следует установить значения 1 и 0 соответственно. В поле c_lflag следует сбросит стандартный режим (обозначаемый константой ICANON) и, как правило, режим автоматического отображения символа (обозначаемый константой ECHO). При этом не следует изменять значения битов, кодирующих другие разновидности режимов. Поэтому последовательность команд, решающая рассматриваемую задачу, имеет вид

struct termios sterm, term; int htty ;

.. .

htty=open("/dev/tty", O_RDON LY); if (htty = = -1) {. . . exit(1);}

Соседние файлы в предмете Операционные системы