Нарисовать треугольник на ассемблере

Создание графических примитивов на Ассемблере

Хотелось бы рассмотреть что-то интересное и полезное вплане использования, поэтому выбор пал на ассемблер, а именно на создание примитивной графики.

Язык ассемблер — это низкоуровневый язык программирования или же программа, которая исходный текст программы, написанный на языке ассемблера, переводит в программу на машинный язык. Язык, по некоторым меркам сложный, но ведь создание примитивов графики берет начало именно тут. Я же хочу рассмотреть ассемблер под Windows, а именно MASM, который, на ряду с Visual Studio, не так давно использовал для создания графических примитивов. Об этом с иллюстрациями и подробностями далее.

Приступая к работе

Рассмотрим маломальски простенькую структуру, которая необходима для создания приложений под Windows:
1) помещаем все константы, стpуктуpы и функции, относящиеся к Windows в начале нашего .asm файла — экономим силы и время;
2) используем диpективу includelib, чтобы указать библиотеки импоpта — это укажет компилятоpу на то, что пpогpамма будет использовать функции из этих библиотек импоpта;
3) объявляйте пpототипы API-функций, стpуктуp и/или констант в подключаемом файле с использованием тех же имен, что и в Windows include файлах, по крайней мере старайтесь, поскольку это избавит всех от головной боли в будующем;
4) используйте makefile, чтобы автоматизиpовать пpоцесс компиляции.

Я же отступлю кое-где и кое-как, но в целом у нас должна получиться отличная программа, которая нарисует нам довольно интересный таки примитив. Рассмотрим пример структуры программы на Ассемблере (см. Листинг 1)

Листинг 1. Пример структуры программы

.type_process ; описание типа процессора
.model ; описание модели памяти

include lib ; подключение inc
includelib lib ; подключение lib

.DATA ; иницилизиpуемые данные
; имя класса и окна

.DATA? ; неиницилизиpуемые данные
; дескриптор пpогpаммы

.CODE ; здесь начинается код программы

Определение графических примитивов
Контекст Устройства и WM_PAINT

В Windows окно само отвечает за перерисовку себя. Для того чтобы окно осуществило перерисовку, оно должно получить сообщение WM_PAINT.

Обычно используют один из трех методов:

а) рабочая область может быть восстановлена, если ее содержимое формируется с помощью каких-либо вычислений;
б) последовательность событий, формирующих рабочую область, может быть сохранена, а затем «проиграна» сколь угодно раз;
в) можно создавать виртуальное окно и направлять весь вывод в виртуальное окно, а при получении основным окном сообщения WM_PAINT копировать содержимое виртуального окна в основное (будет использовано для демонстрации написанного позже приложения).

Установка текущей позиции

Для установки текущей позиции используется функция MoveToEx(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI MoveToEx(HDC, int, int, LPPOINT);

Первый аргумент — это контекст устройства, второй и третий — координаты точки, в которую устанавливается текущая графическая позиция. Последний аргумент — указатель на структуру типа POINT, в которую функция запишет координаты старой текущей позиции.

Рисование линии

Для прорисовки линии используется функцию LineTo(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI LineTo(HDC, int, int);

Первый аргумент — контекст устройства, второй и третий аргументы — координаты точек.

Рисование прямоугольника

Для прорисовки прямоугольника используется функция Rectangle(), где функция описывается следующим способом:

WINGDIAPI BOOL WINAPI Rectangle(HDC, int, int, int, int);

Первый аргумент — это контекст устройства, все же остальные аргументы — координаты верхнего левого и нижнего правого углов прямоугольника.

Рисование эллипса

Для прорисовки эллипса необходимо вызвать функцию Ellipse(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI Ellipse(HDC, int, int, int, int);

Первый аргумент — это контекст устройства.
Примечание: эллипс ограничен прямоугольником и именно через координаты этого прямоугольника и определяется прорисовываемый эллипс. Второй и третий аргументы — координаты левого верхнего угла прямоугольника, четвертый и пятый аргументы — координаты нижнего правого угла.

Рисование прямоугольника с закругленными краями

Для прорисовки прямоугольника с закругленными краями используется функция RoundRect(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI RoundRect(HDC, int, int, int, int, int, int);

Первые пять аргументов полностью идентичны аргументам функции Rectangle(). Последние два аргумента содержат ширину и высоту эллипса, определяющего дуги.

Написание и разбор .asm кода

Для написания примитива рассмотрим шаги, которые необходимы для создания и отрисовки графики:
1) получение дескриптора для программы;
2) регистрация класса окна;
3) создание окна;
4) отображение окна на экpане;
5) обновление содержимого экpана в окне;
6) выход из пpогpаммы.

Приступим к созданию, но для начала создадим новый проект в Visual Studio: File -> New Project

Нарисовать треугольник на ассемблере

Выбираем пустой прокт: Empty project

Нарисовать треугольник на ассемблере

Создаем новый файл: правой кнопкой по Source -> Add -> New Item

Нарисовать треугольник на ассемблере

Создаем новый файл (.asm):
1-ый способ — дописать при создании нового файла file.asm (я таким способом создавал)
2-ой способ — изменить расширение файлу после его создания (file.txt -> rename -> file.asm)

Нарисовать треугольник на ассемблере

Используем masm в Visual Studio: нажимаем правой кнопкой по преокту -> Build Customization

Нарисовать треугольник на ассемблере

