Начало программирования на Ассемблере для Windows
🕛 30.10.2006, 15:53
Создание полноценного приложения для Windows, использующего все возможности данной системы,- довольно сложная задача. В то же время возможно написание каких-то небольших приложений или подпрограмм на ассемблере.Создание приложение для Windows в общем случае имеет больше стадий чем для Dos. Это объясняется тем, что при компоновке Windows-программы в нее включается не только код исходных модулей, но и ресурсы. Итак создание программы для Windows должно содержать следующие этапы :
разработка исходного текста программы
создание ресурсов, которые используются программой
создание текстового файла описания используемых ресурсов
создание текстового файла описания программного модуля
компиляция и компоновка программы с использованием файла описания программного модуля
компиляция файла ресурсов и включение его в готовый исполняемый файл
В данной статье будет использоваться компилятор и компоновщик фирмы Borland - Turbo Assembler и Turbo Link. Конечно возможно некоторым покажеться странным использование данного продукта, но могу вас уверить на мой взгляд удобнее инструмента для программирования на ассемблере для Windows вы не найдете. Я использую версию Turbo Assembler 5.0.
В этой статье я также не буду рассказывать о создании ресурсов и присоеденении их к исполняемому файлу, мы рассмотрим только простой пример программы, и попробуем найти какие-то сходства с программированием под Dos.
Итак маленький пример:
Файл hello.asm
.model large, WINDOWS PASCAL ; Подключаем файл где описаны константы (типа "MB_OK", "MB_ICONEXCLAMATION") include windows.inc ; Говорим что будем использовать функцию API MessageBox extrn MESSAGEBOX:proc ; Сегмент данных .data ; Пустое место для информации Program Manager'a freespace db 16 dup(0) ; Заголовок диалогового окна lpszTitle db 'Generic Sample Assembly Application',0 ; Текст диалогового окна lpszText db 'Hello World !',0 ; Сегмент кода .code ; Наш старый "добрый" start (На самом деле WinMain) start: ; Инициализируем задачу и получаем входные параметры call INITTASK or ax,ax ; Если инициализация прошла успешно jnz @@OK ; Если ошибка jmp @@Fail @@OK: ; Сохраняем HINSTANCE mov hInstance,di ; Инициализируем приложение call INITAPP,hInstance or ax,ax jnz @@InitOK @@Fail: ; Если инициализация завершилась неудачно mov ax, 4CFFh int 21h @@InitOK: ; Выводим на экран диалоговое окно call MESSAGEBOX,0,ds offset lpszText,ds offset lpszTitle,MB_OK+MB_ICONEXCLAMATION ; Хе-хе а вот и выход mov ax,4c00h int 21h end start
Как видите разработка простых приложений для Windows практически не отличается от программирования для Dos. Однако есть маленькие особенности которые мы сейчас рассмотрим:
.model large, WINDOWS PASCAL
Про эту строчку много говорить и не надо, но она имеет одну особенность. Система Windows и ее функции используют алгоритм "прямой" передачи параметров через стэк, как в языке Pascal. Поэтому мы и вводим этот модификатор в директиву modal.
include windows.inc
Подключение файлов определений тоже не представляет ничего нового, но если вы не хотите самостоятельно перекапывать файлы Windows и выяснять какие константы имеют какие значение просто включите эту строчку в вашу программу.
extrn MESSAGEBOX:proc
Наверно одна из самых существенных строк этого примера. Она говорит компилятору, что мы будем использовать внешнюю функцию MessageBox. Я думаю у вас не возникает никаких сомнений - существование этой функции в Windows API.
freespace db 16 dup(0)
Зачем в начале сегмента данных стоят 16 байт с нулями ? Для приложений Win16 эти 16 байт являются обязательным условием нормальной работы программы. При загрузке Вашего приложения Windows заменяет эти 16 байт своей информацией. Для более подробного разъяснения этой проблеммы обратитесь к книге Мэта Патрика (Matt Patrick) "Внутри Windows" ("Windows Internals"), 1993, Addison Wesley.
Всем известно, что каждое приложение для Windows начинается с WinMain. Через параметры переданные этой функции можно было получить командную строку, идентификатор предидущего экземпляра программы и так далее и тому подобнее. Немного сложнее процедура инициализации приложения происходит в Assembler.
Первым шагом в инициализации приложения под Windows будем считать вызов функции INITTASK. После выполнения эта функция возвращает:
При успешном завершении AX=1
DX содержит nCmdShow, то есть параметр, указывающий на стиль просмотра окна
ES:BX содержит адрес командной строки
SI содержит идентификатор ранее загруженной программы hPrevInstance
DI содержит идентификатор загруженной программы hInstance
После обработки входных параметров нужно инициализировать приложение, используя функцию INITAPP. Единственным параметром этой функции служит идентификатор приложения hInstance.Функция INITAPP возвращает в AX=1 если приложение успешно проинициализировано.
Для того, что бы посмотреть, что же эта программа умеет делать вы должны создать еще один файл .def
Файл hello.def
NAME HELLO
EXETYPE WINDOWS
CODE MOVABLE DISCARDABLE
DATA MOVABLE MULTIPLE DISCARDABLE
STACKSIZE 5120
HEAPSIZE 4096
DESCRIPTION 'Copyright(C) 1999 by BlackWolf'
Содержимое этого файла мы рассмотрим подробнее :
NAME HELLO
Имя получаемого модуля "HELLO".
EXETYPE WINDOWS
Тип получаемого модуля - исполняемый файл Windows.
CODE MOVABLE DISCARDABLE
Ключевым словом CODE мы можем выставлять параметры нашего кодового сегмента. О возможных параметрах сегмента смотрите ниже.
DATA MOVABLE MULTIPLE DISCARDABLE
Тоже самое только для сегмента данных.
STACKSIZE 5120
Устанавливаем размер стэка равным 5120.
HEAPSIZE 4096
Устанавливаем размер "кучи" равным 4096.
DESCRIPTION 'Copyright(C) 1999 by BlackWolf'
Директива DESCRIPTION вставляет в файл кода указанный текст и обычно применяется для примечания о авторских правах.
Теперь рассмотрим параметры сегментов. Они могут быть различных типов и в принципе формат инструкций CODE и DATA выглядит таким образом:
CODE [FIXED|MOVABLE] [DISCARDABLE|NONDISCARDABLE] [PRELOAD|LOADONCALL]
Скобки в данном случае показывают, что элемент не обязателен. Вертикальная черта означает "ИЛИ". Опции директивы CODE означают:
FIXED - Сегмент имеет фиксированный адрес.
MOVABLE - Сегмент может быть перемещен, что бы освободить пространство в памяти.
DISCARDABLE - Сегмент может быть выгружен, чтобы освободить пространство.
NONDISCARDABLE - Сегмент не может быть выгружен.
PRELOAD - Сегмент загружается в память при запуске приложения.
LOADONCALL - Сегмент загружается в память только когда происходит обращение к некоторому его элементу.
DATA [NONE|SINGLE|MULTIPLE] [READONLY|READWRITE] [PRELOAD|LOADONCALL] [SHARED|NONSHARED]
Скобки в данном случае показывают, что элемент не обязателен. Вертикальная черта означает "ИЛИ". Опции директивы DATA означают:
NONE - Cегмент данных отсутствует (применяется только для DLL).
SINGLE - Единственный сегмент данных, разделяемый всеми процессами (применяется по умолчанию для DLL).
MULTIPLE - Несколько сегментов данных (применяется по умолчанию для исполняемых файлов).
READONLY - Данные в сегменте можно только читать, но не изменять.
READWRITE - Данные в сегменте можно как читать, так и изменять.
PRELOAD - Сегмент заранее автоматически загружается в память.
LOADONCALL - Сегмент загружается в память при обращении к нему.
SHARED - Одна копия сегмента данных разделяется между всеми процессами (применяется по умолчанию для 16-битных DLL)
NONSHARED - Отдельная копия сегмента данных загружается для каждого процесса (применяется по умолчанию для приложений и 32-битных DLL).
Существует также еще одна очень интересная директива - STUB. Она вставляет в файл .EXE кода для Windows программу Dos. Эта директива никогда не применяется для библиотек. Если вы не указываете директиву STUB Turbo Assembler вставит в ваш исполняемый файл программу WINSTUB.EXE. Эта программа является заглушкой для запуска Windows приложений под Dos. При запуске вашего приложения под Dos вы получите примерно такое сообщение :
This program must be run under Microsoft Windows.
Используя данную директиву вы можете написать программу которая будет прекрасно работать как под Dos так и под Windows.
После написания файлов hello.asm и hello.def вы должны скомпилировать и скомпоновать программу используя такие параметры для Turbo Assembler:
tasm hello.asm
После компиляции вы должны получить .OBJ файл.
tlink hello,hello,,import.lib(*),hello
После компоновки вы получите готовый исполняемы файл Windows. :)
(*) Если вы используете импорт функций API или импорт функций из других DLL, вы обязательно должны включить в компоновку библиотеку import.lib для 16-битных приложений, или библиотеку import32.lib для 32-битных приложений.
На этом я закончу первую вводную статью о программировании на ассемблере для Windows.