Assembler & Win32

Усов Александр

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

 

 

Мне достаточно редко приходилось серьёзно заниматься разработкой макроопределений при программировании под 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) остаётся за нами. В этом случае нет нужды в дополнительном поле, которое сохраняет код сообщения. Тогда каждый элемент вектора будет содержать только адрес обработчика, а найти нужный элемент весьма просто. Из полученной константы, пришедшей в сообщении, вычитается идентификатор первого пункта меню или первого управляющего элемента, это и будет номер нужного элемента вектора. Остаётся только сделать переход на обработчик.

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