Все-таки, это случилось!!! Вышел новый выпуск рассылки!!! Несмотря на все препятсвия (завал на работе и мой переезд в другой город
на лето), мы снова вместе ;-).
Сегодня я вам дам адресок сайта моей команды вот он TLRH.nm.ru. На нем в данный момент находится архив рассылки и пара статей
моего коллеги по команде. В ближайшее время на этом сайте появятся книги и различная документация по ассемблеру, которые
по нашему мнению явяляются наиболее интересными.
Ну что же, приступаем к занятиям!!!
Hello World. Первая программа с графическим интерфейсом .
Сегодня я решил дать вам немного отдохнуть от тонкостей низкоуровневого программирования. Сегодня мы напишем несколько
разных программ, которые можно назвать полноценными Windows-программами.
Я вам расскажу о двух API-функциях: функции выведения окна-сообщения (MessageBox) и выхода из программы
(ExitPtrocess).
Для первого знакомства предостаточно.
Прежде чем мы приступим к написанию программы, немного теории (а как же без этого ;-) ). Основой в программирование под
Windows, является использование использования специальных функций - API-функции. API - Application Programming Interface
( прикладной программный интерфейс). Windows API - это большая коллекция функций, располагающихся непосредственно в
операционной системе. Эти функции находятся в нескольких динамически компонуемых библиотек (DLLs), таких как kernel32.dll,
user32.dll и gdi32.dll. Kernel32.dll содержит API функции, взаимодействующие с памятью и управляющие процессами.
User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех,
существуют также другие библиотеки, которые вы можете использовать. Windows программы динамически подсоединяется к
этим библиотекам, то есть код API функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать
ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows
читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и библиотек, в которых эти
функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих
функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одна для ANSI и другая для Unicode. Hа конце имен API функций для ANSI стоит "A",
например, MessageBoxА. В конце имен функций для Unicode находится "W". Windows 95 изначально поддерживает ANSI и WIndows NT
Unicode. Мы обычно имеем дело с ANSI строками (массивы символов, оканчивающиеся нулем. Размер ANSI-символа - 1 байт. ANSI достаточна для
европейских языков, но она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях
в дело вступает UniCode. размер символа UNICODE - 2 байта, и поэтому может поддерживать 65536 уникальных символов.
Сейчас мы шаг за шагом будем писать исходный текст нашей программы, и по ходу я буду объяснять, что мы делаем и почему так.
Каркас программы вам хорошо знаком, вот он:
Для того чтобы программа работала с API-функциями, как уже было сказано, необходимо импортировать функции из
динамически компонуемых библиотек Windows. Чтобы это сделать, необходимо подключить к вашей программе библиотеки
импорта (*.lib). Это осуществляется с помощью директивы includelib. Эти библиотеки входят в состав пакета MASM32.
Их имена соответствуют именам динамически компануемых библиотек (далее DLL), например, если мы импортируем функции
из библиотеки Kernel32.dll, то нам необходимо подключить библиотеку kernel32.lib. С этим все просто.
Обе API-функции,
которые мы рассматриваем на сегодняшнем уроке находятся в kernel32.dll. Теперь наш каркас приобретает такой вид:
.486
.model flat, stdcall
option casemap:none
includelib C:\masm32\lib\kernel32.lib
.data
.data?
.const
.code
start:
end start
Как видите после директивы includelib пишется полный путь к библиотеке импорта.
Если вы инсталлировали MASM32 по умолчанию в корневой каталог, то путь у вас будет именно, таким, если в
другое место, то и путь придется писать другой. Кроме того, что нам необходимо импортировать API-функции, для того
чтобы их использовать, нам необходимо описать их прототип. Вот как выглядит прототип функции ExitProcess
ExitProcess proto uExitCode:DWORD
Прототип указывается, для того чтобы ассемблер знал, какую функцию мы будем вызывать, и с каким количеством данных, и какого
размера, эта функция будет работать. Как узнать, каким образом описывать прототип? Для этого нам понадобиться справочник
Win32 Programmer's Reference, он входит в пакет Delphi и Builder. Так же его можно скачать здесь. Открываем справочник и
находим в нем ExitProcess. В нем вы увидите следующие строки
VOID ExitProcess (
UINT uExitCode
);
Что это? Это API-функция в том виде, в котором она используется при
программировании на языке Cи. Мы замечаем, что у
этой функции всего один параметр - uExitCode. Как узнать размер этого параметра? Размер написан в начале этого
параметра - UINT.
Вы, естественно возмущаетесь, что это за размер такой? Мы таких не знаем! Знаем! Просто, господа, которые создавали
API были с интересным складом ума. Они понапридумывали столько... Со всеми этими "наворотами" я буду вас знакомить по мере
введения новых "терминов". UINT - размер Dworld. Значение этого параметра должно быть 0 (нуль)
Теперь несколько слов о синтаксисе прототипа. Вот полный вид
конструкции прототипа:
ИмяФункции PROTO [ИмяПаpаметpа]:ТипДанных,[ИмяПаpаметpа]:ТипДанных,...
Как видите ничего сложного. Даже комментарии излишне. Теперь пришло
время познакомится с одной ассемблерной командой,
которая осуществляет вызов подпрограммы, в нашем случае API-функции. Имя этой команды - call. Как я говорил в предыдущем
выпуске рассылки, данные, используемые API функции необходимо загружать в стек. В следствии этого, собственно вызов
функции будет иметь следующий вид:
Push 0 ; кладем в стек нуль (параметр uint)
Call ExitProcess; вызов функции выхода.
А текст программы будет таким.
.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
ExitProcess proto uExitCode:DWORD
.data
.code
start:
push 0
call ExitProcess
end start
Вот программа работающая, но ни как себя не проявляющая, с чего бы это ;-).
Теперь когда вы обладаете небольшим запасом теоретических знаний переходим к написанию программы выводящей
окно-сообщение. Это уже интересно!!! Сейчас мы пройдем все этапы, которые вы будете проходить при использовании
не известных вам API-функций. Открываем Win32 Programmer's Reference, ищем функцию MessageBox. Выписываем Сишный
вид API-функции:
int MessageBox
(
HWND hWnd, // хендл родительского окна
LPCTSTR lpText, // адрес текста окна
LPCTSTR lpCaption, // адрес заголовка окна
UINT uType // стиль окна
);
Как много всего нового, непонятного... Ничего, сейчас будем разбираться. Мы начнем разбираться с середины - с размера
параметров.
HWND
h
хэндл
DWORD
LPCTSTR
lp
указатель
DWORD
Теперь разберемся с комментариями. Хендл - новое для вас понятие. Хендл - 32-разрядный номер, при помощи
которого окно может быть однозначно идентифицировано. Что такое родительское окно? Мы к этому вопросу вернемся
позже, сейчас скажу, что в данном случае у нас родительское окно отсутствует, поэтому этот параметр будет равен нулю.
Про адреса вы немного уже знаете ;-), чтобы поместить куда-либо (в регистр, в стек) адрес строки необходимо применять
директиву offset. Поясняю, если нам необходимо в регистр eax поместить адрес строки, то данная команда будет иметь
следующий вид:
mov eax, offset Stroka
если необходимо поместить в стек, то вид такой
push offset Stroka
Теперь про стиль окна. Я приведу текст из справочника,
в моем переводе =).
uType
Определяет набор флажков, которые определяют содержание и поведение диалогового окна.
Определите один из следующих флажков, чтобы указать кнопки, необходимые в окне сообщений:
Флажок
  Значение
