Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скляров И. Изучаем Assembler за 7 дней (2010).pdf
Скачиваний:
1335
Добавлен:
23.02.2015
Размер:
2.11 Mб
Скачать

http://www.sklyaroff.ru

171

Здесь же стоит заметить, что существуют функции API, которые могут заменить ресурсы.

Например, меню можно определять в ресурсах, а можно программно с помощью функций Win32 API. Возможен также комбинированный подход, когда часть меню создается в ресурсах, а другая часть с помощью API-функций.

7.6.1. Подключение ресурсов к исполняемому файлу

Для подключения ресурсов в программу нужно создать их описание на специальном скриптовом языке и сохранить это описание в текстовый файл с расширением .RC. С языком описания ресурсов мы познакомимся позже.

Затем полученный текстовый файл необходимо откомпилировать специальным компилятором ресурсов. В пакет MASM32 входит компилятор ресурсов RC.EXE. В результате получится объектный файл с расширением .RES.

В исполняемый файл RES-файл подключается компоновщиком.

Файлы RC и RES вы можете создавать с помощью специальных редакторов ресурсов, которые входят, например, в пакеты Visual Studio и Borland. Ресурсы с помощью этих редакторов создаются в визуальном режиме, поэтому от программиста не требуется знание языка описания ресурсов.

Если вам будет нужно, вы научитесь самостоятельно работать с этими программами, т. к. это очень просто, а мы в этой книге изучим язык описания ресурсов.

7.6.2. Язык описания ресурсов

7.6.2.1. Пиктограммы

Пиктограмма описывается одной строкой вида:

имя_ID ICON имя_файла

Где имя_ID — целочисленный идентификатор ресурса

имя_файла — имя файла иконки. Если файл находится не в текущей директории, то необходимо указывать полный путь к файлу.

Примеры описания иконок:

123 ICON "desk.ico"

200 ICON "ivan.ico"

Целочисленный идентификатор ресурса — это произвольное целое число. При выборе числа нужно помнить, что ресурсы не должны иметь одинаковые идентификаторы.

Обычно программисты присваивают символьные имена числовым значениям. Присваивание в описании ресурсов делается с помощью оператора #define. Программисты на Си могут заметить, что аналогичный оператор имеется в языке Си. Пример выше можно переписать следующим образом:

#define ID_ICON1 123 #define ID_ICON2 200 ID_ICON1 ICON "desk.ico" ID_ICON2 ICON "ivan.ico"

В программе на ассемблере можно без проблем использовать целочисленные идентификаторы ресурсов, но для того чтобы можно было использовать символьные имена ID_ICON1 и ID_ICON2, необходимо в секции .data или .const сделать соответствующие присваивания:

ID_ICON1 equ 123 ID_ICON2 equ 200

http://www.sklyaroff.ru

172

Затем можно вызывать функцию LoadIcon, которая возвращает дескриптор иконки в регистр EAX, и заполнять этим значением соответствующие поля структуры

WNDCLASSEX:

invoke LoadIcon,hInstance,ID_ICON1 mov wc.hIcon,eax

mov wc.hIconSm,eax

Описание функции LoadIcon можете посмотреть в MSDN.

7.6.2.2. Курсоры

Курсор описывается строкой вида:

имя_ID CURSOR имя_файла

Где имя_ID — целочисленный идентификатор ресурса

имя_файла — имя файла курсора. Если файл находится не в текущей директории, то необходимо указывать полный путь к файлу.

Пример:

1 CURSOR "hand.cur"

2 CURSOR "arrow.cur"

Или так:

#define ID_CURSOR1 1 #define ID_CURSOR2 2 ID_CURSOR1 CURSOR "hand.cur"

ID_CURSOR2 CURSOR "arrow.cur"

Для того чтобы в программе можно было использовать символьные имена

ID_CURSOR1 и ID_CURSOR2, необходимо в секции .data или .const сделать соответствующие присваивания:

