- •Глава 3. Директивы и операторы ассемблера
- •3.1. Структура программы
- •3.2. Директивы распределения памяти
- •3.2.1. Псевдокоманды определения переменных
- •3.2. Директивы распределения памяти
- •3.2.1. Псевдокоманды определения переменных
- •3.2.2. Структуры
- •3.3. Организация программы
- •3.3.1. Сегменты
- •3.2. Директивы распределения памяти
- •3.2.1. Псевдокоманды определения переменных
- •3.2.2. Структуры
- •3.3. Организация программы
- •3.3.1. Сегменты
- •3.3.2. Модели памяти и упрощенные директивы определения сегментов
- •3.3.3. Порядок загрузки сегментов
- •3.3.4. Процедуры
- •3.3.5. Конец программы
- •3.3.6. Директивы задания набора допустимых команд
- •3.3.7. Директивы управления программным счетчиком
- •3.3.8. Глобальные объявления
- •3.3.9. Условное ассемблирование
- •3.4. Выражения
- •3.5. Макроопределения
- •3.6. Другие директивы
- •3.6.1. Управление файлами
- •4. Основы программирования для ms-dos
- •4.1. Программа типа сом
- •4.2. Программа типа ехе
- •4.3. Вывод на экран в текстовом режиме
- •4.3.1. Средства dos
- •4.4. Ввод с клавиатуры
- •4.4.1. Средства dos
- •4.5. Графические видеорежимы
- •4.5.1. Работа с vga-режимами
- •4.6. Работа с мышью
- •4.7. Другие устройства
- •4.7.1. Системный таймер
- •4.8. Работа с файлами
- •4.9. Управление памятью
- •4.9.1. Обычная память
- •4.10. Загрузка и выполнение программ
- •4.11. Командные параметры и переменные среды
4.6. Работа с мышью
Все общение с мышью в DOS выполняется через прерывание 33h, обработчик которого устанавливает драйвер мыши, загружаемый обычно при запуске системы. Современные драйверы поддерживают около 60 функций, позволяющих настраивать разрешение мыши, профили ускорений, виртуальные координаты, настраивать дополнительные обработчики событий и т.п. Большинство этих функций требуются редко, сейчас рассмотрим основные:
INT 33h, AX = 0— Инициализация мыши
Ввод: |
AX = 0000h |
Вывод: |
АХ = 0000h, если мышь или драйвер мыши не установлены АХ = FFFFh, если драйвер и мышь установлены ВХ = число кнопок: 0002 или FFFF — две 0003 — три 0000 — другое количество |
Выполняется аппаратный и программный сброс мыши и драйвера.
INT 33h, AX = 1— Показать курсор
Ввод: |
AX = 0001h |
INT 33h, AX = 2— Спрятать курсор
Ввод: |
AX = 0002h |
Драйвер мыши поддерживает внутренний счетчик, управляющий видимостью курсора мыши. Функция 2 уменьшает значение счетчика на единицу, а функция 1 увеличивает его, но только до значения 0. Если значение счетчика — отрицательное число, он спрятан, если ноль — показан. Это позволяет процедурам, использующим прямой вывод в видеопамять, вызывать функцию 2 в самом начале и 1 — в самом конце, не заботясь о том, в каком состоянии был курсор мыши у вызвавшей эту процедуру программы.
INT 33h, AX = 3 — Определить состояние мыши
Ввод: |
AX = 0003h |
Вывод: |
ВХ = состояние кнопок: бит 0 — нажата левая кнопка бит 1 — нажата правая кнопка бит 2 — нажата средняя кнопка СХ = Х-координата DX = Y-координата |
Возвращаемые координаты совпадают с пиксельными координатами соответствующей точки на экране в большинстве графических режимов, кроме 04, 05, 0Dh, 13h, в которых Х-координату мыши нужно разделить на 2, чтобы получить номер столбца соответствующей точки на экране. В текстовых режимах обе координаты надо разделить на 8, чтобы получить номер строки и столбца соответственно.
В большинстве случаев эта функция не используется в программах, так как для того, чтобы реагировать на нажатие кнопки или перемещение мыши в заданную область, требуется вызывать это прерывание постоянно, что приводит к трате процессорного времени. Функции 5 (определить положение курсора при последнем нажатии кнопки), 6 (определить положение курсора при последнем отпускании кнопки) и 0Bh (определить расстояние, пройденное мышью) могут помочь оптимизировать работу программы, самостоятельно следящей за всеми передвижениями мыши, но гораздо эффективнее указать драйверу самому следить за ее передвижениями (чем он, собственно, и занимается постоянно) и передавать управление в программу, как только выполнится заранее определенное условие, например пользователь нажмет на левую кнопку мыши. Такой сервис обеспечивает функция 0Ch — установить обработчик событий.
INT 33h, AX = 0Ch— Установить обработчик событий
Ввод: |
AX = 000Ch ES:DX = адрес обработчика СХ = условие вызова бит 0 — любое перемещение мыши бит 1 — нажатие левой кнопки бит 2 — отпускание левой кнопки бит 3 — нажатие правой кнопки бит 4 — отпускание правой кнопки бит 5 — нажатие средней кнопки бит 6 — отпускание средней кнопки СХ = 0000h — отменить обработчик |
Обработчик событий должен быть оформлен, как дальняя процедура (то есть завершаться командой RETF). На входе в процедуру обработчика АХ содержит условие вызова, ВХ — состояние кнопок, СХ, DX — X- и Y-координаты курсора, SI, DI — счетчики последнего перемещения по горизонтали и вертикали (единицы измерения для этих счетчиков — мики, 1/200 дюйма), DS — сегмент данных драйвера мыши. Перед завершением программы установленный обработчик событий должен быть обязательно удален (вызов функции 0Ch с СХ = 0), так как иначе при первом же выполнении условия управление будет передано по адресу в памяти, с которого начинался обработчик.
Функция 0Ch используется так часто, что у нее появилось несколько модификаций — функция 14h, позволяющая установить одновременно три обработчика с разными условиями, и функция 18h, также позволяющая установить три обработчика и включающая в условие вызова состояние клавиш Shift, Ctrl и Alt. Воспользуемся обычной функцией 0Ch, чтобы написать простую программу для рисования.
; mousedr.asm
; Рисует на экране прямые линии с концами в позициях, указываемых мышью
;
.model tiny
.code
org 100h ; СОМ-файл
.186 ; для команды shr cx,3
start:
mov ax,12h
int 10h ; видеорежим 640x480
mov ax,0 ; инициализировать мышь
int 33h
mov ax,1 ; показать курсор мыши
int 33h
mov ax,000Ch ; установить обработчик событий мыши
mov cx,0002h ; событие - нажатие левой кнопки
mov dx,offset handler ; ES:DX - адрес обработчика
int 33h
mov ah,0 ; ожидание нажатия любой клавиши
int 16h
mov ax,000Ch
mov cx,0000h ; удалить обработчик событий мыши
int 33h
mov ax,3 ; текстовый режим
int 10h
ret ; конец программы
; Обработчик событий мыши: при первом нажатии выводит точку на экран,
; при каждом дальнейшем вызове проводит прямую линию от предыдущей
; точки к текущей
handler:
push 0A000h
pop es ; ES - начало видеопамяти
push cs
pop ds ; DS - сегмент кода и данных этой программы
push сх ; СХ (Х-координата) и
push dx ; DX (Y-координата) потребуются в конце
mov ax, 2 ; спрятать курсор мыши перед выводом на экран
int 33h
cmp word ptr previous_X,-1 ; если это первый вызов,
je first_point ; только вывести точку,
call line_bresenham ; иначе - провести прямую
exit_handler:
pop dx ; восстановить СХ и DX
pop сх
mov previous_X,cx ; и запомнить их как предыдущие
mov previous_Y,dx ; координаты
mov ax,1 ; показать курсор мыши
int 33h
retf ; выход из обработчика - команда RETF
first_point:
call putpixel1b ; вывод одной точки (при первом вызове)
jmp short exit_handler
; Процедура рисования прямой линии с использованием алгоритма Брезенхама
; Ввод: СХ, DX - X, Y конечной точки
; previous_X,previous_Y - X, Y начальной точки
line_bresenham:
mov ax, сх
sub ax,previous_X ; AX = длина проекции прямой на ось X
jns dx_pos ; если АХ отрицательный -
neg ax ; сменить его знак, причем
mov word ptr X_increment,1 ; координата X при выводе
jmp short dx_neg ; прямой будет расти,
dx_pos: mov word ptr X_increment,-1 ; а иначе - уменьшаться
dx_neg: mov bx,dx
sub bx,previous_Y ; BX = длина проекции прямой на ось Y
jns dy_pos ; если ВХ отрицательный -
neg bx ; сменить его знак, причем
mov word ptr Y_increment,1 ; координата Y при выводе
jmp short dy_neg ; прямой будет расти,
dy_pos: mov word ptr Y_increment,-1 ; а иначе - уменьшаться
dy_neg: shl ax,1 ; удвоить значения проекций,
shl bx,1 ; чтобы избежать работы с полуцелыми числами
call putpixel1b ; вывести первую точку (прямая рисуется от
; CX,DX к previous_X,previous_Y)
cmp ax,bx ; если проекция на ось X больше, чем на Y:
jna dx_le_dy
mov di,ax ; DI будет указывать, в какую сторону мы
shr di,1 ; отклонились от идеальной прямой
neg di ; оптимальное начальное значение DI:
add di,bx ; DI = 2 * dy - dx
cycle:
cmp ex,word ptr previous_X ; основной цикл выполняется,
je exit_bres ; пока Х не станет равно previous_X
cmp di,0 ; если DI > 0,
jl fractlt0
add dx,word ptr Y_increment ; перейти к следующему Y
sub di,ax ; и уменьшить DI на 2 * dx
fractlt0:
add cx,word ptr X_increment ; следующий Х (на каждом шаге)
add di,bx ; увеличить DI на 2 * dy
call putpixel1b ; вывести точку
jmp short cycle ; продолжить цикл
dx_le_dy: ; если проекция на ось Y больше, чем на X
mov di,bx
shr di,1
neg di ; оптимальное начальное значение DI:
add di,ax ; DI = 2 * dx - dy
cycle2:
cmp dx,word ptr previous_Y ; основной цикл выполняется,
je exit_bres ; пока Y не станет равным previous_Y,
cmp di,0 ; если DI > 0,
jl fractlt02
add cx,word ptr X_increment ; перейти к следующему X
sub di,bx ; и уменьшить DI на 2 * dy
fractlt02:
add dx,word ptr Y_increment ; следующий Y (на каждом шаге)
add di,ax ; увеличить DI на 2 * dy
call putpixel1b ; вывести точку
jmp short cycle2 ; продолжить цикл
exit_bres:
ret ; конец процедуры
; Процедура вывода точки на экран в режиме, использующем один бит для
; хранения одного пикселя.
; DХ = строка, СХ = столбец
; Все регистры сохраняются
putpixel1b:
pusha ; сохранить регистры
xor bx,bx
mov ax,dx ; AX = номер строки
imul ах,ах,80 ; АХ = номер строки * число байт в строке
push cx
shr сх,3 ; СХ = номер байта в строке
add ах,сх ; АХ = номер байта в видеопамяти
mov di,ax ; поместить его в SI и DI для команд
mov si,di ; строковой обработки
pop cx ; СХ снова содержит номер столбца
mov bx,0080h
and cx,07h ; последние три бита СХ =
; остаток от деления на 8 = номер бита в байте, считая справа налево
shr bx,cl ; теперь в BL установлен в 1 нужный бит
lods es:byte ptr some_label ; AL = байт из видеопамяти
or ax,bx ; установить выводимый бит в 1,
; чтобы стереть пиксель с экрана, эту команду OR можно заменить на
; not bx
; and ax,bx
; или лучше инициализировать ВХ не числом 0080h, а числом FF7Fh и использовать
; только and
stosb ; и вернуть байт на место
рора ; восстановить регистры
ret ; конец
previous_X dw -1 ; предыдущая Х-координата
previous_Y dw -1 ; предыдущая Y-координата
Y_increment dw -1 ; направление изменения Y
X_increment dw -1 ; направление изменения X
some_label: ; метка, используемая для переопределения
; сегмента-источника для lods с DS на ES
end start
Алгоритм Брезенхама, использованный в этой программе, является самым распространенным алгоритмом рисования прямой. Существуют, конечно, и более эффективные алгоритмы, например алгоритм Цаолинь By, работающий на принципе конечного автомата, но алгоритм Брезенхама стал стандартом де-факто.
Примечание: Приведенную реализацию этого алгоритма можно значительно ускорить, использовав самомодифицирующийся код, то есть после проверки на направление прямой в начале алгоритма вписать прямо в дальнейший текст программы команды INС СХ, DEC CX, INС DX и DEC DX вместо команд сложения этих регистров с переменными X_increment и Y_increment. Самомодифицирующийся код часто применяется при программировании для DOS, но во многих многозадачных системах текст программы загружается в область памяти, защищенную от записи, так что в последнее время область применения этого приема становится ограниченной.