Ассемблер и Win32

         

Программирование на ассемблере под Win32


ASSEMBLER & WIN32

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

В отличие от программирования под DOS, где программы написанные на языках высокого уровня (ЯВУ) были мало похожи на свои аналоги, написанные на ассемблере, приложения под Win32 имеют гораздо больше общего. В первую очередь, это связано с тем, что обращение к сервису операционной системы в Windows осуществляется посредством вызова функций, а не прерываний, что было характерно для DOS. Здесь нет передачи параметров в регистрах при обращении к сервисным функциям и, соответственно, нет и множества результирующих значений возвращаемых в регистрах общего назначения и регистре флагов. Следовательно проще запомнить и использовать протоколы вызова функций системного сервиса. С другой стороны, в Win32 нельзя непосредственно работать с аппаратным уровнем, чем “грешили” программы для DOS. Вообще написание программ под Win32 стало значительно проще и это обусловлено следующими факторами:

-          отсутствие startup кода, характерного для приложений и динамических библиотек написанных под Windows 3.x;

-          гибкая система адресации к памяти: возможность обращаться к памяти через любой регистр общего назначения; “отсутствие” сегментных регистров;

-          доступность больших объёмов виртуальной памяти;

-          развитый сервис операционной системы, обилие функций, облегчающих разработку приложений;

-          многообразие и доступность средств создания интерфейса с пользователем (диалоги, меню и т.п.).

Современный ассемблер, к которому относится и TASM 5.0 фирмы Borland International Inc., в свою очередь, развивал средства, которые ранее были характерны только для ЯВУ. К таким средствам можно отнести макроопределение вызова процедур, возможность введения шаблонов процедур (описание прототипов) и даже объектно-ориентированные расширения. Однако, ассемблер сохранил и такой прекрасный инструмент, как макроопределения вводимые пользователем, полноценного аналога которому нет ни в одном ЯВУ.

Все эти факторы позволяют рассматривать ассемблер, как самостоятельный инструмент для написания приложений под платформы Win32 (Windows NT и Windows 95). Как иллюстрацию данного положения, рассмотрим простой пример приложения, работающего с диалоговым окном.


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


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


Сейчас в макроопределении WndMessage можно начинать обработку. Теперь существо обработки, скорее всего, будет понятно без дополнительных пояснений.
Обработка сообщений в Windows не является линейной, а, как правило, представляет собой иерархию. Например, сообщение WM_COMMAND может заключать в себе множество сообщений поступающих от меню и/или других управляющих элементов. Следовательно, данную методику можно с успехом применить и для других уровней каскада и даже несколько упростить её. Действительно, не в наших силах исправить код сообщений, поступающих в процедуру окна или диалога, но выбор последовательности констант, назначаемых пунктам меню или управляющим элементам (controls) остаётся за нами. В этом случае нет нужды в дополнительном поле, которое сохраняет код сообщения. Тогда каждый элемент вектора будет содержать только адрес обработчика, а найти нужный элемент весьма просто. Из полученной константы, пришедшей в сообщении, вычитается идентификатор первого пункта меню или первого управляющего элемента, это и будет номер нужного элемента вектора. Остаётся только сделать переход на обработчик.
Вообще тема макроопределений весьма поучительна и обширна. Мне редко доводится видеть грамотное использование макросов и это досадно, поскольку с их помощью можно сделать работу в ассемблере значительно проще и приятнее.
Резюме
Для того, чтобы писать полноценные приложения под Win32 требуется не так много:
-          собственно компилятор и компоновщик (я использую связку TASM32  и TLINK32 из пакета TASM 5.0). Перед использованием рекомендую “наложить” patch, на данный пакет. Patch можно взять на site www.borland.com
или на нашем ftp сервере ftp.uralmet.ru.
-          редактор и компилятор ресурсов (я использую Developer Studio и brcc32.exe);
-          выполнить перетрансляцию header файлов с описаниями процедур, структур и констант API Win32 из нотации принятой в языке Си, в нотацию выбранного режима ассемблера: Ideal или MASM.
В результате у Вас появится возможность писать лёгкие и изящные приложения под Win32, с помощью которых Вы сможете создавать и визуальные формы, и работать с базами данных, и обслуживать коммуникации, и работать multimedia инструментами. Как и при написании программ под DOS, у Вас сохраняется возможность наиболее полного использования ресурсов процессора, но при этом сложность написания приложений значительно снижается за счёт более мощного сервиса операционной системы, использования более удобной системы адресации и весьма простого оформления программ.

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


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

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

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

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

       sub    esp,SIZE WndClassEx

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

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



