The Computer Journal, Issue 52

Z-System Corner

© Jay Sage

Reproduced with permission of author and publisher.

Programming for Compatibility: Z-System and CP/M

As noted in the sidebar to my columns, I serve as the Z-System sysop on GEnie. My principal duty is to conduct a Real Time Conference (RTC) at 10 pm Eastern time on the first Wednesday of each month. These sessions are group chats, and they give GEnie callers a chance to ask questions and make comments. When I accepted this position with GEnie, I had expected that many Z-System enthusiasts would take advantage of the opportunity to discuss Z-System issues with me, but it has not turned out that way. Relatively few people show up for these sessions.

They have not been without value, however, and one of the most valuable took place this past June. David Goodenough, the author of QTERM (the program for Z80 computers that is currently the number-one subject of interest), was in attendance as usual, and he and I got into a discussion about the lack of sharing and commonality between the Z-System community and the community of vanilla CP/M users.

The Issue of Compatibility

The discussion started when I mentioned that we were beginning work on a replacement for BYE. David asked if it would work under CP/M, and I replied that it would absolutely require a Z-System. David found this very annoying and complained about the way the Z community ignores the large part of the 8-bit computing world that, for whatever reason, sticks with standard CP/M.

In the case of BYE, I do not accept David's criticism. The main motivation for writing a new version of BYE is that many of the functions and a large part of the code in BYE is completely unnecessary on a Z-System. For example, Z-Systems are inherently secure and do not need BYE to provide security. Furthermore, the externally accessible multiple command line makes it possible for BYE to have the operating system perform functions that are currently included in the BYE code.

A BYE for Z-Systems could be significantly smaller and more flexible than a BYE for CP/M, and we would not want to carry the overhead demanded by CP/M. David's response to that was to question, then, why he should carry any overhead for Z-System in the programs he writes (since he never uses any form of Z-System).

I think the difference is that BYE is a piece of resident code, something that becomes part of the operating system of the computer. As such, it is much more important for the code to be as short as possible. In an application or utility program, however, the importance of keeping code short is much less, and the importance of compatibility and wide-ranging applicability is much greater. With respect to those kinds of programs I think that David is absolutely right, and we should be making a greater effort at compatibility. That is the issue I will address here.

Why Should We Want Compatibility?

Compatibility, like motherhood, is almost good by definition! Obviously, a great deal of creative effort is going into Z-System development, and it is best if the fruits of that effort can be shared by the largest number of people. But there are also some very specific reasons why we in the Z community should be interested in compatibility with CP/M.

One reason that affects Z users directly is that with NZCOM and Z3PLUS we are not locked into Z-System, and we are much more likely to drop back to CP/M to perform some tasks. Sometimes we are going to use an application that is a real memory hog. At other times we have to use a program that cannot operate under Z-System, such as Uniform (for working with foreign-format diskettes). Uniform works by introducing patches directly into the operating system code, and it will work, therefore, only when the machine is running the exact version of CP/M for which it was written. (I have made perfunctory attempts at finding a way to adapt Uniform to NZCOM, but so far I have not succeeded.) It would be very convenient if our familiar Z-System tools would still work after we dropped back to CP/M. And we certainly don't want them causing a system crash if we invoke them by accident under CP/M.

A second advantage, and one that David Goodenough raised to encourage me to pursue greater compatibility, is that people still using vanilla CP/M would get a taste of what Z-System can offer. Under CP/M, Z-System tools would require an onerous patching process. If more CP/M users realized the kinds of programs that would become available to them directly, with no need for patching, they would be more likely to upgrade to Z-System.

What Does Compatibility Demand?

What do we mean when we talk about Z-System programs being compatible with CP/M, and what coding requirements would such compatibility impose?

As Bridger Mitchell pointed out long ago in his "Advanced CP/M" column that used to appear in TCJ (and which we all hope will reappear in the future), all programs should examine the environment in which they have been called on to operate. Then, they must either adapt properly to the environment or terminate gracefully with an appropriate message.

Two basic things that programs might have to determine before they attempt to perform their function are: (1) the kind of CPU - 8080/8085, Z80, 64180, Z280; and (2) the kind of operating system - CP/M-2.2, CP/M-Plus, Z-System. In the case of a Z-System, it might be further necessary to determine the version of ZCPR3 (3.0, 3.3, 3.4) and the type of implementation (manual, NZCOM, Z3PLUS).

