Воробьёвы

(-:

№37. Тайны макросов в MASM32

#Бредисловие от HI-TECH Group

Edmond / HI-TECH

Руководство по проектированию макросов в MASM32

(часть 1.1)

Пойми в Хаосе Разное, и стань человеком.
Осознай Единое в Различном – и будь Богом.

Автор

I. От автора

В этом руководстве раскрывается тема создания, использования (а главное – проектирования) макросов и макрофункций в проектах на MASM32.

Что не так важно в ЯВУ, то очень важно в программировании на ассемблере. Если выстроить по приоритетам недостатки программирования на ассемблере, то первым недостатком будет не объём строк написанного кода (как нестранно), а отсутствие средств, обеспечивающих хороший стиль написания кода.

Что значит стиль? А что значит плохой или хороший? Это можно быстро понять на простом примере.

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

  1. Вспомнить её алгоритм (если забыли)
  2. Вспомнить особенности реализации (у вас должны быть комментарии)
  3. Вспомнить какой участок кода, чем занимается.

Если код процедуры был прооптимизирован, вероятней всего вам захочется, чтобы после модификации он остался настолько же оптимальным, а поэтому вы должны вспомнить все тонкости кода, или только того участка, который подлежит модификации.

А это не так то просто, даже если исходник написан вами, в вашем неповторимом стиле.

Если этот стиль будет хорошим, вы потратите меньшее время, если бы стиль был бы плохим.

Хороший стиль программирования – это сэкономленное время, которое можно потратить на понимание, модификацию или, как это называют, сопровождение кода.
Стиль программирования – это архитектура исходного кода – не только его внешнее оформление, но и использование констант, разбиения кода на функции или процедуры, способы вызова функций и процедур, согласованность структур, их потенциал к расширению, гибкость алгоритмов и многое другое. Стиль программирования сложно отделить от архитектуры самой программы, так как хорошо спроектированная программа не может иметь плохого стиля программирования.

Конечно же, на ЯВУ легче писать качественно оформленные программы, хотя бы, потому что ЯВУ уже имеет готовые средства выражения, и шаблоны мышления.

Что такое шаблоны мышления? Всё чем вы так активно пользуетесь:
- типы
- функции
- классы
- массивы
- указатели на типы
- пространства имён
- шаблоны (С++)
Всё это направляет ваше понимание программирования как пространства сотканного из таких абстракций.

Недавно я прочёл следующую мысль на форуме WASM.RU:

Да, зачем вы пишите программы на asm под Win32, лучше уже писать под DOS, там хоть нет этого бесконечно однообразного кода создания окон и обработки сообщений.

Такое заявление говорит, что программист не желает писать проекты более чем на 6 000 строк (или 3 000 :)). Вместо того чтобы извлечь великую выгоду из единообразия кода, мы ругаем его. А ведь это первый звонок к автоматизации программирования.

Неужели программирование asm может быть похоже на Delphi (ох как его не любят некоторые)? Снова интегрированная среда? Конечно!!! (Жаль, её всё-таки нет!) Но это не значит, что она играет отрицательную роль. Хотя о средствах автоматизации и их создании мы поговорим в другой работе.

Ассемблер не определяет шаблонов мышления, и практически не имеет средств выражения каких либо шаблонов (из-за чего автор пользуется им).

Очень сложно назвать директиву proc средством выражение процедурной модели программирования.

Однако я могу ручаться, если вы научитесь писать качественно стилизированные программы на ассемблере, то на ЯВУ… ?.

Об искусстве стилизации или проектировании архитектуры написано слишком мало, а рассказать хотелось бы слишком много. Только нельзя объять необъятное, и потому цель этого руководства рассказать об использовании макросов в MASM32, а также о том, как их можно либо нужно использовать, чтобы более качественно стилизировать код.

I.1 Для тех, кто впервые...

Если вы ещё не работали с макросами, или работали, но очень мало, я спешу признаться, что это руководство не предназначалось для начинающих. Но благодаря рекомендациям и советам TheSvin/HI-TECH я решился добавить в него вырезки и упражнения, которые позволят вам быстро войти во вкус макромира MASM32. Если же вы уже имеете дело с макросами, тогда это руководство укрепит ваши знания и представления по данной теме.