Краткие комментарии к динамической библиотеке


Процедура libEntry является точкой входа в динамическую библиотеку, её не надо объявлять как экспортируемую, загрузчик сам определяет её местонахождение. LibEntry может вызываться в четырёх случаях:

-          при проецировании библиотеки в адресное пространство процесса (DLL_PROCESS_ATTACH);

-          при первом вызове библиотеки из потока (DLL_THREAD_ATTACH), например, с помощью функции LoadLibrary;

-          при выгрузке библиотеки потоком (DLL_THREAD_DETACH);

-          при выгрузке библиотеки из адресного пространства процесса (DLL_PROCESS_DETACH).

В нашем примере обрабатывается только первое из событий DLL_PROCESS_ATTACH. При обработке данного события библиотека запрашивает версию OS сохраняет её, а также свой handle of instance.

Библиотека содержит только одну экспортируемую функцию, которая собственно не требует пояснений. Вы, пожалуй, можете обратить внимание на то, как производится запись преобразованных значений. Интересна система адресации посредством двух регистров общего назначения: ebx + ecx, она позволяет нам использовать регистр ecx одновременно и как счётчик и как составную часть адреса.



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


Сразу после метки Start, программа обращается к функции API Win32 GetModuleHandle для получения handle данного модуля (данный параметр чаще именуют как handle of instance). Получив handle, мы вызываем диалог, созданный либо вручную, либо с помощью какой-либо программы построителя ресурсов. Далее программа проверяет результат работы диалогового окна. Если пользователь вышел из диалога посредством нажатия клавиши OK, то приложение запускает MessageBox с текстом приветствия.

Диалоговая процедура обрабатывает следующие сообщения. При инициализации диалога  (WM_INITDIALOG) она просит Windows установить фокус на поле ввода имени пользователя. Сообщение WM_COMMAND обрабатывается в таком порядке: делается проверка на код нажатия клавиши. Если была нажата клавиша OK, то пользовательский ввод копируется в переменную szValue, если же была нажата клавиша Cancel, то копирования не производится. Но и в том и другом случае вызывается функция окончания диалога: EndDialog. Остальные сообщения в группе WM_COMMAND просто игнорируются, предоставляя Windows действовать по умолчанию.

Вы можете сравнить приведённую программу с аналогичной программой, написанной на ЯВУ, разница в написании будет незначительна. Очевидно те, кто писал приложения на ассемблере под Windows 3.x, отметят тот факт, что исчезла необходимость в сложном и громоздком startup коде. Теперь приложение выглядит более просто и естественно.



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


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



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


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

IDD_DIALOG    =      65     ; 101

IDR_NAME      =      3E8    ; 1000

IDC_STATIC    =      -1

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

#define IDD_DIALOG                      101

#define IDR_NAME                        1000

#define IDC_STATIC                      -1

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

NAME          TEST

DESCRIPTION   'Demo dialog'

EXETYPE              WINDOWS

EXPORTS              DlgProc                    @1

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

#   Make file for Demo dialog

#       make -B

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)\..\lib\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) $&.asm

$(RES): $(NAME).RC

       BRCC32 -32 $(NAME).RC



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


Файл описания 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)\..\lib\import32

!else

IMPORT=import32

!endif

$(NAME).EXE: $(OBJS) $(DEF)

       tlink32 /Tpd /aa /c $(LINKDEBUG) $(OBJS),$(NAME),, $(IMPORT), $(DEF)

.asm.obj:

       tasm32 $(TASMDEBUG) $(TASMOPT) $&.asm

$(RES): $(NAME).RC

       BRCC32 -32 $(NAME).RC



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


Файл описания 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

        MENUITEM "Save",                        ID_SAVE

        MENUITEM SEPARATOR

        MENUITEM "Exit",                        ID_EXIT

    END

    MENUITEM "Other",                           65535

