
Assembly Language Step by Step 1992
.pdf
A comment header does not relieve you of the responsibility of comment-ing the
individual lines of code within the procedure. It's a good idea to put a short comment to the right of every line that contains a machine instruction mnemonic, and also (in longer procedures) a comment block describing every major functional block within the procedure.
Examine EAT3.ASM, and notice the various commenting conventions. For a very short program such as this, such elaborate internal documentation might seem overkill. Once your programs get serious, however, you'll be very glad you expended the effort.
8.4 Building External Libraries of Procedures
You'll notice that the EAT3.ASM program, listed at the end of the previous section devoted most of its bulk to procedures. This is as it should be. Notice, however, that the procedures EAT3.ASM uses are the kind you're likely to use in any and all of your assembly-language programs. When this is the case, break the utility procedures out into an external library that you can assemble only once, and then link into every program that uses its procedures without assembling the library every time you assemble the program. This is called modular programming, and it is an extremely effective tool for programming efficiently in any language, assembly language not excluded. (Keeping cursor movement and screen-clearing routines in source-code form in every single program you write is a waste of space, and can clutter up the program in a way that makes it less easy to understand.)
I described this process briefly back in Chapter 3, and showed it pictorially in Figures 3.4 and 3.5. A program might consist of three or four separate .ASM files, each of which is assembled separately to a separate .OBJ file. To produce the final executable .EXE file, the linker weaves all of the .OBJ files together, resolving all of the references from one to the other, finally creating an .EXE file.
Each .ASM file is considered a module, and each module contains one or more procedures and possibly some data definitions. When all the declarations are done correctly, all of the modules may freely call one another, and any procedure may refer to any data definition.
The trick, of course, is to get all the declarations right.
Public and External Declarations
If you reference a label in your program (by, say, including a CALL instruction to that label) without defining that label anywhere in the program, the assembler will gleefully
give you an error message. (You've probably already experi-enced this if you've begun writing your own programs in assembler.) In modular programming, you're frequently going to be calling procedures that don't exist anywhere in your program. How to get past the assembler's watchdogs?
The answer is to declare a procedure external. This works very much like it sounds: the assembler is told that a given label will have to be found outside the program somewhere, in another module. Once told that, that assembler is happy to give you a pass on an undefined label. You've promised the assembler you'll provide it later, and the assembler accepts your promise and keeps going without flagging the undefined label.
The promise looks like this:
EXTRN ClrScr : PROC
Here, you've told the assembler that the label ClrScr represents a procedure, and that it will be found somewhere external to the current module. That's all the assembler needs to know to withhold its error message.
And having done that, the assembler's part is finished. It leaves in place an empty socket in your program where the external procedure can later be plugged in. I sometimes think of it as an eyelet where the external procedure will later hook in.
Over in the other module where procedure ClrScr exists, you not only have to define the procedure, you must give the eyelet a hook. That is, you have to warn the assembler that ClrScr will be referenced from outside the module. The assembler needs to forge the hook that will hook into the eyelet. You forge the hook by declaring the procedure public, meaning that other modules may freely reference the procedure. Declaring a procedure public is simplicity itself:
PUBLIC ClrScr
That done, who actually connects the hook and the eyelet? The linker does that during the link operation. After all, why call it a linker if it doesn't link anything? At link time, the
linker takes
the two .OBJ files generated by the assembler, one from your program and the other from the module containing ClrScr, and combines them into a single .EXE file. When the

.EXE file is loaded and run, the program can call ClrScr as cleanly and quickly as though both had been declared in the same source-code file.
This process is summarized in Figure 8.2.
What works for procedures works for data as well, and it can work in either direction. Your program can declare a variable as public with the PUBLIC directive, and that variable can then be used by any module in which the same variable name is declared as external with the EXTRN directive.
We sometimes say that a program or module containing procedures or variables declared as public exports those items. Also, we say that a program or module that uses procedures or variables that are external to it imports those items.
The Mechanics of Publics and Externals
I've described the source-code mechanics of assembly-language programs in detail in the last few chapters. EAT1.ASM, EAT2.ASM, and EAT3.ASM are good examples. External modules are similar to programs. There are two major differences, concerning things that external modules lack:
• External modules have no main program and hence no start address.
That is, no label is given after the END directive that concludes the source-code file. External modules are not intended to be run by themselves, so a start address is both unnecessary and (if one were added) a temptation to chaos.
• External modules have no stack segment. This is not an absolute require-ment (there are few such requirements in assembler work), but for simple assembly-language programming it's true enough. Your stack segment should be defined in your main program module. External modules should have none—they use the one defined by the programs that call them.
External modules can have a data segment. If the external module is to define a variable that is to be shared by the main program or by other externals, it obviously must have a data segment for that variable to reside in. But less obviously, if the external is to share a variable with another
external or with the main program, it must still define a data segment, even if that data segment is empty except for the external declaration.
This is easier to demonstrate than to explain. Take a look at the following external module, which is a library containing all of the simple display control procedures introduced in EAT3.ASM.


:


VIDLIB.ASM has both a code segment and a data segment. Note well that both segments are declared with the PUBLIC keyword. A common mistake made by beginners is to declare the procedures and variables public, but not the segments that they reside in. Non obvious it may be, but essential nonetheless: make your module segments public if they contain any public declarations!
The code segment contains all the procedures. The data segment, on the other hand, contains only the following statement:
EXTRN CRLF:BYTE,LRXY:WORD
VIDLIB.ASM declares no variables of its own. Instead, it uses two variables declared within the main program module EAT4.ASM. (EAT4.ASM is identical to EAT3.ASM, save that it has had its procedures removed and declared as external, and two of its variables declared public. The program's function is exactly the same as that of
EAT3.ASM.)
The EXTRN statement above indicates that two variables referenced within the module
are to be imported from somewhere. You don't have to specify from where. The names of the variables and their types have to be there. The linker and assembler are not case sensitive.
The directives following the colons in the EXTRN statement are type specifiers. The assembler builds hooks into the .OBJ it creates from the external module's source file. These hooks will then mate with the appropriate hooks in the .OBJ file that exports the imported variables. To get the hooks right, however, the assembler needs to know what kind of item is being imported. The name of the variable is just a label and gives no information about the type or size of data being imported. The type specifier must match the definition of the variable being imported. Table 8.1 summarizes what commonly used type specifiers correspond to what data declaration directives.
The most important piece of information contained in the type specifier is the size of the item being imported. Machine instructions assemble to different binary opcodes depending on the size of their memory data operands. An opcode that acts on byte-sized data in memory will be different from an opcode that acts on word-sized data. To get the hooks right, then, the assembler has to know the size of the imported item at assembly time.
Table 8.1. Type specifiers for external declarations
Specifier |
Use with directive |
Specifies |
PROC |
PROC |
Procedure |
BYTE |
DB |
Byte or string |
WORD |
DW |
Word-sized data |
DWORD |
DD |
Double word-sized data |
Dividing a Segment Across Module Boundaries
Note that the names of the code segment and data segment in the external module are the same as the names of the code segment and data segment in the main program module. The data segment is MyData in both, and the code segment is MyCode in both. This is not an absolute requirement, but it simplifies things greatly and is a good way to set things up while you're just learning your way around in assembly language. Regardless of the number of external modules that link with your main program, the program as a whole contains only one code segment and one data segment. Until your data requirements and code size get very large, you won't need more than a single code and data segment.
As long as the code and data segments are declared with the PUBLIC directive in all the