The Z-System is a far more varied object than CP/M, and one's determination of the environment has only begun when a Z-System has been detected. The simplest form of Z-System, I suppose, would have only one module: the ENV descriptor. The other modules and facilities in Z-System's long list are, I believe, all optional: named directory register (NDR), multiple command line, shell stack, flow control package (FCP), message buffer, command search path, and so on.

Because of the range of Z-System implementations that are possible, it is already incumbent on Z programs to make sure that the facilities they expect or depend on are, in fact, available. Most of the routines in the assembly language programming libraries used to write Z programs (i.e., VLIB, Z3LIB, and SYSLIB) already return error codes when the facility requested is not available. All Z programs should check these return codes and proceed in appropriate ways when things don't work out as intended.

What should be expect from Z programs when they are run under CP/M? At the very least, as I mentioned earlier, Z programs should absolutely be safe under CP/M. We might settle for a simple return to the CP/M command prompt, but ideally, programs should report that they could not run and why. It is quite unnerving to invoke a program and just get the command prompt back.

A higher level of compatibility would be for the Z program to perform those of its functions that make sense under CP/M. When running under Z-System the program might recognize named directories and return information in Z-System message buffer registers; under CP/M, only DU: directory references could be handled, and information that would normally be recorded in the message buffer would be discarded.

The highest level of compatibility, which we will explore in more detail later, is to have the program perform its full range of operations under CP/M by including an internal ENV module (including the TCAP component, which tells how to operate the console terminal). In this way, the program would think it was running under Z-System even when actually running under CP/M.

I have not had time to think through all these issues fully, and my views may be refined with further thought and input from others. At the moment, I can see only one fundamental difference between a minimum capability Z-System and a CP/M system. That is the difference in command processors, and specifically the more sophisticated parsing of the command line that ZCPR3 performs.

The first two tokens on the command line are treated as file references, and the default file control blocks (FCBs) at 5CH and 6CH are filled in with information about those two files. With ZCPR3, directory references of the form DU: and, if named directories are implemented, DIR: are recognized. Besides placing the proper drive letter into the FCB, the user number for the file is placed into a special location.

Under CP/M, these operations will not take place, and an internal ENV will not help. In fact, the internal ENV, which will make the program think that it is running under Z-System, could cause problems. To make programs that rely on the default FCB data work, we would have to add significant additional code to the program. First, we would have to detect the use of the internal ENV (this would be easy), and then we would have to provide alternative code for manually parsing the filename tokens into the FCBs. While this might represent a significant extra burden in the code, I would recommend that we do this in the future for those programs that can afford the extra code and whose functionality is not already available in a standard CP/M program.

Where Does Compatibility Stand Now?

David Goodenough was under the impression Z-System programmers totally ignore CP/M and that nearly all Z-System programs will not work under CP/M. This impression is not really fair.

First of all, a great many Z programs - perhaps even the majority - could not possibly run under CP/M because their function would make no sense under CP/M. Here are a few examples:

  1. PATH or ZPATH: configures the Z-System search path for COM files;

  2. PWD: reports the names and associated drive/users of currently defined named directories;

  3. SALIAS: full-screen tool for defining stand-alone multiple-command-line aliases;

  4. ZEX: a sophisticated batch processor that feeds commands to the multiple command line and uses the message buffer for control communication;

  5. ADIR: displays the names of alias scripts defined for the ARUNZ extended command processor;

  6. LSH: a full-screen history shell and command-line editor.

It was interesting to see what happened when these programs were operated under CP/M (which I could do easily on my Televideo 803H, which runs NZCOM). ZPATH politely reported that there was no ZCPR3 path, and PWD announced that there was no NDR (named directory register) allocated. ZEX gave a more general message indicating that the facilities it required for operation were not available. These responses were all acceptable and reasonable, and they meet the requirements I outlined above.

ADIR did not do so well. It tried to run and ended up accessing a bogus drive, from which I had to recover by pressing control-C. SALIAS, to my surprise, did even worse. Although one time it gave me the message "TCAP?", indicating that it was checking the TCAP for adequate terminal support, all the other times it crashed and locked up the system. So did LSH. Obviously, these programs are not checking properly that the memory referenced by the embedded ENV pointer actually contains a Z-System environment (ENV) module; they must just be plowing on ahead.

I tried a lot of other programs of this sort, and in almost all cases they failed in an acceptable way. Many gave messages; others simply returned to the command prompt without having done anything (at least nothing apparent, so I assume they did not do any harm).

