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

Итак, дорогие друзья, вы уже знаете многое, но от этого область вашего невежества становится лишь все больше и больше. Так уж устроен мир: пока мы не задумываемся о принципах существования или устройства того или иного объекта, мы имеем пробел лишь в одной области — области этого объекта, однако стоит лишь начать изучать этот объект, как оказывается, что список связанных с ним неизвестных нам доселе объектов уходит за горизонт нашего сознания.

Для тех, кто решительно настроен покорять эти горизонты в мире программирования под Windows, я продолжаю свой рассказ про FASM. Сегодня мы закрываем тему ресурсов и приступаем к созданию собственного текстового редактора. Именно поэтому в прошлой статье я просил вас рассмотреть самостоятельно ресурсы в примере MINIPAD из папки EXAMPLES. Если у вас с этим возникли затруднения — попробуйте разобраться со второго захода, теперь уже с моими пояснениями:

format PE GUI 4.0
entry start

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

section '.data' data readable writeable

title db 'Мой Первый Текстовый Редактор',0
class db 'FASMWIN32',0
_edit db 'EDIT',0
nofnc db 'Эта функция недоступна в демо-версии',0
errtxt db 'Код ошибки: %u',0
errbuf rb $-errtxt+10
hwnd dd ?
hmenu dd ?
hedit dd ?
hacc dd ?
font dd ?

wc WNDCLASS 0,WindowProc,0,0,0,0,0,COLOR_BTNFACE+1,0,class
msg MSG
client RECT
menuinfo MENUITEMINFO sizeof.MENUITEMINFO,MIIM_STATE

section '.code' code readable executable

start:
invoke GetModuleHandle,0
mov [wc.hInstance],eax
invoke LoadIcon,[wc.hInstance],IDI_MAIN
mov [wc.hIcon],eax
invoke LoadCursor,0,IDC_ARROW
mov [wc.hCursor],eax
invoke RegisterClass,wc
cmp eax,0
je error
invoke LoadAccelerators,[wc.hInstance],IDA_MAIN
mov [hacc],eax
invoke LoadMenu,[wc.hInstance],IDM_MAIN
mov [hmenu],eax
invoke CreateWindowEx,0,class,title,WS_VISIBLE+WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,\
0,eax,[wc.hInstance],0
cmp eax,0
je error
mov [hwnd],eax
msg_loop:
invoke GetMessage,msg,0,0,0
cmp eax,0
je end_loop
invoke TranslateAccelerator,[hwnd],[hacc],msg
cmp eax,0
jne msg_loop
invoke TranslateMessage,msg
invoke DispatchMessage,msg
jmp msg_loop

error:
invoke GetLastError
invoke wsprintf,errbuf,errtxt,eax
invoke MessageBox,0,errbuf,0,MB_OK

end_loop:
invoke ExitProcess,[msg.wParam]

proc WindowProc hwnd,wmsg,wparam,lparam
push ebx esi edi
cmp [wmsg],WM_COMMAND
je .wmcommand
cmp [wmsg],WM_CREATE
je .wmcreate
cmp [wmsg],WM_SIZE
je .wmsize
cmp [wmsg],WM_SETFOCUS
je .wmsetfocus
cmp [wmsg],WM_DESTROY
je .wmdestroy
.defwndproc:
invoke DefWindowProc,[hwnd],[wmsg],[wparam],[lparam]
jmp .finish
.wmcommand:
mov eax,[wparam]
cmp ax,IDM_NEW
je .NEW
cmp ax,IDM_OPEN
je .OPEN
cmp ax,IDM_SAVE
je .SAVE
cmp ax,IDM_SAVEAS
je .SAVEAS
cmp ax,IDM_EXIT
je .EXIT
cmp ax,IDM_UNDO
je .UNDO
cmp ax,IDM_CUT
je .CUT
cmp ax,IDM_COPY
je .COPY
cmp ax,IDM_PASTE
je .PASTE
cmp ax,IDM_DELETE
je .DELETE
cmp ax,IDM_SELECTALL
je .SELECTALL
cmp ax,IDM_ABOUT
je .ABOUT
jmp .finish
; обработчики сообщений меню файл:
.NEW:
invoke MessageBox,0,nofnc,title,MB_OK
jmp .finish
.OPEN:
invoke MessageBox,0,nofnc,title,MB_OK
jmp .finish
.SAVE:
invoke MessageBox,0,nofnc,title,MB_OK
jmp .finish
.SAVEAS:
invoke MessageBox,0,nofnc,title,MB_OK
jmp .finish
.EXIT:
invoke DestroyWindow,[hwnd]
jmp .finish
; обработчики сообщений меню правка:
.UNDO:
mov eax,EM_UNDO
jmp .send2editbox
.CUT:
mov eax,WM_CUT
jmp .send2editbox
.COPY:
mov eax,WM_COPY
jmp .send2editbox
.PASTE:
mov eax,WM_PASTE
jmp .send2editbox
.DELETE:
mov eax,WM_CLEAR
jmp .send2editbox
.send2editbox:
invoke SendMessage,[hedit],eax,0,0
jmp .finish
.SELECTALL:
invoke SendMessage,[hedit],EM_SETSEL,0,-1
jmp .finish
.ABOUT:
invoke DialogBoxParam,[wc.hInstance],IDD_ABOUT,[hwnd],AboutDialog,0
jmp .finish