END

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

#define MyMenu                          101

#define ID_OPEN                         40001

#define ID_SAVE                         40002

#define ID_EXIT                         40003

Файл компиляции 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)\..\lib\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 $&.asm

$(RES): $(NAME).RC

       BRCC32 -32 $(NAME).RC



Программа работы с диалогом


Файл, содержащий текст приложения, dlg.asm

IDEAL

P586

RADIX  16

MODEL  FLAT

%NOINCL

%NOLIST

include       "winconst.inc"             ; API Win32 consts

include       "winptype.inc"             ; API Win32 functions prototype

include       "winprocs.inc"             ; API Win32 function

include       "resource.inc"             ; resource consts

MAX_USER_NAME =      20

DataSeg

szAppName     db     'Demo 1', 0

szHello              db     'Hello, '

szUser        db     MAX_USER_NAME dup (0)

CodeSeg

Start:        call   GetModuleHandleA,    0

              call   DialogBoxParamA,     eax, IDD_DIALOG, 0, offset DlgProc, 0

              cmp    eax,IDOK

              jne    bye

              call   MessageBoxA,         0, offset szHello,   \

                                         offset szAppName,    \

                                         MB_OK or MB_ICONINFORMATION

bye:          call   ExitProcess,         0

public stdcall       DlgProc

proc   DlgProc       stdcall

arg    @@hDlg :dword,       @@iMsg :dword,       @@wPar :dword,       @@lPar :dword

              mov    eax,[@@iMsg]

              cmp    eax,WM_INITDIALOG

              je     @@init

              cmp    eax,WM_COMMAND

              jne    @@ret_false

              mov    eax,[@@wPar]

              cmp    eax,IDCANCEL

              je     @@cancel

              cmp    eax,IDOK

              jne    @@ret_false

              call   GetDlgItemTextA,     @@hDlg, IDR_NAME,    \

                                         offset szUser, MAX_USER_NAME

              mov    eax,IDOK

@@cancel:     call   EndDialog,           @@hDlg, eax

@@ret_false:  xor    eax,eax

              ret

@@init:              call   GetDlgItem,          @@hDlg, IDR_NAME

              call   SetFocus,            eax

              jmp    @@ret_false

endp   DlgProc

end    Start

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

#include "resource.h"

IDD_DIALOG DIALOGEX 0, 0, 187, 95

STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_CAPTION | WS_SYSMENU

EXSTYLE WS_EX_CLIENTEDGE

CAPTION "Dialog"

FONT 8, "MS Sans Serif"

BEGIN

    DEFPUSHBUTTON   "OK",IDOK,134,76,50,14

    PUSHBUTTON      "Cancel",IDCANCEL,73,76,50,14

    LTEXT           "Type your name",IDC_STATIC,4,36,52,8

    EDITTEXT        IDR_NAME,72,32,112,14,ES_AUTOHSCROLL

END

Остальные файлы из данного примера, приведены в приложении 1.



Динамическая библиотека


Написание динамических библиотек под Win32 также значительно упростилось, по сравнению с тем, как это делалось под Windows 3.x. Исчезла необходимость вставлять startup код, а использование четырёх событий инициализации/деинициализации на уровне процессов и потоков, кажется логичным.

Рассмотрим простой пример динамической библиотеки, в которой всего одна функция, преобразования целого числа в строку в шестнадцатеричной системе счисления.

Файл mylib.asm

Ideal

P586

Radix  16

Model  flat

DLL_PROCESS_ATTACH   = 1

extrn  GetVersion:   proc

DataSeg

hInst         dd     0

OSVer         dw     0

CodeSeg

proc   libEntry      stdcall

arg    @@hInst       :dword,       @@rsn  :dword,       @@rsrv :dword

              cmp    [@@rsn],DLL_PROCESS_ATTACH

              jne    @@1

              call   GetVersion

              mov    [OSVer],ax

              mov    eax,[@@hInst]

              mov    [hInst],eax

@@1:          mov    eax,1

              ret

endP   libEntry

public stdcall       Hex2Str

proc   Hex2Str       stdcall

arg    @@num  :dword,       @@str  :dword

uses   ebx

              mov    eax,[@@num]

              mov    ebx,[@@str]

              mov    ecx,7

