Ассемблер под Windows для чайников. Часть 18

Приветствую начинающих и продолжающих изучение ассемблера! Когда-то давно люди даже не мечтали о том, что компьютер будет способен выводить на дисплей какую-либо графическую информацию, кроме букв и цифр. Сегодня же большинство людей просто не умеют работать вне графического интерфейса операционной системы. Поэтому современные программы для более удобного и приятного восприятия пользователем повсеместно снабжаются всевозможными графическими изображениями. Картинки в программе могут быть использованы как просто для красоты, так и для более быстрого ориентирования пользователя в структуре управления программой. Сегодня мы научимся нескольким способам внедрения простых растровых картинок типа bitmap (битовая карта) в программу для Windows, написанную на языке ассемблера для компилятора FASM.

Первый, и самый простой, способ — использование диалогового окна (DialogBox) и указание в его шаблоне статического элемента — картинки из ресурсов файла программы. Для использования этого способа нам придется вспомнить, во-первых, что такое ресурсы (5-я часть данного цикла статей), а во-вторых — что такое диалоговые окна (6-я и 8-я части). Для правильной компиляции ресурсов вам понадобится создать в папке с исходным кодом программы картинку bitmap.bmp и иконку bmp.ico. Приведенный ниже код программы рассчитан на картинку размером 320x240 и стандартную иконку любого понравившегося вам размера. Картинки других форматов не поддерживаются, так как для их приведения к типу bitmap потребовалось бы программное конвертирование содержимого картинки. Если вы скачаете электронную копию данного примера по указанному в конце статьи адресу, то в архиве с примером уже будут присутствовать необходимые картинка и иконка.

format PE GUI 4.0
entry start

include 'win32a.inc'
include 'encoding\WIN1251.INC'

HTCAPTION = 2

section '.data' data readable writeable

caption db 'Сообщение',0
msgOK db 'Была нажата кнопка ОК.',0
hInstance dd ?

section '.code' code readable executable

start:
invoke GetModuleHandle,0
mov [hInstance],eax
invoke DialogBoxParam,eax,37,HWND_DESKTOP,DialogProc,0
or eax,eax
jz exit

invoke MessageBox,HWND_DESKTOP,msgOK,caption,0
jmp exit

exit:
invoke ExitProcess,0

proc DialogProc hwnddlg,msg,wparam,lparam
push ebx esi edi
mov eax,[msg]
cmp eax,WM_COMMAND
je wmcommand
cmp eax,WM_INITDIALOG
je wminitdialog
cmp eax,WM_CLOSE
je wmclose
xor eax,eax
jmp finish
wmcommand:
cmp [wparam],IDCANCEL
je wmclose
cmp [wparam],IDOK
jne processed
invoke EndDialog,[hwnddlg],1
jmp processed
wminitdialog:
invoke LoadIcon,[hInstance],17
invoke SendMessage,[hwnddlg],WM_SETICON,ICON_SMALL,eax
jmp processed
wmclose:
invoke EndDialog,[hwnddlg],0
processed:
mov eax,1
finish:
pop edi esi ebx
ret
endp

section '.idata' import data readable writeable

library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL'

include 'api\kernel32.inc'
include 'api\user32.inc'

section '.rsrc' resource data readable
IDR_PICT = 27

directory RT_DIALOG,dialogs,\
RT_BITMAP,bitmaps,\
RT_ICON,icons,\
RT_GROUP_ICON,group_icons,\
RT_VERSION,versions

resource icons,\
1,LANG_NEUTRAL,icon_data
resource group_icons,\
17,LANG_NEUTRAL,main_icon
resource versions,\
2,LANG_ENGLISH+SUBLANG_DEFAULT,version
resource dialogs,\
37,LANG_ENGLISH+SUBLANG_DEFAULT,main
resource bitmaps,\
IDR_PICT,LANG_NEUTRAL,pict

dialog main,'Картинка в диалоге',10,10,213,147,WS_VISIBLE+WS_POPUP+
WS_CAPTION+WS_SYSMENU+DS_CENTER+DS_MODALFRAME
dialogitem 'STATIC',IDR_PICT,-1,0,0,0,0,WS_VISIBLE+SS_BITMAP
dialogitem 'BUTTON','&OK',IDOK,50,130,45,15,WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON
dialogitem 'BUTTON','&Cancel',IDCANCEL,120,130,45,15,WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON
enddialog

bitmap pict,'bitmap.bmp'
icon main_icon,icon_data,'bmp.ico'
versioninfo version,VOS__WINDOWS32,VFT_APP,VFT2_UNKNOWN,LANG_ENGLISH+SUBLANG_DEFAULT,0,\
'FileDescription','Bitmap example',\
'LegalCopyright',<'Copyright ',0A9h,' BarMentaLisk 2008'>,\
'FileVersion','0.1',\
'ProductVersion','0.1',\
'OriginalFilename','bmp_example'

