Отображение цветовых слоев.
Рисунок 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"); }
Содержание раздела