French Dep't UCLA
Los Angeles, CA 90024
It's laborious but fairly straightforward to write memory-
resident programs in assembly language. You know the size of
your program; you know how much memory it needs; you know where
to enter it from your interrupt routine. All this changes for
the worse when you work in a higher-level language. Compilers
don't come with a "makeres" function that rearranges their code
for this purpose. Even a language as flexible as C requires you
to handle most of the residency process through assembly-language
LDRES is a tool for MS/PC-DOS 2+ that simplifies the process
of turning a utility written in C, Pascal or whatever into a
resident program. It requires a few fixups from the programmer,
but if you know what you're doing, the extra work is minimal.
What LDRES does
LDRES takes a COM file as input, and tacks a couple of
modules on the end of it, including an interrupt (9) routine.
When the modified program is first run, control jumps to an
initialization module that sets up the interrupt routine and
leaves the program memory-resident. As expected by COM files,
the interrupt routine sets all segment registers to the CS value
before passing control to your program. On return, all registers
are restored and an IRET is executed.
The command-line "m" switch allows you to specify the
additional memory needed for stack & heap functions (and to avoid
"insufficient memory" errors at run-time). The memory is added
below the interrupt routine; you must request enough to make sure
this routine is not overwritten by your compiler's initialization
procedures. The default is 100h bytes for a minimal stack; on
program entry the stack pointer is set to the top of this area,
just below the interrupt routine. (Fourteen bytes are added to
this figure by registers pushed by the interrupt routine.) Don't
forget that even if your program doesn't use the stack, one is
needed for the interrupts that will occur during its execution.
So that your program can check to see if it is already
memory-resident (and not load a second time), LDRES sets up a
dummy interrupt address into which it places the first two
letters of the filename. The default setting for this is int
7eh; it can be changed by using the "i" switch.
LDRES allows you to remove your program from memory when
already resident by entering "fn /r" on the DOS command line.
This should generally only be done if your program is the last
loaded, since interrupt 9 control is passed back to the preceding
LDRES returns to you with a "program return address" which
you should note down. You then patch the exit function(s) of your
program to JMP to this address, and presto, your program has been
converted into a memory-resident utility that will be invoked
whenever a certain key combination is pressed.
You may have to make a few other changes. The following
refers to my own experience with the Lattice C compiler (v3.1);
other compilers may pose other problems.
1. For one thing, you can't use standard DOS input/output
functions. You'll have to write assembly-language routines using
interrupts 16h and 10h. Writing to the screen buffer is easy
enough, but getting input forces you to do your own editing. I
won't go into the matters of windows, saving the user's screen,
etc. Some other DOS functions won't work either; it took me
several days to discover that the innocent-appearing function 30h
(Get DOS version) sends memory-resident programs out to lunch.
2. You will have to disable the memory-management function
of your compiler that wants to return all extra memory to DOS
when your program runs. The program doesn't know about the
interrupt routine sitting on top of it. Since your program is
already resident, i.e., uses a fixed memory space, the simplest
thing is just to kill (NOP out) such functions. Look out for DOS
function 4Ah (or maybe 49h) here.
3. Compilers expect your program to run once and then return
to DOS. You may have to reinitialize data areas that are changed
by the program, or better still, find a way of keeping them
unchanged. Routines to get the environment or command-line
parameters are unnecessary in a resident program--if you need to
look at the environment, it's better to see a fresh copy.
Lattice C kindly gives you the source for their initialization
module (C.ASM), so it's not too hard to modify it for this
purpose; most languages build this function directly into the
compiler, so you may have to patch your COM file.
If you write in C, be sure to end your program with _exit(),
not exit(). The latter closes all file handles, including
standard input & output, expecting DOS to reopen them. This
would be disastrous if you ran your memory-resident program from
within another application. Of course the _exit() function must
be patched with a JMP to the program return address in place of
its return to DOS. The _tinymain() function will do this for
you, while saving time by not opening stdin, stderr and the like.
Format: ldres [\path\]fn.com [/snn /cnn /inn /mnnnn /wblabl]
The switches may be used in any order (enter all numbers in HEX):
/snn = BIOS shift byte value. The program default for this byte
is 8, meaning that the Alt key is pressed. The shift byte is
located at 0000:0417h. If you don't have documentation, XRAY.COM
will display these values on your screen.
/cnn = scan code. This is for the key you press along with the
shift(s). The default is 35h, or 53, the "/" key. (Be careful
not to enter the scan code in decimal.)
/inn = dummy interrupt number. This comes set to 7eh, and can be
changed to anything you like; just be sure not to use an active
/mnnnn = additional bytes of memory. Unless you are converting a
small assembly-language program, you should go beyond the default
of 100h (=256 bytes); the minimal figure for Lattice C is around
500h = 1280 bytes.
/wblabla = message to be added to the filename on loading. This
is a string of ASCII characters (no cr's, linefeeds, tabs) up to
a maximum of 47. The string must be terminated by a '$'. When
your program is loaded into memory, it will output "Installing
one after the filename. If you want the filename in capital
letters, enter it that way on the LDRES command line.
A Sample Session with LDRES
This recounts my experience in creating MCALC.COM, the
program for which I wrote LDRES in the first place.
1. Write the higher-level program (e.g. in C), being sure
to get the I/O right. Use _TINYMAIN, and exit with _exit().
2. Fix up the load module C.ASM to eliminate: (a) the
rbrk() memory-management routine (function 4A); (b) the
environment/command-line routines; (c) sticky calls, like
function 30 mentioned above.
3. Compile & link using the modified load module (I called
4. Run LDRES with the COM file and the appropriate
5. Using a debugger, kill all remaining troublesome
functions and set a JMP at exit to the "program return address"
output by LDRES.
6. Put the program name in your AUTOEXEC file, reset the
computer and pray.
7. Here you have the advantage over me of not having to look
for bugs in LDRES if something goes wrong. If your program hangs
the computer, the problem is probably in items (1) or (2), so
loop until done.
A final word of advice; try out LDRES first on an
short experimental program to learn what tricks you'll have to
play on your compiler in order to turn its product into a memory-
resident application. Good luck, and keep your finger near the
big red switch!