.wmcreate:
invoke GetClientRect,[hwnd],client
invoke
CreateWindowEx,WS_EX_CLIENTEDGE,_edit,0,WS_VISIBLE+WS_CHILD+ WS_HSCROLL+WS_VSCROLL+ES_AUTOHSCROLL+ES_AUTOVSCROLL+ ES_MULTILINE,[client.left],[clien t.top],[client.right],[client.bottom],[hwnd],0,[wc.hInstance],NULL
cmp eax,0
je .failed
mov [hedit],eax
invoke
CreateFont,16,0,0,0,0,FALSE,FALSE,FALSE,RUSSIAN_CHARSET, OUT_RASTER_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY, FIXED_PITCH+FF_DONTCARE,NULL cmp eax,0
je .failed
mov [font],eax
invoke SendMessage,[hedit],WM_SETFONT,eax,FALSE
mov eax,0
jmp .finish
.failed:
mov eax,-1
jmp .finish
.wmsize:
invoke GetClientRect,[hwnd],client
invoke MoveWindow,[hedit],[client.left],[client.top],[client.right],[client.bottom],TRUE
mov eax,0
jmp .finish
.wmsetfocus:
invoke SetFocus,[hedit]
mov eax,0
jmp .finish
.wmdestroy:
invoke PostQuitMessage,0
mov eax,0
.finish:
pop edi esi ebx
ret
endp

proc AboutDialog hwnd,msg,wparam,lparam
push ebx esi edi
cmp [msg],WM_COMMAND
je .close
cmp [msg],WM_CLOSE
je .close
mov eax,0
jmp .finish
.close:
invoke EndDialog,[hwnd],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',\
gdi32,'GDI32.DLL'

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

section '.rsrc' resource data readable

IDM_MAIN = 101
IDA_MAIN = 201
IDD_ABOUT = 301
IDI_MAIN = 401

IDM_NEW = 1101
IDM_OPEN = 1102
IDM_SAVE = 1103
IDM_SAVEAS = 1104
IDM_EXIT = 1109

IDM_UNDO = 1201
IDM_CUT = 1202
IDM_COPY = 1203
IDM_PASTE = 1204
IDM_DELETE = 1205
IDM_SELECTALL = 1206

IDM_ABOUT = 1401

directory RT_MENU,menus,\
RT_ACCELERATOR,accelerators,\
RT_DIALOG,dialogs,\
RT_GROUP_ICON,group_icons,\
RT_ICON,icons,\
RT_VERSION,versions

resource menus,\
IDM_MAIN,LANG_RUSSIAN+SUBLANG_DEFAULT,main_menu

resource accelerators,\
IDA_MAIN,LANG_ENGLISH+SUBLANG_DEFAULT,main_keys

resource dialogs,\
IDD_ABOUT,LANG_RUSSIAN+SUBLANG_DEFAULT,about_dialog

resource group_icons,\
IDI_MAIN,LANG_NEUTRAL,main_icon

resource icons,\
1,LANG_NEUTRAL,main_icon_data

resource versions,\
1,LANG_NEUTRAL,version