Задаем этот самый masm: ставим галочку напротив masm

Нарисовать треугольник на ассемблере

Приступаем к написанию этого самого примитива, а сам листинг смотрите ниже.

Листинг 2. Написание кода на ассемблере

.386
.model stdcall, flat
option casemap:none

includelib kernel32.lib
include kernel32.inc
includelib user32.lib
include user32.inc
include windows.inc
include gdi32.inc

hwnd dd 0
hInst dd 0
szTitleName db ‘АиПОС. Лабороторная работа №6’, 0
szClassName db ‘Приложение Win32’, 0
msg MONMSGSTRUCT
wc WNDCLASS
ps PAINTSTRUCT

Main PROC
invoke GetModuleHandle, 0 ;получение значения баз. адреса,
mov hInst, eax ;по которому загружен модуль.
mov wc.style, CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
mov wc.lpfnWndProc, offset WndProc ;адрес оконной процедуры
mov wc.cbClsExtra, 0
mov wc.cbWndExtra, 0
mov eax, hInst ;дескриптор приложения
mov wc.hInstance, eax ;в поле hInstance
invoke LoadIcon, 0, IDI_APPLICATION
mov wc.hIcon, eax ;дескриптор значка в поле hIcon
invoke LoadCursorA, 0, IDC_ARROW
mov wc.hCursor, eax ;дескриптор курсора в поле hCursor
mov wc.hbrBackground, WHITE_BRUSH ;цвет бекграунда окна белый
mov dword ptr wc.lpszMenuName, 0 ;главного меню нет
mov dword ptr wc.lpszClassName, offset szClassName ;имя класса окна
invoke RegisterClassA, offset wc ;регистрация класас окна
invoke CreateWindowEx, 0, offset szClassName, offset szTitleName,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInst, 0
mov hwnd, eax ;создание окна
invoke ShowWindow, hwnd, SW_SHOWNORMAL ;показ окна
invoke UpdateWindow, hwnd ;перерисовывка содержимого окна
cycle1: ;цикл сообщений
invoke GetMessage, offset msg, 0, 0, 0
cmp ax, 0
je end_c
invoke TranslateMessage, offset msg ;трансляция ввода с клавиатуры
invoke DispatchMessage, offset msg ;отправляем сообщение
;оконной процедуре
jmp cycle1
end_c:
invoke ExitProcess, 0 ;выход из приложения
Main ENDP

WndProc PROC USES ebx edi esi, _hwnd:DWORD, _wmsg:DWORD, _wparam:DWORD, _lparam:DWORD
local _hdc:DWORD
cmp _wmsg, WM_DESTROY
je wmdestroy
cmp _wmsg, WM_PAINT
je wmpaint
invoke DefWindowProcA, _hwnd, _wmsg, _wparam, _lparam ;обраб. по умолчанию
jmp exit_proc
wmpaint:
invoke BeginPaint, _hwnd, offset ps ;получаем контекст устройства
mov _hdc, eax
invoke Rectangle, _hdc, 170, 120, 310, 260 ;тело
invoke Rectangle, _hdc, 120, 120, 170, 140 ;левая лапа
invoke Rectangle, _hdc, 310, 120, 360, 140 ;правая лапа
invoke Rectangle, _hdc, 170, 260, 190, 310 ;левая ноголапа
invoke Rectangle, _hdc, 290, 260, 310, 310 ;правая ноголапа
invoke Rectangle, _hdc, 210, 80, 270, 120 ;башка
invoke Rectangle, _hdc, 220, 85, 225, 90 ;левый глаз
invoke Rectangle, _hdc, 250, 85, 255, 90 ;правый глаз
invoke Rectangle, _hdc, 225, 105, 255, 120 ;рот
invoke EndPaint, _hdc, offset ps ;освобождаем контекст
mov eax, 0 ;возвращаемое значение — 0
jmp exit_proc
wmdestroy:
invoke PostQuitMessage, 0 ;послать сообщение WM_QUIT
mov eax, 0 ;возвращаемое значение — 0
exit_proc:
ret
WndProc ENDP
END Main

Результат

Нарисовать треугольник на ассемблере

По ходу действий и написания кода проставлял комментарии, но чтобы понять полную суть, рассмотрю подробнее все, что сделал и написал.

Разбор полётов

Строка с .386 передает MASM, что используется набор инструкций пpоцессоpа 80386. Строка .model stdcall, flat передает MASM, что будет использоваться плоская модель памяти. А саму передачу паpаметpов использовали типом STDCALL как по умолчанию.
Подключил windows.inc в начале кода, поскольку он содеpжит системный стpуктуpы и константы, котоpые потpебовались для реализации примитивов в пpогpамме. Поскольку пpогpамма вызывает API функции Windows, которые находятся в user32.dll (CreateWindowEx и другие) и kernel32.dll (ExitPocess и другие) их необходимо тоже прописать.
Описываем прототип главной функции PROC.
Следом идёт .data, где: szClassName — имя нашего класса окна и szTitleName — имя нашего окна.
В .code содеpжит все инстpукции, где код должен pасполагаться между и end .
Пеpвая же инстpукция — вызов GetModuleHandle, чтобы получить дескриптор нашей пpогpаммы. Она используется как паpаметp, пеpедаваемый функциям API, которые вызываются нашей пpогpаммой.