Для исследования макромира MASM мы воспользуемся директивой echo, которая позволит вывести нам на экран то, что творится в препроцессоре MASM. Очень удобно, а главное наглядно. Я уверен, что вы быстро усвоите этот материал.

I.2. Примечания (обо всём понемногу)

В данной работе я часто пишу: «Препроцессор ML». Кто-то из умников (или просто жаждущих подловить «на горячем») воскликнет: «Да какой же такой ML.EXE – препроцессор? Наглая ложь». На всякий случай оговорю, что здесь имеется ввиду не утверждение «ML – препроцессор», а именование его подсистемы – препроцессор.

Всё, что есть в этом руководстве не взято с потолка, и не является вымышленным. Весь код проверен, и работает именно так как описано, если только автор случайно не ошибся, что так же случается.

Многое из того, что написано в этом руководстве недокументированно (или плохо документировано) в официальном. Поэтому вы всегда должны помнить, что если в следующих версиях ML (например, 8.0) что-то не будет работать, никто не виноват.

Если вы думаете, что я дизассемблировал ML.EXE – то ошибаетесь. Алгоритмы работы, приведённые здесь, получены логическим путём на основе знаний работы компиляторов, а поэтому их не следует воспринимать как истинные. Важна сама логика работы, понимание которой, поможет вам безболезненно использовать макро, допуская меньшее количество ошибок.

На самом деле MASM очень плохо документирован, и видно MS совсем не относится к нему как к продукту (что вполне очевидно). Хотя уже в MSDN 2002 был внесён раздел MASM Reference, и всё равно – вы не найдёте лучше описания чем в MASM32 by Hutch.

Когда вы прочтете, то воскликните: «Да, зачем мне такой ML?». Есть NASM и FASM – главная надежда мира ассемблерщиков. Однако и теперь ML всё ещё выигрывает у них по удобству эксплуатации, большей частью видимо благодаря Хатчу, и многим замечательным людям, поддерживающим MASM32. Кто знает, может после этой статьи кто-то воскликнет: «Я знаю, какой должен быть компилятор мечты асмовцев!». И напишет новый компилятор. (Автор шутит ?)

Уверен, что программисты из MS вряд ли прочтут эту статью (они плохо знакомы с русским), и оно к лучшему. Возможно, такая статья могла бы их огорчить, а я не люблю портить настроение людям, трудами которых пользуюсь. (Снова шутит, только про что?)

И наконец-то мне в свою очередь хочется порадоваться, что многие вопросы по макросам в MASM закрыты на долгое время, во всяком случае, для русскоязычной аудитории. (Шутит, или нет? Гм…)

I.3. Особенности терминологии

Терминология этой статьи различается от терминологии принятой в MASM.

В частности автором было предложено называть:

    MacroConstant    EQU  123          ;; Числовая макроконстанта
    MacroVar         =  123            ;; Числовая макропеременная
    MacroText        EQU  <string>     ;; строковая макропеременная
    MacroText        TEXTEQU <string>  ;; строковая макропеременная

В MASM:

    MacroConstant    EQU  123           ;; numeric equates
    MacroVar         =  123             ;; numeric equates
    MacroText         EQU  <string>     ;; text macro
    MacroText        TEXTEQU <string>   ;; text macro 

Можно было бы попросту выбрать терминологию MASM, однако последняя не позволяет объяснять материал систематически. То есть все четыре вида выражений – по сути, являются переменными или константами. Однако в терминологии MASM два последних определения называются текстовыми макро, подчёркивая их связь с макросами.

Если пойти этим путём, то тогда и первые два определения – являются упрощёнными определениями макро. Если разработчики желали подчеркнуть, что сама суть внутренней реализации ML представляет текстовые макросы как макро, то тогда не ясны те все эффекты функциональности, обсуждаемые в этой статье.

