ASSEMBLER & WIN32

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

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

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

Все эти факторы позволяют рассматривать ассемблер, как самостоятельный инструмент для написания приложений под платформы Win32 (Windows NT и Windows 95). Как иллюстрацию данного положения, рассмотрим простой пример приложения, работающего с диалоговым окном. Пример 1. Программа работы с диалогом Файл, содержащий текст приложения, 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.

Сразу после метки 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 коде. Теперь приложение выглядит более просто и естественно.

Пример 2. Динамическая библиотека

Написание динамических библиотек под 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.

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

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

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

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

Пример 3. Оконное приложение

Файл 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

        x               dd      0

        y               dd      0

ends    Point



struc   msgStruc

        hwnd            dd      0

        message         dd      0

        wParam          dd      0

        lParam          dd      0

        time            dd      0

        pnt             Point   <>

ends    msgStruc



MyMenu                  = 0065

ID_OPEN                 = 9C41

ID_SAVE                 = 9C42

ID_EXIT                 = 9C43



CS_HREDRAW              = 0001

CS_VREDRAW              = 0002

IDI_APPLICATION         = 7F00

IDC_ARROW               = 00007F00

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               ; получаем hInstance

        mov     [hInst],eax



        sub     esp,SIZE WndClassEx                     ; выделяем место в стеке

                                                        ; заполняем структуру 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

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

Здесь мне хотелось в первую очередь продемонстрировать использование прототипов функций 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 @@&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

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

При написании процедуры окна Вы можете использовать макроопределение 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

 Обработку каждого сообщения можно завершить тремя способами:

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

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

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

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

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

Резюме

Для того, чтобы писать полноценные приложения под Win32 требуется не так много:

В результате у Вас появится возможность писать лёгкие и изящные приложения под 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)\..\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

Файл заголовков 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)\..\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

Приложение 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

        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)\..\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
Используются технологии uCoz