@@1:          mov    edx,eax

              shr    eax,4

              and    edx,0F

              cmp    edx,0A

              jae    @@2

              add    edx,'0'

              jmp    @@3

@@2:          add    edx,'A' - 0A

@@3:          mov    [byte ebx + ecx],dl

              dec    ecx

              jns    @@1

              mov    [byte ebx + 8],0

              ret

endp   Hex2Str

end    libEntry

Остальные файлы, которые необходимы для данного примера, можно найти в приложении 2.



Оконное приложение


Файл dmenu.asm

Ideal

P586

Radix  16

Model  flat

struc  WndClassEx

       cbSize        dd     0

       style         dd     0

       lpfnWndProc   dd     0

       cbClsExtra    dd     0

       cbWndExtra    dd     0

       hInstance     dd     0

       hIcon         dd     0

       hCursor              dd     0

       hbrBackground dd     0

       lpszMenuName  dd     0

       lpszClassName dd     0

       hIconSm              dd     0

ends   WndClassEx

struc  Point

       left          dd     0

       top           dd     0

       right         dd     0

       bottom        dd     0            

ends   Point

struc  msgStruc

       hwnd          dd     0

       message              dd     0

       wParam        dd     0

       lParam        dd     0

       time          dd     0

       pt            Point  <>

ends   msgStruc

MyMenu               = 0065

ID_OPEN                    = 9C41

ID_SAVE                    = 9C42

ID_EXIT                    = 9C43

CS_VREDRAW           = 0001

CS_HREDRAW           = 0002

IDI_APPLICATION            = 7F00

IDC_ARROW            = 7F00

COLOR_WINDOW         = 5

WS_EX_WINDOWEDGE     = 00000100

WS_EX_CLIENTEDGE     = 00000200

WS_EX_OVERLAPPEDWINDOW     = WS_EX_WINDOWEDGE OR WS_EX_CLIENTEDGE

WS_OVERLAPPED        = 00000000

WS_CAPTION           = 00C00000

WS_SYSMENU           = 00080000

WS_THICKFRAME        = 00040000

WS_MINIMIZEBOX             = 00020000

WS_MAXIMIZEBOX             = 00010000

WS_OVERLAPPEDWINDOW  =      WS_OVERLAPPED     OR \

                           WS_CAPTION        OR \

                           WS_SYSMENU        OR \

                           WS_THICKFRAME     OR \

                           WS_MINIMIZEBOX    OR \

                           WS_MAXIMIZEBOX

CW_USEDEFAULT        = 80000000

SW_SHOW                    = 5

WM_COMMAND           = 0111

WM_DESTROY           = 0002

WM_CLOSE             = 0010

MB_OK                = 0


PROCTYPEааааа ptGetModuleHandleааа stdcallаааааа \
аааааааааааааааааааа lpModuleNameа :dword
PROCTYPEааааа ptLoadIconаааааааааа stdcallаааааа \
аааааааааааааааааааа hInstanceаааа :dword,аааааа \
аааааааааааааааааааа lpIconNameааа :dword
PROCTYPEааааа ptLoadCursorаааааааа stdcallаааааа \
аааааааааааааааааааа hInstanceаааа :dword,аааааа \
аааааааааааааааааааа lpCursorNameа :dword
PROCTYPEааааа ptLoadMenuаааааааааа stdcallаааааа \
аааааааааааааааааааа hInstanceаааа :dword,аааааа \
аааааааааааааааааааа lpMenuNameааа :dword
PROCTYPEааааа ptRegisterClassExааа stdcallаааааа \
аааааааааааааааааааа lpwcxаааааааа :dword
PROCTYPEааааа ptCreateWindowExаааа stdcallаааааа \
аааааааааааааааааааа dwExStyleаааа :dword,аааааа \
аааааааааааааааааааа lpClassNameаа :dword,аааааа \
аааааааааааааааааааа lpWindowNameа :dword,аааааа \
аааааааааааааааааааа dwStyleааааааааааааа :dword,аааааа \
аааааааааааааааааааа xаааааааааааа :dword, \
аааааааааааааааааааа yаааааааааааа :dword,аааааа \
аааааааааааааааааааа nWidthааааааа :dword,аааааа \
аааааааааааааааааааа nHeightааааааааааааа :dword,аааааа \
аааааааааааааааааааа hWndParentааа :dword,аааааа \
аааааааааааааааааааа hMenuаааааааа :dword, \
аааааааааааааааааааа hInstanceаааа :dword,аааааа \
аааааааааааааааааааа lpParamааааааааааааа :dword
PROCTYPEааааа ptShowWindowаааааааа stdcallаааааа \
аааааааааааааааааааа hWndааааааааа :dword,аааааа \
аааааааааааааааааааа nCmdShowааааа :dword
PROCTYPEааааа ptUpdateWindowаааааааааааа stdcallаааааа \
аааааааааааааааааааа hWndааааааааа :dword
PROCTYPEааааа ptGetMessageаааааааа stdcallаааааа \
аааааааааааааааааааа pMsgааааааааа :dword,аааааа \
аааааааааааааааааааа hWndааааааааа :dword,аааааа \
аааааааааааааааааааа wMsgFilterMin :dword,аааааа \
аааааааааааааааааааа wMsgFilterMax :dword
PROCTYPEааааа ptTranslateMessageаа stdcallаааааа \
аааааааааааааааааааа lpMsgаааааааа :dword
PROCTYPEааааа ptDispatchMessageааа stdcallаааааа \