Что имеет ввиду автор?
Посмотрите что такое макроопределение – это некий текст, который как бы «вставляется» препроцессором в исходный текст программы в месте вызова макро.
А что такое в терминологии MASM numeric equates, или text macro – это некоторые переменные, значения которых «подставляются» в исходный текст программы во время компиляции вместо имён этих переменных.
Таким образом, можно сказать, что определения представленные выше – макро, но в упрощённом их виде.

Этот спор не решаем, что не так и важно. Поэтому автор отдаёт предпочтение двум терминам для «text macro»: «текстовой макро» и «строковая макропеременная».

Понятие: «numeric equates» является общим для первых двух случаев, и разрывает смысловую связь с двумя последними определениями. Поэтому я пользуюсь своим вариантом терминологии, который подчёркивает, что определения:

    MacroConstant    EQU  123       ;; Числовая макроконстанта
    MacroVar         =  123         ;; Числовая макропеременная

являются подобными макро. А, кроме того, первое из низ – константа, а второе – переменная.

I.4. Благодарности

Не могу не написать этот пункт, ибо не только автору обязана эта статья.

Она обязана замечательной версии Win98 с инсталляцией от 2000, которая отформатировала весь мой винчестер, и унесла в небытие первый вариант настоящей статьи. :)

Не малая заслуга в вопросе терминологии MASM, и его разрешении принадлежит Four-F, который как он сам мне признался, съел на макросах собаку, при чём без соли :).

Когда я думаю, чтобы было бы без самого Маниакального редактора в Inet, CyberManiacа, то понимаю: без его правок мои статьи приводили бы в ужас, и лишали разума всех морально неустойчивых читателей. CyberManiac: «Только такой замечательный безумец как ты может выдержать ЭТО!!!» :).

FatMoon, Rustam, The Svin – вы дали понять мне то, что такая статья действительно нужна, и это, наверное, самое главное. Вряд ли я бы так долго работал над ней, если бы меня никто не подталкивал.

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

С уважением, Edmond/HI-TECH

II. Лень – двигатель Макро

Когда говорят, что лень – это двигатель прогресса, видимо лицемерят или преувеличивают. Скорее это нежелание выполнять одну и ту же работу очень часто. Первая парадигма к созданию макро звучит так:

Если есть что-то похожее, что нужно делать очень часто, я могу оформить это как макроопределение.

Ассемблер, дающий программисту полную свободу в использовании методик программирования, совершенно лишает его средств для выражения этих методик. Например, ООП. В MASM32 нет классов, конструкторов и других механизмов, поддерживающих эту абстракцию. Зато вместо ООП Вы можете придумать множество других методик и абстракций (как, например модель серверов).

Та или иная методика программирования обязательно состоит из каких-либо компонентов, которые являются подобными друг другу. Например, следующие макро очень любимы в примерах пакета MASM32:

      m2m MACRO M1, M2
            push M2
            pop  M1
          ENDM

          return MACRO arg
            mov eax, arg
            ret
          ENDM

Предположим, что кому-то так надоело писать:

            push переменная2
            pop  переменная1

И он решил придумать макро для этого. Эта пара команд осуществляет пересылку данных из одной ячейки памяти в другую. То есть теперь в программе, когда вы захотите написать push/pop, вы можете заменить это некой m2m операнд1, операнд2. Посмотрите на эти два участка кода:

    mov wc.cbWndExtra,     NULL
    m2m wc.hInstance,      hInst   
    mov wc.hbrBackground,  COLOR_BTNFACE+1
    -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    mov wc.cbWndExtra,     NULL
    push hInst   
    pop wc.hInstance,      
    mov wc.hbrBackground,  COLOR_BTNFACE+1

Первый вариант не только занимает меньше строк (что тоже важно), но и намного понятнее, чем push/pop (если вы, знаете что такое m2m). Конечно, если говорить о макро m2m, то он имеет и очень важный недостаток.

Мощь макро была бы сказочной, если бы MASM умел следить за кодом, или ему можно было бы указать, что, например, сейчас регистр ebx == 0, или eax никем не используется. Хотя мы попробуем достичь подобного эффекта самостоятельно.

