┌──┌─┐┌──
──┘├─┘──┘ Presents
┐  ┌┐┐┌─┤ VMag, Issue 2, 1 June 1998
└─┘┘ ┘└─┘ ──────────────────────────

                         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).  Как иллюстрацию данного  положения,
рассмотрим простой пример приложения, работающего с диалоговым окном.

───────────────────

               Пример 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 short 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  short @@init
    cmp eax,WM_COMMAND
    jne short @@ret_false

    mov eax,[@@wPar]
    cmp eax,IDCANCEL
    je  short @@cancel
    cmp eax,IDOK
    jne short @@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 short @@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);

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

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

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

        В  нашем  примере  обрабатывается  только  первое  из  событий
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_OWERLAPPEDWINDOW     = 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).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,      \ registered class name
                                offset wndTitle,        \ window name
                                WS_OWERLAPPEDWINDOW,    \ window style
                                CW_USEDEFAULT,          \ horizontal position of window
                                CW_USEDEFAULT,          \ vertical position of window
                                CW_USEDEFAULT,          \ window width
                                CW_USEDEFAULT,          \ window height
                                0,                      \ 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_OWERLAPPEDWINDOW, \ extended window style
                                offset classTitle,      \ registered class name
                                offset wndTitle,        \ window name
                                WS_OWERLAPPEDWINDOW,    \ 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  <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

───────────────────

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

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

        - редактор   и  компилятор  ресурсов  (я  использую  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)\..\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

───────────────────

ASSEMBLER & WIN32                                       Александр Усов
(c) 27.11.97                            Alex Usov, 2:5080/82.3@FidoNet