Not all Z-System programs manipulate or take specific advantage of Z-System facilities; There are many Z programs that perform functions that make perfect sense under standard CP/M. Some authors have taken great care to ensure that their programs will work in both environments. Hal Bower's COPY program (derived from MCOPY) that comes with the ZSDOS disk-operating-system replacement is a good example of this. Here are some other examples of programs whose function makes some sense under CP/M:

  1. FF: FindFile searches all drives and user areas for specified file names;

  2. DIFF: performs a byte-by-byte comparison of two files;

  3. CD: changes the logged drive/user;

  4. LBREXT: extracts member files from LBR files and optionally uncompresses them;

  5. LPUT: builds a new LBR file from the files named on the command line.

How did these fare under CP/M? Although FF performs a function that would be useful under standard CP/M, it is coded to make mandatory use of Z-System features, and it delivers an error message when one attempts to use it under CP/M.

This is a good example of a program that probably should be upgraded to CP/M compatibility. Obviously, FF would not recognize or report named directories, and it would have to assume that all user areas are available. As for the drives it should search, it would have to be configured manually, and I don't see why ZCNFG, the program that is currently used under Z-System to configure it, could not perform this function under CP/M, too.

DIFF, like FF, gives an error message when it is invoked under CP/M (in fact, it requires ZCPR33 or later). DIFF makes extensive use of Z-System facilities. It determines the dimensions of the terminal display, stores certain results of the file comparison in Z-System registers, and performs advanced error-recovery operations when the program encounters certain conditions. DIFF also extracts and compares the date stamps for the two files. Thus, although the essential function of DIFF would be useful under CP/M, it might be difficult to make the code work without those Z facilities. It would be worth looking into, I think.

CD (Change Directory) is primarily intended to log into another drive and user area using a named-directory reference and to run a special alias when it gets there. Under CP/M it currently appears to do nothing but return to the command prompt. It should be made to give an error message. It might even be reasonable for it to accept DU: syntax to log into the indicated area under CP/M.

The functions of LBREXT are totally appropriate for CP/M, and there is no reason why it should not work perfectly under CP/M. It almost does. The following command works fine:


As is perhaps to be expected, it does have problems if one tries to use a named directory reference. The one real mistake I noticed in the code is that it is coded to determine its own name (for use in the help screen) by looking in the Z-System external FCB. Under CP/M, garbage appears. Apparently, the code does not verify that the system has an external FCB. This is a mistake even for operation under Z-System.

LPUT, which also performs a function that is entirely appropriate for CP/M, misbehaves seriously under CP/M. It tries to work, but it gets its user areas confused. Since it always assumes user 0 for the LBR file, even when one is given explicitly in the command, my guess is that it is using the default FCB for the first command line token. Second, for the list of files to be put into the library, LPUT assumes user 0 unless one is given explicitly, in which case it is recognized correctly. This suggests that it has not determined properly what its logged-in (default) user area is. Small changes in the code could probably correct these problems.

I probably should have clarified one thing earlier in this discussion. I have not done these experiments strictly under CP/M but rather under ZCPR2, and this may have affected the results. A stripped-down ZCPR2 is the most primitive system I will consider running. It is a direct, drop-in replacement for the Digital Research command processor; nothing else in the system changes. I can imagine (albeit with some difficulty) why someone would still today refuse to use Z-System (it does take up some memory), but it is completely beyond me as to why anyone would continue to use the DRI CP/M-2.2 CCP.

Compatibility Via an Internal Environment

I was so intrigued by the possibility of making Z programs run under CP/M even when they required full-screen terminal capabilities that I could not wait to experiment with some real code. Mind you, this is no small thing. I have been so busy with other activities that I have done virtually no code writing for years! My influence on the Z community has been only as a mentor. This time I just could not wait for someone else to perform the experiments.

As the subject for my first tests, I chose the disk utility program, DU3. There were two reasons for this. First, this is a program whose CP/M counterpart lacks some of the most useful features of DU3, and I have long wished that it would work after I dropped down to CP/M. Second, I had just put up on my Z-Node a new version, DU34, that Gorm Helt-Hansen of Denmark had sent to me, so I knew I had current source code to work with.

