
EIN.LIB was the machine; CPM.LIB is the operating system. It is the library HiSoft shipped for every CP/M computer, and it works on the Einstein because XtalDOS answers CP/M's calls - which is the reason the compiler runs here at all (S2). It is nearly seven hundred lines, so this section reads the parts a program is likely to want, and the one function underneath them all.
When you type ARGS ONE TWO 3, the DOS loads ARGS.COM and leaves the rest of the line - ONE TWO 3, with its length in front - at address 80h, where any CP/M program can find it. cpm_cmd_line turns that into what C programs everywhere call argc and argv:
void cpm_cmd_line(aargc, aargv, buffer)
int *aargc;
char **aargv;
char *buffer;
{
FAST char *tbuff;
FAST int i;
tbuff = cast(char_ptr)0x80;
tbuff[*tbuff + 1] = NULL;
*aargc = parse_args(tbuff + 1, buffer, buffer + MAXARGS*2);
*aargv = buffer;
}
Everything in it is S16: a cast of the number 80h to a pointer (char_ptr is a typedef at the top of the file); the first byte at that address is the length, so tbuff[*tbuff + 1] is the byte after the last character, and writing 0 there makes the tail a string; then parse_args cuts it at the spaces into pieces, stores pointers to them in buffer, and returns how many. FAST is a #define in STDIO.H for static. The results come back through the two pointers, which is why the call is cpm_cmd_line(&argc, &argv, buffer) - and why argv has to be a variable of type char **, not an array, since the library assigns to it.
argv[0] is "HiSoft". CP/M does not tell a program its own name, so the library's #define CMD_NAME stands in; change it if it matters. The DOS has already turned the line into capitals, so two arrived as TWO.
Every service CP/M offers - and XtalDOS provides - is a numbered function called through address 5, with the number in the Z80's C register and an argument in DE. CPM.LIB wraps that once:
int cpm_bdos(func, param)
{
reg_bc = func;
reg_de = param;
inline(
PUSH_IX,
LD_BC_from, ®_bc,
LD_DE_from, ®_de,
CALL, 5,0,
POP_IX);
}
The same inline you read in S23, with CALL 5 where EIN.LIB had RST 08h: the CP/M front door instead of MOS's. The result is left in HL, which is where this compiler expects a function's value, so the function has no return and still returns something. Everything else in the file is cpm_bdos with a number: cpm_version() is function 12 and returned 20 in hexadecimal here, meaning "2.0, or compatible" - XtalDOS's answer; cpm_drive(-1) asks function 25 and got 0, drive A; unlink(name) deletes a file with function 19, rename(old, new) with 23. The manual for CP/M lists the numbers; the library has already looked them up.
read_file(name, address) opens a file and copies every byte of it to memory from address on, with getc in a loop, and returns TRUE or, if the file cannot be opened, prints ERROR - Cannot open file and returns ERROR (which STDIO.H defines as -1). write_file(name, address, length) is its opposite. Between them they are how a program loads a level, a screen or a table of high scores in one call, into a malloced block (S21) or a global array. Both are S20's functions in a loop, and if you have read S20 you could have written them; the point of reading them is to see that the library did nothing cleverer.
seek, fseek and ftell move about inside an open file by record - CP/M's files are read in 128-byte pieces, as S20 found - and are there for a program that keeps a big file and wants one part of it.
strupper and strlower change a string's case in place, with toupper and tolower in a loop: S17's exercise, done for you. itob writes a number in binary into a string. cpm_user changes CP/M's user number, which XtalDOS has too. And the file begins with #list- and ends with #list+, so that HC #list+ does not scroll seven hundred lines of library past you (S6) - which is why you read it in the editor instead.
One trap, met while writing exercise 34.2. strupper returns a char *, and it lives in CPM.LIB, which is read after your program. S13 said what follows: the compiler assumes int at the call, meets the real definition later, and stops with ERROR 20 ... duplicate declaration - type mismatch - at line 652 of CPM.LIB, which you did not write. STDIO.H avoids this for the standard library with a line of declarations, extern char *strcat(), *strcpy(), ...;, and you do the same for any pointer-returning function from CPM.LIB you call:
extern char *strupper();
above main. The extern matters: the same line without it compiled and the program crashed.
read_file calls fopen and getc; the error paths call fprintf. The same rule as EIN.LIB (S23): #include ?CPM.LIB? goes above #include ?STDIO.LIB?, so that the standard library is searched after the calls in CPM.LIB have been seen.
#include STDIO.H
main()
{
int argc, i;
char **argv;
char buffer[80];
cpm_cmd_line(&argc, &argv, buffer);
printf("%d arguments\n", argc);
for (i = 0; i < argc; i++)
printf("%d: %s\n", i, argv[i]);
printf("CP/M version %x\n", cpm_version());
printf("drive %d\n", cpm_drive(-1));
}
#include ?CPM.LIB?
#include ?STDIO.LIB?
buffer is where parse_args keeps the pointers and the pieces; eighty bytes is plenty for a command line, which XtalDOS limits to the width of the screen.
Starting from
Typed as ARGS.C; HC ARGS.C; then ARGS ONE two 3, and ARGS on its own.
What you should see
0:ARGS ONE TWO 3
4 arguments
0: HiSoft
1: ONE
2: TWO
3: 3
CP/M version 20
drive 0
0:ARGS
1 arguments
0: HiSoft
CP/M version 20
drive 0
(two came back as TWO: the DOS upper-cases what you type.)
argv as char *argv[10]; instead of char **argv;. What does the compiler say, and at which line?CMD_NAME in a copy of CPM.LIB - ED CPM.LIB, and save it under another name with GRPH-K X - to "ARGS", include the copy, and run. What is argv[0] now?cpm_bdos(12, 0) as well as cpm_version(). Are they the same?34.1 Write ECHO.C that prints its arguments on one line separated by spaces, like a shell's echo.
34.2 Write UPPER.C that takes a file name on the command line, reads the file into a malloced block of 5,000 bytes with getc, puts a 0 after the last byte, upper-cases it with strupper and prints it. (Declare strupper first - see above.)
34.3 Write WIPE.C that unlinks the file named on its command line, after asking Sure? with rawin.
Worked solutions are in Appendix II.
| Symptom | Cause |
|---|---|
need an lvalue at the cpm_cmd_line call | argv is an array. Declare it char **argv;. |
undefined symbol fopen (or getc) at the end of the compile | ?CPM.LIB? is below ?STDIO.LIB?. |
| Arguments come back in capitals | The DOS did that before the program ran. |
argv[0] is HiSoft | It always is; CMD_NAME in CPM.LIB. |
argc is 1 and the argument is missing | Nothing was typed after the name, or the tail at 80h was overwritten before cpm_cmd_line ran - call it first. |
duplicate declaration - type mismatch at a line inside CPM.LIB | A function that returns a pointer (strupper, strlower, instr) called without an extern char *name(); declaration above. |
cpm_cmd_line(&argc, &argv, buffer) gives a program its command line from address 80h; cpm_bdos(n, param) is every CP/M service through CALL 5, and the rest of the library is that with a number; read_file and write_file move whole files; the library goes above ?STDIO.LIB?.
S35, Where You Go From Here - what you know, what this compiler left out, and three directions on.