Макросы могут иметь аргумент. Аргумент представляет собой какой-либо идентификатор, который будет повсюду заменён в теле макроса тем, что будет указанно при использовании.
Синтаксис:
macro name argument { тело макроса }
Например:
macro add5 where
{
add where, 5
}
add5 ax
add5 [variable]
add5 ds
add5 ds+2
получим:
add ax, 5
add [variable], 5
add ds, 5 ;такой инструкции не существует
;но препроцессор это не волнует.
;ошибка появится на стадии ассемблирования.
add ds+2,5 ;ошибка синтаксиса, как и ранее
;определится при анализе синтаксиса (parsing).
(разумеется, комментарии в результате работы препроцессора не появятся:)
У макросов может быть несколько аргументов, разделённых запятыми,
macro movv where, what
{
push what
pop where
}
movv ax, bx
movv ds, es
movv [var1], [var2]
преобразуется в:
push bx
pop ax
push es
pop ds
push [var2]
pop [var1]
Если несколько аргументов имеют одно и тоже имя, то будет использован первый из них:).
Если при использовании макроса указать меньше аргументов, чем при определении, то значения неуказанных будет пустым:
macro pupush a1, a2, a3, a4
{
push a1 a2 a3 a4
pop a4 a3 a2 a1
}
pupush eax, dword [3]
получим:
push eax dword [3]
pop dword [3] eax
Если в аргументе макроса необходимо указать запятую, необходимо аргумент заключить в скобочки из символов
<
и >
.
macro safe_declare name, what
{
if used name
name what
end if}
safe_declare var1, db 5
safe_declare array5,
safe_declare string,
получим:
if used var1
var1 db 5
end if
if used array5
array5 dd 1,2,3,4,5
end if
if used string
string db "привет, я просто строка",0
end if
Конечно же, можно использовать символы
<
и >
и внутри тела макроса:
macro a arg {db arg}
macro b arg1,arg2 {a }
b <1,1>,2
получим:
db 1,1,2,3
Возможно, появится необходимость объявить метку внутри тела макроса:
macro pushstr string
{
call behind ; помещаем в стек адрес string и переходим к behind
db string, 0
behind:
}
но если использовать такой макрос 2 раза, то и метка
behind
будет объявлена дважды, что приведёт к ошибке. Эта проблема решается объявлением локальной метки behind
. Это и делает директива LOCAL
.
Синтаксис:
local label_name
Директива должна применяться внутри тела макроса. Все метки label_name внутри макроса становятся локальными. Так что, если макрос используется дважды никаких проблем не появляется:
macro pushstr string
{
local behind
call behind
db string,0
behind:
}
pushstr 'aaaaa'
pushstr 'bbbbbbbb'
call something
На самом деле, behind заменяется на
behind?XXXXXXXX
, где XXXXXXXX
— какой-то шестнадцатеричный номер генерируемый препроцессором. Последний пример может быть преобразован к чему-то вроде:
call behind?00000001
db 'aaaaa', 0
behind?00000001:
call behind?00000002
db 'bbbbbbbb', 0
behind?00000002:
call something
Заметьте, Вы не сможете напрямую обратиться к метке содержащей
?
так как это специальный символ в FASM, поэтому он и используется в локальных метках. К примеру, aa?bb
рассматривается как идентификатор aa
, специальный символ ?
и идентификатор bb
.
Если Вам нужно несколько локальных меток — не проблема, их можно указать в одной директиве
LOCAL
, разделив запятыми ,
:
macro pushstr string ; делает то же, что и предыдущий макрос
{
local addr, behind
push addr
jmp behind
addr db string,0
behind:
}
Всегда хорошо бы начинать все локальные метки макросов с двух точек
..
— это значит, что они не будут менять текущую глобальную метку. К примеру:
macro pushstr string
{
local behind
call behind
db string, 0
behind:
}
MyProc:
pushstr 'aaaa'
.a:
будет преобразовано в:
MyProc:
call behind?00000001
db 'aaaa', 0
behind?00000001:
.a:
в результате получим метку
behind?00000001.a
вместо MyProc.a
. Но если в примере выше behind
заменить на ..behind
, текущая глобальная метка не изменится и будет определена метка MyProc.a
:
macro pushstr string
{
local ..behind
call ..behind
db string,0
..behind:
}
MyProc:
pushstr 'aaaa'
.a:
У макроязыка FASMа есть ещё одна возможность — манипуляции с идентификаторами. Делается это оператором
#
, который объединяет два идентификатора в один. К примеру, a#b
становится ab
, а aaa bbb#ccc ddd
— aaa bbbccc ddd
.
Оператор
#
может быть использован только внутри тел макросов, а объединение символов происходит после замены аргументов макроса параметрами. Так что его можно использовать для создания новых идентификаторов из переданных в макрос параметров:
macro string name, data
{
local ..start
..start:
name db data,0
sizeof.#name = $ —..start
}
string s1,'нудные макросы'
string s2,<'а вот и я',13,10,'заставлю тебя их видеть во сне'>
получим:
..start?00000001:
s1 db 'нудные макросы',0
sizeof.s1 = $ —..start?00000001
..start?00000002:
s2 db 'а вот и я',13,10,'заставлю тебя их видеть во сне',0
sizeof.s2 = $ —..start?00000002
так что для всех строк, создаваемых этим макросом будет определён идентификатор
sizeof.имя строки
, равный количеству байт строки.
Оператор
#
способен так же объединять символьные строки:
macro debug name
{
db 'name: '#b,0
}
debug '1'
debug 'foobar'
будет:
db 'name: 1',0
db 'name: foobar',0
Это полезно при передаче аргументов из макроса в макрос:
macro pushstring string
{
local ..behind
call ..behind
db string,0
..behind:}
macro debug string
{
push MB_OK
push 0 ;empty caption
pushstring 'debug: '#string ;принимает один аргумент
push 0 ;нет окна-предка
call [MessageBox]
}
Обратите внимание, нельзя использовать
#
совместно с идентификаторами, определёнными local
, так как local
обрабатывается препроцессором раньше, чем #
. Из-за этого подобный код работать не будет:
macro a arg
{
local name_#arg
}
a foo
Существует оператор, преобразующий идентификатор в символьную строку. Он так же может быть использован только внутри макросов:
macro proc name
{
name:
log `name ;log - макрос, принимающий параметр-строку
}
proc DummyProc
получим:
DummyProc:
log 'DummyProc'
Пример посложнее, с использованием
#
macro proc name
{
name:
log 'начинается подпрограмма: '#`name
}
proc DummyProc
retn
proc Proc2
retn
будет:
DummyProc:
log 'начинается подпрограмма: DummyProc'
retn
Proc2:
log 'начинается подпрограмма: Proc2'
retn