There are quite a few possible approaches to patching in an internal ENV. I am going to start by discussing a little test program that I wrote after I had already succeeded with DU34 (now DU35). Rather than trying to abstract from the DU code, it was easier to write a simple demonstration program. ZTEST, shown in Listing 1, includes only enough functionality to prove that it works.

The key idea is to place in the code a two-record module containing the ENV (1 record) and the TCAP (1 record). The ENV pointer in the program header is initialized to point to this internal environment. Then, when the program is executed under CP/M, that is the ENV that the program will see and use. On the other hand, when the program is run under a modern Z-System (ZCPR33 or later), the command processor will poke the address of the true (external) Z-System ENV into that pointer, and the program will then see and use the real environment.

The most difficult part of this project was writing the CPMENV.LIB code. The ENV part was pretty straightforward. Almost all the module addresses and lengths are zero! The part that took some time was the TCAP. I finally located a good source-code version of the latest TCAP standard and was able to import it. A condensed version is shown in Listing 2.

For patching DU3, I took a slightly simpler approach that would not have required modifying the source code at all. In fact, I did make a few changes to correct some errors I noticed in the version 3.4 code and to make a cosmetic change. For one thing, the names of some library routines had mistakenly shorted to six characters, rendering the code unusable with the SLR tools in SLR mode. Using the full names should be equally acceptable to M80/L80.

The most serious error was the inclusion of a large block of initialized data near the end of the data segment (DSEG). This resulted in a COM file substantially larger than necessary, since all the uninitialized data now had to be loaded into the COM file. I moved that initialized data to the beginning of the DSEG to join the other initialized data. I called the new version DU35.

Aside from those modifications, which were made for other reasons, the patching process actually starts with the same assembled REL file as would have been used to generate the standard Z-System version of the program. When I linked it with the libraries (VLIB, Z3LIB, and SYSLIB), I made note of where the data segment (DSEG) started. It was at 2D48H. To make room for an internal ENV from 2D50H to 2E4FH, I simply relinked the program with the DSEG specified as 2E50H. Now all I had to do was to patch in the ENV code.

Initially I did this manually using the command

GET 100 :DU35.COM

to load DU35.COM into memory. Then I used a second GET command to load an assembled version of the dummy ENV and TCAP to the proper address:


To make sure that the ENV address in this ENV module was self-consistent, I poked the correct value in at ENV+1BH:

POKE 2D6B 50 2D

Next, I had to poke the ENV address into the pointer at 109H. The command was:

POKE 109 50 2D

Now the memory image had the correct code, and I just had to save the file:

:SAVE 100-2E50 DU.COM

I presented the procedure above to illustrate how handy the Z-System GET, POKE, and transient SAVE.COM programs can be when doing this kind of work. (The peek command, P, also came in handy to let me see what I was doing.) To make the process easier for other people to carry out, I then developed a patch program called DU-CPM.Z80. Excerpts are shown in Listing 3, where the installation procedure is described.


I hope this column will inspire Z program authors to take a careful look at their programs to see how they can be made better behaved and more compatible with standard CP/M. I hope it will also inspire CP/M programs to think about the advantages that Z-System offers to many Z80 computer users and to devote the effort required to allow their CP/M programs to run effectively under Z-System as well. We will all benefit by bringing the CP/M and Z-System communities closer together.


Listing 1

Source code for the test program that incorporates an internal ENV/TCAP for when the program is run under standard CP/M.

; AUTHOR:       Jay Sage
; DATE:         June 16, 1991

; This is a program to test the technique of including
; an internal ENV and TCAP.

; ---------- External References

        maclib          cpmenv.lib

        .request        vlib,syslib0

        extrn           z3vinit,cls,at  ; VLIB routines
        extrn           vprint

; ---------- Standard Z-System program header

; To make the code work transparently with CP/M, the header
; is initialized to point to the internal ENV.  When the
; program is run under ZCPR33 or later, the address of the
; external Z-System ENV will be poked into the code by the
; command processor at run time.

        jp      start

        db      'Z3ENV'         ; Signature
        db      1               ; ENV type
        dw      intenv          ; Pointer to ENV

; Material for use with ZCNFG can be included here.

; ---------- Internal ENV and TCAP placed here

intenv: cpmenv                  ; Use macro

; ---------- Actual Program Code

        ld      hl,(envptr)     ; Initialize
        call    z3vinit

        call    cls             ; Clear screen
        call    at              ; Position cursor
        db      10,10
        call    vprint          ; Display a message
        db      'This is '
        db      1               ; Highlighting on
        db      'highlighted'
        db      2               ; Highlighting off
        db      ' video!'
        db      0
        call    at              ; Put cursor at bottom
        db      22,1