ID_CURSOR1 equ 1 ID_CURSOR2 equ 2

Затем можно вызывать функцию LoadCursor, которая возвращает дескриптор иконки в регистр EAX, и заполнять этим значением соответствующие поля структуры

WNDCLASSEX:

invoke LoadCursor,hInstance,ID_CURSOR1 mov wc.hCursor,eax

Описание функции LoadCursor можете посмотреть в MSDN.

7.6.2.3. Растровые изображения

Растровые изображения описываются подобно иконкам и курсорам строкой вида:

имя_ID BITMAP имя_файла

Пример:

10 BITMAP "ivan.bmp"

13 BITMAP "c:\project\sklyaroff.bmp"

Или так:

#define ID_ BITMAP1 1 #define ID_ BITMAP2 2

ID_ BITMAP1 BITMAP "ivan.bmp"

ID_ BITMAP2 BITMAP "c:\project\sklyaroff.bmp"

http://www.sklyaroff.ru

173

Пример использования в программе ресурса с растровым изображением рекомендую посмотреть в рассылке от Iczelion "Урок 25. Пpостой битмэп" [3].

7.6.2.4. Строки

Строки описываются с помощью следующей структуры, называемой также таблицей строк:

STRINGTABLE

{

имя_ID строка

. . .

}

Ключевое слово STRINGTABLE и фигурные скобки обязательны, даже если описывается всего одна строка. Фигурные скобки могут быть заменены словами

BEGIN и END:

STRINGTABLE BEGIN

имя_ID строка

. . .

END

В отличие от других ресурсов может быть только одна таблица строк. Пример:

#define

IDS_HELLO

1

#define

IDS_GOODBYE

2

STRINGTABLE

{

IDS_HELLO, "Hello" IDS_GOODBYE, "Goodbye"

}

Использовать строки из ресурсов можно с помощью функции LoadString, которая имеет следующее описание:

int LoadString( HINSTANCE hInstance, UINT uID,

LPTSTR lpBuffer, int nBufferMax

);

Параметры:

hInstance — дескриптор экземпляра приложения, получаемый с помощью функции GetModuleHandle;

uID — номер строки в файле ресурсов;

lpBuffer — адрес буфера, в который будет помещена строка после выполнения функции;

nBufferMax — размер буфера.

http://www.sklyaroff.ru

174

В секции данных в программе необходимо определить буфер под строку. Например, выделим два буфера для двух строк:

BUF1 db 50 dup(0)

BUF2 db 50 dup(0)

;Помещаем первую и вторую строки из ресурсов в соответствующие буфера invoke LoadString, hInstance, 1, OFFSET BUF1, 50

invoke LoadString, hInstance, 2, OFFSET BUF2, 50

;Теперь строки из буферов можно использовать, например,

;в вызове функции MessageBoxA

invoke MessageBoxA,0,addr BUF1,addr BUF2, MB_OK

7.6.2.5. Диалоговые окна

Диалоговые окна описываются с помощью следующей структуры:

DialogName DIALOG x, y, width, height CAPTION "Заголовок окна"

STYLE Стили_диалогового_окна FONT n, имя_шрифта

{

Описание элементов диалога

}

DialogName — имя диалогового окна. Диалоговому окну можно присвоить целочисленный идентификатор также как другим ресурсам, однако принято к диалоговым окнам обращаться по именам.

x, y — координаты верхнего левого угла диалогового окна.

width, height — ширина и высота диалогового окна.

CAPTION — название которое будет отображаться в заголовке диалогового окна

STYLE — описывает стили окна. Здесь можно использовать как стили, применяемые для описания обычных окон, так и стили, применяемые только в диалоговых окнах.

ErrorDialog DIALOG 10, 10, 300, 110 STYLE WS_POPUP | WS_BORDER

CAPTION "Error!"

{

CTEXT "Select One:", 1, 10, 10, 280, 12 PUSHBUTTON "&Retry", 2, 75, 30, 60, 12 PUSHBUTTON "&Abort", 3, 75, 50, 60, 12 PUSHBUTTON "&Ignore", 4, 75, 80, 60, 12

}