Далее идет инициализация класса окна — оно опpеделяет некотоpые важные хаpактеpистики окна, такие как иконка, куpсоp, функцию, ответственную за окно и так далее. Тут же и описываем дескриптор самого приложения, дескриптор значка и дескриптор курсора. Дескриптора меню в реализованном приложении нет, поскольку это увеличило бы код программы, а функциональности ему не добавило, тем более, что это примитив и он тут вовсе не нужен. Параметры, которые могут или были использованы для создания окна:

1) cbSize: задает размеp общей стpуктуpы WDNCLASSEX в байтах;
2) style: задает стиль окона;
3) cbClsExtra: задается количество дополнительных байтов, котоpые нужно будет зарезервировать для самой программы;
4) hInstance: задает дескриптор модуля;
5) hIcon: задает дескриптор иконки, а его получение просходит посредством обращения функции LoadIcon;
6) hCursor: задает дескриптор куpсоpа, а его получение просходит посредством обращения функции LoadCursor;
7) hbrBackground: задает цвет фона;
8) lpszMenuName: задается дескриптор меню для окон;
9) lpszClassName: задается имя класса окна.

После pегистpации класса окна функцией RegisterClassEx, происходит вызов CreateWindowEx, чтобы создать наше окно, основанное на этом класе.

Основной и немаловажной является процедура WndProc PROC USES ebx edi esi, _hwnd:DWORD, _wmsg:DWORD, _wparam:DWORD, _lparam:DWORD.Не обязательно ее было называть ее WndProc, где пеpвый паpаметp, _hwnd — это хэндл окна, котоpому пpедназначается сообщение,_wmsg — передаваемое сообщение. Стоит сказать, что _wmsg — это не msg стpуктуpа, но это всего лишь число. _wparam и _lparam — это дополнительные паpаметpы, которые используются некоторыми сообщениями.

В конце концов подошли к заключительной части, где и описываются задаваемые фигуры, их координаты и возвращаемые значения. Это ключевая часть, поскольку именно здесь pасполагается логика действий пpогpаммы. Тут же описываем освобождение контекста и возравщаем значения, где далее посылаем сообщение о завершении. Единственное сообщение, которое осталось обработать — wmdestroy — это сообщение будет посылаться окну, когда оно закpывается. В то вpемя, когда пpоцедуpа окна его получает, окно уже исчезло с экpана. После выполнения wmdestroy вызывается PostQuitMessage, котоpый посылает сообщение о выходе и это вынуждает GetMessage веpнуть нулевое значение в eax, а это уже выход из программы.

Видео:КАК НАРИСОВАТЬ ТРЕУГОЛЬНИК В КОНСОЛИ C# | C# ДОМАШНИЕ ЗАДАНИЯ | #5Скачать

КАК НАРИСОВАТЬ ТРЕУГОЛЬНИК В КОНСОЛИ C# | C# ДОМАШНИЕ ЗАДАНИЯ | #5

Нарисовать треугольник на ассемблере

Следующая процедура считывает строку ASCIIZ с клавиатуры.

Перевод чисел в двоичную форму (в виде строки)

Данная процедура конвертирует 16-битное слово в строку ASCIIZ, т.е. число 7 преобразовывается в строку 0000000000000111. Лидирующие нули включаются в строку. Строка ASCIIZ — это набор символов, завершающихся 0.

Чтение значения счетчика времени

В памяти по адресу 40:6C расположено двойное слово, которое увеличивается на единицу приблизительно 18.2 раза в секунду. Системное время можно получить, считывая это слово. Младший байт может быть использован для многих «временных» задач, в т.ч. в качестве исходного значения для генератора псевдослучайных чисел (а в некторых случаях и заменить его).

Определяем тип процессора

Следующая процедура WhatCPU определяет тип процессора, установленного в системе. Результат возвращается в регистре AX. Процедура может быть откомпилирована и 16-битным компилятором, несмотря на то, что в ней используются 32-битные инструкции для определения различия между 386, 486 и Pentium.

Установка видеорежимов VGA

Требуемый видеорежим устанавливается вызовом функции BIOS

Этот фрагмент также очищает экран. Содержимое AX не сохраняется. Стандартный BIOS не возвращает никакой информации, сигнализирующей об ошибке. В подерживаемых режимах можно читать и писать в видеопамять путем вызовов соответствующих функций (функции 8,9,0Ch,0Dh). Нормальный текстовый режим DOS — это режим 3.

Следующий фрагмент загружает набор символов из ROM в RAM и соответственно корректирует высоту отображения символов.

Линейные преобразования в системах с фиксированной точкой

Следующая подпрограмма переводит дюймы в миллиметры, но также она может быть использована для любых линейных преобразований, для чего достаточно изменить коэффициент преобразования.

Числа предствалены в 32-битном формате с фиксированной точкой. Старшее слово содержит целую часть числа, а младшее слово — дробную часть. Предполагается, что используются только положительные числа.

Код использует 32-битные инструкции, но может быть откомпилирован и 16-битным компилятором.

Таблица размещения файлов FAT

Первая таблица FAT начинается с B. Ее копия располагается в секторе B+F etc.
Можно детально рассмотреть FAT используя утилиту DEBUG. Не вносите изменений в таблицу FAT на жестком диске, если вы не уверены, что вы делаете.
Первая запись таблицы FAT выглядит так: Затем, с кластера 2 начинаются элементы таблицы. Возможные значения перечислены в следующей таблице: Вы можете читать сектора, используя прерывание 25h. Отметим, что это прерывание сохраняет флаги в стеке, так что после выполнения прерывания они должны быть восстановлены

