Меню сайта

Урок 2. ASSEMBLER & WIN32 ( 2 Часть )

Комментарии к программе

Здесь мне хотелось в первую очередь продемонстрировать
использование прототипов функций API Win32. Конечно их (а также
описание констант и структур из API Win32) следует вынести в
отдельные подключаемые файлы, поскольку, скорее всего Вы будете
использовать их и в других программах. Описание прототипов функций
обеспечивает строгий контроль со стороны компилятора за
количеством и типом параметров, передаваемых в функции. Это
существенно облегчает жизнь программисту, позволяя избежать
ошибок времени исполнения, тем более, что число параметров в
некоторых функциях API Win32 весьма значительно.

Существо данной программы заключается в демонстрации вариантов
работы с оконным меню. Программу можно откомпилировать в трёх
вариантах (версиях), указывая компилятору ключи VER2 или VER3 (по
умолчанию используется ключ VER1). В первом варианте программы
меню определяется на уровне класса окна и все окна данного класса
будут иметь аналогичное меню. Во втором варианте, меню
определяется при создании окна, как параметр функции
CreateWindowEx. Класс окна не имеет меню и в данном случае, каждое
окно этого класса может иметь своё собственное меню. Наконец, в
третьем варианте, меню загружается после создания окна. Данный
вариант показывает, как можно связать меню с уже созданным окном.

Директивы условной компиляции позволяют включить все варианты в
текст одной и той же программы. Подобная техника удобна не только
для демонстрации, но и для отладки. Например, когда Вам требуется
включить в программу новый фрагмент кода, то Вы можете применить
данную технику, дабы не потерять функционирующий модуль. Ну, и
конечно, применение директив условной компиляции – наиболее
удобное средство тестирования различных решений (алгоритмов) на
одном модуле.

Представляет определённый интерес использование стековых фреймов
и заполнение структур в стеке посредством регистра указателя стека
(esp). Именно это продемонстрировано при заполнении структуры
WndClassEx. Выделение места в стеке (фрейма) делается простым
перемещением esp:

sub esp,SIZE WndClassEx

Теперь мы можем обращаться к выделенной памяти используя всё тот
же регистр указатель стека. При создании 16-битных приложений такой
возможностью мы не обладали. Данный приём можно использовать
внутри любой процедуры или даже произвольном месте программы.
Накладные расходы на подобное выделение памяти минимальны,
однако, следует учитывать, что размер стека ограничен и размещать
большие объёмы данных в стеке вряд ли целесообразно. Для этих целей
лучше использовать «кучи” (heap) или виртуальную память (virtual
memory).

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

Макроопределения

Мне достаточно редко приходилось серьёзно заниматься разработкой
макроопределений при программировании под DOS. В Win32 ситуация
принципиально иная. Здесь грамотно написанные макроопределения
способны не только облегчить чтение и восприятие программ, но и
реально облегчить жизнь программистов. Дело в том, что в Win32
фрагменты кода часто повторяются, имея при этом не принципиальные
отличия. Наиболее показательна, в этом смысле, оконная и/или
диалоговая процедура. И в том и другом случае мы определяем вид
сообщения и передаём управление тому участку кода, который
отвечает за обработку полученного сообщения. Если в программе
активно используются диалоговые окна, то аналогичные фрагменты
кода сильно перегрузят программу, сделав её малопригодной для
восприятия. Применение макроопределений в таких ситуациях более
чем оправдано. В качестве основы для макроопределения,
занимающегося диспетчеризацией поступаю щих сообщений на
обработчиков, может послужить следующее описание.

Пример макроопределений —