аааааааааааааааааааа pmsgааааааааа :dword
PROCTYPEааааа ptSetMenuааааааааааа stdcallаааааа \
аааааааааааааааааааа hWndааааааааа :dword,аааааа \
аааааааааааааааааааа hMenuаааааааа :dword
PROCTYPEааааа ptPostQuitMessageааа stdcallаааааа \
аааааааааааааааааааа nExitCodeаааа :dword
PROCTYPEааааа ptDefWindowProcааааааааааа stdcallаааааа \
аааааааааааааааааааа hWndааааааааа :dword,аааааа \
аааааааааааааааааааа Msgаааааааааа :dword,аааааа \
аааааааааааааааааааа wParamааааааа :dword,аааааа \
аааааааааааааааааааа lParamааааааа :dword
PROCTYPEааааа ptSendMessageааааааа stdcallаааааа \
аааааааааааааааааааа hWndааааааааа :dword,аааааа \
аааааааааааааааааааа Msgаааааааааа :dword,аааааа \
аааааааааааааааааааа wParamааааааа :dword,аааааа \
аааааааааааааааааааа lParamааааааа :dword
PROCTYPEааааа ptMessageBoxаааааааа stdcallаааааа \
аааааааааааааааааааа hWndааааааааа :dword,аааааа \
аааааааааааааааааааа lpTextааааааа :dword,аааааа \
аааааааааааааааааааа lpCaptionаааа :dword,аааааа \
аааааааааааааааааааа uTypeаааааааа :dword
PROCTYPEааааа ptExitProcessааааааа stdcallаааааа \
аааааааааааааааааааа exitCodeааааа :dword
extrnаааааааа GetModuleHandleAаааа :ptGetModuleHandle
extrnаааааааа LoadIconAааааааааааа :ptLoadIcon
extrnаааааааа LoadCursorAааааааааа :ptLoadCursor
extrnаааааааа RegisterClassExAаааа :ptRegisterClassEx
extrnаааааааа LoadMenuAааааааааааа :ptLoadMenu
extrnаааааааа CreateWindowExAааааааааааа :ptCreateWindowEx
extrnаааааааа ShowWindowаааааааааа :ptShowWindow
extrnаааааааа UpdateWindowаааааааа :ptUpdateWindow
extrnаааааааа GetMessageAааааааааа :ptGetMessage
extrnаааааааа TranslateMessageаааа :ptTranslateMessage
extrnаааааааа DispatchMessageAаааа :ptDispatchMessage
extrnаааааааа SetMenuааааааааааааааааааа :ptSetMenu
extrnаааааааа PostQuitMessageааааааааааа :ptPostQuitMessage
extrnа аааааа DefWindowProcAаааааааааааа :ptDefWindowProc
extrnаааааааа SendMessageAаааааааа :ptSendMessage
extrnаааааааа MessageBoxAааааааааа :ptMessageBox