Listing 2

This is macro code that defines an ENV and TCAP for a CP/M system.

; AUTHOR:       Jay Sage
; DATE:         June 16, 1991

; --------------------------------------------------------

; System configuration information (***** USER EDIT *****)

cpumhz  equ     4               ; CPU speed in MHz

; Operating system addresses and sizes.

biospg  equ     0d1h            ; Page where BIOS starts

bios    equ     100h * biospg
doss    equ     28              ; Size of DOS in records
dos     equ     bios - 80h * doss
ccps    equ     16              ; Size of CCP in records
ccp     equ     dos - 80h * ccps

; Information about drives and user areas available

;               PONMLKJIHGFEDCBA
drvec   equ     0000000000001111B
highdsk equ     'D'             ; Letter of highest drive
maxdisk equ     highdsk - '@'   ; Highest drive (A=1)
maxuser equ     31              ; Highest user area

; Data about console screen and printers

crtwid  equ     80              ; Width of CRT screen
crtlen  equ     24              ; Number of lines on screen
crtuse  equ     crtlen -2       ; Number of lines to use

prtwid  equ     80              ; Printer width
prtlen  equ     66              ; Printer total length
prtuse  equ     prtlen - 8      ; Printer lines to use
prtff   equ     1               ; Formfeed flag (1 if used)
; --------------------------------------------------------

; Here is a macro to define and internal ENV for use
; under CP/M.

cpmenv  macro

        jp      0       ; Dummy jump address

        db      'Z3ENV' ; Environment ID
        db      81h     ; ENV type

        dw      0       ; external path
        db      0       ; elements in path

        dw      0       ; RCP address
        db      0       ; number of records in RCP


        dw      intenv  ; ZCPR3 Environment Descriptor
        db      2       ; number of records in ENV


        db      cpumhz  ; Processor Speed in MHz

        db      maxdisk ; maximum disk
        db      maxuser ; maximum user

        db      1       ; 1=OK to accept DU, 0=not OK

        db      0,0

        db      crtwid  ; width of CRT
        db      crtlen  ; number of lines on CRT
        db      crtuse  ; number of lines of text on CRT

        dw      drvec
        db      0

        db      prtwid  ; data for printer
        db      prtlen
        db      prtuse
        db      prtff

        db      0,0,0,0

        dw      ccp
        db      ccps

        dw      dos
        db      doss

        dw      bios

        db      'SH      '      ; shell variable filename
        db      'VAR'           ; shell variable filetype

        db      '        '      ; filename 1
        db      '   '           ; filetype 1


;  Fill unused space with nulls

        rept    128-($-intenv)
        db      0

;  End of Environment Descriptor -- beginning of TCAP

; ***** USER EDIT *****

; Extended Termcap Data

ESC     EQU     27      ; ASCII escape character

; I have adopted the convention that a terminal name is
; terminated with a space character, therefore no spaces
; within the name. Also that the terminal name is unique
; in the first eight characters.

NZTCAP: DB      'WYSE-50D     ' ; Terminal name (13 bytes)

; The Graphics section is no longer fixed so we must
; provide an offset to it.  One byte is sufficient for a
; two-record TCAP.

        DB      GOELD-NZTCAP    ; Offset to GOELD

; Bit 7 of B14 indicates the new Extended TCAP.  Bits 6-0
; are undefined.

        DB      10000000B       ; Extended TCAP

; B15 b0        Standout        0 = dim, 1 = inverse
; B15 b1        Power Up Delay  0 = None, 1 = 10-sec delay
; B15 b2        No Wrap         0 = Line Wrap, 1 = No Wrap
; B15 b3        No Scroll       0 = Scroll, 1 = No Scroll
; B15 b4        ANSI            0 = ASCII, 1 = ANSI

        DB      00000111B

        DB      'K'-'@'         ; Cursor up
        DB      'J'-'@'         ; Cursor down
        DB      'L'-'@'         ; Cursor right
        DB      'H'-'@'         ; Cursor left

        DB      00              ; Clear-screen delay
        DB      00              ; Cursor movement delay
        DB      00              ; Clear-to-end-of-line delay

