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

Мне достаточно редко приходилось серьёзно заниматься разработкой макроопределений при программировании под 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


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

— вернуть значение TRUE, для этого необходимо использовать переход на метку @@ret_true;

— вернуть значение FALSE, для этого необходимо использовать переход на метку @@ret_false;

— перейти на обработку по умолчанию, для этого необходимо сделать переход на метку @@default.

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

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

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

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

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

Загрузка...