extrnаааааааа ExitProcessааааааааа :ptExitProcess
UDataSeg
hInstаааааааа ddааааааааааа ?
hWndааааааааа ddааааааааааа ?
IFNDEF VER1
hMenuаааааааа ddааааааааааа ?
ENDIF
DataSeg
msgаааааааааа msgStrucааааа <>
classTitleааа dbаааа 'Menu demo', 0
wndTitleааааа dbаааа 'Demo program', 0
msg_open_txtа dbаааа 'You selected open', 0
msg_open_tltа dbаааа 'Open box', 0
msg_save_txtа dbаааа 'You selected save', 0
msg_save_tltа dbаааа 'Save box', 0
CodeSeg
Start: callаа GetModuleHandleA,ааа 0ааааа ; эх юс чрЄхы№эю, эю цхырЄхы№эю
аааааа movааа [hInst],eax
аааааа subааа esp,SIZE WndClassExааааааа ; юЄтхф¬ь ьхёЄю т ёЄхъх яюф ёЄЁєъЄєЁє
аааааа movааа [(WndClassEx esp).cbSize],SIZE WndClassEx
аааааа movааа [(WndClassEx esp).style],CS_HREDRAW or CS_VREDRAW
аааааа movааа [(WndClassEx esp).lpfnWndProc],offset WndProc
аааааа movааа [(WndClassEx esp).cbWndExtra],0
аааааа movааа [(WndClassEx esp).cbClsExtra],0
аааааа movааа [(WndClassEx esp).hInstance],eax
аааааа callаа LoadIconA,аааааааааа 0, IDI_APPLICATION
аааааа movааа [(WndClassEx esp).hIcon],eax
аааааа callаа LoadCursorA,аааааааа 0, IDC_ARROW
аааааа movааа [(WndClassEx esp).hCursor],eax
аааааа movааа [(WndClassEx esp).hbrBackground],COLOR_WINDOW
IFDEFа VER1
аааааа movааа [(WndClassEx esp).lpszMenuName],MyMenu
ELSE
аааааа movааа [(WndClassEx esp).lpszMenuName],0
ENDIF
аааааа movааа [(WndClassEx esp).lpszClassName],offset classTitle
аааааа movааа [(WndClassEx esp).hIconSm],0
аааааа callаа RegisterClassExA,ааа espааа ; чрЁхушёЄЁшЁєхь ъырёё юъэр
аааааа addааа esp,SIZE WndClassExааааааа ; тюёёЄрэютшь ёЄхъ
аааааааааааааааааааааааааааааааааааааааа ; ш ёючфрфшь юъэю
IFNDEF VER2
аааааа callаа CreateWindowExA,аааа WS_EX_OVERLAPPEDWINDOW,
\ extended window style
ааааааааааааааааааааааааааааааааа offset classTitle, \ pointer to registered class name
ааааааааааааааааааааааааааааааааа offset wndTitle,\ pointer to window name
ааааааааааааааааааааааааааааааааа WS_OVERLAPPEDWINDOW, \ window style