MB_ABORTRETRYIGNORE Окно сообщений содержит три кнопки : Прервать, Повторить, и Игнорировать.
MB_OK
 
 
 
Окно сообщений содержит одну кнопку : OK. Это -
значение по умолчанию.
MB_OKCANCEL
 
  Окно сообщений содержит две кнопки: OK и Отмена.
MB_RETRYCANCEL
 
Окно сообщений содержит две кнопки: Повторить и Отмена.
MB_YESNO
 
 
  Окно сообщений содержит две кнопки: Да и Нет
MB_YESNOCANCEL
 
Окно сообщений содержит три кнопки: Да, Нет, и Отмена.
Определите один из следующих флажков, чтобы отобразить иконку в окне сообщений:
Мы рассмотрим несколько вариантов использования флажков. С теми,
которые останутся, вы сможете разобраться самостоятельно.
Пишем прототип функции.
MessageBox PROTO hWnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
Как я уже говорил, многие API-функции бывают двух видов, вызов окна с сообщением как раз к таким и относится.
Поэтому прототип функции мы должны немного изменить. На конце слова "MessageBox", мы должны дописать либо "A" либо "W",
мы, естественно, выбираем "A" (объяснение читайте выше), в связи с этим прототип нашей функции примет такой вид:
MessageBoxA PROTO hWnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
Чтобы нам было проще работать с функцией мы в коде своей программы будем использовать не MessageBoxA, а MessageBox,
но для этого мы должны "познакомить". Для этого мы напишем вот такую строку в исходном коде:
MessageBox equ <MessageBoxA>
В данной строке вам встретилась новая "директива". Почему слово
"директива", я поставил в кавычки? Дело в том, что это
не совсем директива. Это так называемый "псевдооператор". Этот псевдооператор предназначен для присвоения некоторому
выражению символического имени. Что мы и сделали. Более подробно разберем псведооператоры (он не один), когда будем
разбирать макросредства MASM'a. Я думаю, вам это не о чем не говорит ;-), но мое дело предупредить.
Для начала мы напишем программу, выводящую окно с одной кнопкой и без каких-либо стилей. Вот исходник программы:
MessageBoxA PROTO hWnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
MessageBox equ <MessageBoxA>
ExitProcess PROTO uExitCode:DWORD
.data
Caption db "SeDoYHg tutorial ;-)",0
text db "Asm is cool!!!",0
.code
start:
push 0
push offset Caption
push offset text
push 0
call MessageBox
push 0
call ExitProcess
end start
Откомпилировав этот код, и запустив его, вы увидите окошко. Ну, что же для начала совсем не плохо ;-). Теперь давайте
попробуем немного "раскрасить" наше окошко, для этого применим один из стилей.
Я хочу сделать окно с двумя кнопками, и с иконкой знака вопроса. Чтобы объединить стили, необходимо объединить их с
помощью директивы or. Но тут еще один момент, неободимо заменить имена стилей на их значение. Что это значит? Каждому
стилю соответствует определенное число - константа, эти значения известны. Вот "список соответствия" стилей и их
числовые значения:
MB_OK
equ
0h
MB_OKCANCEL
equ
1h
MB_ABORTRETRYIGNORE
equ
2h
MB_YESNOCANCEL
equ
3h
MB_YESNO
equ 4h
MB_RETRYCANCEL
equ
5h
MB_ICONHAND
equ
10h
MB_ICONQUESTION
equ
20h
MB_ICONEXCLAMATION equ
30h
MB_ICONASTERISK
equ
40h
Сразу возникает вопрос, где я нашел значения этих стилей? Скажу по секрету ;-), я их нашел в файле Winuser.h, который
входит в состав пакета MS Visual C++. Стили в этом файле описаны немного по другому, но для меня это не проблема =).
Предвижу вашу реакцию: "где мы возьмем этот файл?" Не много подождите я вам все объясню ;-).
Прежде чем мы приступим к написанию кода, я вам должен рассказать об одной важной вещи. Неужели нужно каждый раз набивать
строки с прототипами и описаниями стилей и прочее, прочее? В этом нет необходимости, мы можем создать файл, в котором
будут находится все "описания", и подсоединять его к нашему коду. Каким образом это сделать? Ну, во-первых, такой файл
должен быть с расширением *.inc (такие файлы принято называть инклуды), и в начале нашего кода мы должны написать такую
строку:
include \masm32\include\*.inc
Главным словом этой строки является директива include, она означает, что нам необходимо "объединить" файл нашего
кода и инклуд, путь к которому пишется после директивы include. На месте "звездочки" будет имя нашего инклуда.
Если инклуд находится в одной папке с файлом кода, то путь можно не писать:
include *.inc
Давайте создадим три инклуда: kernel32.inc, user32.inc и windows.inc. В инклуде kernel32.inc будут описаны прототипы
API-функций импортируемых из библиотеки kernel32.dll, в user32.inc - из user32.dll, а в инклуде windows.inc - будут
описаны стили. Таким образом, у нас будет три инклуда следующего содержания:
kernel32.inc
ExitProcess PROTO :DWORD
user32.inc
MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
MessageBox equ <MessageBoxA>
windows.inc
MB_OK
equ
0h
MB_OKCANCEL
equ
1h
MB_ABORTRETRYIGNORE
equ
2h
MB_YESNOCANCEL
equ
3h
MB_YESNO
equ 4h
MB_RETRYCANCEL
equ
5h
MB_ICONHAND
equ
10h
MB_ICONQUESTION
equ
20h
MB_ICONEXCLAMATION equ
30h
MB_ICONASTERISK
equ
40h
MB_ICONERROR
equ MB_ICONHAND
MB_ICONINFORMATION
equ MB_ICONASTERISK
MB_ICONSTOP
equ
MB_ICONHAND
MB_ICONWARNING
equ
MB_ICONEXCLAMATION
Давайте создадим для инклудов отдельную папку в той же папке, где находятся ваши исходники, с названием include.
Вот теперь мы готовы написать программу с двумя кнопками и иконкой!!!
Вод код этой программы:
.386
.model flat, stdcall
option casemap:none
include include\windows.inc
include include\kernel32.inc
include include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
Caption db "SeDoYHg tutorial ;-)",0
text db "Asm is cool!!!",0
.code
start:
push MB_YESNO or MB_ICONINFORMATION
push offset Caption
push offset text
push 0
call MessageBox
push 0
call ExitProcess
end start
Тут есть один момент, который я не рассказал, догадайтесь сами (это будет вашим первым домашним заданием. Задание на
внимание =).
Теперь вы можете потренироваться с разными иконками, с разными кнопками, образец я вам дал.
Конечно, "повествование" о MessageBox'е на этом не закончено, я продожу объяснение в следующем выпуске рассылки, который
будет скоро (обещаю =). Я думаю вам хватит материала для размышления ;-), на ближайших 3-4 дня.
За сим прощаюсь. С уважением, SeDoYHg[TLRH].
P.S. жду ваших вопросов и предложений. А то у меня складывается такое впечатление, что кроме меня эта рассылка никому не
нужна, постарайтесь разубедить меня своими письмами =) или вопросами на форуме моей команды.
Если вы хотите что-то спросить по ассемблеру, крэку, или просто поболтать прошу на Форум моей команды ,
там вы сможете получить ответы от меня, и моих товарищей Mafia32, formatC
Вы можете отправить письмо на мой почтовый ящик , только в том случае если вопрос имеет отношение к рассылке.
Обязательно заполняйте поле "Тема", письма без темы, я не буду читать. Для вашего удобства я разместил в рассылке e-mail
форму, вы можете прямо из нее отправлять свое письмо, но для этого должна быть настроена ваша почтовая программа.
Копирайты
Вся информация, содержащаяся в рассылках, является интеллектуальной собственностью своих законных авторов.
Перепечатка и распространение материалов рассылки только с разрешения автора.