Текстовый режим
В текстовых режимах на экране могут отображаться только текстовые символы, а также символы псевдографики. Текстовые режимы работы видеоадаптеров рекомендуется использовать всегда, когда приложению не нужно выводить на экран графическую информацию.
Стандартные текстовые режимы работы видеоадаптеров позволяют вывести на экран 25 строк по 40 или 80 символов. Если перепрограммировать некоторые регистры видеоадаптера, то можно увеличить число отображаемых строк для EGA до 43, а для VGA до 50.
Для кодирования каждого знакоместа экрана (символа) используются два байта. Первый из них содержит ASCII-код отображаемого символа, а второй - атрибуты символа. ASCII-коды символов экрана располагаются в нулевом цветовом слое, а их атрибуты - в первом цветовом слое.
Рисунок 6.3 Структура видеопамяти в текстовых режимах.
Атрибуты определяют цвет символа и цвет фона. Благодаря такому режиму храненеия информации достигается значительная экономия памяти. При отображении символа на экране происходит преобразование его из формата ASCII в двумерный массив пикселов, выводимых на экран. Для этого преобразования используется таблица трансляции символов (таблица знакогенератора).
Данная таблица знакогенератора хранится во втором слое видеопамяти:
Рисунок 6.4 Преобразование кода ASCII в образ символа на экране.
При непосредственном доступе к видеопамяти нулевой и первый цветовые слои отображаются на общее адресное пространство. При этом происходит чередование байтов из нулевого и первого слоев. Коды символов имеют четные адреса, а их атрибуты - нечетные.
Рисунок 6.5 Отображение цветовых слоев.
Ниже приведен дамп видеопамяти в текстовом режиме с разрешением 80х25 символов:
Адрес 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
B800:0000 91 07 E2 07 E0 07 AE 07-AA 07 A0 07 20 07 AD 07 С.т.р.о.к.а. .н. B800:0010 AE 07 AC 07 A5 07 E0 07-20 07 30 07 20 07 20 07 о.м.е.р. .0. . . B800:0020 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0030 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0040 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0050 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0060 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0070 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0080 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0090 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
B800: 00A0 91 07 E2 07 E0 07 AE 07-AA 07 A0 07 20 07 AD 07 С.т.р.о.к.а. .н. B800:00B0 AE 07 AC 07 A5 07 E0 07-20 07 31 07 20 07 20 07 о.м.е.р. .1. . . B800:00C0 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:00D0 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:00E0 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:00F0 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0100 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0110 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0120 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . . B800:0130 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
Из этого дампа видно, что байты кодов символов из нулевого цветового слоя видеопамяти чередуются с байтами атрибутов символов из первого цветового слоя. Байты кодов символов расположены по четным адресам, а байты атрибутов, которые для данного участка видеопамяти имеют значение 07h - по нечетным.
Следующая программа GRAB демонстрирует непосредственный доступ к видеопамяти в текстовых режимах. Это резидентная программа, содержащая все элементы, необходимые для ее "безопасной" работы.
Программа предназначена для копирования содержимого видеобуфера в файл. Запись в файл активизируется при нажатии комбинации клавиш Ctrl+PrtSc. После каждой записи имя файла изменяется.
В самом начале своей работы программа проверяет наличие своей копии в памяти, так как повторное переназначение векторов прерываний приведет систему к краху.
При нажатии комбинации клавиш Ctrl+PrtSc вызывается функция write_buf. Функция write_buf определяет текущий номер видеорежима а также такие его параметры как число символов в строке и количество строк на экране. Эти параметры считыватся из области данных видеофункций BIOS. Определяется также адрес начала видеопамяти и смещение относительно нее области, отображаемой на экране. Далее, если видеоадаптер находится в текстовом режиме мы записываем данные из видеопамяти в файл. При этом записывается только каждый второй байт, так как в видеопамяти байты символов чередуются с байтами атрибутов. Естественно информация о цвете символов при этом теряется.
Подробное описание области данных видеофункций BIOS и регистра начального адреса контроллера ЭЛТ вы найдете в следующих главах книги.
Итак, текст программы:
#include <dos.h> #include <stdio.h> #include <stdlib.h>
// файл sysp.h приведен в приложении #include "sysp.h"
// Выключаем проверку стека и указателей
#pragma check_stack( off ) #pragma check_pointer( off )
// Макро для подачи звукового сигнала
#define BEEP() _asm { \ _asm xor bx, bx \ _asm mov ax, 0E07h \ _asm int 10h \ }
// Указатели на старые обработчики прерываний
void (_interrupt _far *old8)(void); // Таймер void (_interrupt _far *old9)(void); // Клавиатура void (_interrupt _far *old28)(void); // Занятость DOS void (_interrupt _far *old2f)(void); // Мультиплексор
// Новые обработчики прерываний
void _interrupt _far new8(void); void _interrupt _far new9(void); void _interrupt _far new28(void); void _interrupt _far new2f(unsigned _es, unsigned _ds, unsigned _di, unsigned _si, unsigned _bp, unsigned _sp, unsigned _bx, unsigned _dx, unsigned _cx, unsigned _ax, unsigned _ip, unsigned _cs, unsigned flags);
int iniflag; // Флаг запроса на вывод экрана в файл int outflag; // Флаг начала вывода в файл int name_counter; // Номер текущего выводимого файла char _far *crit; // Адрес флага критической секции DOS
// =======================================
void main(void); void main(void) {
union REGS inregs, outregs; struct SREGS segregs;
unsigned size; // Размер резидентной части // TSR-программы
// Вызываем прерывание мультиплексора с AX = FF00 // Если программа GRAB уже запускалась, то новый // обработчик прерывания мультиплексора вернет // в регистре AX значение 00FF. // Таким способом мы избегаем повторного изменения // содержимого векторной таблицы прерываний.
inregs.x.ax = 0xff00; int86(0x2f, &inregs, &outregs);
if(outregs.x.ax == 0x00ff) { printf("\nПрограмма GRAB уже загружена\n"); hello(); exit(-1); }
// Выдаем инструкцию по работе с программой GRAB
hello();
// Вычисляем размер программы в параграфах // Добавляем 1 параграф на случай // некратной параграфу длины
size = (12000 >> 4) + 1;
// Устанавливаем начальные значения флагов
outflag=iniflag=0;
// Сбрасываем счетчик файлов. Первый файл будет // иметь имя GRAB0.DOC. В дальнейшем этот счетчик // будет увеличивать свое значение на 1.
name_counter=0;
// Получаем указатель на флаг критической секции DOS. // Когда этот флаг равен 0, TSR-программа может // пользоваться функциями DOS
inregs.h.ah = 0x34; intdosx( &inregs, &outregs, &segregs ); crit=(char _far *)FP_MAKE(segregs.es,outregs.x.bx);
// Устанавливаем собственные обработчики прерываний.
old9 = _dos_getvect(0x9); _dos_setvect(0x9, new9);
old8 = _dos_getvect(0x8); _dos_setvect(0x8, new8);
old28 = _dos_getvect(0x28); _dos_setvect(0x28, new28);
old2f = _dos_getvect(0x2f); _dos_setvect(0x2f, new2f);
// Завершаем программу и остаемся в памяти
_dos_keep(0, size); }
// =======================================
// Новый обработчик прерывания мультиплексора. // Используется для предохранения программы // от повторного встраивания в систему как резидентной.
void _interrupt _far new2f(unsigned _es, unsigned _ds, unsigned _di, unsigned _si, unsigned _bp, unsigned _sp, unsigned _bx, unsigned _dx, unsigned _cx, unsigned _ax, unsigned _ip, unsigned _cs, unsigned flags) { // Если прерывание вызвано с содержимым // регистра AX, равным FF00, возвращаем // в регистре AX значение 00FF, // в противном случае передаем управление // старому обработчику прерывания
if(_ax != 0xff00) _chain_intr(old2f); else _ax = 0x00ff;
}
// =======================================
// Новый обработчик аппаратного прерывания таймера
void _interrupt _far new8(void) {
// Вызываем старый обработчик
(*old8)();
// Если была нажата комбинация клавиш Ctrl+PrtSc // (iniflag при этом устанавливается в 1 // новым обработчиком прерывания 9) и // если запись в файл уже не началась, // то при значении флага критической секции // DOS, равном 0, выводим содержимое экрана // в файл
if((iniflag != 0) && (outflag == 0) && *crit == 0) {
outflag=1; // Устанавливаем флаг начала вывода _enable(); // Разрешаем прерывания
write_buf(); // Записываем содержимое // буфера экрана в файл
outflag=0; // Сбрасываем флаги в исходное iniflag=0; // состояние } }
// =======================================
// Новый обработчик прерывания 28h, которое вызывает // DOS, если она ожидает ввода от клавиатуры. // В этот момент TSR-программа может пользоваться // функциями DOS.
void _interrupt _far new28(void) {
// Если была нажата комбинация клавиш Ctrl+PrtSc // (iniflag при этом устанавливается в 1 // новым обработчиком прерывания 9) и // если уже не началась запись в файл, // то выводим содержимое экрана в файл
if((iniflag != 0) && (outflag == 0)) {
outflag=1; // Устанавливаем флаг начала вывода _enable(); // Разрешаем прерывания
write_buf(); // Записываем содержимое видеобуфера // в файл
outflag=0; // Сбрасываем флаги в исходное iniflag=0; // состояние }
// Передаем управление старому обработчику // прерывания 28
_chain_intr(old28); }
// =======================================
// Новый обработчик клавиатурного прерывания. // Он фиксирует нажатие комбинации клавиш Ctrl+PrtSc // и устанавливает флаг iniflag, который сигнализирует // о необходимости выбрать подходящий момент и // записать содержимое видеобуфера в файл
void _interrupt _far new9(void) {
// Если SCAN-код равен 0x37 (клавиша PrtSc), // нажата клавиша Ctrl (бит 4 байта состояния // клавиатуры, находящийся в области данных // BIOS по адресу 0040:0017 установлен в 1) // и если не установлен флаг iniflag, // то устанавливаем флаг iniflag в 1.
if((inp(0x60) == 0x37) && (iniflag == 0) && (*(char _far *)FP_MAKE(0x40,0x17) & 4) != 0) {
// Выдаем звуковой сигнал
BEEP(); BEEP(); BEEP();
_disable(); // Запрещаем прерывания
// Разблокируем клавиатуру // и разрешаем прерывания
_asm { in al,61h mov ah,al or al,80h out 61h,al xchg ah,al out 61h,al
mov al,20h out 20h,al }
// Устанавливаем флаг запроса // на запись содержимого видеобуфера // в файл
iniflag = 1; _enable(); // Разрешаем прерывания }
// Если нажали не Ctrl+PrtSc, то // передаем управление старому // обработчику прерывания 9
else _chain_intr(old9);
}
// =======================================
// Функция возвращает номер // текущего видеорежима
int get_vmode(void) {
char _far *ptr;
ptr = FP_MAKE(0x40,0x49); // Указатель на байт // текущего видеорежима return(*ptr); }
// =======================================
// Функция возвращает сегментный адрес // видеобуфера. Учитывается содержимое // регистров начального адреса видеобуфера.
int get_vbuf(int vmode) {
unsigned vbase; unsigned adr_6845; unsigned high; unsigned low; unsigned offs;
// В зависимости от видеорежима базовый адрес // видеобуфера может быть 0xb000 или 0xb800
vbase = (vmode == 7) ? 0xb000 : 0xb800;
// получаем адрес порта контроллера ЭЛТ
adr_6845 = *(unsigned _far *)(FP_MAKE(0x40,0x63));
// Считываем содержимое регистров 12 и 13 // контроллера ЭЛТ (регистров начального адреса)
outp(adr_6845,0xc); high = inp(adr_6845+1);
outp(adr_6845,0xd); low = inp(adr_6845+1);
offs = ((high << 8) + low) >> 4;
// Добавляем к базовому адресу видеобуфера // смещение, взятое из регистров видеоконтроллера
vbase += offs;
return(vbase);
}
// =======================================
// Функция возвращает количество символов в строке // для текущего видеорежима
int get_column(void) {
return(*(int _far *)(FP_MAKE(0x40,0x4a))); }
// =======================================
// Функция возвращает количество строк // для текущего видеорежима
int get_row(void) {
unsigned char ega_info;
ega_info = *(unsigned char _far *)(FP_MAKE(0x40,0x87));
// Если нет EGA, то используется 25 строк, // если EGA присутствует, считываем число // строк. Это число находится в области данных // BIOS по адресу 0040:0084.
if(ega_info == 0 ( (ega_info & 8) != 0) ) { return(25); } else { return(*(unsigned char _far *) (FP_MAKE(0x40,0x84)) + 1); }
}
// =======================================
// Функция записи содержимого видеобуфера в // файл
int write_buf(void) {
// Видеопамять состоит из байтов символов и байтов // атрибутов. Нам нужны байты символов chr.
typedef struct _VIDEOBUF_ { unsigned char chr; unsigned char attr; } VIDEOBUF;
VIDEOBUF _far *vbuf; int i, j, k, max_col, max_row; FILE *out_file; char fname[20],ext[8];
i=get_vmode(); // Получаем номер текущего // видеорежима
// Для графического режима ничего не записываем
if(i > 3 && i != 7) return(-1);
// Устанавливаем указатель vbuf на видеобуфер
vbuf=(VIDEOBUF _far *)FP_MAKE(get_vbuf(i),0);
// Определяем размеры экрана
max_col = get_column(); max_row = get_row();
// Формируем имя файла для записи образа экрана
itoa(name_counter++,ext,10); strcpy(fname,"!grab"); strcat(fname,ext); strcat(fname,".doc");
out_file=fopen(fname,"wb+");
// Записываем содержимое видеобуфера в файл
for(i=0; i<max_row; i++) { for(j=0; j<max_col; j++) {
fputc(vbuf->chr,out_file); vbuf++;
}
// В конце каждой строки добавляем // символы перевода строки и // возврата каретки
fputc(0xd,out_file); fputc(0xa,out_file); } fclose(out_file); return(0); }
// =======================================
// Функция выводит на экран инструкцию по // использованию программы GRAB
int hello(void) { printf("\nУтилита копирования содержимого" "\nэкрана в файл GRAB<n>.DOC" "\nCopyright (C)Frolov A.,1990" "\n" "\nДля копирования нажмите Ctrl+PrtSc" "\n"); }
Содержание раздела