macro — — MessageVector — — message1, message2:REST
— — — — — — — IFNB — — — —
— — — — — — — — — — — — — — — dd — — — — — message1
— — — — — — — — — — — — — — — dd — — — — — offset @@&amp-message1
— — — — — — — — — — — — — — — @@VecCount = @@VecCount + 1
— — — — — — — — — — — — — — — MessageVector — — message2
— — — — — — — ENDIF
endm — — — MessageVector
macro — — WndMessages — — — — VecName, message1, message2:REST
— — — — — — — @@VecCount — — — — — = 0
DataSeg
label — — @@&amp-VecName — —
— — — dword
— — — — — — — MessageVector — — message1, message2
— — — — — — — @@&amp-VecName&amp-Cnt — = @@VecCount
CodeSeg
— — — — — — — — — — — — — — — mov — — — — ecx,@@&amp-VecName&amp-Cnt
— — — — — — — — — — — — — — — mov — — — — eax,[@@msg]
@@&amp-VecName&amp-_1: — dec — — — — ecx
— — — — — — — — — — — — — — — js — — — — — @@default
— — — — — — — — — — — — — — — cmp — — — — eax,[dword ecx * 8 + offset @@&amp-VecName]
— — — — — — — — — — — — — — — jne — — — — @@&amp-VecName&amp-_1
— — — — — — — — — — — — — — — jmp — — — — [dword ecx + offset @@&amp-VecName + 4]
@@default: — — — — — call — — — DefWindowProcA, [@@hWnd], [@@msg], [@@wPar], [@@lPar]
@@ret: — — — — — — — — — ret
@@ret_false: — — — xor — — — — eax,eax
— — — — — — — — — — — — — — — jmp — — — — @@ret
@@ret_true: — — — — mov — — — — eax,-1
— — — — — — — — — — — — — — — dec — — — — eax
— — — — — — — — — — — — — — — jmp — — — — @@ret
endm — — — WndMessage

Комментарии к макроопределениям

При написании процедуры окна Вы можете использовать
макроопределение WndMessages, указав в списке параметров те
сообщения, обработку которых намерены осуществить. Тогда
процедура окна примет вид: —

proc — — — WndProc stdcall
arg — — — — @@hWnd: dword, — @@msg: — dword, — @@wPar: dword, — @@lPar: dword
WndMessages — — — — WndVector, — — — — — WM_CREATE, WM_SIZE, WM_PAINT, WM_CLOSE, WM_DESTROY
@@WM_CREATE:
— — — — — — — — здесь обрабатываем сообщение WM_CREATE
@@WM_SIZE:
— — — — — — — — здесь обрабатываем сообщение WM_SIZE
@@WM_PAINT:
— — — — — — — — здесь обрабатываем сообщение WM_PAINT
@@WM_CLOSE:
— — — — — — — — здесь обрабатываем сообщение WM_CLOSE
@@WM_DESTROY:
— — — — — — — — здесь обрабатываем сообщение WM_DESTROY

endp — — — WndProc

-Обработку каждого сообщения можно завершить тремя способами:
вернуть значение TRUE, для этого необходимо использовать
переход на метку @@ret_true-
вернуть значение FALSE, для этого необходимо использовать
переход на метку @@ret_false-
перейти на обработку по умолчанию, для этого необходимо
сделать переход на метку @@default.

Отметьте, что все перечисленные метки определены в макро
WndMessages и Вам не следует определять их заново в теле процедуры.

Теперь давайте разберёмся, что происходит при вызове
макроопределения WndMessages. Вначале производится обнуление
счётчика параметров самого макроопределения (число этих параметров
может быть произвольным). Теперь в сегменте данных создадим метку
с тем именем, которое передано в макроопределение в качестве
первого параметра. Имя метки формируется путём конкатенации
символов @@ и названия вектора. Достигается это за счёт
использования оператора &amp-. Например, если передать имя TestLabel, то
название метки примет вид: @@TestLabel. Сразу за объявлением метки
вызывается другое макроопределение MessageVector, в которое
передаются все остальные параметры, которые должны быть ничем
иным, как списком сообщений, подлежащих обработке в процедуре
окна. Структура макроопределения MessageVector проста и
бесхитростна. Она извлекает первый параметр и в ячейку памяти
формата dword заносит код сообщения. В следующую ячейку памяти
формата dword записывается а дрес метки обработчика, имя которой
формируется по описанному выше правилу. Счётчик сообщений
увеличивается на единицу. Далее следует рекурсивный вызов с
передачей ещё не зарегистрированных сообщений, и так продолжается
до тех пор, пока список сообщений не будет исчерпан.

Сейчас в макроопределении WndMessage можно начинать обработку.
Теперь существо обработки скорее всего будет понятно без
дополнительных пояснений.