7.6.2.6. Меню

Перед тем как рассмотреть создание меню вспомним его устройство.

Под заголовком окна приложения располагается меню верхнего уровня называемое также главным меню (main menu). Меню содержит пункты меню.

Различают два типа пунктов меню:

пункт-команда — это конечный пункт в иерархическом дереве меню. При выборе пункта-команды Windows посылает процедуре окна сообщение WM_COMMAND, содержащее идентификатор команды, в результате приложение выполняет какое-нибудь действие.

http://www.sklyaroff.ru

 

175

пункт-подменю — вызывает меню следующего, более

низкого уровня

(подменю) называемое также всплывающим меню (popup-меню).

 

 

Главное меню описывается следующей структурой:

 

 

MenuName MENU [параметры]

{

Описание всех элементов меню

}

MenuName — имя меню. Вместо имени можно использовать целочисленный идентификатор, однако принято к меню, также как и к диалогам, обращаться по имени.

Список элементов меню состоит из выражений MENUITEM и POPUP. MENUITEM описывает пункт-команду:

MENUITEM имя_пункта, MenuID [,параметры]

MenuID – идентификатор (произвольное целое число), который будет послан процедуре окна.

Если вместо имя_пункта использовано слово SEPARATOR следующим образом:

MENUITEM SEPARATOR

то будет отображена горизонтальная разделительная линия вместо пункта меню. Обычно эти горизонтальные линии используются для разделения элементов подменю на группы.

Всплывающее меню описывается следующим образом:

POPUP "Имя" [,параметры]

{

Описание всех элементов очередного уровня

}

За исключением слова POPUP в заголовке (первая строка) синтаксис и семантика описаний главного меню и подменю совпадает.

Если в имени пункта меню встречается символ "&", то следующий за ним символ на экране будет подчеркнут одинарной чертой. Это означает, что данный элемент меню можно будет вызывать с клавиатуры комбинацией клавиш Alt и подчеркнутого символа.

Меню можно создавать как в обычном, так и в диалоговом окне. Существует несколько способов подключения ресурса меню к программе.

1. Этот способ связывает одно меню со всеми создаваемыми окнами путем определения его в классе Windows. При регистрации класса полю lpszMenuName структуры WNDCLASS нужно присвоить указатель на строку, содержащую имя меню, например

mov wc.lpszMenuName, OFFSET MenuName

Когда меню не используется, этому полю присваивается 0.

В диалоговых окнах меню устанавливается иным способом, но этот способ может быть использован и в обычных окнах.

http://www.sklyaroff.ru

176

2. Если вы хотите присвоить меню конкретному окну, то можно вначале загрузить меню функцией LoadMenu, прототип этой функции:

HMENU LoadMenu(HINSTACE hInstance, LPCTSTR lpMenuName);

При успешном выполнении данная функция возвращает дискриптор меню, который затем можно передать функции создания окна CreateWindowEx:

.DATA

MenuName db "FirstMenu",0 hMenu HMENU ?

...........................

...........................

.CODE

...........................

invoke LoadMenu, hInst, OFFSET MenuName mov hMenu, eax

invoke CreateWindowEx,NULL,OFFSET ClsName,\ OFFSET Caption, WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,\

NULL,\

hMenu,\

hInst,\

NULL\

...........................

3. Этот способ похож на предыдущий, только после того как дескриптор меню получен функцией LoadMenu, он передается не функции CreateWindowEx, а функции SetMenu, которое устанавливает меню конкретному окну. В функции CreateWindowEx при этом может быть указан NULL в качестве имени меню.

BOOL SetMenu(HWND hWnd, HMENU hMenu);

В листинге 7.6 показан пример программы с меню с использованием первого способа подключения ресурса меню к программе. Текст файла, содержащий определение меню показан в листинге 7.7.

