Chapter 9
|
Using Macros |
A macro is a symbolic name you give to a series of characters (a text macro) or to one or more statements (a macro procedure or function). As the assembler evaluates each line of your program, it scans the source code for names of previously defined macros. When it finds one, it substitutes the macro text for the macro name. In this way, you can avoid writing the same code several places in your program.
This chapter describes the following types of macros:
Text macros, which expand to text within a source statement.
Macro procedures, which expand to one or more complete statements and can optionally take parameters.
Repeat blocks, which generate a group of statements a specified number of times or until a specified condition becomes true.
Macro functions, which look like macro procedures and can be used like text macros but which also return a value.
Predefined macro functions and string directives, which perform string
operations.
This chapter explains how to use macros for simple code substitutions and how to write sophisticated macros with parameter lists and repeat loops. It also describes how to use these features in conjunction with local symbols, macro operators, and predefined macro functions.
|
|
You can give a sequence of characters a symbolic name and then use the name in place of the text later in the source code. The named text is called a text macro.
The TEXTEQU directive defines a text macro, as these examples show:
name TEXTEQU <text>
name TEXTEQU macroId | textmacro
name TEXTEQU %constExpr
In the previous lines, text is a sequence of characters enclosed in angle brackets, macroId is a previously defined macro function, textmacro is a previously defined text macro, and %constExpr is an expression that evaluates to text.
Here are some examples:
msg TEXTEQU <Some text> ; Text assigned to symbol
string TEXTEQU msg ; Text macro assigned to symbol
msg TEXTEQU <Some other text> ; New text assigned to symbol
value TEXTEQU %(3 + num) ; Text representation of resolved
; expression assigned to symbol
The first line assigns text to the symbol msg. The second line equates the text of the msg text macro with a new text macro called string. The third line assigns new text to msg. Although msg has new text, string retains its original text value. The fourth line assigns 7 to value if num equals 4. If a text macro expands to another text macro (or macro function, as discussed on page 248), the resulting text macro will expand recursively.
Text macros are useful for naming strings of text that do not evaluate to integers. For example, you might use a text macro to name a floating-point constant or a bracketed expression. Here are some practical examples:
pi TEXTEQU <3.1416> ; Floating point constant
WPT TEXTEQU <WORD PTR> ; Sequence of key words
arg1 TEXTEQU <[bp+4]> ; Bracketed expression
If your program must perform the same task many times, you can avoid repeatedly typing the same statements each time by writing a macro procedure. Think of macro procedures (commonly called macros) as text-processing mechanisms that automatically generate repeated text.
This section uses the term macro procedure rather than macro when necessary to distinguish between a macro procedure and a macro function. Macro functions are described in Returning Values with Macro Functions.
Conforming to common usage, this chapter occasionally speaks of calling a macro, a term that deserves further scrutiny. Its natural to think of a program calling a macro procedure in the same way it calls a normal subroutine procedure, because they seem to perform identically. However, a macro is simply a representative for real code. Wherever a macro name appears in your program, so in reality does all the code the macro represents. A macro does not cause the processor to vector off to a new location as does a normal procedure. Thus, the expression calling a macro may imply the effect, but does not accurately describe what actually occurs.
You can define a macro procedure without parameters by placing the desired statements between the MACRO and ENDM directives:
name MACRO
statements
ENDM
For example, suppose you want a program to beep when it encounters certain errors. You could define a beep macro as follows:
beep MACRO
mov ah, 2 ;; Select DOS Print Char function
mov dl, 7 ;; Select ASCII 7 (bell)
int 21h ;; Call DOS
ENDM
The double semicolons mark the beginning of macro comments. Macro comments appear in a listing file only at the macros initial definition, not at the point where the macro is referenced and expanded. Listings are usually easier to read if the comments arent repeatedly expanded. However, regular comments (those with a single semicolon) are listed in macro expansions. See Appendix C for listing files and examples of how macros are expanded in listings.
Once you define a macro, you can call it anywhere in the program by using the macros name as a statement. The following example calls the beep macro two times if an error flag has been set.
.IF error ; If error flag is true
beep ; execute macro two times
beep
.ENDIF
|
|
During assembly, the instructions in the macro replace the macro reference. The listing file shows:
.IF error
0017 80 3E 0000 R 00 * cmp error, 000h
001C 74 0C * je @C0001
beep
001E B4 02 1 mov ah, 2
0020 B2 07 1 mov dl, 7
0022 CD 21 1 int 21h
beep
0024 B4 02 1 mov ah, 2
0026 B2 07 1 mov dl, 7
0028 CD 21 1 int 21h
.ENDIF
002A *@C0001:
Contrast this with the results of defining beep as a procedure using the PROC directive and then calling it with the CALL instruction.
Many such tasks can be handled as either a macro or a procedure. In deciding which method to use, you must choose between speed and size. For repetitive tasks, a procedure produces smaller code, because the instructions physically appear only once in the assembled program. However, each call to the procedure involves the additional overhead of a CALL and RET instruction. Macros do not require a change in program flow and so execute faster, but generate the same code multiple times rather than just once.
By defining parameters for macros, you can define a general task and then execute variations of it by passing different arguments each time you call the macro. The complete syntax for a macro procedure includes a parameter list:
name MACRO parameterlist
statements
ENDM
The parameterlist can contain any number of parameters. Use commas to separate each parameter in the list. You cannot use reserved words as parameter names unless you disable the keyword with OPTION NOKEYWORD. You must also set the compatibility mode with OPTION M510 or the /Zm command-line option.
To pass arguments to a macro, place the arguments after the macro name when you call the macro:
macroname arglist
The assembler treats as one item all text between matching quotation marks in an arglist.
The beep macro introduced in the previous section used the MS-DOS interrupt to write only the bell character (ASCII 7). We can rewrite the macro with a parameter that accepts any character:
writechar MACRO char
mov ah, 2 ;; Select DOS Print Char function
mov dl, char ;; Select ASCII char
int 21h ;; Call DOS
ENDM
Whenever it expands the macro, the assembler replaces each instance of char with the given argument value. The rewritten macro now writes any character to the screen, not just ASCII 7:
writechar 7 ; Causes computer to beep
writechar A ; Writes A to screen
If you pass more arguments than there are parameters, the additional arguments generate a warning (unless you use the VARARG keyword; see page 242). If you pass fewer arguments than the macro procedure expects, the assembler assigns empty strings to the remaining parameters (unless you have specified default values). This may cause errors. For example, a reference to the writechar macro with no argument results in the following line:
mov dl,
The assembler generates an error for the expanded statement but not for the macro definition or the macro call.
You can make macros more flexible by leaving off arguments or adding additional arguments. The next section tells some of the ways your macros can handle missing or extra arguments.
Macro parameters can have special attributes to make them more flexible and improve error handling. You can make parameters required, give them default values, or vary their number. Variable parameters are used almost exclusively with the FOR directive, so are covered in FOR Loops and Variable-Length Parameters, later in this chapter.
|
|
The syntax for a required parameter is:
parameter:REQ
For example, you can rewrite the writechar macro to require the char
parameter:
writechar MACRO char:REQ
mov ah, 2 ;; Select DOS Print Char function
mov dl, char ;; Select ASCII char
int 21h ;; Call DOS
ENDM
If the call does not include a matching argument, the assembler reports the error in the line that contains the macro reference. REQ can thus improve error reporting.
You can also accommodate missing parameters by specifying a default value, like this:
parameter:=textvalue
Suppose that you often use writechar to beep by printing ASCII 7. The following macro definition uses an equal sign to tell the assembler to assume the parameter char is 7 unless you specify otherwise:
writechar MACRO char:=<7>
mov ah, 2 ;; Select DOS Print Char function
mov dl, char ;; Select ASCII char
int 21h ;; Call DOS
ENDM
If a reference to this macro does not include the argument char, the assembler fills in the blank with the default value of 7 and the macro beeps when called.
Enclose the default parameter value in angle brackets so the assembler recognizes the supplied value as a text value. This is explained in detail in Text Delimiters and the Literal-Character Operator, later in this chapter.
Missing arguments can also be handled with the IFB, IFNB, .ERRB, and .ERRNB directives. They are described in the section Conditional Directives in chapter 1 and in Help. Here is a slightly more complex macro that uses some of these techniques:
Scroll MACRO distance:REQ, attrib:=<7>, tcol, trow, bcol, brow
IFNB <tcol> ;; Ignore arguments if blank
mov cl, tcol
ENDIF
IFNB <trow>
mov ch, trow
ENDIF
IFNB <bcol>
mov dl, bcol
ENDIF
IFNB <brow>
mov dh, brow
ENDIF
IFDIFI <attrib>, <bh> ;; Dont move BH onto itself
mov bh, attrib
ENDIF
IF distance LE 0 ;; Negative scrolls up, positive down
mov ax, 0600h + (-(distance) AND 0FFh)
ELSE
mov ax, 0700h + (distance AND 0FFh)
ENDIF
int 10h
ENDM
In this macro, the distance parameter is required. The attrib parameter has a default value of 7 (white on black), but the macro also tests to make sure the corresponding argument isnt BH, since it would be inefficient (though legal) to load a register onto itself. The IFNB directive is used to test for blank arguments. These are ignored to allow the user to manipulate rows and columns directly in registers CX and DX at run time.
The following shows two valid ways to call the macro:
; Assume DL and CL already loaded
dec dh ; Decrement top row
inc ch ; Increment bottom row
Scroll -3 ; Scroll white on black dynamic
; window up three lines
Scroll 5, 17h, 2, 2, 14, 12 ; Scroll white on blue constant
; window down five lines
This macro can generate completely different code, depending on its arguments. In this sense, it is not comparable to a procedure, which always has the same code regardless of arguments.
|
|
You can make a symbol local to a macro by identifying it at the start of the macro with the LOCAL directive. Any identifier may be declared local.
You can choose whether you want numeric equates and text macros to be local or global. If a symbol will be used only inside a particular macro, you can declare it local so that the name will be available for other declarations outside the macro.
You must declare as local any labels within a macro, since a label can occur only once in the source. The LOCAL directive makes a special instance of the label each time the macro appears. This prevents redefinition of the label when expanding the macro. It also allows you to reuse the label elsewhere in your code.
You must declare all local symbols immediately following the MACRO statement (although blank lines and comments may precede the local symbol). Separate each symbol with a comma. You can attach comments to the LOCAL statement and list multiple LOCAL statements in the macro. Here is an example macro that declares local labels:
power MACRO factor:REQ, exponent:REQ
LOCAL again, gotzero ;; Local symbols
sub dx, dx ;; Clear top
mov ax, 1 ;; Multiply by one on first loop
mov cx, exponent ;; Load count
jcxz gotzero ;; Done if zero exponent
mov bx, factor ;; Load factor
again:
mul bx ;; Multiply factor times exponent
loop again ;; Result in AX
gotzero:
ENDM
If the labels again and gotzero were not declared local, the macro would work the first time it is called, but it would generate redefinition errors on subsequent calls. MASM implements local labels by generating different names for them each time the macro is called. You can see this in listing files. The labels in the power macro might be expanded to ??0000 and ??0001 on the first call and to ??0002 and ??0003 on the second.
|
|
You should avoid using anonymous labels in macros (see Anonymous Labels in Chapter 7). Although legal, they can produce unwanted results if you expand a macro near another anonymous label. For example, consider what happens in the following:
Update MACRO arg1
@@: .
.
.
loop @B
ENDM
.
.
.
jcxz @F
Update ax
@@:
Expanding Update places another anonymous label between the jump and its target. The line
jcxz @F
consequently jumps to the start of the loop rather than over the loop exactly the opposite of what the programmer intended.
In writing macros, you will often assign and modify values assigned to symbols. Think of these symbols as assembly-time variables. Like memory variables, they are symbols that represent values. But since macros are processed at assembly time, any symbol modified in a macro must be resolved as a constant by the end of assembly.
The three kinds of assembly-time variables are:
Macro parameters
Text macros
Macro functions
When the assembler expands a macro, it processes the symbols in the order