Breakout, the game the course builds in HiSoft C, running on the Tatung Einstein: coloured brick rows, the bat and the ball on a black court
← Back to Courses
module
34

Reading A Real Program: CPM.LIB

Introduction

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.

What A Program Was Told

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.

Asking The System Anything

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, &reg_bc,
    LD_DE_from, &reg_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.

Files Whole

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.

Small Things

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.

Functions That Return A Pointer

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.

Why It Goes Before STDIO.LIB

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.

The Code

#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.)

Change One Thing

  • Declare argv as char *argv[10]; instead of char **argv;. What does the compiler say, and at which line?
  • Change 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?
  • Print cpm_bdos(12, 0) as well as cpm_version(). Are they the same?

Exercises

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.

When It Goes Wrong

SymptomCause
need an lvalue at the cpm_cmd_line callargv 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 capitalsThe DOS did that before the program ran.
argv[0] is HiSoftIt always is; CMD_NAME in CPM.LIB.
argc is 1 and the argument is missingNothing 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.LIBA function that returns a pointer (strupper, strlower, instr) called without an extern char *name(); declaration above.

Summary

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?.

Next

S35, Where You Go From Here - what you know, what this compiler left out, and three directions on.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.