Запуск дочерней программы

DOS выделяет всю доступную память текущей программе, независимо от того, какой объем реально необходим. Поэтому вы должны освободить часть памяти для того, чтобы загрузить и выполнить дочернюю программу. Это выполняется процедурой Setmem. Каждый параграф занимает 16 байт. Пространство, необходимое текущей программе вычисляется как размер в параграфах = Lseg — Psp + 1
где Lseg — сегмент, расположенный после последнего байта программы, а Psp — сегмент, в котором расположен psp программы.

Следующий фрагмент кода запускает программу CHILD.COM с параметром /HELP.

Чтение параметров командной строки

Параметры командной строки (сразу после имени файла) могут быть прочитаны с помощью следующей процедуры ReadCL .

Например, если ваша программа называется KOE.COM и вы запускаете ее, набрав команду

в командной строке DOS, то процедура ReadCL вернет строку 4abcs в формате ASCIIZ.

TSR: Завершаемся и остаемся в памяти

Инсталляция TSR-программы выполняется в три этапа:

  • Загрузка резидентной части в память. Проверка, не находится ли наша программа уже в памяти. Сохранение необходимой информации для дальнейшего удаления резидента из памяти. Освобождение памяти, занятой копией переменных окружения для экономии.
  • Установка параметров для работы резидентной части. Обычно на этом этапе перехватываются прерывания.
  • Завершение установочной программы, при этом резидентная часть остается в памяти.

Рисование в SVGA

Пикселы расположены линейно в памяти видеоадаптера. В 256-цветных режимах пиксел представляется одним байтом. Поэтому смещение точки с координатами (x,y) можно вычислить как 640*y+x в режиме с 640 пикселами по горизонтали. Единственное ограничение, связанное с такими вычисленими, — это то, что последний доступный пиксел, к которому может быть получен доступ, имеет координаты x=255, y=102, его смещение 65535. Это известное ограничение 64Kбайтных сегментов.

Чтобы обойти это ограничение, применяется переключение банков памяти. При этом переопределяется расположение физического адреса, которое соответствует логическому адресу. Так, логический адрес 0 соответствует физическому адресу 65536 если активен первый банк в видеодаптером с размером «окна» (granularity) 64 KB.

Логический адрес точки с координатами (x,y) определяется как 640*y+x-B*WG где B — номер банка и WG — размер «окна». Банк памяти может быть переключен с помощью функции AX=4F05h прерывания 10h в видеоадаптерах, поддерживающих стандарт VESA.

Следующая процедура рисует пиксел на экране. Координаты пиксела находятся в регистрах AX и BX, а в регистре CX передается цвет пиксела. В процедуре предполагается, что размер «окна» равен 64 KB, что справедливо, например, для чипов S3.

Пишем напрямую в видеопамять

Рисуем пиксел в графическом режиме

Графические режимы могут быть разбиты на шесть групп в зависимости от количества бит, отводимых каждому пикселу: 1 бит/пиксел, 2 цвета, одна битовая плоскость: CGA mode 6 разрешение 640*200 2 бит/пиксел, 4 цвета, одна битовая плоскость: CGA mode 4 разрешение 320*200 4 бит/пиксел, 16 цветов, четыре битовых плоскости: EGA mode 0Dh разрешение 320*200 EGA mode 0Eh разрешение 640*200 EGA mode 10h разрешение 640*350 VGA mode 12h разрешение 640*480 VESA mode 102h разрешение 800*600 VESA mode 104h разрешение 1024*768 VESA mode 106h разрешение 1280*1024 8 бит/пиксел, 256 цветов, одна битовая плоскость: VGA mode 13h разрешение 320*200 VESA mode 100h разрешение 640*400 VESA mode 101h разрешение 640*480 VESA mode 103h разрешение 800*600 VESA mode 105h разрешение 1024*768 16 бит/пиксел, 65536 цветов, одна битовая плоскость(существуют также 32768-цветные режимы): VESA mode 111h разрешение 640*480 VESA mode 114h разрешение 800*600 24 бит/пиксел, 16777216 цветов, одна битовая плоскость: VESA mode 112h разрешение 640*480

Исключая 4-битные режимы пикселы в памяти располагаются на одной плоскости (plane), т.е., если координаты пиксела (x,y), то адрес, по которому располагается этот пиксел в памяти может быть вычислен как
Address = LineLength*y + Bits*x/8
где LineLength — количество байтов, занимаемых каждой строкой пикселов, а Bits — количество бит, занимаемым пикселом.

Исключениями являются режимы CGA номер 4 и 6, у которых четные и нечетные линии расположены в различных сегментах памяти.

В шестнадцатицветных режимах экранная память разделяется на 4 битовые плоскости. Каждый бит значения цвета пиксела расположен на своей плоскости. Адрес байта, хранящего пиксел с координатами x,y можно вычислить как
Address = LineLength*y + x/8
где LineLength — число байтов, занимаемых одной строкой.

Рисование пиксела с координатами x,y в 16-цветных режимах подразумевает установку бита во всех четырех плоскостях. Активная в данный момент плоскость выбирается записью в соответствующие порты видеокарты.

