Автор работы: Пользователь скрыл имя, 25 Апреля 2012 в 22:09, статья
Обучение созданию окна. Окно это достаточно сложная, но, в то же время, очень важная штуковина в windows. Windows (от англ. окна) потому так и называется, что большинство операций в ней производится с окнами программ. Так удобнее всего работать с множеством приложений. Может быть когда-нибудь на смену окнам придут кубики или шарики, но на современных компьютерах пользователю удобнее всего работать с окнами.
Windows (от англ.
окна) потому так и называется,
что большинство операций в
ней производится с окнами
программ. Так удобнее всего работать
с множеством приложений. Может
быть когда-нибудь на смену
окнам придут кубики или
format PE GUI 4.0
entry start
include ‘win32a.inc’
section ‘.data’ data readable writeable
_class db ‘FASMWIN32′,0
_title db ‘Пустое Окно’,0
_error db ‘Ошибка’,0
wc WNDCLASS 0,WindowProc,0,0,0,0,0,COLOR_
msg MSG
section ‘.code’ code readable executable
start:
invoke GetModuleHandle,0
mov [wc.hInstance],eax
invoke LoadIcon,0,IDI_APPLICATION
mov [wc.hIcon],eax
invoke LoadCursor,0,IDC_ARROW
mov [wc.hCursor],eax
invoke RegisterClass,wc
cmp eax,0
je error
invoke CreateWindowEx,0,_class,_
cmp eax,0
je error
msg_loop:
invoke GetMessage,msg,0,0,0
cmp eax,0
je end_loop
invoke TranslateMessage,msg
invoke DispatchMessage,msg
jmp msg_loop
error:
invoke MessageBox,0,_error,0,MB_
end_loop:
invoke ExitProcess,[msg.wParam]
proc WindowProc hwnd,wmsg,wparam,lparam
push ebx esi edi
cmp [wmsg],WM_DESTROY
je .wmdestroy
.defwndproc:
invoke DefWindowProc,[hwnd],[wmsg],[
jmp .finish
.wmdestroy:
invoke PostQuitMessage,0
mov eax,0
.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’
Столько кода, и ничего особенного в
результате. В MessageBox хотя бы присутствовала
кнопка ОК, а тут - пустое окно. Не волнуйтесь,
как только мы разберемся с устройством
простейшего окна, мы сразу же перейдем
к навешиванию на него прочих элементов.
Используя MessageBox, мы были ужасно ограничены
набором стандартных параметров этой
функции. Кроме того, программа не могла
продолжать исполняться до тех пор, пока
пользователь не прореагирует нажатием
кнопки в окошке или его закрытием. В случае
использования окна, программа может исполняться,
параллельно реагируя на действия пользователя.
Сразу будет нелегко понять алгоритм работы
окна, но, как только вы его поймете, все
остальное пойдет намного легче. Приступаем
к разбору полетов.
В секции данных, после объявления трех
текстовых строк (класс окна, его заголовок,
сообщение об ошибке), объявляются две
структуры данных. Первая - WNDCLASS - структура
класса окна. Она описана в файле FASM\INCLUDE\EQUATES\USER32.
После объявления структур, wc будет считаться указателем на первый байт структуры данных, созданной в соответствии с шаблоном WNDCLASS, а msg - указателем на MSG. Элементам структуры wc присваиваются соответствующие значения, разделенные запятыми (в основном пока что ноли). Значения элементов структуры msg считаются сейчас неопределенными, хотя на практике они будут иметь нулевые значения, правильно считать их значения неопределенными. В тексте программы можно обращаться к отдельным элементам структуры, например: wc.hCursor или wc.hIcon, хотя в оперативной памяти все данные будут просто идти друг за другом, безо всяких пометок. Элементы структур для 32-битных версий windows, обычно имеют размер 32 бита, это 4 байта или двойное слово (dword), поэтому в описании структуры написано dd (data in dwords). Следовательно, мы могли бы заменить, например, wc.hInstance на wc+16, потому что элементы структуры wc в адресном пространстве будут выглядеть примерно так:
|
Можно было бы сейчас не вдаваться в эти подробности, но лучше, если вы сразу получите правильное представление о размещении данных в памяти.
Теперь попробуем
разобраться с исполняемым
Теперь, когда все необходимые данные структуры wc находятся на своих местах, вызывается функция RegisterClass. Единственный параметр этой функции - указатель на структуру, содержащую описание класса окна. Если класс был успешно зарегистрирован - возвращаемое значение будет отлично от ноля. Если по каким-либо причинам не удалось зарегистрировать класс, то в eax вернется ноль. Исходя из этого мы сравниваем eax и 0 (cmp), и в случае равенства прыгаем на метку ошибки (je error). Если не равно нолю, - значит все в порядке и мы переходим к созданию окна. Функция CreateWindowEx создает окно. Параметры соответственно: расширенный стиль окна; указатель на зарегистрированное имя класса; указатель на имя окна; стандартный стиль окна; X координата левого верхнего угла окна; Y координата левого верхнего угла окна; ширина окна; высота окна; идентификатор родительского окна или окна-владельца; идентификатор меню или дочернего окна; идентификатор исполняемого модуля, с которым связано окно; указатель на значение, которое передается окну через структуру CREATESTRUCT в параметре lParam сообщения WM_CREATE. Возвращаемое значение - идентификатор созданного окна. В случае ошибки возвращается ноль. Названия расширенных стилей окна вы можете найти в файле EQUATES\USER32.INC в группе Extended Window Styles, названия стандартных стилей находятся чуть выше в группах Window Styles и Common Window Styles.
Далее следует
цикл msg_loop. Этот цикл будет повторяться
до тех пор, пока окно не будет закрыто.
Функция GetMessage получает сообщение из очереди
сообщений приложения. Если сообщения
отсутствуют - функция ожидает их и цикл
приостанавливается до появления нового
сообщения. Сообщения посылаются окну
операционной системой, когда с окном
происходит какое-либо действие, например,
когда окно перемещается, изменяется его
размер, или даже, когда курсор мыши просто
движется над областью окна. Также окну
могут передаваться сообщения от других
процессов. Параметры функции следующие:
указатель на структуру, в которой разместятся
элементы сообщения; идентификатор окна-получателя
сообщения, если ноль, то сообщения принимаются
для любого окна данного приложения; минимальное
значение сообщения; максимальное значение
сообщения. Последние два параметра исполняют
роль фильтра сообщений. Так как все сообщения
являются целочисленными значениями,
можно установить фильтрацию типа “от…
и до…”. Если максимальное и минимальное
значения равняются нолю, фильтрация не
выполняется и принимаются все сообщения
без исключения. Если функция получает
сообщение WM_QUIT (выход), то возвращает ноль.
В других случаях eax не будет равен нолю.
Следовательно, если eax равен нолю, мы выходим
из цикла (je end_loop), иначе цикл продолжается.
TranslateMessage переводит комбинации wm_KeyDown/Up
в wm_Char или wm_DeadChar, а комбинации wm_SysKeyDown/Up
в wm_SysChar или wm_SysDeadChar, и отправляет переведенное
сообщение снова в очередь. Таким образом
в процедуру обработки сообщений поступят
и виртуальные клавиши и их символьные
значения. DispatchMessage передает сообщения
процедуре обработки сообщений (WindowProc).
В газетной статье нет возможности привести
список всех сообщений и их описание, поэтому
я рекомендую вам скачать англоязычную
справку по API-функциям размером около
22 мегабайт: http://ghirai.com/hutch/files/
Процедура обработки сообщений WindowProc вызывается каждый раз при получении окном нового сообщения и передаче его через DispatchMessage. Синтаксис записи этой и других процедур предельно прост: макроинструкция proc, имя процедуры и ее параметры через запятую, которые по умолчанию считаются 32-битными (4 байта каждый). Возврат из процедуры осуществляет команда ret. Каждая процедура должна завершаться инструкцией endp, которая указывает компилятору, где заканчивается код данной процедуры, и начинается следующий фрагмент кода. Команда push помещает значение указанного регистра в стек, а команда pop извлекает последнее значение из стека в указанный регистр. Стек - это специально выделенная область памяти для передачи или сохранения данных. Его можно представить, например, как вертикальную штангу тренажера, на которую можно одевать по одному грузу и снимать тоже по одному. Команда push будто указывает, откуда взять груз, а команда pop сообщает, куда поместить снятый со штанги груз. Такую аналогию я привел для того, чтобы вы уже таки уяснили, что если мы хотим сохранить содержимое нескольких регистров в стек, а потом вернуть эти значения в регистры, то нам необходимо сохранять в одном порядке, а извлекать - в обратном. Это важно запомнить и не путать: если сохраняем push eax ebx ecx, - извлекаем pop ecx ebx eax. А сохраняем мы содержимое этих регистров, чтобы предотвратить потерю данных: функция DefWindowProc спокойно может затереть содержимое этих регистров.
Так как окно у нас простейшее и на нем нет никаких кнопок, кроме крестика для закрытия, единственное сообщение которое мы должны обработать - это WM_DESTROY, которое будет послано окну перед его удалением. Стало быть, если сообщение равно WM_DESTROY, мы прыгаем на метку .wmdestroy, где будет вызвана функция PostQuitMessage параметром которой является код завершения. Ноль означает, что программа самостоятельно завершает работу. Эта функция посылает нашему процессу сообщение WM_QUIT, после обработки которого функция GetMessage вернет ноль, и цикл обрабоки сообщений прервется переходом на end_loop - выход из программы. Если сообщение не WM_DESTROY, то выполняется следующая функция - DefWindowProc. Эта функция обычно вызывается после того как обработаны все предусмотренные нами сообщения (в нашем случае предусмотрено только одно, но их ведь может быть и сотня), для того, чтобы операционная система своими средствами произвела стандартную обработку сообщения, которое получило наше окно. Параметры функции: идентификатор окна-получателя сообщения; сообщение; дополнительная информация о сообщении; дополнительная информация о сообщении. Что ж, теперь мы можем навесить на наше окно кнопку. Для этого добавим в секцию данных (section ‘.data’) ее класс и имя, а заголовок главного окна немного подправим: _title db ‘НеПустое Окно’,0 _classb db ‘BUTTON’,0 _textb db ‘КНОПКА’,0 В секцию кода добавим обработку сообщения WM_CREATE, которое приходит окну один раз при его создании, чтобы при создании главного окна создавать дочерние окна. Кнопка - это окно стандартного класса BUTTON, поэтому регистрировать этот класс нам не понадобится. Также добавим обработку сообщения WM_COMMAND, которое приходит окну, когда пользователь выбирает пункт меню или совершает действие с другим дочерним элементом окна (в нашем случае - нажатие на кнопку):
proc WindowProc hwnd,wmsg,wparam,lparam push ebx esi edi
cmp [wmsg],WM_CREATE
je .wmcreate
cmp [wmsg],WM_COMMAND
je .wmcommand
cmp [wmsg],WM_DESTROY
je .wmdestroy
.defwndproc:
invoke DefWindowProc,[hwnd],[wmsg],[
jmp .finish
.wmcreate:
invoke CreateWindowEx,0,_classb,_
jmp .finish
.wmcommand:
cmp [wparam],1001
jne .finish
invoke MessageBox,[hwnd],_textb,_
jmp .finish
.wmdestroy:
invoke PostQuitMessage,0
mov eax,0
.finish:
pop edi esi ebx
ret
endp
Теперь при создании окна, выполнятся команды после метки .wmcreate и на главном окне будет создана кнопка с идентификатором 1001. При нажатии на эту кнопку окно получает сообщение WM_COMMAND, а wparam сообщения будет содержать в старших двух байтах BN_CLICKED (кликнута кнопка), а в младших двух байтах - идентификатор кнопки (1001). Так как константа BN_CLICKED равна нолю (убеждаемся в этом в EQUATES\USER32.INC), можно не учитывать ее и просто сравнить wparam с 1001, чтобы убедиться, что была нажата наша кнопка. Если не равно, - значит не наша или не нажата, - jne .finish . Иначе, - показываем месадж-бокс.
По аналогии можете самостоятельно добавить еще пару кнопок, только не забывайте изменять координаты их местоположения, иначе они будут накладываться друг на друга и вы увидите лишь одну из них. Поэкспериментируйте со стилями и другими параметрами. К следующему занятию постарайтесь четко усвоить принципы создания и работы простых окон. Чтобы мы смело могли двигаться дальше.
Все приводимые примеры были протестированы на правильность работы под Windows XP и, скорее всего, будут работать под другими версиями Windows, однако я не даю никаких гарантий их правильной работы на вашем компьютере.