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