Под Windows написать приложение гораздо

advertisement
ЛАБОРАТОРНАЯ РАБОТА №5
РАЗРАБОТКА ПРИЛОЖЕНИЙ ПОД Win32
1. ЦЕЛЬ РАБОТЫ
Цель данной работы - изучение приемов программирования приложений под Win32.
2. Простейшая программа под Win32
Под Windows написать приложение гораздо проще, чем для DOS благодаря большому
количеству функций вызываемых из системных библиотек (свыше 2000). Например, для
запуска другого приложения необходим лишь один вызов функции. Точно так же можно
загружать не только программы, но и документы, графические и текстовые файлы и даже
почтовые и WWW-адреса — все, для чего в реестре Windows записано действие,
выполняющееся при попытке открытия.
; winurl.asm
; Пример програмы для win32.
; Запускает установленный по умолчанию браузер на адрес, указанный в строке URL
; аналогично можно запускать любую программу, документ, и любой другой файл,
; для которого определена операция open
;
include shell32.inc
include kernel32.inc
.386
.model flat
.const
URL db 'http://www.lionking.org/~cubbi/',0
.code
_start:
; метка точки входа должна начинаться с подчеркивания
xor
ebx,ebx
push
ebx
; для исполнимых файлов - способ показа
push
ebx
; рабочий каталог
push
ebx
; командная строка
push
offset URL ; имя файла с путем
push
ebx
; операция open или print (если NULL - open)
push
ebx
; идентификатор окна, которое получит сообщения
call
ShellExecute ; ShellExecute(NULL,NULL,url,NULL,NULL,NULL)
push
ebx
; код выхода
call
ExitProcess ; ExitProcess(0)
end
_start
В этой программе выполняется вызов двух системных функций Win32 — ShellExecute()
(открыть файл) и ExitProcess() (завершить процесс). Чтобы вызвать системную функцию
Windows, программа должна поместить в стек все параметры от последнего к первому и
передать управление дальней командой CALL. Все эти функции сами освобождают стек
(завершаясь командой RET N) и возвращают результат работы в регистре ЕАХ. Такая
договоренность о передаче параметров называется STDCALL. С одной стороны, это
позволяет вызывать функции с нефиксированным числом параметров, а с другой —
вызывающая сторона не должна заботиться об освобождении стека. Кроме того, функции
Windows сохраняют значение регистров ЕВР, ESI, EDI и EBX, Поэтому 0 хранится в
регистре EBX на всем протяжении кода.1-байтная команда PUSH EBX применяется
вместо 2-байтной PUSH 0.
В файлы kernel32.inc и shell32.inc помещаются директивы, описывающие вызываемые
системные функции (из kernel32.dll и shell32.dll):
; kernel32.inc
; включаемый файл с определениями функций из kernel32.dll
;
ifdef _TASM_
includelib import32.lib
; имена используемых функций
extrn
ExitProcess:near
else
includelib kernel32.lib
; истинные имена используемых функций
extrn
__imp__ExitProcess@4:dword
; присваивания для облегчения читаемости кода
ExitProcess equ __imp__ExitProcess@4
endif
; shell32.inc
; включаемый файл с определениями функций из shell32.dll
ifdef _TASM_
includelib import32.lib
; имена используемых функций
extrn ShellExecuteA:near
; присваивания для облегчения читаемости кода
ShellExecute equ ShellExecuteA
else
includelib shell32.lib
; истинные имена используемых функции
extrn __imp__ShellExecuteA@24:dword
; присваивания для облегчения читаемости кода
ShellExecute equ __imp__ShellExecuteA@24
endif
Имена всех системных функций Win32 модифицируются так, что перед именем функции
ставится подчеркивание, а после — знак «@» и число байт, которое занимают параметры,
передаваемые ей в стеке, так ExitProcess() превращается в _ExitProcess@4(). Компиляторы
с языков высокого уровня часто останавливаются на этом и вызывают функции по имени
_ExitProcess@4(), но реально вызывается небольшая процедура-заглушка, которая ничего
не делает, а только передает управление на такую же метку, но с добавленным
«__imp_» — __imp__ExitProcess@4(). Во всех наших примерах мы будем обращаться
напрямую к __imp__ExitProcess@4(). К сожалению, TASM (а точнее TLINK32) использует
собственный способ вызова системных функций, который нельзя так обойти, и
программы, скомпилированные с его помощью, оказываются немного больше и в
некоторых случаях работают медленнее. Мы отделили описания функций для TASM во
включаемых файлах при помощи директив условного ассемблирования, которые будут
использовать их, если в командной строке ассемблера указать /D_TASM_.
Кроме этого, все функции, работающие со строками (как, например, ShellExecute()),
существуют в двух вариантах. Если строка рассматривается в обычном смысле, как набор
символов ASCII, к имени функции добавляется «A» (ShellExecuteA()). Другой вариант
функции, использующий строки в формате UNICODE (два байта на символ),
заканчивается буквой «U». Во всех наших примерах будем использовать обычные ASCIIфункции, но, если вам потребуется перекомпилировать программы на UNICODE,
достаточно только поменять «А» на «U» во включаемых файлах.
Итак, теперь, когда у нас есть все необходимые файлы, можно скомпилировать первую
программу для Windows.
Компиляция MASM:
ml /с /coff /Cp winurl.asm
link winurl.obj /subsystem:windows
(здесь и далее используется 32-битная версия link.exe)
Компиляция TASM:
tasm /m /ml /D_TASM_ winurl.asm
tlink32 /Tpe /aa /c /x winurl.obj
Компиляция WASM:
wasm winurl.asm
wlink file winurl.obj form windows nt op с
Также для компиляции потребуются файлы kernel32.lib и shell32.lib в первом и третьем
случае и import32.lib — во втором. Эти файлы входят в дистрибутивы любых средств
разработки для Win32 от соответствующих компаний — Microsoft, Watcom (Sybase) и
Borland (Inprise), хотя их всегда можно воссоздать из файлов kernel32.dll и shell32.dll,
находящихся в каталоге WINDOWS/SYSTEM.
Иногда вместе с дистрибутивами различных средств разработки для Windows идет файл
windows.inc, в котором дано макроопределение Invoke или заменена макросом команда
call так, что они принимают список аргументов, первым из которых идет имя вызываемой
функции, а затем через запятую — все параметры. С использованием этих
макроопределений наша программа выглядела бы так:
_start:
xor
ebx,ebx
Invoke ShellExecute, ebx, ebx, offset URL, ebx, \
ebx, ebx
Invoke ExitProcess, ebx
end
_start
И этот текст компилируется в точно такой же код, что и у нас, но выполняется вызов не
функции __imp__ExitProcess@4(), а промежуточной функции _ExitProcess@4().
Использование этой формы записи не позволяет применять отдельные эффективные
приемы оптимизации, которые мы будем приводить в наших примерах, — помещение
параметров в стек заранее и вызов функции командой JMP. И наконец, файла windows.inc
у вас может просто не оказаться, так что будем писать push перед каждым параметром
вручную.
3. Работа с меню и диалогом
Пример ниже демонстрирует работу с графическим интерфейсом Windows (GUI Graphical User Interface). Для организации такого интерфейса необходимо создать окно и
определить процедуру - обработчик событий. Данная процедура имеет следующий
прототип:
LRESULT WndProc(HWND,UINT,WPARAM,LPARAM)
Где LRESULT 32 битное возвращаемое значение, зависимое от типа сообщения
(события).
Все параметры тоже 32 битные значения:
HWND – дескриптор окна,
UINT - тип сообщения (события),
WPARAM – W сообщение
LPARAM – L сообщение.
W и L сообщения зависят от типа сообщения.
Существуют предопределенные окна – диалоговые панели. Данный вид окон также имеет
обработчик событий. Но для создания такого окна нужно описать специальный ресурс.
Все виды ресурсов представляют собой бинарные файлы с определенной структурой. Для
создания ресурса необходимо выполнить описание на специальном языке. В файле *.rc и
откомпилировать этот файл компилятором ресурсов.
Существует множество видов ресурсов. Кроме того, Windows позволяет создавать
пользовательские виды ресурсов. В лабораторной работе будут использованы два вида
ресурсов: диалог и меню.
В состав Win32 входят несколько групп функций для работы с ресурсами. Для указания
системе как найти нужный ресурс используется следующая информация: дескриптор
приложения, тип ресурса, имя ресурса.
Ниже приведен пример описания ресурсного файла:
// windlg.rc
// файл ресурсов, описывающий диалог для программы windlg.asm
// все следующие определения стилей можно заменить на #include <winuser.h>
// стили диалогов
#define DS_CENTER
0x0800L
#define DS_MODALFRAME 0x80L
#define DS_3DLOOK
0x0004L
// стили окон
#define WS_MINIMIZEBOX 0x00020000L
#define WS_SYSMENU
0x00080000L
#define WS_VISIBLE
0x10000000L
#define WS_OVERLAPPED 0x00000000L
#define WS_CAPTION
0xC00000L
// стили для edit control
#define ES_AUTOHSCROLL 0x80L
#define ES_LEFT
0
#define ZDLG_MENU
7
// идентификаторы элементов диалоговой панели
#define IDC_EDIT 0
#define IDC_BUTTON
1
#define IDC_EXIT 2
// идентификаторы пунктов меню
#define IDM_GETTEXT
10
#define IDM_CLEAR
11
#define IDM_EXIT 12
ZZZ_Dialog DIALOG 10,10,205,30
// x, y, ширина, высота
STYLE DS_CENTER | DS_MODALFRAME | DS_3DLOOK | WS_CAPTION |
WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED
CAPTION "win32 assembly dialog example"
// заголовок
MENU ZDLG_MENU
// меню
BEGIN
// список контролов диалога
EDITTEXT IDC_EDIT,15,7,111,13,ES_AUTOHSCROLL | ES_LEFT
PUSHBUTTON "E&xit",IDC_EXIT,141,8,52,13
END
ZDLG_MENU MENU
BEGIN
POPUP "Test"
BEGIN
MENUITEM "Get Text",IDM_GETTEXT
MENUITEM "Clear Text",IDM_CLEAR
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
END
END
// меню диалога
Скомпилировав данный файл компилятором ресурсов, получаем файл с расширением res.
Этот файл «понимает» редактор связей tlink32.exe. Файл описывает данные для создания
диалоговой панели и меню.
Текст программы работающий с данными ресурсами:
; windlg.asm
; Графическое win32-приложение, демонстрирующее работу с диалогом
;
; Компиляция TASM
; tasm /m /ml /D_TASM_ windlg.asm
; brcc32 windlg.rc
; tlink32 /Tpe /aa /c /x windlg.obj,,,,,windlg.res
;
; идентификаторы контролов (элементов диалога)
IDC_EDIT
equ 0
IDC_BUTTON equ 1
IDC_EXIT
equ 2
; идентификаторы элементов меню
IDM_GETTEXT equ 10
IDM_CLEAR
equ 11
IDM_EXIT
equ 12
include def32.inc
include kernel32.inc
include user32.inc
.386
.model flat
.data
dialog_name db
"ZZZ_Dialog",0 ; имя диалога в ресурсах
.data?
buffer
db
512 dup(?)
; буфер для введённого текста
.code
_start:
xor
ebx,ebx
; в EBX будет 0 для команд push 0 (короче в 2 раза)
; определим идентификатор нашей программы
; Вызов GetModuleHandle(NULL)
push ebx
call
GetModuleHandle
; запустим диалог
; Вызов DialogBoxParam(hModule,DlgName,hwnd,DlgProc,NULL)
push ebx
; значение, которое перейдёт как параметр WM_INITDIALOG
push offset dialog_proc ; адрес процедуры типа DialogProc
push ebx
; идентификатор окна-предка (0 - ничей диалог)
push offset dialog_name ; адрес имени диалога в ресурсах
push eax
; идентификатор программы, в ресурсах которой
; находится диалог (наш идентификатор в EAX)
call
DialogBoxParam
; выход из программы
; Вызов ExitProcess(NULL)
push ebx
call
ExitProcess
;
; процедура dialog_proc
; обработчик событий
; вызывается диалогом каждый раз, когда в нём что-нибудь происходит
; именно здесь будут происходить вся работа программы
;
; процедура не должна изменять регистры EBP,EDI,ESI и EBX !
;
dialog_proc proc near
; так как мы получаем параметры в стеке, построим стековый кадр
push ebp
mov ebp,esp
; процедура типа DialogProc вызывается со следующими параметрами
dp_hWnd equ dword ptr [ebp+08h] ; идентификатор диалога
dp_uMsg equ dword ptr [ebp+0Ch] ; номер сообщения
dp_wParam equ dword ptr [ebp+10h]
; первый параметр
dp_lParam equ dword ptr [ebp+14h] ; второй параметр
mov ecx,dp_hWnd ; ECX будет хранить идентификатор диалога
mov eax,dp_uMsg ; а EAX - номер сообщения
cmp eax,WM_INITDIALOG
; если мы получили WM_INITDIALOG
jne
not_initdialog
;Вызов GetDlgItem(hwnd,id)
push IDC_EDIT
push dp_hWnd
call
GetDlgItem
; определим идентификатор
;Вызов SetFocus(hwnd)
push eax
; окошка редактирования текста
call
SetFocus
; и передадим ему фокус
not_initdialog:
cmp eax,WM_CLOSE
; если мы получили WM_CLOSE
jne
not_close
;Вызов EndDialog(hwnd,0)
push 0
push ecx
call
EndDialog
; закрыть диалог
not_close:
cmp eax,WM_COMMAND
; если мы получили WM_COMMAND
jne
not_command
mov eax,dp_wParam
; EAX = wParam (номер сообщения)
cmp dp_lParam,0 ; если lparam ноль - сообщение от меню
jne
lParam_not_0
cmp ax,IDM_GETTEXT ; если это пункт меню Get Text
jne
not_gettext
;Вызов GetDlgItemText(hwnd,id,buffer,size_of_buffer)
push 512
; размер буфера
push offset buffer
; адрес буфера
push IDC_EDIT
; номер конрола редактирования
push ecx
call
GetDlgItemText
; считаем текст в buffer
;Вызов MessageBox(hwnd,Text,Title,mode)
push MB_OK
push offset dialog_name
push offset buffer
push dp_hWnd
call
MessageBox
; и покажем его в MessageBox
not_gettext:
cmp eax,IDM_CLEAR
; если это пункт меню Clear
jne
not_clear
;Вызов SetDlgItemText(hwnd,id,text)
push 0
; NULL
push IDC_EDIT
; номер контрола
push ecx
call
SetDlgItemText
; установим новый текст
not_clear:
cmp eax,IDM_EXIT
; если это пункт меню Exit
jne
not_exit
;Вызов EndDialog(hwnd,0)
push 0
; код возврата
push ecx
; идентификатор диалога
call
EndDialog
; закрыть диалог
lParam_not_0:
; lParam не ноль - сообщение от контрола
cmp eax,IDC_EXIT
; если сообщение от кнопки Exit
jne
not_exit
shr
eax,16
cmp eax,BN_CLICKED ; если её нажали
jne
not_exit
;Вызов EndDialog(hwnd,0)
push 0
; код возврата
push ecx
; идентификатор диалога
call
EndDialog
; закрыть диалог
not_exit:
xor
eax,eax
; после обработки команды
inc
eax
; DialogProc должен возвращать TRUE (eax=1)
leave
ret
16
; конец процедуры
not_command:
; сюда передаётся управление если мы получили
; какое-то незнакомое сообщение
xor
eax,eax
; код возврата FALSE (eax=0)
leave
ret
16
; конец процедуры
dialog_proc endp
end _start
Текст программы состоит из трех частей.
1. Описание данных
2. Вход в программу
3. Обработчик диалоговой панели.
При запуске программы вызывается функция, создающая диалоговую панель из ресурсов.
Далее до закрытия диалоговой панели работают системные функции Windows. При
возникновении событий (нажатии кнопок, выборов элементов меню, перемещении мыши
и т.д.), Windows вызывает пользовательский обработчик событий. Именно обработчик
сообщений является особенностью программирования под Windows.
Он представляет собой программный блок типа switch/case, который по передаваемым
параметрам в процедуру устанавливает тип сообщения и связанные с ним данные
(параметры WPARAM и LPARAM). Действия выполняются на основе событий.
В программе обрабатываются следующие события:
WM_INITDIALOG создание диалоговой панели
WM_CLOSE
закрытие диалоговой панели (пользователь нажал на кнопку
закрыь окно)
WM_COMMAND сообщения от элементов диалоговой панели или меню.
В свою очередь, при возникновении события WM_COMMAND проверяется какой
элемент вызвал это событие (IDM_GETTEXT, IDM_CLEAR,IDM_EXIT – нажатие на
пункты меню, IDC_EXIT – нажатие на кнопку «Exit»)
В программе используются следующие функции Win32:
GetModuleHandle – для получения дескриптора текущей программы,
DialogBoxParam – для создания диалоговой панели из ресурсов,
ExitProcess – для выхода из программы,
GetDlgItem – получение дескриптора элемента диалога по его идентификатору,
EndDialog – для закрытия диалоговой панели,
GetDlgItemText – для получения введенного пользователем текста в определенный
элемент диалога,
MessageBox – выдача информационного окна,
SetDlgItemText – установка текста в определенный элемент диалога
4. Варианты заданий
Используя вышеприведенный шаблон выполнить согласно варианту следующие
преобразования с введенными пользователем данными
1. Вывести количество гласных букв во введенной строке
2. Определить четное или нечетное число введено
3. Определить количество цифровых символов в строке
4. Определить количество слов в строке
5. Вывести название месяца (или диагностировать ошибку ввода) для даты,
введенной в цифровом формате
6. Вывести количество секунд, прошедших с полуночи, для времени, введенном в
формате чч:мм:СС
7. Перевести число, введенное в десятичном формате в шестнадцатеричное
8. Вывести площадь квадрата, сторону которого ввели в виде строки
9. Вывести длину в сантиметрах, введенную в дюймах
10. Вывести строку прописью для двухразрядного десятичного числа, введенного
цифрами
Download