Этот недостаток потеря контроля над оптимальностью кода. Например, более быстрыми, по сравнению с парой команд push/pop, являются mov eax,… Употребляя макро m2m, вы получаете худший код, если стремитесь оптимизировать по скорости. И здесь есть две стороны проектирования кода:

  1. Эффективность кода
  2. Совершенство стилистики

Используя макро m2m, вы повышаете уровень стилистики, так как сокращаете время на понимание исходного кода (вами же или другим программистом). Однако с другой стороны вы теряете эффективность.

Это одна из вечных задач архитектора – найти баланс между эффективностью в коде и совершенством стилистики.

Другая парадигма использования макро звучит так:

Если, объединяя что-то в одно целое, я улучшаю стиль кода – это можно сделать в виде макроопределения.

Эта парадигма отличается от предыдущей тем, что создание макроопределения обуславливается только улучшением стилизации кода, и не имеет особой практической ценности. Например, я определил такие макро для определения кода начала и конца в главном модуле программы:

    $$$WIN32START  macro
    PUBLIC l$_ExitProgram
    _start:                       
     xor ebx,ebx
       endm
                                                       
    $$$WIN32END  macro
    l$_ExitProgram:              
     push $$$__null
     call ExitProcess
       end _start
       
       endm

В этих макро нет по сути никакой пользы, кроме эстетической. Зато, глядя на код, можно сразу понять, что это не что иное, как начало программы нечто вроде main() в C++.

И последняя парадигма использования макро:

Если ты используешь технологию программирования – попытайся заключить её в комплекс макроопределений.

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

Наиболее важная часть использования макро. Посмотрите, например, файл Objects.INC из пакета MASM32 в папке oop (NaN & Thomas).

Мы начнём создание первых макро со следующей задачи.

Наверное, вы знаете, что EXE приложения всегда могут загружаться по адресу равному:

    PROGRAM_IMAGE_BASE EQU 400000h

Во-первых, это даёт нам право убрать из приложения всю Relock секцию, тем самым, уменьшив объём образа (если эта секция нужна для систем плагинов, её можно держать отдельно).

Во-вторых мы можем более не вызывать функцию GetModuleHandle, что так же полезно для нас. Использование константы PROGRAM_IMAGE_BASE очень удобно. Однако, что будет значить это удобство, если всё-таки PROGRAM_IMAGE_BASE не определено? Это будет означать, что мы обязаны переписать весь код. А если этого кода много?

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

III. Макромир MASM

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

Пример:
Создайте небольшой модуль с именем macro.asm.
И напишите в нём несколько строчек
                            .386
                            .data
                            .code
echo Hello!!!
echo Ты должен увидеть во время компиляции

end

Так действует директива echo. С помощью неё можно подсмотреть значения переменных.

Mycount = 1
%echo @CatStr(%Mycount)

Если вы не знаете, как это работает, не волнуйтесь, обо всём будет рассказано. А пока несколько экспериментов:

Напишите:

MyMacro  macro reg

              dec    reg     
  endm

  .code

              mov eax,5
MyMacro reg
MyMacro reg

Взгляните на код программы под отладчиком. Что у вас получилось? Что будет, если вы измените текст внутри макроопределения?

Теперь напишите:

MyVar = 1

MyMacro  macro

MyVar = MyVar+1
%echo MyVar = @CatStr(%MyVar)

  endm

MyMacro
MyMacro
MyMacro
MyMacro

Каким будет вывод на экран во время компиляции?

С этого момента вам придётся различать в ассемблере ML две подсистемы: препроцессор и компилятор. Если компилятор переводит код мнемоник в машинный код, вычисляет значения меток и смещений, то препроцессор занимается вычислением выражений этапа компиляции, и что самое важное – процессом раскрытия макросов.

Подобно многим объектам мира программирования макро имеет два состояния в исходном тексте: определение, и использование.

Таким образом, мы будем иметь дело с определением макроса (макроопределением), и его вызовом (использованием макроса).

Макроопределением называется любой текст, заключённый между ключевыми словами:

    MacroName  macro paramlist
    макроопределение
       endm

При каждом вызове макро, а именно:

    …
    MacroName
    или
      mov eax, MacroName()
    …

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