menu main_menu
menuitem '&Файл',0,MFR_POPUP
menuitem <'Созд&ать',9,'Ctrl+N'>,IDM_NEW,0,MFS_GRAYED
menuitem <'&Открыть…',9,'Ctrl+O'>,IDM_OPEN,0,MFS_GRAYED
menuitem <'&Сохранить',9,'Ctrl+S'>,IDM_SAVE,0,MFS_GRAYED
menuitem 'Сохранить &как…',IDM_SAVEAS,0,MFS_GRAYED
menuseparator
menuitem <'В&ыход',9,'Ctrl+Q'>,IDM_EXIT,MFR_END

menuitem '&Правка',0,MFR_POPUP
menuitem <'&Отменить',9,'Ctrl+Z'>,IDM_UNDO
menuseparator
menuitem <'&Вырезать',9,'Ctrl+X'>,IDM_CUT
menuitem <'&Копировать',9,'Ctrl+C'>,IDM_COPY
menuitem <'Вст&авить',9,'Ctrl+V'>,IDM_PASTE
menuitem <'&Удалить',9,'Del'>,IDM_DELETE
menuseparator
menuitem <'Выделить в&се',9,'Ctrl+A'>,IDM_SELECTALL,MFR_END

menuitem '&Вид',0

menuitem '&Справка',0,MFR_POPUP+MFR_END
menuitem '&О программе',IDM_ABOUT,MFR_END

accelerator main_keys,\
FVIRTKEY+FNOINVERT+FCONTROL,'N',IDM_NEW,\
FVIRTKEY+FNOINVERT+FCONTROL,'O',IDM_OPEN,\
FVIRTKEY+FNOINVERT+FCONTROL,'S',IDM_SAVE,\
FVIRTKEY+FNOINVERT+FCONTROL,'Q',IDM_EXIT,\
FVIRTKEY+FNOINVERT+FCONTROL,'Z',IDM_UNDO,\
FVIRTKEY+FNOINVERT+FCONTROL,'X',IDM_CUT,\
FVIRTKEY+FNOINVERT+FCONTROL,'C',IDM_COPY,\
FVIRTKEY+FNOINVERT+FCONTROL,'V',IDM_PASTE,\
FVIRTKEY+FNOINVERT+FCONTROL,'A',IDM_SELECTALL

dialog about_dialog,'О программе',40,40,172,60,WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME
dialogitem 'STATIC',<'Мой Первый Текстовый Редактор',0Dh,0Ah,'Copyright ',0A9h,' BarMentaLisk 2008.'>,-
1,27,10,144,40,WS_VISIBLE+SS_CENTER
dialogitem 'STATIC',IDI_MAIN,-1,8,8,32,32,WS_VISIBLE+SS_ICON
dialogitem 'STATIC','',-1,4,34,164,11,WS_VISIBLE+SS_ETCHEDHORZ
dialogitem 'STATIC','Написан при помощи FASM',-1,12,42,100,20,WS_VISIBLE+SS_LEFT
dialogitem 'BUTTON','OK',IDOK,124,40,42,14,WS_VISIBLE+ WS_TABSTOP+BS_DEFPUSHBUTTON
enddialog

icon main_icon,main_icon_data,'1.ico'

versioninfo version,VOS_NT_WINDOWS32,VFT_APP,VFT2_UNKNOWN,LANG_RUSSIAN+ SUBLANG_DEFAULT,0,\
'Comments','Написан при помощи FASM',\
'CompanyName','BarMentaLisk',\
'FileDescription','Текстовый редактор',\
'ProductName',<'Мой Первый',0Dh,0Ah,'Текстовый Редактор'>,\
'LegalCopyright',<'Copyright ',0A9h, 'BarMentaLisk 2008'>,\
'FileVersion','0.1.0.0',\
'OriginalFilename','editor1.EXE'