Секция данных в этом примере, как видите, практически не содержит ничего важного, и при особом желании можно было бы даже обойтись без нее. Секция исполняемого кода также весьма невелика. В ней после получения дескриптора нашего исполняемого модуля осуществляется вызов функции DialogBoxParam с параметрами: дескриптор исполняемого модуля; идентификатор шаблона диалогового окна; дескриптор окна владельца (в нашем случае HWND_DESKTOP указывает на то, что диалог не имеет владельца и является самостоятельным окном); указатель на процедуру диалогового окна; значение, передаваемое процедуре окна с сообщением WM_INITDIALOG. Если по завершении данной функции возвращается ноль, то осуществляется переход на метку "exit:". Иначе выдается сообщение, что была нажата кнопка ОК. Таким образом, можно выполнять определенные действия в зависимости от того, какое значение получили на выходе из функции. Определить возвращаемое функцией значение можно во втором параметре функции EndDialog, вызываемой из тела процедуры диалогового окна. Первый параметр этой функции должен являться дескриптором диалогового окна. В данном случае при получении сообщения WM_COMMAND (событие с участием управляющего элемента окна) на метке "wmcommand:" мы выясняем, произошло ли нажатие кнопки OK или Cancel, сравнивая первый параметр полученного сообщения с идентифицирующими данные кнопки значениями, и выполняем соответствующие действия. Если нажата кнопка Cancel, то переходим на метку завершения диалога с возвратом нулевого значения (invoke EndDialog,[hwnddlg],0). Если не нажата вторая и последняя из проверяемых кнопок — кнопка OK, то переходим на метку "processed:", так как после обработки сообщения процедура диалогового окна должна поместить в eax единицу и выполнить команду возврата из процедуры (ret). Если сообщение не было обработано процедурой, то в eax должен быть помещен ноль — тогда операционная система сама выполнит требуемые действия по обработке сообщения. Ну, а если все-таки была нажата кнопка OK, то мы вызываем завершение диалоговой функции с возвратом единицы. Если бы на нашем диалоговом окне было много разных управляющих элементов, и требовалось производить различные действия по завершению диалога в зависимости от нажатых кнопок или состояния всяких пунктов и флажков, то мы бы завершали диалог с различными значениями, а потом в зависимости от этих значений выполняли необходимые действия после выполнения функции DialogBoxParam. В данной же ситуации у нас всего два варианта завершения диалога: OK и Cancel (не будем считать нажатие крестика, так как "отмена" и "закрыть" обычно считаются синонимами). Поэтому мы можем позволить себе надеяться на то, что, если диалог не завершен с нулевым значением, то полученное значение — единица. Есть, правда, еще вариант: в случае ошибки функция DialogBoxParam может вернуть минус единицу даже не создавая диалоговое окно, например, если вы указали неверный идентификатор шаблона с диалогом. Я надеюсь, что вы все укажете верно, однако ошибка может возникнуть не только в случае неверной компиляции программы. Может произойти и непредвиденный сбой системы, и поэтому еще на этапе разработки программы следует исходить из полной картины возможной ситуации. Действия, выполняемые автоматически в случае ошибки какой-либо функции, должны приносить максимальную пользу или хотя бы минимальный вред.

Отметим еще, что для корректного отображения иконки (ну мало ли пригодится) при инициализации окна диалога по получению сообщения WM_INITDIALOG мы должны выполнить загрузку иконки, если она не была уже загружена ранее, и установить ее текущей иконкой диалогового окошка. Постоянные читатели в общих чертах познакомились с методикой загрузки иконки из ресурсов исполняемого файла еще в 3-й и 6-й частях статьи. Что ж, пришло время узнать подробности, чтобы можно было уже закрыть данную тему и больше к ней не возвращаться. Под иконки выделено аж два типа ресурсов. Это RT_ICON и RT_GROUP_ICON. Подобным образом, кстати, дела обстоят только с курсорами (RT_CURSOR и RT_GROUP_CURSOR). По большому счету, все, что сейчас будет сказано об иконках, можно отнести и к курсорам, так что читайте внимательно и не спрашивайте потом, как это работает. RT_GROUP_ICON — это тип ресурса, который должен указывать на один или несколько ресурсов типа RT_ICON, каждый из которых содержит по одному изображению иконки. Это позволяет организовать иконки разных размеров и глубины цвета в один ресурс типа RT_GROUP_ICON, идентификатор которого можно использовать, к примеру, в функции LoadIcon. Функция сама выберет изображение подходящего размера из имеющегося набора. В самом начале секции ресурсов указывается макроинструкция directory, которая определяет типы содержащихся в секции ресурсов. После нее парами следуют значения, разделенные запятыми: первое в каждой паре — идентификатор типа ресурса, а второе — имя поддиректории, содержащей ресурсы указанного типа. В случае с иконкой из нашего примера это пары: RT_ICON,icons и RT_GROUP_ICON,group_icons.