ааааааааааааааааааааааааааааааааа CW_USEDEFAULT,аааааа \ horizontal position of window
ааааааааааааааааааааааааааааааааа CW_USEDEFAULT,аааааа \ vertical position of window
ааааааааааааааааааааааааааааааааа CW_USEDEFAULT,аааааа \ window width
ааааааааааааааааааааааааааааааааа CW_USEDEFAULT,аааааа \ window height
аааааа аааааааааааааааааааааааааа 0,ааааааааааа \ handle to parent or owner window
ааааааааааааааааааааааааааааааааа 0,аааа \ handle to menu, or child-window identifier
ааааааааааааааааааааааааааааааааа [hInst],ааааа \ handle to application instance
ааааааааааааааааааааааааааааааааа 0аааааааааааа ; pointer to window-creation data
ELSE
аааааа callаа LoadMenu,ааааааааааа hInst, MyMenu
аааааа movааа [hMenu],eax
аааааа callаа CreateWindowExA,аааа WS_EX_OVERLAPPEDWINDOW,ааа \ extended window style
ааааааааааааааааааааааааааааааааа offset classTitle, \ pointer to registered class name
ааааааааааааааааааааааааааааааааа offset wndTitle,аааа \ pointer to window name
ааааааааааааааааааааааааааааааааа WS_OVERLAPPEDWINDOW, \ window style
ааааааааааааааааааааааааааааааааа CW_USEDEFAULT,аааааа \ horizontal position of window
аааааааааааааааааааа ааааааааааааа CW_USEDEFAULT,аааааа \ vertical position of window
ааааааааааааааааааааааааааааааааа CW_USEDEFAULT,аааааа \ window width
ааааааааааааааааааааааааааааааааа CW_USEDEFAULT,аааааа \ window height
ааааааааааааааааааааааааааааааааа 0,ааааааааааа \ handle to parent or owner window
ааааааааааааааааааааааааааааааааа eax,аа \ handle to menu, or child-window identifier
ааааааааааааааааааааааааааааааааа [hInst],ааааа \ handle to application instance
ааааааааааааааааааааааааааааааааа 0аааааааааааа ; pointer to window-creation data
ENDIF
аааааа movааа [hWnd],eax
аааааа callаа ShowWindow,ааааааааа eax, SW_SHOWаааааааа ; show window
аааааа callаа UpdateWindow,ааааааа [hWnd]аааааааааааааа ; redraw window
IFDEFа VER3
аааааа callаа LoadMenuA,аааааааааа [hInst], MyMenu
аааааа movааа [hMenu],eax
аааааа callаа SetMenu,аааааааааааа [hWnd], eax


ENDIF
msg_loop:
аааааа callаа GetMessageA,аааааааа offset msg, 0, 0, 0
аааааа orаааа ax,ax
аааааа jzаааа exit
аааааа callаа TranslateMessage,ааа offset msg
аааааа callаа DispatchMessageA,ааа offset msg
аааааа jmpааа msg_loop
exit:а callаа ExitProcess,аааааааа 0
public stdcallаааааа WndProc
procаа WndProcаааааа stdcall
argааа @@hwnd:аааааа dword, @@msg: dword, @@wPar:аааааа dword, @@lPar:аааааа dword
аааааа movааа eax,[@@msg]
аааааа cmpааа eax,WM_COMMAND
аааааа jeаааа @@command
аааааа cmpааа eax,WM_DESTROY
аааааа jneааа @@default
аааааа callаа PostQuitMessage,аааа 0
аааааа xorааа eax,eax
аааааа jmpааа @@ret
@@default:
аааааа callаа DefWindowProcA,ааааа [@@hwnd], [@@msg], [@@wPar], [@@lPar]
@@ret: ret
@@command:
аааааа movааа eax,[@@wPar]
аааааа cmpааа eax,ID_OPEN
аааааа jeаааа @@open
аааааа cmpааа eax,ID_SAVE
аааааа jeаааа @@save
аааааа callаа SendMessageA,ааааааа [@@hwnd], WM_CLOSE, 0, 0
аааааа xorааа eax,eax
аааааа jmpааа @@ret
@@open:аааааа movааа eax, offset msg_open_txt
аааааа movааа edx, offset msg_open_tlt
аааааа jmpааа @@mess
@@save:аааааа movааа eax, offset msg_save_txt
аааааа movааа edx, offset msg_save_tlt
@@mess:аааааа callаа MessageBoxA,аааааааа 0, eax, edx, MB_OK
аааааа xorааа eax,eax
аааааа jmpааа @@ret
endpаа WndProc
endааа Start

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


macro  MessageVector message1, message2:REST

       IFNB   <message1>

              dd     message1

              dd     offset @@&message1

              @@VecCount = @@VecCount + 1

              MessageVector message2

       ENDIF

endm   MessageVector

macro  WndMessages   VecName, message1, message2:REST

       @@VecCount    = 0

DataSeg

label  @@&VecName    dword

       MessageVector message1, message2

       @@&VecName&Cnt       = @@VecCount

CodeSeg

              mov    ecx,@@&VecName&Cnt

              mov    eax,[@@msg]

@@&VecName&_1:       dec    ecx

              js     @@default

              cmp    eax,[dword ecx * 8 + offset @@&VecName]

              jne    @@&VecName&_1

              jmp    [dword ecx + offset @@&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