Видали, сколько текста? Я бы на вашем месте не рискнул все это набирать вручную, а воспользовался бы электронной копией с форума. Хотя при вводе кода вручную он намного лучше понимается и запоминается. Действует такой же принцип, как и при конспектировании услышанного материала. В секции данных вам все должно быть понятно, кроме rb $-errtxt+10. У символа $ есть несколько значений в зависимости от контекста. К примеру, он может использоваться для обозначения шестнадцатеричного числа, как в паскале: $FACE8D. Он также является специальным символом, обозначающим текущее относительное смещение. Подробно о смещениях я планирую рассказать вам в следующих статьях, но сейчас тем из вас, кто вообще не имеет представления о смещениях, скажу "по-колхозному": смещение — это номер байта в программе, причем отсчет начинается с нулевого байта, затем идет первый, второй и так далее. Наши данные укладываются в секцию данных друг за дружкой в каждый байт. Мы знаем, что title является указателем на первый символ строки заголовка, а class — на первый символ строки класса, причем class идет следом за title. Значит, class-title=30, потому что ровно 30 байт занимают данные title (29 символов + завершающий ноль). Под буфер errbuf необходимо выделить ровно на 10 байт больше памяти, чем занимает строка errtxt. В таком случае мы могли бы использовать инструкцию rb 15+10 или rb 25. Но тогда каждый раз при изменении длины строки придется изменять и размер буфера. А если подобных строк и буферов много, то очень легко запутаться. Так что, если размер буфера относителен, то надежнее выражать его таким образом. $ — это относительный номер текущего байта — значит, $-errtxt (при условии, что выражение следует сразу за errtxt) при компиляции будет всегда замещаться размером этой строки в байтах. Такие вот дела. Напоминаю, что LoadIcon загружает указанную иконку из ресурсов исполняемого файла. Первый параметр — идентификатор исполняемого файла или ноль для загрузки стандартной иконки. Второй параметр — строка — имя иконки из ресурсов либо идентификатор стандартной иконки, если первый параметр ноль. Теперь мы указываем наше приложение в качестве первого параметра и идентификатор нашей иконки (для удобства используется псевдоним IDI_MAIN=401) в качестве второго. Курсор оставляем стандартный. Меню и акселераторы ставим свои.

Акселераторы — это горячие клавиши или комбинации клавиш для быстрого выполнения тех или иных действий в программе. Они также являются одним из типов ресурсов, тип — RT_ACCELERATOR. Описываются макроинструкцией accelerator, за которой после имени ресурса тройками через запятую следуют параметры: флаги акселератора; код виртуальной клавиши или символ ASCII; идентификатор. Список флагов (Accelerator flags) и кодов виртуальных клавиш (Virtual key codes), как обычно, смотрим в EQUATES\USER32.INC. Атрибуты FALT, FCONTROL и FSHIFT означают, что вместе с указанной клавишей нажаты Alt, Ctrl, Shift или их комбинация. Атрибут FVIRTKEY означает, что событие происходит при нажатии соответствующей виртуальной клавиши (символ клавиши в кавычках должен быть в верхнем регистре). При отсутствии этого атрибута горячей клавишей будет считаться указанный символ — в этом случае Alt, Ctrl, Shift не учитываются, а также клавиша получится зависимой от регистра и раскладки. FNOINVERT означает, что выбранный при помощи акселератора пункт не будет подсвечиваться — этот атрибут является устаревшим и используется лишь для обратной совместимости с 16-битными версиями Windows. Функция LoadAccelerators используется аналогично функции LoadMenu, только возвращаемое ею значение — дескриптор загруженной таблицы акселераторов. В цикл обработки сообщений включена функция TranslateAccelerator. Она обрабатывает сообщения о нажатии горячих клавиш — акселераторов нашего меню, переводя их в сообщения WM_COMMAND или WM_SYSCOMMAND. Чтобы переведенные функцией сообщения можно было отличить от сообщений меню или элементов управления, старший байт параметра wparam сообщения устанавливается в единицу (табл. 1). Параметры: дескриптор обрабатываемого окна; дескриптор таблицы акселераторов; указатель на структуру MSG. При ошибке функция возвращает ноль. Если результат отличен от нуля — значит, сообщение обработано и не нуждается в дополнительной обработке функцией TranslateMessage.

Сообщение от:wParam (старшее слово)wParam (младшее слово)lParam
Меню0идентификатор меню (IDM_*)0
Акселератора1идентификатор акселератора(IDM_*)0
Элемента управлениясобытиеидентификатор элементадескриптор элемента (handle)