Поддиректории размещаются ниже в этой же секции. Они объявляются макроинструкцией resource. За макроинструкцией следует имя поддиректории (соответствующее имени, указанному в макросе directory), затем тройками идут параметры ресурсов — первый параметр является идентификатором ресурса (выбирается программистом, используется для доступа к ресурсу из программы), второй параметр определяет язык, а третий — имя ресурса. Если ресурс не имеет языковой принадлежности, следует использовать константу LANG_NEUTRAL. В нашем примере для отдельной иконки за макросом resource следуют: имя поддиректории "icons", идентификатор "1", язык "LANG_NEUTRAL", имя ресурса "icon_data". Для группы иконок, которая у нас состоит всего лишь из одной иконки: имя поддиректории "group_icons", идентификатор "17", язык "LANG_NEUTRAL", имя ресурса "main_icon". Вы можете использовать свои удобные вам идентификаторы и имена — главное — чтобы они были уникальными у каждого ресурса и директории. Для размещения иконки в секции ресурсов используется макроинструкция "icon". Первый ее параметр — имя ресурса группы иконок (в нашем случае — "main_icon"). Далее парами через запятую следуют имена ресурсов отдельных иконок и взятые в кавычки имена реальных файлов с отдельными иконками. В нашем случае с единственной иконкой в группе пара всего одна: имя ресурса отдельной иконки — "icon_data", имя файла с иконкой — "'bmp.ico'". Не знаю, насколько доступно у меня получилось вам это объяснить, но лично я считаю, что понять можно, хотя и сложно. В любом случае на практике вам не потребуется все это запоминать — достаточно лишь понять, какую роль играет каждое из значений в описании ресурсов, чтобы впредь не путаться в них. Единственное, что надо запомнить, — какой из идентификаторов использовать во втором параметре функции LoadIcon. В первом параметре указываем дескриптор исполняемого модуля, из ресурсов которого будет загружена иконка. А во втором параметре — идентификатор группы иконок, из которой функция сама выберет наиболее подходящую. Итак, при успешной загрузке иконки функция LoadIcon возвращает в EAX ее дескриптор. Теперь мы можем послать нашему только что созданному диалоговому окну сообщение WM_SETICON, первый параметр которого определяет тип иконки (ICON_BIG или ICON_SMALL), а второй должен являться дескриптором загруженной в память иконки. Мы не проверяем возвращаемые значения на предмет ошибки, потому что даже если ошибка и произойдет на данном этапе, она не является критической и не дает особого повода для каких-либо действий. Диалоговые окна с картинкой на всю клиентскую область, как в нашем примере, часто можно увидеть во всяких мелких кряках и генераторах ключей. Правда, их отличает от нашего окошка еще кое-что, кроме наличия программы взлома. У них обычно отсутствует стандартная шапка окна. Чтобы получить подобное окошко, удалим из параметров стиля нашего диалога значения WS_CAPTION, WS_SYSMENU и DS_MODALFRAME:

dialog main,'Картинка в диалоге',10,10,213,147,WS_VISIBLE+WS_POPUP+DS_CENTER


Только вот теперь нам не за что ухватиться мышкой, чтобы переместить окно диалога. Вспоминаем, за какое место обычно берут кейген, чтобы передвинуть его, а? Правильно, за любое, кроме управляющих элементов. А как нам этого достичь? Для этого при нажатии левой кнопкой мышки на клиентской области окошка нам надо соврать операционной системе, что нажатие произошло в зоне визуально отсутствующей шапки окна. Добавляем в обработчик сообщений процедуры диалога следующие строки:

cmp eax,WM_LBUTTONDOWN
je wmlbuttondown

wmlbuttondown:
invoke ReleaseCapture
invoke SendMessage,[hwnddlg],WM_NCLBUTTONDOWN,HTCAPTION,0
jmp processed


Теперь при нажатии левой кнопки мыши на клиентской области окна будет осуществлен переход на метку "wmlbuttondown:". Там вызовом функции ReleaseCapture мы освобождаем окно от захвата курсора и тут же сообщением WM_NCLBUTTONDOWN (параметры: зона события, координаты курсора) говорим окну, что было произведено нажатие левой кнопки мыши в области заголовка (шапки) окна. Теперь до тех пор, пока кнопка мыши не будет отпущена, окно будет думать, что мы удерживаем левую кнопку мыши в области заголовка окна, и будет перемещаться вместе с мышиным курсором по всему экрану. Еще ввиду нынешнего отсутствия крестика закрытия окна можно добавить альтернативный вариант завершения диалога, например, при отпускании правой кнопки мыши над клиентской областью окна:

cmp eax,WM_RBUTTONUP
je wmclose


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

Надеюсь, вы осилите сегодняшний материал к выходу очередного продолжения статьи. А в следующий раз мы продолжим разговор про вывод картинок в окне программы, и не только про это. Успехов вам во всем, и до новых встреч!

Все приводимые примеры были протестированы на правильность работы под Windows XP и, скорее всего, будут работать под другими версиями Windows, однако я не даю никаких гарантий их правильной работы на вашем компьютере. Исходные тексты программ вы можете найти на форуме: сайт

BarMentaLisk, SASecurity gr., q@sa-sec.org


Компьютерная газета. Статья была опубликована в номере 42 за 2008 год в рубрике программирование

©1997-2024 Компьютерная газета