; Strings start here.

        DB      ESC,'+',0       ; Clear-screen string
        DB      ESC,'=%+ %+ ',0 ; Cursor movement string
        DB      ESC,'T',0       ; Clear-to-end-of-line
        DB      ESC,')',0       ; Standout-on string
        DB      ESC,'(',0       ; Standout-end string
        DB      0               ; Terminal init string
        DB      ESC,'(',0       ; Terminal deinit string

; Extensions to Standard Z3TCAP

        DB      ESC,'R',0       ; Line Delete
        DB      ESC,'E',0       ; Line Insert
        DB      ESC,'Y',0       ; Clear-to-end-of-screen

; Set Attribute strings once again included.

        DB      ESC,'G',0       ; Set Attributes
        DB      '0248',0        ; Attributes

; These two allow reading the Terminal's screen.

        DB      ESC,'?',0       ; Read current cursor pos
        DB      ESC,'6',0       ; Read line until cursor

; Graphics start here.

GOELD:  DB      0               ; On/Off Delay

; Graphics strings offset from Delay value.

        DB      ESC,'H',2,0     ; Graphics mode On
        DB      ESC,'H',3,0     ; Graphics mode Off
        DB      ESC,'`0',0      ; Cursor Off
        DB      ESC,'`1',0      ; Cursor On

; Graphics Characters

        DB      '2'             ; Upper left corner
        DB      '3'             ; Upper right corner
        DB      '1'             ; Lower left corner
        DB      '5'             ; Lower right corner
        DB      ':'             ; Horizontal line
        DB      '6'             ; Vertical line
        DB      '7'             ; Full block
        DB      ';'             ; Hashed block
        DB      '0'             ; Upper intersect
        DB      '='             ; Lower intersect
        DB      '8'             ; Mid intersect
        DB      '9'             ; Right intersect
        DB      '4'             ; Left intersect

;  Fill unused space with nulls

        REPT    128-($-NZTCAP)
        DB      0

;  End of NZTCAPD




Listing 3

The essential material in the patch for installing an internal environment into DU35.

; PROGRAM:      DU-CPM.Z80
; AUTHOR:       Jay Sage
; DATE:         June 15, 1991

; This code is a patch that can be used with DU35 to embed
; an internal environment descriptor and TCAP so that DU35
; can be used under standard CP/M (2.2 or 3) as well as Z-
; -System.  To use this patch, DU35.Z80 must be assembled as
; usual to a REL file and then linked with the data segment
; (DSEG) moved 100H higher in memory to provide a place to
; insert the ENV.  The appropriate linker commands with the
; version-4 libraries are as follows:
;       SLRNK DU35/N,/P:100,/D:2E50,DU35,
;                  VLIB/S,Z3LIB/S,SYSLIB0/S,/E
; or
;       ZML DU35,VLIB/,Z3LIB/,SYSLIB0/ D2E50
; This linking leaves the space from 2D50H to 2E4FH free for
; an internal ENV and TCAP, which are defined below in this
; patch.  As distributed, this patch is set up with an ENV
; for my Televideo 803H computer and a TCAP for a Wyse-50
; terminal (this works on the Televideo, too).  You should
; edit the file so that it describes your system (search for
; the sections marked with "***** USER EDIT *****".
; Once the patch has been edited, assemble it to a HEX file
; and then use MLOAD (or MYLOAD) to apply the overlay:

; An alternative way to install the TCAP is to extract a
; binary TCAP from one of the distribution files (such as
; Z3TCAP.TCP, which is a library collection of TCAPs)
; Then, after installing the DU-CPM patch, use a debugger or
; file editor to install the desired TCAP into DU.COM at
; address 2DD0H (on top of the one installed by this patch).

; -------------------------------------------------------

; System configuration information (***** USER EDIT *****)


intenv  equ     02d50h  ; Place in DU35 for the internal ENV

; -------------------------------------------------------

; Install ENV address at beginning of code

        org     109h            ; Place for the ENV address

        dw      intenv          ; Internal ENV address

; -------------------------------------------------------

; Install the dummy ENV in the DU35 code

        org     intenv

        ; IN CPMENV.LIB

;  End of NZTCAPD


[This article was originally published in issue 52 of The Computer Journal, P.O. Box 12, South Plainfield, NJ 07080-0012 and is reproduced with the permission of the author and the publisher. Further reproduction for non-commercial purposes is authorized. This copyright notice must be retained. (c) Copyright 1991 Socrates Press and respective authors]