Режимы CGA, EGA и VGA поддерживаются всеми стандартными BIOS. Переключение в эти режимы обычно осуществляется простым вызовом функций BIOS.

Pixel$

Во всех режимах VGA следующая процедура Pixel$ может нарисовать пиксел. Нужно отметить, что процедура достаточно медленная, т.к. используются вызовы функций BIOS.

Самый интересный режим VGA — это режим 13h с возможностью отображения 256 цветов и разрешением 320*200. Номер цвета 0. 255 соответствуют значениям в палитре, где все цвета представлены в виде определенных сочетаний красной, зеленой и синей компонент. Следующая процедура VGApxl$ рисует пиксел в этом режиме. Она работает достаточно быстро, однако существуют еще более быстрые варианты.

Функция синуса в 32-битной системе с фиксированной точкой

Процедура Rsin$ вычисляет тригонометрическую функцию sin от 32-битного аргумента. 32-битная система с фиксированной точкой определяется следующим образом:

Использование процедуры:
Входные данные: смещение аргумента в BX, смещение результата в AX. Аргумент задает угол в градусах.
Выходные данные: значение функции sin, записываемое в переменную, смещение которой определяется регистром AX. Значения регистров не сохраняются.

Например, sin(30.5°) вычисляется так:

В результате такого вызова вы получите результат 0.50752 в то время как правильное значени еравно 0.50754

Видео:ЯЗЫК АССЕМБЛЕРА С НУЛЯ | #1 НАЧАЛОСкачать

ЯЗЫК АССЕМБЛЕРА С НУЛЯ | #1 НАЧАЛО

Нарисовать треугольник на ассемблере