Каждый раз, когда препроцессор встречает макроопределение, он помещает его имя в специальную таблицу, и копирует его тело к себе в память (это не обязательно именно так, но вам должна быть понятна суть). Встретив макроопределение, препроцессор не проверяет, а есть ли макро с таким же именем. Это значит, что макро можно переопределять.

    MyMacro         macro
    echo Это макро 1
                    endm

    MyMacro         macro
    echo Это макро 2
                    endm

    MyMacro

Вы можете самостоятельно удалять макроопределения, из памяти препроцессора используя директиву PURGE:

    PURGE macroname

После этой директивы макро с именем macroname перестаёт существовать. И вы освобождаете память для компилятора.

Конечно же, использование макро было бы бесполезным, если бы макро не имел формальных параметров. При вызове макро, препроцессор заменяет все имена формальных параметров их непосредственными значениями в теле макроопределения. Список формальных параметров разделяется запятой, и может иметь вид:

    MyMacro macro param0, param1:REQ, param2 := <0>,param3:VARARG

Здесь: Param0 – пример определения параметра.
Param1:REQ – ключевое слово REQ указывает на то, что этот параметр обязательный. То есть, если он не будет указан, вы получите ошибку этапа компиляции.
Param2:=<0> – пример параметра, который имеет значение по умолчанию. То есть если этот параметр не будет указан при вызове макро, он будет равен этому значению.

Заметьте, что при вызове макро параметр может быть не определён:

      MyMacro param1,,param3

Значение второго параметра неопределенно.

Param3:vararg – становится именем параметра, который воспринимает всё остальное как строку. При этом запятые между параметрами так же попадают в строку, а значит число параметров макроса в принципе неограниченно.

Ограничениям являются особенности архитектуры компилятора. Так, например, компилятор имеет ограничение на длину логической строки, которая равна 512 байтам.

Конечно же, после параметра с директивой vararg не возможно объявить другие параметры.

Обратите внимание, что если при определении формального параметра в макро нет директивы – он считается необязательным параметром. Более подробно о вызове макро и значении параметров я расскажу далее.


Пример:
Так что же происходит с формальными параметрами?
Посмотрите, как работает препроцессор ML:


       MyMacro     macro param1,param2

              mov    eax, param1
              mov    ebx, param2

                   endm

       MyMacro var, 123
    
      

1. Препроцессор берёт текст внутри макро, и заменяет в нём все
слова param1, param2, на их значения:
«
mov eax, var
mov ebx, 123
»

2. Полученный текст вставляет на место вызова макро, и передаёт компилятору.

Вот интересно, а что будет если:


MyMacro  macro param1,param2


          MyMacro2   macro  param1
              mov    eax, param1
              mov    ebx, param2
                     endm
         
         endm

MyMacro var, 123
      

Можно различать два вида макро – макропроцедуры и макрофункции.

В официальном руководстве MASM различается четыре основных вида макро.
Text macros – текстовый макрос
Macro procedures – макро-процедура
Repeat blocks – блок повторения
Macro functions – макро-функция
Однако автор считает, что разделение макро на два вида – лучше систематизирует материал, и отражает суть темы.

Макрофункции в отличие от макропроцедур могут возвращать результат, и получают список формальных параметров в скобках, подобно функциям в С. Например:

             mov     eax,@GetModuleHandle()

Заметьте, что к макрофункции невозможно обратится как к макро, вы всегда должны заключать формальные параметры макрофункции между «()», иначе MASM не будет распознавать её как макрофункцию:

             mov     eax,@GetModuleHandle
     error A2148: invalid symbol type in expression
     : @GetModuleHandle

Препроцессор MASM анализирует текст макроопределения на наличие директивы exitm, и помечает макрос как макрофункцию.

Ключевое слово exitm <retval>, аналогично оператору return в C++, выполнение макро заканчивается, и возвращается необязательный параметр retval. Этот параметр – строка, которую должен вернуть макрос.

