
Assembly Language Step by Step 1992
.pdfmodules sharing the segments, the linker will consider all to be part of the same code and data segments.
It is also necessary to have an ASSUME statement in every module sharing segments in this fashion. Furthermore, it should be the same ASSUME state-ment as the one in the main program, with CS associated with your single code segment and DS associated with your single data segment:
ASSUME CS:MyCode,DS:MyData
This ensures that the assembler does not get confused as it puts together references to the two segments in the .OBJ files it builds.
Your Main Program Module
Below is our backhanded advertising program, which has been modified for use with an external display control module:


This is easy to forget but you must keep it in mind: the segments containing imported or exported items as well as the imported or exported items themselves must be declared as public.
Take note of the declaration of two of the variables in the data segment declared as public:
PUBLIC LRXY,CRLF
The PUBLIC directive allows external modules to use these two variables. The other variables declared in the main program, Eatl, Eat2. and TextPos, are not declared as public and are inaccessible from external modules. We would say that those three variables are private to the main program module EAT4.ASM.
EAT4.ASM contains no procedure declarations of its own. All the procedures it uses are imported from VIDLIB.ASM, and all are therefore declared as external in the code segment, using this statement:
EXTRN GotoXY:PROC.Write:PROC.Writeln:PROC,ClrScr:PROC
Something to keep in mind is that while VIDLIB.ASM exports seven procedures (seven labels, actually, since four are entry points to the ClrScr procedure) EAT4.ASM only imports four. The ClrWin, ScrlWin, and VIDEO6 entry points to procedure ClrScr are declared as public in VIDLIB.ASM, but they are not declared as external in EAT4.ASM. EAT4.ASM only uses the four it imports. The other three are available, but the EAT4.ASM does not call them and therefore does not bother declaring them as external. If you were to expand EAT4.ASM to use one of the three other entry points to ClrScr, you would have to add the entry point to the EXTRN list.
Once all the external and public declaration are in place, your machine instructions may reference procedures and variables across module boundaries as though they were all within the same large program. No special qualifiers have to be added to the instructions. This CALL ClrScr instruction is written the same way, whether ClrScr is declared in the main program module or in an external module like VIDLIB.ASM.
Linking Multiple Modules
The linker hasn't had to do much linking so far. Once you have multiple modules, however, the linker begins to earn its keep. To link multiple modules, you must specify the name of the .OBJ file for each module on the linker command line.
Up until now, the linker command line contained only the name of the main program module:
TLINK EAT3
Now you must add the names of all external modules to the linker command line:
TLINK EAT4 VIDLIB
If you're using JED, display the Commands screen by pressing F4 and edit the linker command line. For example, to use TASM to link EAT4.OBJ and VIDLIB.OBJ, the linker command line would be the following:
TLINK ~ VIDLIB
Remember that the tilde character (~) stands for the currently loaded file in JED. Pretty obviously, if you forget to name an external module on the linker command line, the linker will not be able to resolve the external references involving the missing .OBJ file, and you will get linker error messages like this one, one for each unresolved external reference:
• Undefined symbol 'CLRSCR' in module EAT4.ASM
External Module Summary
Here are some points to keep in mind when you're faced with splitting a single program up into a main program and one or more external modules:
•Declare the code segments public in all modules, and give them all the same name.
•Declare the data segments public in all modules, and give them all the same name.
•Declare all exported procedures, entry points, and variables as Public. Place the
PUBLIC directive inside the segment where the exported items are declared.
•Declare all imported procedures, entry points, and variables as external. Put the external directive inside the segment where the imported items are to be used. Data is used in the data segment, code in the code segment.
•Make sure that there is a common ASSUME statement in the code segment of every module associating the CS register with the shared code segment and the DS register with the shared data segment.
•Finally, don't forget to add the names of all external modules to the linker
command line in the link step.
If this still seems fuzzy to you, follow VIDLIB.ASM and EAT4.ASM as a model. You will certainly find it useful to beef up VIDLIB.ASM by adding more screen control procedures.
8.5 Creating and Using Macros
Procedures are the easiest way to split an assembly-language program into more manageable chunks. The mechanism for calling and returning from procedures is built right into the CPU, and is independent of any given assem-bler product.
Today's two major assemblers (Microsoft's MASM and Borland's TASM) provide another complexity-management tool that works a little differently: macros. They're hardly a minor feature; their name is built right into Microsoft's product, which after all is the Microsoft Macro Assembler.
Macros are a different breed of cat entirely. Whereas procedures are implemented by the use of CALL and RET instructions built right into the instruction set, macros are a trick of the assembler, and do not depend on any particular instruction or group of instructions. Most simply put, a macro is a label that stands for some sequence of text lines. This sequence of text lines can be (but does not have to be) a sequence of instructions. When the assembler encounters the macro label in a source code file, it replaces the macro label with the text lines that the macro label represents. This is called expanding the macro, because the name of the macro (occupying one text line) is replaced by several lines of text, which are then assembled just as though they had appeared in the source-code file all along.
Macros bear some resemblance to Include files in high-level languages like Pascal. In
Turbo Pascal, an include command might look like this:
{$1 ENGINE.DEF}
When this include command is encountered, the compiler goes out to disk and finds the file named ENGINE.DEF. It then opens the file and starts "feeding" the text contained in that file into the source-code file at the point where the include command was placed. The compiler then processes those lines as though they had always been in the source-code file.
You might think of a macro as an include file that's built right into the source-code file. It's a sequence of text lines that is defined once and given a name. The Macro can then be dropped into the source code again and again by simply using the name.
This process is shown in Figure 8.3. The source code stored on disk contains a macro
definition, bracketed between MACRO and ENDM directives. Later in the file, the name of the macro,
ClrScr, appears several times. When the assembler processes this file, it copies the macro definition into a buffer some-where in memory. As it assembles the text read from disk, the assembler "drops" the statements contained in the macro into the text wherever the macro name appears. The disk file is not affected; the expansion of the macros occurs only in memory.
Macros vs. Procedures: Pro and Con
There are advantages to using macros rather than procedures. One of them is speed. It takes time4 to execute the CALL and RET instructions that control entry to and exit from a procedure. In a macro, neither instruction is used. Only the instructions that perform the actual work of the macro are executed, so the macro's work is performed as quickly as possible.
There is a cost to this speed, and the cost is in extra memory used, especially if the macro is invoked a number of times. Notice in Figure 8.3 that