Обработка сообщений в Windows не является линейной, а, как правило,
представляет собой иерархию. Например, сообщение WM_COMMAND
может заключать в себе множество сообщений поступающих от меню
и/или других управляющих элементов. Следовательно, данную методику
можно с успехом применить и для других уровней каскада и даже
несколько упростить её. Действительно, не в наших силах исправить код
сообщений, поступающих в процедуру окна или диалога, но выбор
последовательности констант, назначаемых пунктам меню или
управляющим элементам (controls) остаётся за нами. В этом случае нет
нужды в дополнительном поле, которое сохраняет код сообщения.
Тогда каждый элемент вектора будет содержать только адрес
обработчика, а найти нужный элемент весьма просто. Из полученной
константы, пришедшей в сообщении, вычитается идентификатор
первого пункта меню или первого управляющего элемента, это и будет
номер нужного элемента вектора. Остаётся только сделать переход на
обработчик.

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

Резюме

Для того, чтобы писать полноценные приложения под Win32 требуется
не так много:
собственно компилятор и компоновщик (я использую связку
TASM32 и TLINK32 из пакета TASM 5.0). Перед использованием
рекомендую «наложить” patch, на данный пакет. Patch можно
взять на site -http://www.borland.com/ или на нашем ftp сервере
ftp.uralmet.ru.
редактор и компилятор ресурсов (я использую Developer Studio и
brcc32.exe)-
выполнить перетрансляцию header файлов с описаниями
процедур, структур и констант API Win32 из нотации принятой в
языке Си, в нотацию выбранного режима ассемблера: Ideal или
MASM.

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

Приложение 1. Файлы, необходимые для первого примера

Файл констант ресурсов resource.inc

IDD_DIALOG — — — — — = — — — — — — 65 — — — — — — 101
IDR_NAME — — — — — — — = — — — — — — 3E8 — — — — — 1000
IDC_STATIC — — — — — = — — — — — — -1

Файл определений dlg.def

NAME — — — — — — — — — — — TEST
DESCRIPTION — — — — ‘Demo dialog’
EXETYPE — — — — — — — — WINDOWS
EXPORTS — — — — — — — — DlgProc — — — — — — — — @1

Файл компиляции makefile

# — — Make file for Demo dialog
# — — make –B
# — — make –B –DDEBUG for debug information
NAME — — — = dlg
OBJS — — — = $(NAME).obj
DEF — — — — = $(NAME).def
RES — — — — = $(NAME).res
TASMOPT=/m3 /mx /z /q /DWINVER=0400 /D_WIN32_WINNT=0400
!if $d(DEBUG)
TASMDEBUG=/zi
LINKDEBUG=/v
!else
TASMDEBUG=/l
LINKDEBUG=
!endif
!if $d(MAKEDIR)
IMPORT=$(MAKEDIR)&#92-..&#92-lib&#92-import32
!else
IMPORT=import32
!endif
$(NAME).EXE: $(OBJS) $(DEF) $(RES)
— — — — — — — tlink32 /Tpe /aa /c $(LINKDEBUG) $(OBJS),$(NAME),, $(IMPORT), $(DEF), $(RES)
.asm.obj:
— — — — — — — tasm32 $(TASMDEBUG) $(TASMOPT) $&amp-.asm
$(RES): $(NAME).RC
— — — — — — — BRCC32 -32 $(NAME).RC

Файл заголовков resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dlg.rc
//
#define IDD_DIALOG — — — — — — — — — — — — — — — — — — — — — 101
#define IDR_NAME — — — — — — — — — — — — — — — — — — — — — — — 1000
#define IDC_STATIC — — — — — — — — — — — — — — — — — — — — — -1
// Next default values for new objects
// —
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE — — — — — — — 102
#define _APS_NEXT_COMMAND_VALUE — — — — — — — — 40001
#define _APS_NEXT_CONTROL_VALUE — — — — — — — — 1001
#define _APS_NEXT_SYMED_VALUE — — — — — — — — — — 101
#endif
#endif — —

Приложение 2. Файлы, необходимые для второго примера

Файл описания mylib.def

LIBRARY — — — — — — — — MYLIB
DESCRIPTION — — — — ‘DLL EXAMPLE, 1997’
EXPORTS — — — — — — — — Hex2Str — — — — — — — — @1

Файл компиляции makefile

