Assembly means speed. (programming in assembly language) (Special Anniversary Issue)
by Tom Campbell
This issue's program is a good example of both the benefits and the perils of programing in assembly. It's tiny, but its 100 lines would only require 10 or 12 lines in a higher-level language.
FMFD.COM is a utility that does nothing more than send a form feed to the specified printer. Some text editors and other text-processing tools (such as DOS's COPY command with LPT as the destination) don't offer the courtesy of forcing a page break when a partial page of next is printed. While it's true that you can reach over and push the form feed button on your printer, you may need to perform this operation from a batch file.
The syntax of FMFD is FMFD port, where port is an optional parallel port number from 1 to 4. If you omit the number, port 1 is assumed. Here are some examples: FMFD, FMFD 1, FMFD 3. The first two examples send a page break to LPT 1. The third example sends it to LPT 3. If you use a serial port for your printer. you're out of luck.
The first section of FMFD.ASM consists of equates. Like the #define in C, they serve as a single-object text-replacement facility. Assemblers make a secret first pass at your code so they can collect equates and macros (we'll get to macros shortly); then they expand them into a temporary file, which is actually the file that gets assembled. In MASM and TASM, the /L option creates a file with an LST extension exposing the inner workings of macros and equates. The line FormFeed EQU 12 means that we can later use FormFeed instead of a literal form feed character (which would corrupt the source file) or the number 12 and that the assembler will replace it with 12 internally. You can also use the equal sign for equates.
The PrintMsg macro looks like a subroutine, but it isn't. Macros, like equates, work via text replacement, but they allow parameters and aren't restricted to a single line. So instead of PrintMsg BadChar, the assembler will see, on its second pass, this code:
mov dx, es mov ds, dx mov dx, offset BadChar mov ah, 9 int 21h
The .Model directive, pioneered by Microsoft in MASM 5, is new to assemblers. Its purpose is to make multilanguage programming easier by automatically supplying different default values for procedure calls and data (near versus far in both cases).
No doubt Microsoft's own programmers got tired of writing similar but no identical run-time libraries for all their languages. Ironically, the Tiny model won't work with MASM 5.1, but it will work with 6.0 and QuickAssembler, the one you're more likely to own. Many of QuickAssemblers' features couldn't be found in 5.1 but were debuting in 6.0. Tiny will work fine with Borland's Turbo Assembler.
The .Code directive means executable code will follow. You can alternate several .Code, .Data, and .Stack directives in each file, but few do. It's considered good form to collect each element into one location. The .Code directive stands in for the more archaic _TEXT SEGMENT WORD PUBLIC ~CODE'.
It's said that .Code is a simplified segment directive because of this, and that's absolutely true. The old way was more flexible but is virtually never needed now, except perhaps for writing device drivers. The old segment directives are barbaric and unreadable, and you should be ashamed to use them.
ORG 100h forces execution to start at address 100 hex, or 256 decimal. All COM files do this. While Microsoft tried to prohibit the practice some years ago, even to the point of dropping COM support from every single one of its language products, popular sentiment was overwhelmingly for COM files. The EXE format lets DOS move the program anywhere it needs to (EXE files, in fact, are not completely linked until just before execution starts), but COM files are always at least 512 bytes smaller. This matters if you're using a laptop or a RAM disk. Plus, there's a vast body of source code for COM files free for the asking.
At this point, not a single line of executable code has appeared. That will change. About half of FMFD's 102 bytes are used in parsing the text of the command line, which is located at offset 81h in the PSP. The PSP, or Program Segment Prefix, is a data structure that DOS loads right before your program and which contains lots of useful values. It so happens that in a COM file the segment registers all point to the PSP, so all we have to do to point DS:SI at the command line is write the value 81h to SI.
The LODSB instruction is a nifty little dude that does several things at once. it copies the byte pointed to by DS:SI into AL; then it increments SI to point to the next byte. (The STD instruction can be used to bump SI backward instead.) The command line is terminated by a carriage return (ASCII 13 - see the equates), so we simply loop until a nonblank character or CR is encouraged. Lines like this aren't all that hard to comprehend.
cmp al, CR ; Reached end yet? je @@Finished ; Yes.
The value contained in AL is compared to 13. If they're equal, control passes (via jump if equal) to the code labeled @@Finished. Note that Turbo Assembler treats any labels starting with two @ signs as local to that procedure, so you can reuse generic labels like @@TryNext or @@Exit as often as you want.
CX was set to 0 before starting and is set to 1 if a valid digit it is found. If not, CX is still 0, and the default value of 1 inserted. INT 17h, subfunction 0 writes a byte to the specified printer port whose number is written to DX. Port 1 is 0, Port 2 is 1, and so on. The instruction SUB AL, '1' normalizes the ASCII digit by subtracting the value 49 from whatever's AL, leaving the correct binary value.
While I would normally suggest that you use DOS to open the printer as a file and write to it, using INT 17h ha the welcome side effect of omitting nasty messages such as Abort, Retry, Fail? if the printer device isn't connected properly or is otherwise engaged. It just waits awhile and then quits. There are times when having only a hundred lines is a virtue!
Finally, instead of the usual RET instruction, the more formal technique of exiting with DOS function 21h, subfunction 4Ch is employed. A RET works in COM files but not EXE files, so I avoid it. While it would save a couple of bytes, FMFD.COM still won't occupy even the smallest possible disk sector. After all, this isn't C or Pascal. We can afford the extra two bytes.