Для осуществления ввода с клавиатуры и вывода на экран символьной информации используются функции DOS. Однако DOS не поддерживает ни позиционирование курсора, ни смену цвета выводимых символов. В текстовом режиме расширить возможности DOS можно с помощью драйвера ANSI.SYS. С графическими изображениями дело обстоит хуже, так как в DOS нет никаких графических функций. Нет их также и в драйвере ANSI.SYS, за исключением возможности перевода видеоадаптера в графический режим (с помощью Esc-последовательности Еsc[=режимh). Для того, чтобы вывести на экран графическое изображение необходимо воспользоваться нижним уровнем операционной системы — базовой системы ввода-вывода (Basic In-Out System, BIOS). Программы BIOS находятся в постоянном запоминающем устройстве (ПЗУ) BIOS. В отличие от DOS, ко всем функциям которой можно обратиться с помощью прерывания 21h, в BIOS за каждым устройством компьютера закреплено свое прерывание. Так, программирование диска осуществляется с помощью прерывания int13h, клавиатуры — int16h, экрана – int10h. Прерывание int10h обеспечивает все функции видеоадаптера: смену видеорежима, вывод символьной и текстовой информации, смену шрифтов, настройку цветовой палитры, работу с графическим изображением и т.д. Воспользуемся прерыванием int10h для перехода в графический режим и вывода простейшего графического изображения.

Пример 3.1. Вывод на экран горизонтальной прямой.

В предложениях 1-3 с помощью функции 00h прерывания BIOS 10h осуществляется переключение видеоадаптера в графический режим. Поскольку номер режима заносится в байтовый регистр AL, всего может существовать 256 различных текстовых и графических режимов, из которых на сегодняшний день используются (аппаратурой различных фирм) около ста. Режим 10h обеспечивает вывод графического изображения 16 цветами с разрешением 640х350 точек и широко используется с видеоадаптерами EGA и VGA.
Изображение рисуется по точкам (в BIOS не предусмотрено программных средств вывода каких-либо геометрических фигур или хотя бы линий, как нет и средств закрашивания областей экрана). Для вывода на экран цветной точки (пиксела) используется функция 0Ch прерывания 10h. Эта функция требует занесения в регистр AL кода цвета, в ВН — номера видеостраницы, в CX — Х-координаты выводимой точки в диапазоне 0-349, а в DX — Y-координаты точки в диапазоне 0-639. Поскольку регистр CX используется, как счетчик шагов в цикле, для хранения Х — координаты зарезервирован регистр SI.
Прямая горизонтальная линия в примере 3.1 рисуется путем вызова функции 0Сh в цикле, в каждом шаге которого значение Y-координаты остается неизменным (175 в примере), а значение Х-координаты увеличивается на 1 (предложение 13). После завершения цикла формирования изображения в программе предусмотрена остановка (предложения 16-17) для того, чтобы пользователь мог, оставаясь в графическом режиме, проанализировать результаты работы программы. Для остановки программы используется функция DOS 08h ввода одного символа с клавиатуры, функция 08h, как уже отмечалось, не отображает введенный символ на экране и, тем самым, не искажает графическое изображение. Нажатие любой клавиши (кроме управляющих — Ctrl, Alt, Shift и др.) возобновляет выполнение программы.
В конце рассматриваемого фрагмента предусмотрено переключение видеоадаптера в стандартный текстовый режим с номером 03h (предложения 18. 20). Если такое переключение не выполнить, видеоадаптер останется в графическом режиме, что может помешать правильному выполнению прикладных программ.
Рассмотрим кратко параметры вызова функции 0Ch прерывания 10h. В регистр ВН заносится номер видеостраницы, на которую выводится данная точка. Графический адаптер EGA обеспечивает хранение и отображение двух графических страниц. По умолчанию видимой (активной) делается страница 0, однако рисовать изображение можно как на видимой, так и на невидимой странице. Для переключения страниц предусмотрена функция 05h прерывания 10h.
В регистр AL заносится код цвета точки. Адаптер поддерживает 64 цвета, хотя в каждый момент времени изображение на экране может содержать только 16 цветов. Этот набор из 16 цветов, выводимых на экран (цветовая палитра), задается программно и может легко изменяться. При загрузке машины устанавливается стандартная палитра, коды цветов которой приведены в табл. 3.1.

Таблица 3.1. Коды цветов стандартной цветовой палитры EGA

Код цветаЦветВид
0Черный
1Синий
2Зеленый
3Бирюзовый
4Красный
5Фиолетовый
6Коричневый
7Белый
8Серый
9Голубой
10Салатовый
11Светло-бирюзовый
12Розовый
13Светло-фиолетовый
14Желтый
15Ярко-белый

Основы организации подпрограмм

Все современные программы разрабатываются по модульному принципу – программа обычно состоит из одной или нескольких небольших частей, называемых подпрограммами или процедурами, и одной главной программы, которая вызывает эти процедуры на выполнение, передавая им управление процессором. После завершения работы процедуры возвращают управление главной программе и выполнение продолжается с команды, следующей за командой вызова подпрограммы.
Достоинством такого метода является возможность разработки программ значительно большего объема небольшими функционально законченными частями. Кроме того, эти подпрограммы можно использовать в других программах, не прибегая к переписыванию частей программного кода. В довершение ко всему, так как размер сегмента не может превышать 64К, то при разработке программ с объемом кода более 64К, просто не обойтись без модульного принципа.
Язык программирования Ассемблера поддерживает применение процедур двух типов – ближнего (near) и дальнего (far).
Процедуры ближнего типа должны находится в том же сегменте, что и вызывающая программа. Дальний тип процедуры означает, что к ней можно обращаться из любого другого кодового сегмента.
При вызове процедуры в стеке сохраняется адрес возврата в вызывающую программу:
— при вызове ближней процедуры – слово, содержащее смещение точки вызова относительно текущего кодового сегмента;
— при вызове дальней процедуры – слово, содержащее адрес сегмента, в котором расположена точка возврата, и слово, содержащее смещение точки возврата в этом сегменте.
В общем случае группу команд, образующих подпрограмму, можно никак не выделять в тексте программы. Для удобства восприятия в языке Ассемблера процедуры принято оформлять специальным образом. Описание процедуры имеет следующий синтаксис:

Следует обратить внимание, что в директиве PROC после имени не ставится двоеточие, хотя имя и считается меткой.
Параметр, указываемый после ключевого слова PROC, определяет тип процедуры: ближний (NEAR) или дальний (FAR). Если параметр отсутствует, то по умолчанию процедура считается ближней.
В общем случае, размещать подпрограмму в теле программы можно где угодно, но при этом следует помнить, что сама по себе подпрограмма выполняться не должна, а должна выполняться лишь при обращении к ней. Поэтому подпрограммы принято размещать либо в конце сегмента кода, после команд завершения программы, либо в самом начале сегмента кода, перед точкой входа в программу. В больших программах подпрограммы нередко размещают в отдельном кодовом сегменте.
Передавать фактические параметры процедуре можно несколькими способами. Простейший способ – передача параметров через регистры: основная программа записывает параметры в какие-либо регистры, а процедура по мере необходимости извлекает их из этих регистров и использует в своей работе. Такой способ имеет один основной недостаток: передавать параметры через регистры можно если их немного (если много, то просто не хватит регистров). Решить это проблему можно, передавая параметры через стек. В этом случае основная программа записывает параметры в стек и вызывает подпрограмму, подпрограмма работает с параметрами и, возвращая управление, очищает стек.
Для работы с подпрограммами в систему команд процессора включены специальные команды, это вызов подпрограммы CALL и возврат управления RET.
Все команды вызова CALL безусловны. Внутрисегментный вызов NEAR CALL используется для передачи управления процедуре, находящейся в том же сегменте. Он указывает новое значение регистра IP и сохраняет старое значение счетчика команд (IP) в стеке в качестве адреса возврата. Межсегментный вызов FAR CALL используется для передачи управления процедуре, находящейся в другом сегменте или даже программном модуле. Он задает новые значения сегмента CS и смещения IP для дальнейшего выполнения программы и сохраняет в стеке как регистр IP, так и регистр CS.
Все возвраты RET являются косвенными переходами, поскольку извлекают адрес перехода из вершины стека. Внутрисегментный возврат извлекает из стека одно слово и помещает его в регистр IP, а межсегментный возврат извлекает из стека два слова, помещая слова из меньшего адреса в регистр IP, а слово из большего адреса – в регистр CS. Команда RET может иметь операнд, который представляет собой значение, прибавляемое микропроцессором к содержимому указателя стека SP после извлечения адреса возврата (очистка стека).
Модифицируем программу из примера 3.1, разбив ее на процедуры и организовав в цикле обращение к подпрограмме с передачей ей параметров. Поскольку введение процедур несколько изменяет структуру программы, пример 3.2 приведен не фрагментарно, а полностью, включая описание сегментов.

Пример 3.2. Вывод на экран горизонтальной прямой с помощью подпрограммы.

Программа состоит теперь из двух процедур — главной с именем main и процедуры — подпрограммы с именем draw. Каждая процедура начинается оператором proc, перед которым указывается имя процедуры, а заканчивается оператором endp (end procedure, конец процедуры)(пары предложений 3, 11 и 12, 31). Порядок процедур в программе в большинстве случаев не имеет значения, однако имя главной процедуры, с которой начинается выполнение программы, должно быть указано в качестве операнда директивы end, завершающей текст программы (предложение 42).
Подпрограммы вызываются оператором call (вызов); каждая подпрограмма должна заканчиваться командой ret (return, возврат), которая передает управление в точку возврата, т. е. на команду вызывающей программы, следующую за командой call.
Подпрограмма draw выводит на экран одну точку. В качестве входных параметров она должна получить две координаты точки, ее цвет, а также номер видеостраницы, на которую выводится изображение. В языке ассемблера нет установленных правил передачи параметров подпрограмме. Их можно передать через регистры общего назначения, стек или ячейки памяти. В примере 3.2 используется последний способ, не самый быстрый, но наиболее наглядный. Для хранения и модификации параметров в сегменте данных предусмотрены ячейки х, у, color и vpage. В данном примере вывода горизонтальной линии в трех ячейках хранятся константы, и лишь ячейка х модифицируется.
При использовании подпрограммы основной цикл упрощается. Фактически в нем лишь две содержательные строки: вызов подпрограммы draw и инкремент Х-координаты в ячейке х. Однако сохранение в стеке и восстановление регистра СХ является обязательным, потому что он используется в подпрограмме для задания Х-координаты.
В примере 3.3 показано, как можно, в дополнение к горизонтальной, вывести на экран и вертикальную линию. В данном примере реализовано построение прямоугольника путем рисования горизонтальных и вертикальных линий в соответствующих подпрограммах. Здесь не используется сегмент данных, поэтому он не инициализируется в начале программы.

Пример 3.3. Вывод на экран прямоугольника.

В данном примере рисование прямоугольника осуществляется построением четырех линий: двух горизонтальных и двух вертикальных. Для понимания работы программы достаточно проследить за алгоритмом построения по одной линии каждого типа, поскольку построения двух оставшихся отличаются только координатами начала вывода. Но помните, что для того, чтобы обнаружить на экране прямоугольник, необходимо добавить в программу соответствующие строки. Это не составит труда, если вы поймете, как работает данный алгоритм.
Как уже отмечалось, здесь не используется сегмент данных, поэтому он просто отсутствует в программе. Передача данных из основной программы в процедуру (подпрограмму) осуществляется посредством регистров. Обращаем ваше внимание, что в данном случае подпрограммы расположены перед текстом основной программы. Мы опускаем моменты, связанные с графическими особенностями, поскольку рассчитываем на вашу компетентность в данном вопросе, приобретенную при изучении более ранних примеров этого пособия. Важными здесь являются процедуры vertical и horizontal , реализующие соответственно построение вертикальных и горизонтальных линий. Для успешного выполнения в процедуру необходимо отправить значение начальных X и Y координат. Это выполняется в предложениях 33 (37) и 34 (38). В следующем предложении передается значение длины стороны прямоугольника, которое фактически является значением счетчика цикла подпрограмм в начальном состоянии. После этого можно вызывать процедуру, что и отражено в предложении 36 (40). Переходим к анализу работы подпрограмм. Он не потребует большого умственного напряжения. В самом начале необходимо сохранить в стеке текущее значение счетчика цикла (предложение 5 (18)), это будет осуществляться на каждом шаге цикла. В 9 (22) предложении поместим в регистр СХ значение координаты Х. Далее выводим пиксель на экран. Как вы уже, наверняка, заметили, эти две процедуры отличаются лишь в одном: в первой мы инкрементируем счетчик Y-координаты (предложение 11), а во второй – Х-координаты (предложение 24). После этого не забываем выгрузить из стека значение счетчика цикла. Напомним, что при использовании подпрограмм это важно вдвойне, т.к. к моменту выхода из подпрограммы в стеке не должно остаться ничего, что было туда помещено после вызова подпрограммы. Иначе мы не сможем вернуться в текст основной программы.

Механизм вызова подпрограмм

Рассмотрим механизм выполнения конкретных команд call draw и ret из примера 3.2. На рис. 3.1 приведены фрагменты загрузочного модуля программы 3.2 с указанием расположения некоторых команд, их кодов, смещений, мнемонических обозначений и описания их действия. Показана также часть сегмента данных.
Сегмент команд начинается с процедуры draw. Первая команда этой процедуры mov AH,OCh имеет поэтому смещение (относительный адрес в сегменте команд) ООООП. Процедура draw занимает 14h==20 байт с относительными адресами от OOOOh до 0013h. Последней командой процедуры draw является однобайтовая команда ret с кодом C3h.

Рис. 3.1. Фрагменты загрузочного модуля программы 3.2 с поясняющей информацией.

За процедурой draw располагается главная процедура main. Ее первая команда mov AX,data имеет смещение 0014h. Код команды включает код операции mov (B8h) и значение имени data, равное сегментному адресу сегмента данных. При загрузке программы под управлением отладчика сегментный адрес data оказался равным 4476h.
Команда call draw расположена по адресу 0023h. В ее полный код входит код операции call (E8h) и адрес процедуры draw, на которую надо осуществить переход. Этот адрес записан в виде смещения к началу процедуры draw относительно текущего содержимого IP, т.е. относительно адреса следующей команды (в нашем случае команды inc x). Смещение это знаковое и в данном случае отрицательное, так как процедура draw располагается до процедуры main. Поскольку адрес draw равен 0, а адрес следующей команды равен 26h, в коде команды записано число -26h, которое по правилам записи отрицательных чисел выражается кодом FFDAh (знаковые числа будут рассмотрены позднее).
Главная процедура занимает 18h=24 байт, а первый свободный байт после конца этой процедуры имеет смещение 003Сh. На этом заканчивается сегмент команд. С ближайшего адреса, кратного 16 (44760h в нашем случае), начинается сегмент данных. Относительные адреса в нем опять начинаются с 0, поэтому смещение первой переменной х равно 0, смещение следующей переменной у — 2 и т.д. Весь сегмент данных занимает всего 6 байт.
Вернемся к рассмотрению команд call и ret. При выполнении команды call процессор помещает адрес возврата (содержимое IP, т.е. адрес следующей команды) в стек, а в IP заносит относительный адрес процедуры draw, который находится суммированием текущего содержимого IP и смещения, записанного в коде команды call. В результате указатель стека SP смещается вверх на одно слово, а процессор переходит на выполнение подпрограммы.
Команда ret выполняет обратную операцию — извлекает из верхнего слова стека (с восстановлением исходного состояния указателя стека SP) адрес возврата и загружает его в IP, в результате чего процессор возвращается к выполнению вызывающей процедуры.
Из сказанного ясно, что если в подпрограмме используется стек, с ним надо работать очень аккуратно: все, что заносится в стек в процессе выполнения подпрограммы, должно быть обязательно снято с него до выполнения команды ret, иначе эта команда извлечет из стека и загрузит в IP не адрес возврата, а какое-то данное, что заведомо приведет к нарушению выполнения программы.
Рассмотренный нами вызов подпрограммы носит название прямого ближнего (или внутрисегментного) вызова. Прямым такой вызов называется потому, что адрес перехода хранится непосредственно в коде команды (а это, в свою очередь, получилось потому, что мы указали в качестве операнда команды call имя подпрограммы). Если бы адрес подпрограммы хранился в каком-то другом месте (именно, в регистре или в ячейке памяти), то вызов был бы косвенным. Вторая характеристика вызова говорит о том, что вызываемая подпрограмма находится в том же сегменте, что и вызывающая процедура. В этом случае для перехода на подпрограмму надо знать лишь «половину» полного адреса подпрограммы, именно, относительный адрес точки перехода. Сегментный адрес остается тем же; он не фигурирует в строке вызова подпрограммы и отсутствует в коде команды. В дальнейшем мы рассмотрим и другой вид подпрограмм — дальние подпрограммы, для обращения к которым следует применять межсегментные вызовы.

🔥 Видео

ЯЗЫК АССЕМБЛЕРА за 3 МИНУТЫСкачать

ЯЗЫК АССЕМБЛЕРА за  3 МИНУТЫ

Assembler. Команды mov, add, sub, div, mul [#3]Скачать

Assembler. Команды mov, add, sub, div, mul [#3]

Топ 3 худших программиста на YouTube! #код #айти #программистСкачать

Топ 3 худших программиста на YouTube! #код #айти #программист

Ретрокод на ассемблере для Спектрума, #1. Предисловие и первая программа "Привет Мир!"Скачать

Ретрокод на ассемблере для Спектрума, #1. Предисловие и первая программа "Привет Мир!"

Assembler. Регистры [#2]Скачать

Assembler. Регистры [#2]

Пишем тетрис на ассемблере под DOS (x86)Скачать

Пишем тетрис на ассемблере под DOS (x86)

Фрактал Мандельброта на Ассемблере, часть #3. Рисуем точкуСкачать

Фрактал Мандельброта на Ассемблере, часть #3. Рисуем точку

#10. Функции / 2. Ассемблер и процедуры / Программирование с нуляСкачать

#10. Функции / 2. Ассемблер и процедуры / Программирование с нуля

#1 Первая программа на ассемблере MASMСкачать

#1  Первая программа на ассемблере MASM

Геометрия - Построение правильного треугольникаСкачать

Геометрия - Построение правильного треугольника

#9. Стек / 1. Ассемблер и процедуры / Программирование с нуляСкачать

#9. Стек / 1. Ассемблер и процедуры / Программирование с нуля

Assembler - регистры и арифметические командыСкачать

Assembler - регистры и арифметические команды

Строим треугольник по трем сторонам (Задача 5).Скачать

Строим треугольник по трем сторонам (Задача 5).

Сделал 3D в консоли с нуля | трассировка лучейСкачать

Сделал 3D в консоли с нуля | трассировка лучей

Как страдали программисты в 80-х. Подключаем Arduino модуль к БК 0010 и пишем для него игруСкачать

Как страдали программисты в 80-х. Подключаем Arduino модуль к БК 0010 и пишем для него игру

Написал 3D Игру в Консоли!Скачать

Написал 3D Игру в Консоли!

Как люди представляют программиста, и как он выглядит на самом деле!Скачать

Как люди представляют программиста, и как он выглядит на самом деле!

GNU Assembler x64. Регистр флаговСкачать

GNU Assembler x64. Регистр флагов
Поделиться или сохранить к себе: