Программирование видеоадаптеров CGA,EGA и VGA

       

Отображение цветовых слоев.



Рисунок 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"); }



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