5.9.2. Сложные приемы программирования

сегментный адрес видеопамяти.

0B800h

es

0040h

ds

di.word ptr ds:001Ah di.word ptr ds:0O1Ch exit 09h handler

ax,word ptr [di]

byte ptr

already_active .

ah,1Eh

exit_09h_handler

ptr

al,08h

exit_09h_handler

word ptr ds:001Ch,di

save_screen

cs

ds

display_all

byte ptr we_are_active, l

short exit_09h_handler

ES

DS = сегментный адрес области данных BIOS. Адрес головы буфера клавиатуры. Если он равен адресу хвоста, буфер пуст и нам делать нечего (например если прерывание пришло по

отпусканию клавиши).

Иначе:   считать символ из головы буфера.

Если программа уже активизирована - перейти

к обработке стрелок и т.п.

Если прочитанная клавиша не А (скан-код 1Eh> - выйти.

Иначе: считать байт

состояния клавиатуры.

Если не нажата любая Alt,

выйти.

Иначе:   установить адреса

головы и хвоста буфера одинаковыми,

пометив его тем самым как пустой.

Сохранить область экрана,

накроет всплывающее окно.

которую

Р8 = наш сегментный адрес. Вывести на экран окно программы.

Установить флаг

и выйти из обработчика.

Сюда передается управление,   если программа уже активизирована.

При этом Е8 = 0В800ґі,   Р8 = 0040п,   И = адрес головы буфера клавиатуры,

АХ = символ из головы буфера.

already_active:

mov

push

pop

mov mov

word ptr

cs ds

Установить   адреса ; головы и хвоста буфера одинаковыми, пометив ,его тем самым как пустой.

DS

наш сегментный адрес.

cmp jne

sub

аі.ап ;   Команды стр аі,  ? короче команд стр аЬ, ?,.

ЬП,ЬуТе рбг сиггепб_сРаг  ; Номер выделенного в данный момент

; А8Ш-символа.

а1,48Ь ;   Если нажата стрелка вверх  (скан-код 48Ь),

поб_ир

Ьґі, 16 ; уменьшить номер символа на 16.

not_up: ж

cmp

al,50h

; Если нажата стрелка вниз (скан-код

jne

not_down

 

add

bh, 16

; увеличить номер символа на 16.

cmp

al,4Bh

Если нажата стрелка влево,

jne

not_left

 

dec

bh

; уменьшить номер символа на.1.

cmp

al,4Dh

; Если нажата -стрелка вправо,

jne

not_right

 

inc

t:

bh

увеличить номер символа на 1.

not down:

not left:

not_right:

cmp al,1Ch

je enter_pressed

dec al

jnz exit_with_display exit_after_enter:

call restore_screen

mov byte ptr we_are_active,0

jmp short exitJ9h_handler

exit_with_display:

call

exit_09h_handler:

pop es pop ds popa Iret

db db

byte ptr display_all

0

37h

Если нажата Enter   (скан-код 1Ch),

перейти к его обработчику.

Если не нажата клавиша Esc  (скан-код 1),

выйти из обработчика, оставив

окно нашей программы на экране.

Иначе:

убрать наше окно с экрана, обнулить флаг активности, выйти из обработчика..

Выход с сохранением окна (после нажатия стрелок). Записать новое значение текущего символа. Перерисовать окно.

Выход из обработчика INT

Восстановить регистры •

и вернуться в прерванную программу.

Флаг активности: равен 1, если

программа активна.

Номер ASCII-символа, выделенного

в данный момент.

; Сюда передается управление, если в активном состоянии была нажата Enter. enter_pressed:

mov ah,05h

mov ch,0

mov cl.byte ptr current_char

int 16h

jmp short exit_after_enter

Функция 05п СН = 0

С! = АЗСП-код

Поместить символ в буфер клавиатуры. Выйти из обработчика, стерев окно.

Процедура save_screeп.

Сохраняет в буфере screen_buffer содержимое области экрана, которую закроет наше окно.

Сложные

save_screen mov

push

pop push push

pop mov mov

save_screen_loop

mov rep add dec jnz pop ret

save screen

proc

si,START_POSITION 0B800h

-ds es cs es

di,offsetscreen_buffer dx, 18

ЗЗ

movsw

si,(80-33)*2 dx

s,ave_screen_loop

es

endp

DS:SI - начало в видеопамяти.

этой области

ES:DI - начало буфера

ОХ = счетчик строк.

в программе.

СХ = счетчик символов в строке. Скопироватв строку с экрана в буфер. Увеличитв Б1 до начала следующей строки. Уменьшить счетчик строк. Если он не ноль - продолжить цикл.

Процедура гезРоге.зсгееп.

Восстанавливает содержимое области экрана, которую закрывало всплывающее окно данными из буфера всгееп_Ьиггег.

наше

restore.

.screen

proc near

 

mov

di,START POSITION

 

mov

si,offsetscreen_buffer

 

mov

dx,18

restore.

_screen_

.loop:

 

mov

ЗЗ

 

rep

movsw

 

add

di,(80-33).2

 

dec

dx

 

jnz

resto re_sc reen_loop

 

ret

 

restore

screen

endp

ES:OI - начало области DS:SI - начало буфера.

Счетчик строк.

в видеопамяти.

Счетчик символов в строке.

Скопировать строку.

Увеличить га до начала следующей строки. Уменьшить счетчик строк. Если он не ноль - продолжить.

; Процедура display_all.

; Выводит на экран текущее состояние всплывающего окна нашей программы.

display_all     proc near

; Шаг 1:  вписать значение текущего выделенного байта в нижнюю строку окна.

mov

ptr

. М = выбранный байт.

push

■ ах

 

shr

al,4

; Старшие четыре байта.

cmp

al, 10

; Три команды,

sbb

al,69h

; преобразующие цифру в АЬ

das

 

;   в ее АЭСП-код (0 - 9, А

mov

byte ptr hex_byte1,al

; Записать символ на его

 

 

; место в нижней строке.

pop

ax

 

F) .

and al.OFh . ; Младшие четыре бита,

cmp al,10 ; То же преобразование,

sbb al,69h

das

"   mov byte ptr hex_byte2,al    ' ; Записать  младшую цифру.

Шаг 2:  вывод на экран окна.  Было бы проще хранить его как массив и выводить ; командой movsw, как и буфер в процедуре restore.screen, но такой массив займет еще

; 1190 байт в резидентной части. Код этой части процедуры display_all - всего 69 байт. ; Шаг 2.1:  вывод первой строки.

mov ah,1Fh ■      ; Атрибут белый на синем.

mov di,START_POSITION ; ES:OI - адрес в видеопамяти.

mov si,offset display_line1    : DS:SI - адрес строки.

mov cx,33 ; Счетчик символов в строке. display_loop1:.

mov al.byte ptr  [si] : Прочитатв символ в AL

stosw ; и вывести его с атрибутом из АН.

inc si Увеличить адрес символа в строке.

loop display_loop1

; Шаг 2.2: вывод собственно таблицы

mov dx,16 Счетчик строк.

mov al,-і ; Выводимый символ.

Цикл по строкам.

add di,(80-33)*2 ; Увеличитв DI до начала

push ах ; следующей строки,

mov al,0B3h

stosw ; Вывести первый символ (ОВЗп).

pop ax

mov сх,16 ; Счетчик символов в строке,

display ІоорЗ: ; Цикл по символам в строке,

inc al ; Следующий ASCII-символ.

stosw ; Вывести его на экран.

push ax

mov al,20h Вывести пробел.

stosw

pop ax

loop display_loop3 ; И так 16 раз.

push ax

sub di,2 ; Вернутися назад на 1 символ

mov al,OB3h ; и вывести OB3h на месте

stosw ; последнего пробела.

pop ax

dec dx Уменьшить счетчик строк.

jnz display_loop4

Шаг 2.3: вывод последней

add di,(80-33)*2 ; Увеличить DI до начала следующей строки.

mov сх, 33 ; Счетчик символов в строке,

mov si, offset display_line2   ; DS:SI - адрес строки.

display_loop2: mov

stosw

inc loop

З:

ptr [si]

si

display_loop2

Прочитать символ в АЬ.

Вывести его с атрибутом на экран..

Увеличить адрес символа в строке.

Шаг

подсветка  (изменение атрибута) у текущего выделенного символа.

mov mov mov

and

shl

shr imul

add add

mov

stosb

ptr

ah,0 di.ax di.OFh di,2

; AL

текущий символ.

DI = остаток его

используется

строке).

от деления на 16 (номер в на 2,  так как на экране слово на символ,  и еще раз на 2, так как между символами - пробелы. : АХ = частное от деления на 16  (номер строки). Умножить его на длину строки на экране, сложить результаты, сЛ,БТАВТ_Р051Т10Н+2+80*2+1 ; добавитв адрес начала окна + 2, ; чтобы пропуститв первый столбец,  + 80 х 2, ; чтобы пропустить первую строку,  + 1, чтобы получить адрес атрибута,  а не символа. Атрибут - синий на сером. Вывод на экран.

ах, 4 ах,ах, di, ах

al,071h

ret

display_all

int09h "handler endp endp

Конец обработчика INT 09h.

;

Буфер для хранения содержимого части экрана,  которая накрывается нашим окном.

screen_buffer

db

; Первая строка окна. display_line1 db

Последняя строка окна. display_line2 db hex_byte1 db hex_byte2 db

db

1190 dup(?) ODAh,11 dup <0C4h),

ASCII dup

OCOh,11 dup (0C4h),'* Hex "

? ;  Старшая цифра текущего байта.

? ;  Младшая цифра текущего байта.

" *".10 dup (0C4h),0D9h

reset2D:

retf

ISP: минимальный hw reset.

; Обработчик прерывания INT 2Dh.

;  Поддерживает функции AMIS 3.6 00h, 02h, 03h,  04h и 05h.

int2Dh_handler

jmp old int2Dh

jmp proc far

short actual_int2Dh_handler

dd ?

dw 424Bh

db OOh

short hw_reset2D

db 7 dup (0)

ISP: пропустить блок.

ISP: старый обработчик.

ISP: сигнатура.' 1

ISP: программное прерывание.

ISP: ближний jmp на hw_reset.

ISP: зарезервировано.

;

actual_int2Dh. mux_id

je jmp

its_us:

cmp

jae cbw mov shl

jmp jumptable

int2D_00:

mov mov

push

pop mov

iret

int2D_no:

mov і ret

int2D_02:

mov mov mov

iret

int2D_03:

cmp je

call

push

pop

call

mov

already_popup

mov

iret

int2D_04:

mov mov

iret

handler:

db 80h,OFCh

db ?

its_us

dword ptr cs:old_int2Dh

Начало собственно обработчика INT 2Dh. Начало команды CMP АН, число. Идентификатор программы. Если вызывают с чужим АН - это не нас.

al,06 ; Функции 06h и выше

int2D_no ; не поддерживаются. '

; АХ = номер функции.

DI = номер функции. di,1 Умножить его на 2, так как jumptable. -

таблица слов.

word ptr cs:jumptable[di] Косвенный переход на обработчики функций, dw offset int2D_00,offset int2D_no

dw offset int2D_02,offset int2D_03

dw offset int2D 04,offset lnt2D_05

al.OFFh cx,0100h

cs dx

di, offset amis_sign

; Проверка наличия.

; ЭтОт номер занят.

; Номер версии 1.0.

; РХ:Ю - адрес АМ15-сигнатуры.

; Неподдерживаемая функция.

al.OOh ; Функция не поддерживается.

; Выгрузка программы.

byte ptr cs:disable_point,OCFh ;   Записати код команды IRET

;   по адресу disable_point в обработчик INT 09h.

al,04h ; Программа дезактивизирована, но сама

; выгрузиться не может.

bx.cs ; ВХ - сегментный адрес программы.

;  Запрос на активизацию для. "всплывающих" программ, byte ptr we_are_active, 0  ;   Если окно не на экране, already_popup

save_screen ;  сохранить область экрана,

cs

ds

display_all ; вывести окно

byte ptr we_are_active, 1 ; и поднять флаг.

al,03h ;  Код 03: программа активизирована.

; Получить список перехваченных прерываний. ; Список в DX:BX.

bx, offset amis_hooklist

int2D 05:

mov mov mov і ret int2Dh handler

al.OFFh

cs

bx,offset amis_hotkeys endp

Получить список "горячих" клавиш. Функция поддерживается.

Список в DX:BX.

;   AMIS:   сигнатура для резидентных программ.

amis_sign db "Cubbi..." ;   8 байт - имя автора.

db "ASCII..." ;   8 байт - имя программы.

db "ASCII display and input utility",0 ; ASCIZ-комментарий

; не более 64 байт.

;   AMIS:   список перехваченных прерываний. amis_hooklist      db 09h

dw offset int09hjiandler

db 2Dh

dw offset int2Dh .handler

; AMIS: список

amis_hotkeys

"горячих"

db

db db dw dw db клавиш. 01h

1

lEh

08h

0

1

Клавиши проверяются после стандартного

обработчика  INT 09h.

Число ' клавиш.

Скан-код клавиши (А).1

Требуемые флаги   (любая Alt).

Запрещенные флаги.

Клавиша проглатывается.

Конец резидентной части. Начало процедуры инициализации.

initialize

mov mov

int near

ah,9

dx,offset

21h usage ; Вывести информацию о программе.

;  Проверить, не установлена ли уже наша программа.

more mux:

ah,-1

mov

int 2Dh ' cmp al.OOh

jne not_free

mov       byte ptr mux_id,ah

jmp short' nextjnux

not free:

mov mov mov repe

jcxz alreadyjoaded

Сканирование номеров от OFFh до 00h.

Функция OOh - проверка наличия программы.

прерывание AMIS. Если   идентификатор свободен,

записать его номер прямо в код

обработчика int 2Dh.

Иначе - Е8:В1 = адрес их сигнатуры,

= адрес нашей сигнатуры. Сравнить первые 16 байт.

Если они не совпадают,т

идентификатору,

nextjnux:

dec        ah ;   перейти к следующему

jnz       more_mux ;  пока это не О

;   (на самом деле в нашем примере сканирование происходит от OFFh до 01h, •

;  так как 0 мы используем в качестве признака отсутствия свободного номера

;   в следующей строке).

free mux found:

cmp

byte ptr mux_id,0

Если мы ничего не записали,

je

no_more_mux

идентификаторы кончились.

mov

ax,352Dh

 

AH = 35h, AL = номер прерывания.

int

21h

 

; Получить адрес обработчика INT 2

mov

word ptr

old_int2Dh,bx

и поместить его в old_int2Dh.

mov

word ptr

old_int2Dh+2,es

 

mov

ax,3509b

 

АН = 35h,   AL = номер прерывания.

int

21h

 

; Получитв адрес обработчика INT 0

mov

word ptr

old_int09h,bx

и                его в

mov

word ptr

old_int09h+2,es

 

mov

ax,252Dh

 

АН =             = номер прерывания.

mov

dx,offset

handler

DS:DX - адрес нашего

int

21h

 

обработчика.

mov

ax,2509b

 

АН = 25h,  AL     номер прерывания.

mov

dx,offset

int09h_handler

; DS:DX - адрес нашего

int

21h

 

обработчика.

mov

ah,49h

 

; АН = 49h.

mov

ptr envseg

ES = сегментный адрес среды DOS.

int

21h

 

Освободить память.

mov

ah,9

 

 

mov

dx,offset

installed_msg

Вывод строки об успешной

int

21h

 

; инсталляции.

mov

dx,offset

initialize

DX - адрес первого байта за

 

 

 

концом резидентной части.

int

27h

 

Завершить выполнение, оставшись

;  Сюда передается управление, если alreadyjoaded:

mov.     ah, 9 ;

mov ;

int 21h

ret ;

резидентом.

наша программа обнаружена

AH = 09h

Вывести сообщение об ошибке и завершиться нормально.

;   Сюда передается управление,  если все 255 функций мультиплексора заняты

; резидентными программами.

no_more_mux:

mov        ah,9

mov       dx,offset no_more_mux_msg

int 21h • ret

в

памяти.

; Текст,  который выдает программа при запуске:

usage db "ASCII display and input program"

db " v1,O",0Dh,0Ah

db ' "Alt-A    - активизация",ODh.OAh

db "Стрелки - выбор символа",ODh.OAh

db "Enter     - ввод символа",ODh.OAh

db "Escape   - выход",ODh.OAh

db "$"

;  Текст,  который выдает программа,  если она уже загружена:

alreadyjnsg        db "Ошибка:  программа уже загружена",ODh,OAh, '$'

;  Текст,  который выдает программа,  если все функции мультиплексора заняты:

no_more_mux_msg   db "Ошибка:  Слишком много резидентных программ" :

db ODh.OAh,'$'

; Текст,  который выдает программа при успешной установке:

installedjnsg     db "Программа загружена в память", ODh, OAh, '$'

initialize endp end start

Резидентная часть этой программы занимает в памяти целых 2064 байта, из которых на собственно коды команд приходится только 436. Это вполне терпимо, учитывая, что обычно программа вроде ascii.com запускается перед простыми тек­стовыми редакторами для DOS (edit, multiedit, встроенные редакторы оболочек типа Norton Commander и т. д.), которые не требуют для своей работы полностью свободной памяти. В других случаях, как, например, при создании программы, ко­пирующей изображение с экрана в файл, может оказаться, что на счету Каждый байт. Такие программы часто применяют для сохранения изображений из компь­ютерных игр, которые задействуют все ресурсы компьютера по максимуму. Здесь резидентным программам приходится размещать данные, а иногда и кода в старших областях памяти, пользуясь спецификациями НМА, UMB, EMS или

XMS. В следующей главе рассмотрен простой пример именно такой программы.