three invocations of the macro generate a total of twelve instructions in memory. If the macro had been set up as a procedure, only the four instructions in the body of the procedure, plus one RET instructions and three CALL instructions would be required to do the same work. This would give you a total of eight instructions for the procedure and twelve for the macro. Each additional time the macro was invoked, the difference would grow.
Every time a macro is invoked, all of its instructions are duplicated in the program again.
In short programs, this may not be a problem, and in situations where the code must be as fast as possible—as in graphics drivers—macros have a lot going for them.
By and large, think macros for speed and procedures for compactness.
The Mechanics of Macro Definition
A macro definition looks a lot like a procedure definition, with a slightly different pair of directives: MACRO and ENDM. One other crucial difference is that the name of the macro cannot be repeated in front of the ENDM directive. I'm not sure why this must be so, but it confuses the assembler to no end.
Don't put a RET instruction at the end of the macro! Executing a RET without a previous CALL will corrupt your stack and probably crash your program.
One important shortcoming of macros vis-a-vis procedures is that macros can have only one entry point. The ClrScr procedure described in the last section cannot be converted into a macro without splitting it up into four separate invocations of VIDEO interrupt 10H. If the ClrScr function (clearing the full screen to blanks for the normal video attribute) alone were written as a macro, it would look like this:
ClrScr |
MACRO |
; Upper left corner of full screen |
mov CX,0 |
|
|
mov DX,LRXY |
;Load lower-right XY coordinates into DX |
|
mov AL.O |
|
;0 specifies clear entire region |
mov BH,07H |
;Specify "normal" attribute for blanked line(s) |
|
mov AH,06H |
; Select VIDEO service 6: Initialize/Scroll |
|
int 10H |
|
;Call VIDEO |
ENDM
You can see that ClrScr has shed its RET instruction and its additional entry points, but apart from that it's exactly the same sequence of instructions.
Functionally it works the same way, except that every time you clear your screen, ClrScr's six instructions are dropped into the source code.
Macros are invoked simply by naming them. Don't use the CALL instruction! Just place the macro name on a line:
ClrScr
The assembler will handle the rest.
Defining Macros with Parameters
So far, macros may seem useful but perhaps not especially compelling. What makes macros really sing is their ability to mimic high-level language subroutines and accept arguments through parameters. For example, if you were to define a macro named GotoXY to position the hardware cursor, you could pass it the X and Y values as arguments:
GotoXY 17,3 ; Move the cursor to the Name field
You'd have to pinch yourself to be sure you weren't working in BASIC, no?
Macro parameters are, again, artifacts of the assembler. They are not pushed on the stack or set into COMMON or anything like that. The parameters are simply placeholders for the actual values (called arguments) that you pass to the macro.
I've converted the GotoXY procedure to a macro to show you how this works. Here's the macro:
GotoXY |
MACRO NewX,NewY |
; |
The NewY parameter loads into DH |
|
|
mov DH.NewY |
|
||
|
mov DL.NewX |
; |
; |
The NewX parameter loads into DL |
|
mov AH,02H |
Select VIDEO service 2: Position Cursor |
||
|
mov BH,O |
|
; |
Stay with display page 0 |
ENDM |
int 10H |
|
; |
Call VIDEO |
The two parameters are NewX and NewY. Parameters are a kind of label, and they may be referenced anywhere within the macro. Here, the parameters are referenced as operands to a couple of MOV instructions. The arguments passed to the macro in NewX and NewY are thus loaded into DL and DH.
Don't confuse the arguments (actual values) with the parameters. If you understand Pascal, it's exactly like the difference between formal parameters and actual parameters. A macro's parameters correspond to Pascal's formal parameters, whereas a macro's arguments correspond to Pascal's actual parameters. The macro's parameters are the labels following the MACRO directive where the macro is defined. The arguments are the values specified on the line where the macro is invoked.