# — — Make file for Demo DLL
# — — make –B
# — — make –B –DDEBUG for debug information
NAME — — — = mylib
OBJS — — — = $(NAME).obj
DEF — — — — = $(NAME).def
RES — — — — = $(NAME).res
TASMOPT=/m3 /mx /z /q /DWINVER=0400 /D_WIN32_WINNT=0400
!if $d(DEBUG)
TASMDEBUG=/zi
LINKDEBUG=/v
!else
TASMDEBUG=/l
LINKDEBUG=
!endif
!if $d(MAKEDIR)
IMPORT=$(MAKEDIR)&#92-..&#92-lib&#92-import32
!else
IMPORT=import32
!endif
$(NAME).EXE: $(OBJS) $(DEF)
— — — — — — — tlink32 /Tpd /aa /c $(LINKDEBUG) $(OBJS),$(NAME),, $(IMPORT), $(DEF)
.asm.obj:
— — — — — — — tasm32 $(TASMDEBUG) $(TASMOPT) $&amp-.asm
$(RES): $(NAME).RC
— — — — — — — BRCC32 -32 $(NAME).RC

Приложение 3. Файлы, необходимые для третьего примера

Файл описания dmenu.def

NAME — — — — — — — — — — — TEST
DESCRIPTION — — — — ‘Demo menu’
EXETYPE — — — — — — — — WINDOWS
EXPORTS — — — — — — — — WndProc — — — — — — — — — — — — — — — — @1

Файл ресурсов dmenu.rc

#include «resource.h»
MyMenu MENU DISCARDABLE —
BEGIN — — — POPUP «Files» — — — —
— — BEGIN
— — — — — — — MENUITEM «Open», — — — — — — — — — — — — — — — ID_OPEN
— -&nbs
p- — — — — MENUITEM «Save», — — — — — — — — — — — — — — — ID_SAVE
— — — — — — — MENUITEM SEPARATOR
— — — — — — — MENUITEM «Exit», — — — — — — — — — — — — — — — — — — — — — — — ID_EXIT
— — — END
— — — MENUITEM «Other», — — — — — — — — — — — — — — — — — — 65535
END — — — — — — — — — — — — —

Файл заголовков resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dmenu.rc
//
#define MyMenu — — — — — — — — — — — — — — — — — — — — — — — — — 101
#define ID_OPEN — — — — — — — — — — — — — — — — — — — — — — — — 40001
#define ID_SAVE — — — — — — — — — — — — — — — — — — — — — — — — 40002 —
#define ID_EXIT — — — — — — — — — — — — — — — — — — — — — — — — 40003
// Next default values for new objects
// —
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE — — — — — — — 102
#define _APS_NEXT_COMMAND_VALUE — — — — — — — — 40004
#define _APS_NEXT_CONTROL_VALUE — — — — — — — — 1000
#define _APS_NEXT_SYMED_VALUE — — — — — — — — — — 101
#endif
#endif

Файл компиляции makefile

# — — Make file for Turbo Assembler Demo menu
# — — — — — — make -B
# — — — — — — make -B -DDEBUG -DVERN — for debug information and version
NAME — — — = dmenu
OBJS — — — = $(NAME).obj
DEF — — — — = $(NAME).def
RES — — — — = $(NAME).res
!if $d(DEBUG)TASMDEBUG=/zi
LINKDEBUG=/v
!else
TASMDEBUG=/l
LINKDEBUG=
!endif
!if $d(VER2)
TASMVER=/dVER2
!elseif $d(VER3)
TASMVER=/dVER3
!else
TASMVER=/dVER1
!endif
!if $d(MAKEDIR)
IMPORT=$(MAKEDIR)&#92-..&#92-lib&#92-import32
!else
IMPORT=import32
!endif
$(NAME).EXE: $(OBJS) $(DEF) $(RES)
— — — — — — — tlink32 /Tpe /aa /c $(LINKDEBUG) $(OBJS),$(NAME),, $(IMPORT), $(DEF), $(RES)
.asm.obj:
— — — — — — — tasm32 $(TASMDEBUG) $(TASMVER) /m /mx /z /zd $&amp-.asm
$(RES): $(NAME).RC
— — — — — — — BRCC32 -32 $(NAME).RC


Меню раздела
Блок