Таким образом, окончательно будем считать, что макро, не имеющие в себе вызова директивы exitm – это макропроцедуры, а макро, которые имеют exitm – это макрофункции.

    ;#######################################################
    @GetModuleHandle   macro
    Invoke GetModuleHandle,0
         exitm 
         endm
       .code
    ; Это макрофункция так нельзя
    @GetModuleHandle ;;– ошибка
    ; Так можно
    @GetModuleHandle()
    ;########################################################
    @GetModuleHandle   macro
    Invoke GetModuleHandle,0
         endm
       .code
    ; Это макрос. Так правильно
    @GetModuleHandle
    ; Так можно, но всё равно это вызывает ошибку ?
    ; warning A4006: too many arguments in macro call
    @GetModuleHandle()
    ; Это макро, а не макрофункция так нельзя!!!
      mov eax,@GetModuleHandle
    ; И так нельзя
      mov eax,@GetModuleHandle()
    ;########################################################  

Что касается директивы endm, которая заканчивает каждое макроопределение, в руководстве написано, что при помощи неё так же можно указать возвращаемый параметр:
endm <retvalue>
Однако на практике это не так. ? Очень странно, хотя об этом чётко написано в руководстве.

Заметьте, что макропроцедура может быть вызвана только в начале строки:

    @GetModuleHandle
    ;; Но не так:
    mov eax,@MyMacro

Макрофункция может быть вызвана в любых выражениях:

    ;; Так:
    mov eax,@GetModuleHandle()
    ;; И так:
    @FunMacro()
    ;; И так:
    @GetModuleHandle() EQU eax

III.1. Функционирование макросов

Чтобы строить макросы, важно понимать, как они работают, и как их обрабатывает MASM. Давайте рассмотрим типичный макро, и этапы его обработки.

    MyMacro macro param1,param2,param3:VARARG
    echo param1
    echo param2
    echo param3
          endm 


    MyMacro Параметр 1, Параметр 2, Параметр 3, Параметр 4
    ;; Вывод -=-=-=-=-=-=-=-=
    Параметр 1
    Параметр 2
    Параметр 3,Параметр 4

1. Компилятор встречает лексему MyMacro

2. Он проверяет, содержится ли эта лексема в словаре ключевых слов

3. Если нет, то он проверяет, содержится ли эта лексема в списке макросов.

4. Если да, он передаёт текст, содержащийся в макро препроцессору. Препроцессор заменяет все вхождения формальных параметров в этом тексте на их значения. В данном случае мы имеем:

    echo Параметр 1
    echo Параметр 2
    echo Параметр 3,Параметр 4

5. Препроцессор возвращает компилятору обработанный текст, который после компилируется.

Обратите внимание на пункт 4 и 5. Они ключевые. Очень часто при работе с макроопределениями появляются ошибки из-за неверного понимания порядка генерирования макро текста. Например:

    PROGRAM_IMAGE_BASE EQU 400000h
    FunMacro macro
      exitm <Параметр 3,параметр 4>
          endm

    MyMacro macro param1,param2,param3:VARARG
    echo param1
    echo param2
    echo param3
          endm 

    MyMacro PROGRAM_IMAGE_BASE, FunMacro(),Параметр 5

А теперь самостоятельно опишите порядок действий компилятора при вызове этого макро. Запишите его себе куда-нибудь, так чтобы сравнить, и смотрите на вывод:

    PROGRAM_IMAGE_BASE
    Параметр 3, Параметр 4
    Параметр 5

Прежде чем объяснять действительный порядок, я оговорюсь, что директива echo никогда не обрабатывает определённые константы, такие как PROGRAM_IMAGE_BASE.

Это утверждение справедливо даже тогда, когда перед директивой echo стоит оператор %, который может раскрывать только текстовые макроопределения. То есть выражение:

    echo FunMacro()

Даст результат:

    FunMacro()

Теперь, когда мы немного порассуждали можно привести тот текст, который генерируется из макро:

    echo PROGRAM_IMAGE_BASE
    echo Параметр 3, Параметр 4
    echo Параметр 5

Это означает следующее:

  1. При вызове макро, значение формальных параметров воспринимается как текст, и передаётся в макро как строка.
  2. Исключение составляют лишь макрофункции, результат выполнения которых вычисляется и присваивается значению параметра.