Компиляция:

ml /c /coff /Cp menu.asm rc menu.rc

link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib menu.obj menu.res

В процедуре окна WndProc обрабатывается сообщение WM_COMMAND:

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.IF uMsg==WM_DESTROY

invoke PostQuitMessage,NULL

.ELSEIF uMsg==WM_COMMAND mov eax,wParam

.IF ax==IDM_ABOUT

invoke MessageBox,NULL,ADDR About_string,OFFSET

AppName,MB_OK

.ELSEIF ax==IDM_HELLO

invoke MessageBox, NULL,ADDR Hello_string, OFFSET

AppName,MB_OK

.ELSEIF ax==IDM_GOODBYE

http://www.sklyaroff.ru

177

invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET

AppName, MB_OK

.ELSE

invoke DestroyWindow,hWnd

.ENDIF

.ELSE

invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret

.ENDIF

xor eax,eax ret

WndProc endp

При выборе пользователем пункта меню процедуре окна в младшем слове wParam посылается ID пункта меню вместе с сообщением WM_COMMAND. Поэтому, после того как значение wParam сохраняется в EAX (mov eax,wParam), затем сравнивается значение в AX с ID пунктов меню и выводится соответствующее сообщение

MessageBox.

Если пользователь выберет пункт "Выход", то будет выполнена API-функция DestroyWindow, которая уничтожит окно.

Листинг 7.6. Программа с меню (menu.asm)

.386

.model flat,stdcall option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib

.data

 

 

ClassName

db "SimpleWinClass",0

AppName

db "Our First Window",0

MenuName

db "FirstMenu",0

About_string

db "Пример использования ресурса меню",0

Hello_string

db "Привет, дружище!",0

Goodbye_string

db "Пока, дружище!",0

.data?

 

 

hInstance

HINSTANCE

?

CommandLine

LPSTR

?

.const

 

 

IDM_ABOUT

equ 1

 

IDM_HELLO

equ 2

 

IDM_GOODBYE

equ 3

 

IDM_EXIT

equ 4

 

.code

http://www.sklyaroff.ru

178

start:

 

invoke

GetModuleHandle,NULL

mov

hInstance,eax

invoke

GetCommandLine

mov

CommandLine,eax

invoke

WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT

invoke

ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

LOCAL

wc:WNDCLASSEX

LOCAL

msg:MSG

 

LOCAL

hwnd:HWND

mov

wc.cbSize,SIZEOF WNDCLASSEX

mov

wc.style,CS_HREDRAW or CS_VREDRAW

mov

wc.lpfnWndProc,OFFSET WndProc

mov

wc.cbClsExtra,NULL

mov

wc.cbWndExtra,NULL

push

hInst

 

pop

wc.hInstance

mov

wc.hbrBackground,COLOR_WINDOW+1

mov

wc.lpszMenuName,OFFSET MenuName

mov

wc.lpszClassName,OFFSET ClassName

invoke

LoadIcon,NULL,IDI_APPLICATION

mov

wc.hIcon,eax

mov

wc.hIconSm,eax

invoke

LoadCursor,NULL,IDC_ARROW

mov

wc.hCursor,eax

invoke

RegisterClassEx,addr wc

invoke

CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\

CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\

hInst,NULL

 

mov

hwnd,eax

invoke

ShowWindow,hwnd,SW_SHOWNORMAL

invoke

UpdateWindow,hwnd

.WHILE

TRUE

 

 

INVOKE GetMessage,ADDR msg,NULL,0,0

 

.BREAK .IF (!eax)

.ENDW

INVOKE DispatchMessage,ADDR msg

 

 

mov

eax,msg.wParam

ret

 

 

WinMain endp

 

 

WndProc proc

hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.IF uMsg==WM_DESTROY

 

invoke

PostQuitMessage,NULL

.ELSEIF uMsg==WM_COMMAND

 

mov

eax,wParam

 

.IF ax==IDM_ABOUT