Для дополнительной обработки ошибок использована функция GetLastError. Эта функция возвращает код последней ошибки (если была ошибка), произошедшей при вызове API-Функции. При помощи функции wsprintf мы приводим код ошибки к символьному виду и выводим на экран функцией MessageBox. Список кодов ошибок можно найти в MSDN или в каком-либо справочнике по Windows, хотя даже у самой Microsoft отсутствует полный список кодов ошибок. Обработчики сообщений меню файл за исключением пункта Выход в ресурсах установлены как недоступные (MFS_GRAYED), но на случай их разблокировки каким-нибудь взломщиком обрабатываются выводом сообщения о недоступности функции. Обработку этих пунктов мы добавим и изучим в следующей статье. Выход обрабатывается вызовом функции DestroyWindow. Эта функция предназначена для корректного уничтожения окна со всеми его элементами, зависимыми окнами. Она посылает окну сообщения WM_DESTROY и WM_NCDESTROY. Единственный ее параметр — дескриптор уничтожаемого окна. При обработке пунктов "отменить", "вырезать", "копировать", "вставить", "удалить" производится отправка соответствующего сообщения окну редактирования. Параметры всех сообщений, кроме EM_SETSEL (выделение текста), не используются и должны быть нулями. Параметры EM_SETSEL: номер символа начала выделения; номер символа конца выделения. Если первый параметр равен нулю, а второй — минус единице, то выделяется весь текст в окне. Если первый параметр равен минус единице — все выделения снимаются.

Обработчик пункта О программе вызывает функцию DialogBoxParam. Эта функция создает диалоговое окно (dialog box) — окно, разработанное специально для быстрого и удобного создания окон с дочерними элементами. Все его элементы описываются в одном блоке ресурсов и автоматически создаются средствами операционной системы при создании окна. Параметры: идентификатор исполняемого модуля программы; идентификатор ресурса, содержащего шаблон диалогового окна; дескриптор окна-владельца; указатель на процедуру диалогового окна; значение, передаваемое диалоговому окну в параметре lParam сообщения WM_INITDIALOG. Описание элементов окна в шаблоне интуитивно понятно и схоже с параметрами функции CreateWindowEx. Процедура диалогового окна аналогична процедуре обычного окна. Более подробно о диалоговых окнах мы поговорим в одной из следующих статей. Функция GetClientRect возвращает координаты клиентской области указанного окна в указанную структуру RECT. Параметры: дескриптор окна; указатель на структуру RECT. Причем в элементы структуры left и top возвращаются нули, а в элементы right и bottom — соответственно ширина и высота клиентской области окна. Эти данные необходимы нам, чтобы правильно задать размеры окна для редактирования. Нижеследующие команды имеют смысл лишь при успешном создании окна, поэтому, если в eax возвращается ноль (ошибка), мы пропускаем остальные команды.

CreateFont создает или изменяет шрифт с указанными параметрами: высота шрифта, ноль для высоты по умолчанию; ширина шрифта, ноль для оптимальной пропорции; угол наклона текста в десятых долях градуса; угол наклона символов в десятых долях градуса; жирность шрифта (0-1000, 0 — по умолчанию, 400 — нормальный, 700 — жирный); false либо true = 0 либо 1 = обычный либо курсив; обычный либо подчеркнутый; обычный либо зачеркнутый; набор символов — например, ANSI_CHARSET или DEFAULT_CHARSET (список ищем в INCLUDE\EQUATES\GDI32.INC); флаг степени соответствия шрифта заданным параметрам — например, OUT_DEFAULT_PRECIS (список ищем там же); флаг степени сглаживания шрифта — например, CLIP_DEFAULT_PRECIS (список там же); флаг качества шрифта — например, DEFAULT_QUALITY (список там же); сдвиг и семейство шрифта — например, DEFAULT_PITCH+FF_DONTCARE; указатель на строку с названием шрифта или ноль для оптимального выбора. Возвращаемое в eax значение — дескриптор созданного шрифта либо ноль при ошибке. Сообщение WM_SETFONT устанавливает указанный шрифт. Параметры: дескриптор шрифта; флаг перерисовки окна с новым шрифтом (0 либо 1).

При изменении размеров основного окна (сообщение WM_SIZE) необходимо изменять размеры и дочернего окна редактирования. Для этого мы снова узнаем размер главного окна и перемещаем дочернее — MoveWindow. Эта функция изменяет позицию окна и его размеры. Параметры: дескриптор окна; позиция X; позиция Y; ширина; высота; перерисовки окна (0 либо 1).

На сегодня, думаю, хватит. Продолжение, естественно, следует.

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

Исходные тексты программ вы можете найти на форуме: сайт

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


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

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