Специальный оператор % заставляет ассемблер вычислять текстовую строку, следующую за ним, и только потом подставлять в правое выражение. Например, если мы перепишем макровызов так:

    MyMacro %PROGRAM_IMAGE_BASE, FunMacro,Параметр 5

То получим вывод:

    4194304   ;; Значение PROGRAM_IMAGE_BASE
    Параметр 3, Параметр 4
    Параметр 5

Давайте рассмотрим ещё один пример, который хорошо показывает, как работает макро. Например, вы определили макропроцедуру (именно его, а не макрофункцию). То когда вы пишите такое:

    @Macro что-то, что придёт вам в голову [символ возврата каретки]

Что делает препроцессор ML:

1. Считывает всю строку до символа возврата каретки;

2. Смотрит, как вы определили параметры в макро;

3. Сканирует строку на наличие символа «,» или «<», «>»;

Вам может показаться странным, но препроцессору всё равно, какие символы идут во время вызова макро. То есть вы можете вызвать макро так:

@MyMacro Привет, это кириллица в файле,\
 и ML не будет на неё ругаться
или
@MyMacro !@#$%^&*(){}[]

Посмотрите как СИльно (от буквы ) будет выглядеть макро в MASM:

MyMacro{Это что С++?}
MyMacro[Нет, это MASM]

4. Назначает формальным параметрам (любого типа, кроме VARARG) макро участки строк, которые были определены разделителями запятыми (предварительно очистив от хвостовых и начальных пробелов, если только строка не была определена в угловых кавычках <>);

5. Если макро содержит формальный параметр типа VARARG, то ML сперва инициализирует значениями (согласно пункту 4) обычные формальные параметры, и только потом назначает параметру типа VARARG (который может быть только один в конце списка параметров) всю строку до конца.

-= Обратите внимание =-
Если вы пишите макровызов как
@Macro Param1 , Param2
То значение параметров будут:
param1 = «Param1»
param2 = «Param2»
Если вы хотите передать сами значения строк, то должны заключит их в угловые кавычки:
@Macro < Param1 >,< Param2 >

6. Препроцессор разрешает все вызовы макрофункций, если они есть в лексемах параметра, и присваивает их результат соответствующему параметру. Если лексему в строке параметра предваряет символ %, то он вычисляет её значение до того, как передаст строку внутрь макро.

-= Обратите внимание =-

Благодаря именно такому порядку:
1. Разделение строки на макропараметры
2. Поиск и Вызов макрофункций в значениях макропараметров
3. Присвоение результатов соответствующему макропараметру

в следующем случае:


MyMacro  macro param1,param2,param3
echo param1
         endm
 
--------------------------------------
FunMacro     macro param:VARARG
  exitm param
             endm


MyMacro FunMacro(param1, param2, param3)


OUT:
param1, param2, param3
--------------------------------------  
  


строка, возращаемая макрофункцией присваивается параметру param1, а не param2, param3

Теперь вы в состоянии объяснить следующую ситуацию:

    MyMacro    macro 
    …
               endm


    MyMacro()


    Предупреждение при компиляции:
    : warning A4006: too many arguments in macro call

Как нужно было бы изменить этот макро (именно макро, а не макрофункцию), чтобы предупреждение не выдавалось? А почему оно происходит?

Если вы с лёгкостью ответили на этот вопрос, значит, материал усвоен, иначе советую ещё раз прочитать его, и ответить на следующий вопрос.

Как должен понять компилятор следующий код:

    MyMacro     macro param1
    param1
                endm 

    MyMacro = 2  

Естественно отвечать на этот вопрос вы должны без помощи компилятора (то есть проверить компиляцией). Если вы не можете ответить на этот вопрос, или неуверенны в верности ответа, я поменяю задание:

    MyMacro     macro param1
    echo param1
                endm 

    MyMacro = 2  

Запустите его в ML. Если и теперь вы сомневаетесь – перечитайте этот пункт снова и снова, продолжая экспериментировать.

#Послесловие