home *** CD-ROM | disk | FTP | other *** search
/ For Beginners & Professional Hackers / cd.iso / docum / advdos.doc / s1c08 < prev    next >
Encoding:
Text File  |  1992-04-21  |  78.2 KB  |  1,736 lines

  1. ────────────────────────────────────────────────────────────────────────────
  2. Chapter 8  File Management
  3.  
  4.   The dual heritage of MS-DOS──CP/M and UNIX/XENIX──is perhaps most clearly
  5.   demonstrated in its file-management services. In general, MS-DOS provides
  6.   at least two distinct operating-system calls for each major file or record
  7.   operation. This chapter breaks this overlapping battery of functions into
  8.   two groups and explains the usage, advantages, and disadvantages of each.
  9.  
  10.   I will refer to the set of file and record functions that are compatible
  11.   with CP/M as FCB functions. These functions rely on a data structure
  12.   called a file control block (hence, FCB) to maintain certain bookkeeping
  13.   information about open files. This structure resides in the application
  14.   program's memory space. The FCB functions allow the programmer to create,
  15.   open, close, and delete files and to read or write records of any size at
  16.   any record position within such files. These functions do not support the
  17.   hierarchical (treelike) file structure that was first introduced in MS-DOS
  18.   version 2.0, so they can be used only to access files in the current
  19.   subdirectory for a given disk drive.
  20.  
  21.   I will refer to the set of file and record functions that provide
  22.   compatibility with UNIX/XENIX as the handle functions. These functions
  23.   allow the programmer to open or create files by passing MS-DOS a
  24.   null-terminated string that describes the file's location in the
  25.   hierarchical file structure (the drive and path), the file's name, and its
  26.   extension. If the open or create operation is successful, MS-DOS returns a
  27.   16-bit token, or handle, that is saved by the application program and used
  28.   to specify the file in subsequent operations.
  29.  
  30.   When you use the handle functions, the operating system maintains the data
  31.   structures that contain bookkeeping information about the file inside its
  32.   own memory space, and these structures are not accessible to the
  33.   application program. The handle functions fully support the hierarchical
  34.   file structure, allowing the programmer to create, open, close, and delete
  35.   files in any subdirectory on any disk drive and to read or write records
  36.   of any size at any byte offset within such files.
  37.  
  38.   Although we are discussing the FCB functions first in this chapter for
  39.   historical reasons, new MS-DOS applications should always be written using
  40.   the more powerful handle functions. Use of the FCB functions in new
  41.   programs should be avoided, unless compatibility with MS-DOS version 1.0
  42.   is needed.
  43.  
  44.  
  45. Using the FCB Functions
  46.  
  47.   Understanding the structure of the file control block is the key to
  48.   success with the FCB family of file and record functions. An FCB is a
  49.   37-byte data structure allocated within the application program's memory
  50.   space; it is divided into many fields (Figure 8-1). Typically, the
  51.   program initializes an FCB with a drive code, a filename, and an extension
  52.   (conveniently accomplished with the parse-filename service, Int 21H
  53.   Function 29H) and then passes the address of the FCB to MS-DOS to open or
  54.   create the file. If the file is successfully opened or created, MS-DOS
  55.   fills in certain fields of the FCB with information from the file's entry
  56.   in the disk directory. This information includes the file's exact size in
  57.   bytes and the date and time the file was created or last updated. MS-DOS
  58.   also places certain other information within a reserved area of the FCB;
  59.   however, this area is used by the operating system for its own purposes
  60.   and varies among different versions of MS-DOS. Application programs should
  61.   never modify the reserved area.
  62.  
  63.   For compatibility with CP/M, MS-DOS automatically sets the record-size
  64.   field of the FCB to 128 bytes. If the program does not want to use this
  65.   default record size, it must place the desired size (in bytes) into the
  66.   record-size field after the open or create operation. Subsequently, when
  67.   the program needs to read or write records from the file, it must pass the
  68.   address of the FCB to MS-DOS; MS-DOS, in turn, keeps the FCB updated with
  69.   information about the current position of the file pointer and the size of
  70.   the file. Data is always read to or written from the current disk transfer
  71.   area (DTA), whose address is set with Int 21H Function 1AH. If the
  72.   application program wants to perform random record access, it must set the
  73.   record number into the FCB before issuing each function call; when
  74.   sequential record access is being used, MS-DOS maintains the FCB and no
  75.   special intervention is needed from the application.
  76.  
  77.   Byte offset
  78.   00H ┌───────────────────────────────────────────────────────┐
  79.       │                 Drive identification                  │ Note 1
  80.   01H ├───────────────────────────────────────────────────────┤
  81.       │                Filename (8 characters)                │ Note 2
  82.   09H ├───────────────────────────────────────────────────────┤
  83.       │               Extension (3 characters)                │ Note 2
  84.   0CH ├───────────────────────────────────────────────────────┤
  85.       │                 Current block number                e │ Note 9
  86.   0EH ├───────────────────────────────────────────────────────┤
  87.       │                      Record size                      │ Note 10
  88.   10H ├───────────────────────────────────────────────────────┤
  89.       │                  File size (4 bytes)                  │ Notes 3, 6
  90.   14H ├───────────────────────────────────────────────────────┤
  91.       │                 Date created/updated                  │ Note 7
  92.   16H ├───────────────────────────────────────────────────────┤
  93.       │                 Time created/updated                  │ Note 8
  94.   18H ├───────────────────────────────────────────────────────┤
  95.       │                       Reserved                        │
  96.   20H ├───────────────────────────────────────────────────────┤
  97.       │                 Current-record number                 │ Note 9
  98.   21H ├───────────────────────────────────────────────────────┤
  99.       │           Relative-record number (4 bytes)            │ Note 5
  100.       └───────────────────────────────────────────────────────┘
  101.  
  102.   Figure 8-1.  Normal file control block. Total length is 37 bytes (25H
  103.   bytes). See notes on pages 133─34.
  104.  
  105.   In general, MS-DOS functions that use FCBs accept the full address of the
  106.   FCB in the DS:DX register and pass back a return code in the AL register
  107.   (Figure 8-2). For file-management calls (open, close, create, and
  108.   delete), this return code is zero if the function was successful and 0FFH
  109.   (255) if the function failed. For the FCB-type record read and write
  110.   functions, the success code returned in the AL register is again zero, but
  111.   there are several failure codes. Under MS-DOS version 3.0 or later, more
  112.   detailed error reporting can be obtained by calling Int 21H Function 59H
  113.   (Get Extended Error Information) after a failed FCB function call.
  114.  
  115.   When a program is loaded under MS-DOS, the operating system sets up two
  116.   FCBs in the program segment prefix, at offsets 005CH and 006CH. These are
  117.   often referred to as the default FCBs, and they are included to provide
  118.   upward compatibility from CP/M. MS-DOS parses the first two parameters in
  119.   the command line that invokes the program (excluding any redirection
  120.   directives) into the default FCBs, under the assumption that they may be
  121.   file specifications. The application must determine whether they really
  122.   are filenames or not. In addition, because the default FCBs overlap and
  123.   are not in a particularly convenient location (especially for .EXE
  124.   programs), they usually must be copied elsewhere in order to be used
  125.   safely. (See Chapter 3.)
  126.  
  127.   ──────────────────────────────────────────────────────────────────────────
  128.                                                ; filename was previously
  129.                                                ; parsed into "my_fcb"
  130.                   mov   dx,seg my_fcb          ; DS:DX = address of
  131.                   mov   ds,dx                  ; file control block
  132.                   mov   dx,offset my_fcb
  133.                   mov   ah,0fh                 ; function 0fh = open
  134.                   int   21h
  135.                   or    al,al                  ; was open successful?
  136.                   jnz   error                  ; no, jump to error routine
  137.                   .
  138.                   .
  139.                   .
  140.   my_fcb          db    37 dup (0)             ; file control block
  141.   ──────────────────────────────────────────────────────────────────────────
  142.  
  143.   Figure 8-2.  A typical FCB file operation. This sequence of code attempts
  144.   to open the file whose name was previously parsed into the FCB named
  145.   my_fcb.
  146.  
  147.   Note that the structures of FCBs under CP/M and MS-DOS are not identical.
  148.   However, the differences lie chiefly in the reserved areas of the FCBs
  149.   (which should not be manipulated by application programs in any case), so
  150.   well-behaved CP/M applications should be relatively easy to port into
  151.   MS-DOS. It seems, however, that few such applications exist. Many of the
  152.   tricks that were played by clever CP/M programmers to increase performance
  153.   or circumvent the limitations of that operating system can cause severe
  154.   problems under MS-DOS, particularly in networking environments. At any
  155.   rate, much better performance can be achieved by thoroughly rewriting the
  156.   CP/M applications to take advantage of the superior capabilities of
  157.   MS-DOS.
  158.  
  159.   You can use a special FCB variant called an extended file control block to
  160.   create or access files with special attributes (such as hidden or
  161.   read-only files), volume labels, and subdirectories. An extended FCB has a
  162.   7-byte header followed by the 37-byte structure of a normal FCB (Figure
  163.   8-3). The first byte contains 0FFH, which could never be a legal drive
  164.   code and thus indicates to MS-DOS that an extended FCB is being used. The
  165.   next 5 bytes are reserved and are unused in current versions of MS-DOS.
  166.   The seventh byte contains the attribute of the special file type that is
  167.   being accessed. (Attribute bytes are discussed in more detail in Chapter
  168.   9.) Any MS-DOS function that uses a normal FCB can also use an extended
  169.   FCB.
  170.  
  171.   The FCB file- and record-management functions may be gathered into the
  172.   following broad classifications:
  173.  
  174.   Byte
  175.   offset
  176.   00H ┌───────────────────────────────────────────────────────┐
  177.       │                         0FFH                          │ Note 11
  178.   01H ├───────────────────────────────────────────────────────┤
  179.       │           Reserved (5 bytes, must be zero)            │
  180.   06H ├───────────────────────────────────────────────────────┤
  181.       │                    Attribute byte                     │ Note 12
  182.   07H ├───────────────────────────────────────────────────────┤
  183.       │                 Drive identification                  │ Note 1
  184.   08H ├───────────────────────────────────────────────────────┤
  185.       │                Filename (8 characters)                │ Note 2
  186.   10H ├───────────────────────────────────────────────────────┤
  187.       │               Extension (3 characters)                │ Note 2
  188.   13H ├───────────────────────────────────────────────────────┤
  189.       │                 Current-block number                  │ Note 9
  190.   15H ├───────────────────────────────────────────────────────┤
  191.       │                      Record size                      │ Note 10
  192.   17H ├───────────────────────────────────────────────────────┤
  193.       │                  File size (4 bytes)                  │ Notes 3, 6
  194.   1BH ├───────────────────────────────────────────────────────┤
  195.       │                 Date created/updated                  │ Note 7
  196.   1DH ├───────────────────────────────────────────────────────┤
  197.       │                 Time created/updated                  │ Note 8
  198.   1FH ├───────────────────────────────────────────────────────┤
  199.       │                       Reserved                        │
  200.   27H ├───────────────────────────────────────────────────────┤
  201.       │                 Current-record number                 │ Note 9
  202.   28H ├───────────────────────────────────────────────────────┤
  203.       │           Relative-record number (4 bytes)            │ Note 5
  204.       └───────────────────────────────────────────────────────┘
  205.  
  206.   Figure 8-3.  Extended file control block. Total length is 44 bytes (2CH
  207.   bytes). See notes on pages 133─34.
  208.  
  209.  
  210.   Function                 Action
  211.   ──────────────────────────────────────────────────────────────────────────
  212.   Common FCB file operations
  213.   0FH                     Open file.
  214.   10H                     Close file.
  215.   16H                     Create file.
  216.  
  217.   Common FCB record operations
  218.   14H                     Perform sequential read.
  219.   15H                     Perform sequential write.
  220.   21H                     Perform random read.
  221.   22H                     Perform random write.
  222.   27H                     Perform random block read.
  223.   28H                     Perform random block write.
  224.  
  225.   Other vital FCB operations
  226.   1AH                     Set disk transfer address.
  227.   29H                     Parse filename.
  228.  
  229.   Less commonly used FCB file operations
  230.   13H                     Delete file.
  231.   17H                     Rename file.
  232.  
  233.   Less commonly used FCB record operations
  234.   23H                     Obtain file size.
  235.   24H                     Set relative-record number.
  236.   ──────────────────────────────────────────────────────────────────────────
  237.  
  238.  
  239.   Several of these functions have special properties. For example, Int 21H
  240.   Functions 27H (Random Block Read) and 28H (Random Block Write) allow
  241.   reading and writing of multiple records of any size and also update the
  242.   random-record field automatically (unlike Int 21H Functions 21H and
  243.   22H). Int 21H Function 28H can truncate a file to any desired size, and
  244.   Int 21H Function 17H used with an extended FCB can alter a volume label
  245.   or rename a subdirectory.
  246.  
  247.   Section 2 of this book, "MS-DOS Functions Reference," gives detailed
  248.   specifications for each of the FCB file and record functions, along with
  249.   assembly-language examples. It is also instructive to compare the
  250.   preceding groups with the corresponding groups of handle-type functions
  251.   listed on pages 140─41.
  252.  
  253.   ──────────────────────────────────────────────────────────────────────────
  254.   Notes for Figures 8-1 and 8-3
  255.     1.  The drive identification is a binary number: 00=default drive,
  256.         01=drive A:, 02=drive B:, and so on. If the application program
  257.         supplies the drive code as zero (default drive), MS-DOS fills in the
  258.         code for the actual current disk drive after a successful open or
  259.         create call.
  260.  
  261.     2.  File and extension names must be left justified and padded with
  262.         blanks.
  263.  
  264.     3.  The file size, date, time, and reserved fields should not be
  265.         modified by applications.
  266.  
  267.     4.  All word fields are stored with the least significant byte at the
  268.         lower address.
  269.  
  270.     5.  The relative-record field is treated as 4 bytes if the record size
  271.         is less than 64 bytes; otherwise, only the first 3 bytes of this
  272.         field are used.
  273.  
  274.     6.  The file-size field is in the same format as in the directory, with
  275.         the less significant word at the lower address.
  276.  
  277.     7.  The date field is mapped as in the directory. Viewed as a 16-bit
  278.         word (as it would appear in a register), the field is broken down as
  279.         follows:
  280.  
  281.       F  E  D  C  B  A  9   8     7     6     5    4   3   2   1   0
  282.     ┌─────────────────────┬─────────────────────┬─────────────────────┐
  283.     │        Year         │        Month        │         Day         │
  284.     └─────────────────────┴─────────────────────┴─────────────────────┘
  285.  
  286.     Bits              Contents
  287.     ────────────────────────────────────────────────────────────────────────
  288.     00H─04H           Day (1─31)
  289.     05H─08H           Month (1─12)
  290.     09H─0FH           Year, relative to 1980
  291.     ────────────────────────────────────────────────────────────────────────
  292.  
  293.     8.  The time field is mapped as in the directory. Viewed as a 16-bit
  294.         word (as it would appear in a register), the field is broken down as
  295.         follows:
  296.  
  297.       F   E   D   C   B   A   9   8   7   6   5   4   3   2   1   0
  298.     ┌───────────────────┬───────────────────────┬─────────────────────┐
  299.     │     Hours         │        Minutes        │ 2-second increments │
  300.     └───────────────────┴───────────────────────┴─────────────────────┘
  301.  
  302.     Bits              Contents
  303.     ────────────────────────────────────────────────────────────────────────
  304.     00H─04H           2-second increments (0─29)
  305.     05H─0AH           Minutes (0─59)
  306.     0BH─0FH           Hours (0─23)
  307.     ────────────────────────────────────────────────────────────────────────
  308.  
  309.     9.  The current-block and current-record numbers are used together on
  310.         sequential reads and writes. This simulates the behavior of CP/M.
  311.  
  312.     10. The Int 21H open (0FH) and create (16H) functions set the
  313.         record-size field to 128 bytes, to provide compatibility with CP/M.
  314.         If you use another record size, you must fill it in after the open
  315.         or create operation.
  316.  
  317.     11. An 0FFH (255) in the first byte of the structure signifies that it
  318.         is an extended file control block. You can use extended FCBs with
  319.         any of the functions that accept an ordinary FCB. (See also note
  320.         12.)
  321.  
  322.     12. The attribute byte in an extended FCB allows access to files with
  323.         the special characteristics hidden, system, or read-only. You can
  324.         also use extended FCBs to read volume labels and the contents of
  325.         special subdirectory files.
  326.  
  327.   ──────────────────────────────────────────────────────────────────────────
  328.  
  329. FCB File-Access Skeleton
  330.  
  331.   The following is a typical program sequence to access a file using the
  332.   FCB, or traditional, functions (Figure 8-4):
  333.  
  334.   1.  Zero out the prospective FCB.
  335.  
  336.   2.  Obtain the filename from the user, from the default FCBs, or from the
  337.       command tail in the PSP.
  338.  
  339.   3.  If the filename was not obtained from one of the default FCBs, parse
  340.       the filename into the new FCB using Int 21H Function 29H.
  341.  
  342.   4.  Open the file (Int 21H Function 0FH) or, if writing new data only,
  343.       create the file or truncate any existing file of the same name to zero
  344.       length (Int 21H Function 16H).
  345.  
  346.   5.  Set the record-size field in the FCB, unless you are using the default
  347.       record size. Recall that it is important to do this after a successful
  348.       open or create operation. (See Figure 8-5.)
  349.  
  350.   6.  Set the relative-record field in the FCB if you are performing random
  351.       record I/O.
  352.  
  353.   7.  Set the disk transfer area address using Int 21H Function 1AH, unless
  354.       the buffer address has not been changed since the last call to this
  355.       function. If the application never performs a set DTA, the DTA address
  356.       defaults to offset 0080H in the PSP.
  357.  
  358.   8.  Request the needed read- or write-record operation (Int 21H Function
  359.       14H─Sequential Read, 15H─Sequential Write, 21H─Random Read,
  360.       22H─Random Write, 27H─Random Block Read, 28H─Random Block Write).
  361.  
  362.   9.  If the program is not finished processing the file, go to step 6;
  363.       otherwise, close the file (Int 21H Function 10H). If the file was
  364.       used for reading only, you can skip the close operation under early
  365.       versions of MS-DOS. However, this shortcut can cause problems under
  366.       MS-DOS versions 3.0 and later, especially when the files are being
  367.       accessed across a network.
  368.  
  369.   ──────────────────────────────────────────────────────────────────────────
  370.   recsize      equ   1024                   ; file record size
  371.                .
  372.                .
  373.                .
  374.                mov   ah,29h                 ; parse input filename
  375.                mov   al,1                   ; skip leading blanks
  376.                mov   si,offset fname1       ; address of filename
  377.                mov   di,offset fcb1         ; address of FCB
  378.                int   21h
  379.                or    al,al                  ; jump if name
  380.                jnz   name_err               ; was bad
  381.                .
  382.                .
  383.                .
  384.                mov   ah,29h                 ; parse output filename
  385.                mov   al,1                   ; skip leading blanks
  386.                mov   si,offset fname2       ; address of filename
  387.                mov   di,offset fcb2         ; address of FCB
  388.                int   21h
  389.                or    al,al                  ; jump if name
  390.                jnz   name_err               ; was bad
  391.                .
  392.                .
  393.                .
  394.                mov   ah,0fh                 ; open input file
  395.                mov   dx,offset fcb1
  396.                int   21h
  397.                or    al,al                  ; open successful?
  398.                jnz   no_file                ; no, jump
  399.                .
  400.                .
  401.                .
  402.                mov   ah,16h                 ; create and open
  403.                mov   dx,offset fcb2         ; output file
  404.                int   21h
  405.                or    al,al                  ; create successful?
  406.                jnz   disk_full              ; no, jump
  407.                .
  408.                .
  409.                .                            ; set record sizes
  410.                mov   word ptr fcb1+0eh,recsize
  411.                mov   word ptr fcb2+0eh,recsize
  412.                .
  413.                .
  414.                .
  415.                mov   ah,1ah                 ; set disk transfer
  416.                mov   dx,offset buffer       ; address for reads
  417.                int   21h                    ; and writes
  418.                .
  419.   next:        .                            ; process next record
  420.                .
  421.                mov   ah,14h                 ; sequential read from
  422.                mov   dx,offset fcb1         ; input file
  423.                int   21h
  424.                cmp   al,01                  ; check for end of file
  425.                je    file_end               ; jump if end of file
  426.                cmp   al,03
  427.                je    file_end               ; jump if end of file
  428.                or    al,al                  ; other read fault?
  429.                jnz   bad_read               ; jump if bad read
  430.                .
  431.                .
  432.                .
  433.                mov   ah,15h                 ; sequential write to
  434.                mov   dx,offset fcb2         ; output file
  435.                int   21h
  436.                or    al,al                  ; write successful?
  437.                jnz   bad_write              ; jump if write failed
  438.                .
  439.                .
  440.                .
  441.                jmp   next                   ; process next record
  442.                .
  443.   file_end:    .                            ; reached end of input
  444.                .
  445.                mov   ah,10h                 ; close input file
  446.                mov   dx,offset fcb1
  447.                int   21h
  448.                .
  449.                .
  450.                .
  451.                mov   ah,10h                 ; close output file
  452.                mov   dx,offset fcb2
  453.                int   21h
  454.                .
  455.                .
  456.                .
  457.                mov   ax,4c00h               ; exit with return
  458.                int   21h                    ; code of zero
  459.                .
  460.                .
  461.                .
  462.   fname1       db    'OLDFILE.DAT',0        ; name of input file
  463.   fname2       db    'NEWFILE.DAT',0        ; name of output file
  464.   fcb1         db    37 dup (0)             ; FCB for input file
  465.   fcb2         db    37 dup (0)             ; FCB for output file
  466.   buffer       db    recsize dup (?)        ; buffer for file I/O
  467.   ──────────────────────────────────────────────────────────────────────────
  468.  
  469.   Figure 8-4.  Skeleton of an assembly-language program that performs file
  470.   and record I/O using the FCB family of functions.
  471.  
  472.   Byte Offset  FCB before open       FCB contents       FCB after open
  473.            ┌────────────────────┬────────────────────┬────────────────────┐
  474.        00H │         00         │       Drive        │         03         │
  475.            ├────────────────────┼────────────────────┼────────────────────┤
  476.        01H │         4D         │                    │         4D         │
  477.        02H │         59         │                    │         59         │
  478.        03H │         46         │                    │         46         │
  479.        04H │         49         │      Filename      │         49         │
  480.        05H │         4C         │                    │         4C         │
  481.        06H │         45         │                    │         45         │
  482.        07H │         20         │                    │         20         │
  483.        08H │         20         │                    │         20         │
  484.            ├────────────────────┼────────────────────┼────────────────────┤
  485.        09H │         44         │                    │         44         │
  486.        0AH │         41         │     Extension      │         41         │
  487.        0BH │         54         │                    │         54         │
  488.            ├────────────────────┼────────────────────┼────────────────────┤
  489.        0CH │         00         │                    │         00         │
  490.        0DH │         00         │   Current block    │         00         │
  491.            ├────────────────────┼────────────────────┼────────────────────┤
  492.        0EH │         00         │                    │         80         │
  493.        0FH │         00         │    Record size     │         00         │
  494.            ├────────────────────┼────────────────────┼────────────────────┤
  495.        10H │         00         │                    │         80         │
  496.        11H │         00         │                    │         3D         │
  497.        12H │         00         │     File size      │         00         │
  498.        13H │         00         │                    │         00         │
  499.            ├────────────────────┼────────────────────┼────────────────────┤
  500.        14H │         00         │                    │         43         │
  501.        15H │         00         │     File date      │         0B         │
  502.            ├────────────────────┼────────────────────┼────────────────────┤
  503.        16H │         00         │                    │         A1         │
  504.        17H │         00         │     File time      │         52         │
  505.            ├────────────────────┼────────────────────┼────────────────────┤
  506.        18H │         00         │                    │         03         │
  507.        19H │         00         │                    │         02         │
  508.        1AH │         00         │                    │         42         │
  509.        1BH │         00         │                    │         73         │
  510.        1CH │         00         │      Reserved      │         00         │
  511.        1DH │         00         │                    │         01         │
  512.        1EH │         00         │                    │         35         │
  513.        1FH │         00         │                    │         0F         │
  514.            ├────────────────────┼────────────────────┼────────────────────┤
  515.        20H │         00         │   Current record   │         00         │
  516.            ├────────────────────┼────────────────────┼────────────────────┤
  517.        21H │         00         │                    │         00         │
  518.        22H │         00         │  Relative-record   │         00         │
  519.        23H │         00         │       number       │         00         │
  520.        24H │         00         │                    │         00         │
  521.            └────────────────────┴────────────────────┴────────────────────┘
  522.  
  523.   Figure 8-5.  A typical file control block before and after a successful
  524.   open call (Int 21H Function 0FH).
  525.  
  526. Points to Remember
  527.  
  528.   Here is a summary of the pros and cons of using the FCB-related file and
  529.   record functions in your programs.
  530.  
  531.   Advantages:
  532.  
  533.   ■  Under MS-DOS versions 1 and 2, the number of files that can be open
  534.      concurrently when using FCBs is unlimited. (This is not true under
  535.      MS-DOS versions 3.0 and later, especially if networking software is
  536.      running.)
  537.  
  538.   ■  File-access methods using FCBs are familiar to programmers with a CP/M
  539.      background, and well-behaved CP/M applications require little change in
  540.      logical flow to run under MS-DOS.
  541.  
  542.   ■  MS-DOS supplies the size, time, and date for a file to its FCB after
  543.      the file is opened. The calling program can inspect this information.
  544.  
  545.   Disadvantages:
  546.  
  547.   ■  FCBs take up room in the application program's memory space.
  548.  
  549.   ■  FCBs offer no support for the hierarchical file structure (no access to
  550.      files outside the current directory).
  551.  
  552.   ■  FCBs provide no support for file locking/sharing or record locking in
  553.      networking environments.
  554.  
  555.   ■  In addition to the read or write call itself, file reads or writes
  556.      using FCBs require manipulation of the FCB to set record size and
  557.      record number, plus a previous call to a separate MS-DOS function to
  558.      set the DTA address.
  559.  
  560.   ■  Random record I/O using FCBs for a file containing variable-length
  561.      records is very clumsy and inconvenient.
  562.  
  563.   ■  You must use extended FCBs, which are incompatible with CP/M anyway, to
  564.      access or create files with special attributes such as hidden,
  565.      read-only, or system.
  566.  
  567.   ■  The FCB file functions have poor error reporting. This situation has
  568.      been improved somewhat in MS-DOS version 3 because a program can call
  569.      the added Int 21H Function 59H (Get Extended Error Information) after
  570.      a failed FCB function to obtain additional information.
  571.  
  572.   ■  Microsoft discourages use of FCBs. FCBs will make your program more
  573.      difficult to port to MS OS/2 later because MS OS/2 does not support
  574.      FCBs in protected mode at all.
  575.  
  576.  
  577. Using the Handle Functions
  578.  
  579.   The handle file- and record-management functions access files in a fashion
  580.   similar to that used under the UNIX/XENIX operating system. Files are
  581.   designated by an ASCIIZ string (an ASCII character string terminated by a
  582.   null, or zero, byte) that can contain a drive designator, path, filename,
  583.   and extension. For example, the file specification
  584.  
  585.   C:\SYSTEM\COMMAND.COM
  586.  
  587.   would appear in memory as the following sequence of bytes:
  588.  
  589.   43 3A 5C 53 59 53 54 45 4D 5C 43 4F 4D 4D 41 4E 44 2E 43 4F 4D 00
  590.  
  591.   When a program wishes to open or create a file, it passes the address of
  592.   the ASCIIZ string specifying the file to MS-DOS in the DS:DX registers
  593.   (Figure 8-6). If the operation is successful, MS-DOS returns a 16-bit
  594.   handle to the program in the AX register. The program must save this
  595.   handle for further reference.
  596.  
  597.   ──────────────────────────────────────────────────────────────────────────
  598.                mov   ah,3dh                  ; function 3dh = open
  599.                mov   al,2                    ; mode 2 = read/write
  600.                mov   dx,seg filename         ; address of ASCIIZ
  601.                mov   ds,dx                   ; file specification
  602.                mov   dx,offset filename
  603.                int   21h                     ; request open from DOS
  604.                jc    error                   ; jump if open failed
  605.                mov   handle,ax               ; save file handle
  606.                .
  607.                .
  608.                .
  609.   filename     db    'C:\MYDIR\MYFILE.DAT',0 ; filename
  610.   handle       dw    0                       ; file handle
  611.   ──────────────────────────────────────────────────────────────────────────
  612.  
  613.   Figure 8-6.  A typical handle file operation. This sequence of code
  614.   attempts to open the file designated in the ASCIIZ string whose address is
  615.   passed to MS-DOS in the DS:DX registers.
  616.  
  617.   When the program requests subsequent operations on the file, it usually
  618.   places the handle in the BX register before the call to MS-DOS. All the
  619.   handle functions return with the CPU's carry flag cleared if the operation
  620.   was successful, or set if the operation failed; in the latter case, the AX
  621.   register contains a code describing the failure.
  622.  
  623.   MS-DOS restricts the number of handles that can be active at any one
  624.   time──that is, the number of files and devices that can be open
  625.   concurrently when using the handle family of functions──in two different
  626.   ways:
  627.  
  628.   ■  The maximum number of concurrently open files in the system, for all
  629.      active processes combined, is specified by the entry
  630.  
  631.      FILES=nn
  632.  
  633.      in the CONFIG.SYS file. This entry determines the number of entries
  634.      to be allocated in the system file table; under MS-DOS version 3, the
  635.      default value is 8 and the maximum is 255. After MS-DOS is booted and
  636.      running, you cannot expand this table to increase the total number of
  637.      files that can be open. You must use an editor to modify the CONFIG.SYS
  638.      file and then restart the system.
  639.  
  640.   ■  The maximum number of concurrently open files for a single process is
  641.      20, assuming that sufficient entries are also available in the system
  642.      file table. When a program is loaded, MS-DOS preassigns 5 of its
  643.      potential 20 handles to the standard devices. Each time the process
  644.      issues an open or create call, MS-DOS assigns a handle from the
  645.      process's private allocation of 20, until all the handles are used up
  646.      or the system file table is full. In MS-DOS versions 3.3 and later, you
  647.      can expand the per-process limit of 20 handles with a call to Int 21H
  648.      Function 67H (Set Handle Count).
  649.  
  650.   The handle file- and record-management calls may be gathered into the
  651.   following broad classifications for study:
  652.  
  653.  
  654.   Function                 Action
  655.   ──────────────────────────────────────────────────────────────────────────
  656.   Common handle file operations
  657.   3CH                     Create file (requires ASCIIZ string).
  658.   3DH                     Open file (requires ASCIIZ string).
  659.   3EH                     Close file.
  660.  
  661.   Common handle record operations
  662.   42H                     Set file pointer (also used to find file size).
  663.   3FH                     Read file.
  664.   40H                     Write file.
  665.  
  666.   Less commonly used handle operations
  667.   41H                     Delete file.
  668.   43H                     Get or modify file attributes.
  669.   44H                     IOCTL (I/O Control).
  670.   45H                     Duplicate handle.
  671.   46H                     Redirect handle.
  672.   56H                     Rename file.
  673.   57H                     Get or set file date and time.
  674.   5AH                     Create temporary file (versions 3.0 and later).
  675.   5BH                     Create file (fails if file already exists;
  676.                            versions 3.0 and later).
  677.   5CH                     Lock or unlock file region (versions 3.0 and
  678.                            later).
  679.   67H                     Set handle count (versions 3.3 and later).
  680.   68H                     Commit file (versions 3.3 and later).
  681.   6CH                     Extended open file (version 4).
  682.   ──────────────────────────────────────────────────────────────────────────
  683.  
  684.  
  685.   Compare the groups of handle-type functions in the preceding table with
  686.   the groups of FCB functions outlined earlier, noting the degree of
  687.   functional overlap. Section 2 of this book, "MS-DOS Functions Reference,"
  688.   gives detailed specifications for each of the handle functions, along with
  689.   assembly-language examples.
  690.  
  691. Handle File-Access Skeleton
  692.  
  693.   The following is a typical program sequence to access a file using the
  694.   handle family of functions (Figure 8-7):
  695.  
  696.   1.  Get the filename from the user by means of the buffered input service
  697.       (Int 21H Function 0AH) or from the command tail supplied by MS-DOS in
  698.       the PSP.
  699.  
  700.   2.  Put a zero at the end of the file specification in order to create an
  701.       ASCIIZ string.
  702.  
  703.   3.  Open the file using Int 21H Function 3DH and mode 2 (read/write
  704.       access), or create the file using Int 21H Function 3CH. (Be sure to
  705.       set the CX register to zero, so that you don't accidentally make a
  706.       file with special attributes.) Save the handle that is returned.
  707.  
  708.   4.  Set the file pointer using Int 21H Function 42H. You may set the
  709.       file-pointer position relative to one of three different locations:
  710.       the start of the file, the current pointer position, or the end of the
  711.       file. If you are performing sequential record I/O, you can usually
  712.       skip this step because MS-DOS will maintain the file pointer for you
  713.       automatically.
  714.  
  715.   5.  Read from the file (Int 21H Function 3FH) or write to the file (Int
  716.       21H Function 40H). Both of these functions require that the BX
  717.       register contain the file's handle, the CX register contain the length
  718.       of the record, and the DS:DX registers point to the data being
  719.       transferred. Both return the actual number of bytes transferred in the
  720.       AX register.
  721.  
  722.       In a read operation, if the number of bytes read is less than the
  723.       number requested, the end of the file has been reached. In a write
  724.       operation, if the number of bytes written is less than the number
  725.       requested, the disk containing the file is full. Neither of these
  726.       conditions is returned as an error code; that is, the carry flag is
  727.       not set.
  728.  
  729.   6.  If the program is not finished processing the file, go to step 4;
  730.       otherwise, close the file (Int 21H Function 3EH). Any normal exit
  731.       from the program will also close all active handles.
  732.  
  733.   ──────────────────────────────────────────────────────────────────────────
  734.   recsize      equ     1024                 ; file record size
  735.                .
  736.                .
  737.                .
  738.                mov   ah,3dh                 ; open input file
  739.                mov   al,0                   ; mode = read only
  740.                mov   dx,offset fname1       ; name of input file
  741.                int   21h
  742.                jc    no_file                ; jump if no file
  743.                mov   handle1,ax             ; save token for file
  744.                .
  745.                .
  746.                .
  747.                mov   ah,3ch                 ; create output file
  748.                mov   cx,0                   ; attribute = normal
  749.                mov   dx,offset fname2       ; name of output file
  750.                int   21h
  751.                jc    disk_full              ; jump if create fails
  752.                mov   handle2,ax             ; save token for file
  753.                .
  754.   next:        .                            ; process next record
  755.                .
  756.                mov   ah,3fh                 ; sequential read from
  757.                mov   bx,handle1             ; input file
  758.                mov   cx,recsize
  759.                mov   dx,offset buffer
  760.                int   21h
  761.                jc    bad_read               ; jump if read error
  762.                or    ax,ax                  ; check bytes transferred
  763.                jz    file_end               ; jump if end of file
  764.                .
  765.                .
  766.                .
  767.                mov   ah,40h                 ; sequential write to
  768.                mov   bx,handle2             ; output file
  769.                mov   cx,recsize
  770.                mov   dx,offset buffer
  771.                int   21h
  772.                jc    bad_write              ; jump if write error
  773.                cmp   ax,recsize             ; whole record written?
  774.                jne   disk_full              ; jump if disk is full
  775.                .
  776.                .
  777.                .
  778.                jmp   next                   ; process next record
  779.                .
  780.   file_end:    .                            ; reached end of input
  781.                .
  782.                mov   ah,3eh                 ; close input file
  783.                mov   bx,handle1
  784.                int   21h
  785.                .
  786.                .
  787.                .
  788.                mov   ah,3eh                 ; close output file
  789.                mov   bx,handle2
  790.                int   21h
  791.                .
  792.                .
  793.                .
  794.                mov   ax,4c00h               ; exit with return
  795.                int   21h                    ; code of zero
  796.                .
  797.                .
  798.                .
  799.   fname1       db    'OLDFILE.DAT',0        ; name of input file
  800.   fname2       db    'NEWFILE.DAT',0        ; name of output file
  801.   handle1      dw    0                      ; token for input file
  802.   handle2      dw    0                      ; token for output file
  803.   buffer       db    recsize dup (?)        ; buffer for file I/O
  804.   ──────────────────────────────────────────────────────────────────────────
  805.  
  806.   Figure 8-7.  Skeleton of an assembly-language program that performs
  807.   sequential processing on an input file and writes the results to an output
  808.   file using the handle file and record functions. This code assumes that
  809.   the DS and ES registers have already been set to point to the segment
  810.   containing the buffers and filenames.
  811.  
  812. Points to Remember
  813.  
  814.   Here is a summary of the pros and cons of using the handle file and record
  815.   operations in your program. Compare this list with the one given earlier
  816.   in the chapter for the FCB family of functions.
  817.  
  818.   Advantages:
  819.  
  820.   ■  The handle calls provide direct support for I/O redirection and pipes
  821.      with the standard input and output devices in a manner functionally
  822.      similar to that used by UNIX/XENIX.
  823.  
  824.   ■  The handle functions provide direct support for directories (the
  825.      hierarchical file structure) and special file attributes.
  826.  
  827.   ■  The handle calls support file sharing/locking and record locking in
  828.      networking environments.
  829.  
  830.   ■  Using the handle functions, the programmer can open channels to
  831.      character devices and treat them as files.
  832.  
  833.   ■  The handle calls make the use of random record access extremely easy.
  834.      The current file pointer can be moved to any byte offset relative to
  835.      the start of the file, the end of the file, or the current pointer
  836.      position. Records of any length, up to an entire segment (65,535
  837.      bytes), can be read to any memory address in one operation.
  838.  
  839.   ■  The handle functions have relatively good error reporting in MS-DOS
  840.      version 2, and error reporting has been enhanced even further in MS-DOS
  841.      versions 3.0 and later.
  842.  
  843.   ■  Microsoft strongly encourages use of the handle family of functions in
  844.      order to provide upward compatibility with MS OS/2.
  845.  
  846.   Disadvantages:
  847.  
  848.   ■  There is a limit per program of 20 concurrently open files and devices
  849.      using handles in MS-DOS versions 2.0 through 3.2.
  850.  
  851.   ■  Minor gaps still exist in the implementation of the handle functions.
  852.      For example, you must still use extended FCBs to change volume labels
  853.      and to access the contents of the special files that implement
  854.      directories.
  855.  
  856.  
  857. MS-DOS Error Codes
  858.  
  859.   When one of the handle file functions fails with the carry flag set, or
  860.   when a program calls Int 21H Function 59H (Get Extended Error
  861.   Information) following a failed FCB function or other system service, one
  862.   of the following error codes may be returned:
  863.  
  864.  
  865.   Value                    Meaning
  866.   ──────────────────────────────────────────────────────────────────────────
  867.   MS-DOS version 2 error codes
  868.   01H                      Function number invalid
  869.   02H                      File not found
  870.   03H                      Path not found
  871.   04H                      Too many open files
  872.   05H                      Access denied
  873.   06H                      Handle invalid
  874.   07H                      Memory control blocks destroyed
  875.   08H                      Insufficient memory
  876.   09H                      Memory block address invalid
  877.   0AH (10)                 Environment invalid
  878.   0BH (11)                 Format invalid
  879.   0CH (12)                 Access code invalid
  880.   0DH (13)                 Data invalid
  881.   0EH (14)                 Unknown unit
  882.   0FH (15)                 Disk drive invalid
  883.   10H (16)                 Attempted to remove current directory
  884.   11H (17)                 Not same device
  885.   12H (18)                 No more files
  886.  
  887.   Mappings to critical-error codes
  888.   13H (19)                 Write-protected disk
  889.   14H (20)                 Unknown unit
  890.   15H (21)                 Drive not ready
  891.   16H (22)                 Unknown command
  892.   17H (23)                 Data error (CRC)
  893.   18H (24)                 Bad request-structure length
  894.   19H (25)                 Seek error
  895.   1AH (26)                 Unknown media type
  896.   1BH (27)                 Sector not found
  897.   1CH (28)                 Printer out of paper
  898.   1DH (29)                 Write fault
  899.   1EH (30)                 Read fault
  900.   1FH (31)                 General failure
  901.  
  902.   MS-DOS version 3 and later extended error codes
  903.   20H (32)                 Sharing violation
  904.   21H (33)                 File-lock violation
  905.   22H (34)                 Disk change invalid
  906.   23H (35)                 FCB unavailable
  907.   24H (36)                 Sharing buffer exceeded
  908.   25H─31H (37─49)          Reserved
  909.   32H (50)                 Unsupported network request
  910.   33H (51)                 Remote machine not listening
  911.   34H (52)                 Duplicate name on network
  912.   35H (53)                 Network name not found
  913.   36H (54)                 Network busy
  914.   37H (55)                 Device no longer exists on network
  915.   38H (56)                 NetBIOS command limit exceeded
  916.   39H (57)                 Error in network adapter hardware
  917.   3AH (58)                 Incorrect response from network
  918.   3BH (59)                 Unexpected network error
  919.   3CH (60)                 Remote adapter incompatible
  920.   3DH (61)                 Print queue full
  921.   3EH (62)                 Not enough room for print file
  922.   3FH (63)                 Print file was deleted
  923.   40H (64)                 Network name deleted
  924.   41H (65)                 Network access denied
  925.   42H (66)                 Incorrect network device type
  926.   43H (67)                 Network name not found
  927.   44H (68)                 Network name limit exceeded
  928.   45H (69)                 NetBIOS session limit exceeded
  929.   46H (70)                 Temporary pause
  930.   47H (71)                 Network request not accepted
  931.   48H (72)                 Print or disk redirection paused
  932.   49H─4FH (73─79)          Reserved
  933.   50H (80)                 File already exists
  934.   51H (81)                 Reserved
  935.   52H (82)                 Cannot make directory
  936.   53H (83)                 Fail on Int 24H (critical error)
  937.   54H (84)                 Too many redirections
  938.   55H (85)                 Duplicate redirection
  939.   56H (86)                 Invalid password
  940.   57H (87)                 Invalid parameter
  941.   58H (88)                 Net write fault
  942.   ──────────────────────────────────────────────────────────────────────────
  943.  
  944.  
  945.   Under MS-DOS versions 3.0 and later, you can also use Int 21H Function
  946.   59H to obtain other information about the error, such as the error locus
  947.   and the recommended recovery action.
  948.  
  949. Critical-Error Handlers
  950.  
  951.   In Chapter 5, we discussed how an application program can take over the
  952.   Ctrl-C handler vector (Int 23H) and replace the MS-DOS default handler, to
  953.   avoid losing control of the computer when the user enters a Ctrl-C or
  954.   Ctrl-Break at the keyboard. Similarly, MS-DOS provides a
  955.   critical-error-handler vector (Int 24H) that defines the routine to be
  956.   called when unrecoverable hardware faults occur. The default MS-DOS
  957.   critical-error handler is the routine that displays a message describing
  958.   the error type and the cue
  959.  
  960.   Abort, Retry, Ignore?
  961.  
  962.   This message appears after such actions as the following:
  963.  
  964.   ■  Attempting to open a file on a disk drive that doesn't contain a floppy
  965.      disk or whose door isn't closed
  966.  
  967.   ■  Trying to read a disk sector that contains a CRC error
  968.  
  969.   ■  Trying to print when the printer is off line
  970.  
  971.   The unpleasant thing about MS-DOS's default critical-error handler is, of
  972.   course, that if the user enters an A for Abort, the application that is
  973.   currently executing is terminated abruptly and never has a chance to clean
  974.   up and make a graceful exit. Intermediate files may be left on the disk,
  975.   files that have been extended using FCBs are not properly closed so that
  976.   the directory is updated, interrupt vectors may be left pointing into the
  977.   transient program area, and so forth.
  978.  
  979.   To write a truly bombproof MS-DOS application, you must take over the
  980.   critical-error-handler vector and point it to your own routine, so that
  981.   your program intercepts all catastrophic hardware errors and handles them
  982.   appropriately. You can use MS-DOS Int 21H Function 25H to alter the Int
  983.   24H vector in a well-behaved manner. When your application exits, MS-DOS
  984.   will automatically restore the previous contents of the Int 24H vector
  985.   from information saved in the program segment prefix.
  986.  
  987.   MS-DOS calls the critical-error handler for two general classes of
  988.   errors── disk-related and non-disk-related──and passes different
  989.   information to the handler in the registers for each of these classes.
  990.  
  991.   For disk-related errors, MS-DOS sets the registers as shown on the
  992.   following page. (Bits 3─5 of the AH register are relevant only in MS-DOS
  993.   versions 3.1 and later.)
  994.  
  995.  
  996.   Register           Bit(s)            Significance
  997.   ──────────────────────────────────────────────────────────────────────────
  998.   AH                 7                 0, to signify disk error
  999.                      6                 Reserved
  1000.                      5                 0 = ignore response not allowed
  1001.                                        1 = ignore response allowed
  1002.                      4                 0 = retry response not allowed
  1003.                                        1 = retry response allowed
  1004.                      3                 0 = fail response not allowed
  1005.                                        1 = fail response allowed
  1006.                      1─2               Area where disk error occurred
  1007.                                        00 = MS-DOS area
  1008.                                        01 = file allocation table
  1009.                                        10 = root directory
  1010.                                        11 = files area
  1011.                      0                 0 = read operation
  1012.                                        1 = write operation
  1013.   AL                 0─7               Drive code (0 = A, 1 = B, and so
  1014.                                        forth)
  1015.   DI                 0─7               Driver error code
  1016.                      8─15              Not used
  1017.   BP:SI                                Segment:offset of device-driver
  1018.                                        header
  1019.   ──────────────────────────────────────────────────────────────────────────
  1020.  
  1021.  
  1022.   For non-disk-related errors, the interrupt was generated either as the
  1023.   result of a character-device error or because a corrupted memory image of
  1024.   the file allocation table was detected. In this case, MS-DOS sets the
  1025.   registers as follows:
  1026.  
  1027.   Register           Bit(s)            Significance
  1028.   ──────────────────────────────────────────────────────────────────────────
  1029.   AH                 7                 1, to signify a non-disk error
  1030.   DI                 0─7               Driver error code
  1031.                      8─15              Not used
  1032.   BP:SI                                Segment:offset of device-driver
  1033.                                        header
  1034.   ──────────────────────────────────────────────────────────────────────────
  1035.  
  1036.   To determine whether the critical error was caused by a character device,
  1037.   use the address in the BP:SI registers to examine the device attribute
  1038.   word at offset 0004H in the presumed device-driver header. If bit 15 is
  1039.   set, then the error was indeed caused by a character device, and the
  1040.   program can inspect the name field of the driver's header to determine the
  1041.   device.
  1042.  
  1043.   At entry to a critical-error handler, MS-DOS has already disabled
  1044.   interrupts and set up the stack as shown in Figure 8-8. A critical-error
  1045.   handler cannot use any MS-DOS services except Int 21H Functions 01H
  1046.   through 0CH (Traditional Character I/O), Int 21H Function 30H (Get MS-DOS
  1047.   Version), and Int 21H Function 59H (Get Extended Error Information).
  1048.   These functions use a special stack so that the context of the original
  1049.   function (which generated the critical error) will not be lost.
  1050.  
  1051.   ┌───────┐─┐
  1052.   │ Flags │ │
  1053.   ├───────┤ │  Flags and CS:IP pushed
  1054.   │  CS   │ ├─ on stack by original
  1055.   ├───────┤ │  Int 21H call
  1056.   │  IP   │ │
  1057.   ├───────┤═╡─SS:SP on entry to
  1058.   │  ES   │ │  Int 21H handler
  1059.   ├───────┤ │
  1060.   │  DS   │ │
  1061.   ├───────┤ │
  1062.   │  BP   │ │
  1063.   ├───────┤ │
  1064.   │  DI   │ │
  1065.   ├───────┤ ├─ Registers at point of
  1066.   │  SI   │ │  original Int 21H call
  1067.   ├───────┤ │
  1068.   │  DX   │ │
  1069.   ├───────┤ │
  1070.   │  CX   │ │
  1071.   ├───────┤ │
  1072.   │  BX   │ │
  1073.   ├───────┤ │
  1074.   │  AX   │ │
  1075.   ├───────┤═╡
  1076.   │ Flags │ │
  1077.   ├───────┤ │
  1078.   │  CS   │ ├─ Return address for
  1079.   ├───────┤ │  Int 24H handler
  1080.   │  IP   │ │
  1081.   └──────┘─┘
  1082.         └───── SS:SP on entry to
  1083.                Int 24H handler
  1084.  
  1085.   Figure 8-8.  The stack at entry to a critical-error handler.
  1086.  
  1087.   The critical-error handler should return to MS-DOS by executing an IRET,
  1088.   passing one of the following action codes in the AL register:
  1089.  
  1090.   Code               Meaning
  1091.   ──────────────────────────────────────────────────────────────────────────
  1092.   0                  Ignore the error (MS-DOS acts as though the original
  1093.                      function call had succeeded).
  1094.   1                  Retry the operation.
  1095.   2                  Terminate the process that encountered the error.
  1096.   3                  Fail the function (an error code is returned to the
  1097.                      requesting process). Versions 3.1 and later only.
  1098.   ──────────────────────────────────────────────────────────────────────────
  1099.  
  1100.   The critical-error handler should preserve all other registers and must
  1101.   not modify the device-driver header pointed to by BP:SI. A skeleton
  1102.   example of a critical-error handler is shown in Figure 8-9.
  1103.  
  1104.   ──────────────────────────────────────────────────────────────────────────
  1105.                                   ; prompt message used by
  1106.                                   ; critical-error handler
  1107.   prompt  db      cr,lf,'Critical Error Occurred: '
  1108.           db      'Abort, Retry, Ignore, Fail? $'
  1109.  
  1110.   keys    db      'aArRiIfF'      ; possible user response keys
  1111.   keys_len equ $-keys             ; (both cases of each allowed)
  1112.  
  1113.   codes   db      2,2,1,1,0,0,3,3 ; codes returned to MS-DOS kernel
  1114.                                   ; for corresponding response keys
  1115.  
  1116.   ;
  1117.   ; This code is executed during program's initialization
  1118.   ; to install the new critical-error handler.
  1119.   ;
  1120.           .
  1121.           .
  1122.           .
  1123.           push    ds              ; save our data segment
  1124.  
  1125.           mov     dx,seg int24    ; DS:DX = handler address
  1126.           mov     ds,dx
  1127.           mov     dx,offset int24
  1128.           mov     ax,2524h        ; function 25h = set vector
  1129.           int     21h             ; transfer to MS-DOS
  1130.  
  1131.           pop     ds              ; restore data segment
  1132.           .
  1133.           .
  1134.           .
  1135.   ;
  1136.   ; This is the replacement critical-error handler. It
  1137.   ; prompts the user for Abort, Retry, Ignore, or Fail, and
  1138.   ; returns the appropriate code to the MS-DOS kernel.
  1139.   ;
  1140.  
  1141.   int24   proc    far             ; entered from MS-DOS kernel
  1142.  
  1143.           push    bx              ; save registers
  1144.           push    cx
  1145.           push    dx
  1146.           push    si
  1147.           push    di
  1148.           push    bp
  1149.           push    ds
  1150.           push    es
  1151.   int24a: mov     ax,seg prompt   ; display prompt for user
  1152.           mov     ds,ax           ; using function 9 (print string
  1153.           mov     es,ax           ; terminated by $ character)
  1154.           mov     dx,offset prompt
  1155.           mov     ah,9
  1156.           int     21h
  1157.  
  1158.           mov     ah,1            ; get user's response
  1159.           int     21h             ; function 1 = read one character
  1160.  
  1161.           mov     di,offset keys  ; look up code for response key
  1162.           mov     cx,keys_len
  1163.           cld
  1164.           repne scasb
  1165.           jnz     int24a          ; prompt again if bad response
  1166.  
  1167.                                   ; set AL = action code for MS-DOS
  1168.                                   ; according to key that was entered:
  1169.                                   ; 0 = ignore, 1 = retry, 2 = abort,
  1170.                                   ; 3 = fail
  1171.           mov     al,[di+keys_len-1]
  1172.  
  1173.           pop     es              ; restore registers
  1174.           pop     ds
  1175.           pop     bp
  1176.           pop     di
  1177.           pop     si
  1178.           pop     dx
  1179.           pop     cx
  1180.           pop     bx
  1181.           iret                    ; exit critical-error handler
  1182.  
  1183.   int24   endp
  1184.   ──────────────────────────────────────────────────────────────────────────
  1185.  
  1186.   Figure 8-9.  A skeleton example of a replacement critical-error handler.
  1187.  
  1188.  
  1189. Example Programs: DUMP.ASM and DUMP.C
  1190.  
  1191.   The programs DUMP.ASM (Figure 8-10) and DUMP.C (Figure 8-11) are
  1192.   parallel examples of the use of the handle file and record functions. The
  1193.   assembly-language version, in particular, illustrates features of a
  1194.   well-behaved MS-DOS utility:
  1195.  
  1196.   ■  The program checks the version of MS-DOS to ensure that all the
  1197.      functions it is going to use are really available.
  1198.  
  1199.   ■  The program parses the drive, path, and filename from the command tail
  1200.      in the program segment prefix.
  1201.  
  1202.   ■  The program uses buffered I/O for speed.
  1203.  
  1204.   ■  The program sends error messages to the standard error device.
  1205.  
  1206.   ■  The program sends normal program output to the standard output device,
  1207.      so that the dump output appears by default on the system console but
  1208.      can be redirected to other character devices (such as the line printer)
  1209.      or to a file.
  1210.  
  1211.   The same features are incorporated into the C version of the program, but
  1212.   some of them are taken care of behind the scenes by the C runtime library.
  1213.  
  1214.   ──────────────────────────────────────────────────────────────────────────
  1215.           name    dump
  1216.           page    55,132
  1217.           title   DUMP--display file contents
  1218.  
  1219.   ;
  1220.   ;  DUMP--Display contents of file in hex and ASCII
  1221.   ;
  1222.   ;  Build:   C>MASM DUMP;
  1223.   ;           C>LINK DUMP;
  1224.   ;
  1225.   ;  Usage:   C>DUMP unit:\path\filename.exe [ >device ]
  1226.   ;
  1227.   ;  Copyright (C) 1988 Ray Duncan
  1228.   ;
  1229.  
  1230.   cr      equ     0dh             ; ASCII carriage return
  1231.   lf      equ     0ah             ; ASCII line feed   tab     equ     09h             ; ASCII tab code
  1232.   blank   equ     20h             ; ASCII space code
  1233.  
  1234.   cmd     equ     80h             ; buffer for command tail
  1235.  
  1236.   blksize equ     16              ; input file record size
  1237.  
  1238.   stdin   equ     0               ; standard input handle
  1239.   stdout  equ     1               ; standard output handle
  1240.   stderr  equ     2               ; standard error handle
  1241.   _TEXT   segment word public 'CODE'
  1242.  
  1243.           assume  cs:_TEXT,ds:_DATA,es:_DATA,ss:STACK
  1244.  
  1245.   dump    proc    far             ; entry point from MS-DOS
  1246.  
  1247.           push    ds              ; save DS:0000 for final
  1248.           xor     ax,ax           ; return to MS-DOS, in case
  1249.           push    ax              ; function 4ch can't be used
  1250.  
  1251.           mov     ax,_DATA        ; make our data segment
  1252.           mov     ds,ax           ; addressable via DS register
  1253.  
  1254.                                   ; check MS-DOS version
  1255.           mov     ax,3000h        ; function 30h = get version
  1256.           int     21h             ; transfer to MS-DOS
  1257.           cmp     al,2            ; major version 2 or later?
  1258.           jae     dump1           ; yes, proceed
  1259.  
  1260.                                   ; if MS-DOS 1.x, display
  1261.                                   ; error message and exit
  1262.           mov     dx,offset msg3  ; DS:DX = message address
  1263.           mov     ah,9            ; function 9 = print string
  1264.           int     21h             ; transfer to MS-DOS
  1265.           ret                     ; then exit the old way
  1266.  
  1267.   dump1:                          ; check if filename present
  1268.           mov     bx,offset cmd   ; ES:BX = command tail
  1269.           call    argc            ; count command arguments
  1270.           cmp     ax,2            ; are there 2 arguments?
  1271.           je      dump2           ; yes, proceed
  1272.  
  1273.                                   ; missing filename, display
  1274.                                   ; error message and exit
  1275.           mov     dx,offset msg2  ; DS:DX = message address
  1276.           mov     cx,msg2_len     ; CX = message length
  1277.           jmp     dump9           ; go display it
  1278.  
  1279.   dump2:                          ; get address of filename
  1280.           mov     ax,1            ; AX = argument number
  1281.                                   ; ES:BX still = command tail
  1282.           call    argv            ; returns ES:BX = address,
  1283.                                   ; and AX = length
  1284.  
  1285.           mov     di,offset fname ; copy filename to buffer
  1286.           mov     cx,ax           ; CX = length
  1287.   dump3:  mov     al,es:[bx]      ; copy one byte
  1288.           mov     [di],al
  1289.           inc     bx              ; bump string pointers
  1290.           inc     di
  1291.           loop    dump3           ; loop until string done
  1292.           mov     byte ptr [di],0 ; add terminal null byte
  1293.  
  1294.           mov     ax,ds           ; make our data segment
  1295.           mov     es,ax           ; addressable by ES too
  1296.                                   ; now open the file
  1297.           mov     ax,3d00h        ; function 3dh = open file
  1298.                                   ; mode 0 = read only
  1299.           mov     dx,offset fname ; DS:DX = filename
  1300.           int     21h             ; transfer to MS-DOS
  1301.           jnc     dump4           ; jump, open successful
  1302.  
  1303.                                   ; open failed, display
  1304.                                   ; error message and exit
  1305.           mov     dx,offset msg1  ; DS:DX = message address
  1306.           mov     cx,msg1_len     ; CX = message length
  1307.           jmp     dump9           ; go display it
  1308.  
  1309.   dump4:  mov     fhandle,ax      ; save file handle
  1310.  
  1311.   dump5:                          ; read block of file data
  1312.           mov     bx,fhandle      ; BX = file handle
  1313.           mov     cx,blksize      ; CX = record length
  1314.           mov     dx,offset fbuff ; DS:DX = buffer
  1315.           mov     ah,3fh          ; function 3fh = read
  1316.           int     21h             ; transfer to MS-DOS
  1317.  
  1318.           mov     flen,ax         ; save actual length
  1319.           cmp     ax,0            ; end of file reached?
  1320.           jne     dump6           ; no, proceed
  1321.  
  1322.           cmp     word ptr fptr,0 ; was this the first read?
  1323.           jne     dump8           ; no, exit normally
  1324.  
  1325.                                   ; display empty file
  1326.                                   ; message and exit
  1327.           mov     dx,offset msg4  ; DS:DX = message address
  1328.           mov     cx,msg4_len     ; CX = length
  1329.           jmp     dump9           ; go display it
  1330.   dump6:                          ; display heading at
  1331.                                   ; each 128-byte boundary
  1332.           test    fptr,07fh       ; time for a heading?
  1333.           jnz     dump7           ; no, proceed
  1334.  
  1335.                                   ; display a heading
  1336.           mov     dx,offset hdg   ; DS:DX = heading address
  1337.           mov     cx,hdg_len      ; CX = heading length
  1338.           mov     bx,stdout       ; BX = standard output
  1339.           mov     ah,40h          ; function 40h = write
  1340.           int     21h             ; transfer to MS-DOS
  1341.  
  1342.   dump7:  call    conv            ; convert binary record
  1343.                                   ; to formatted ASCII
  1344.  
  1345.                                   ; display formatted output
  1346.           mov     dx,offset fout  ; DX:DX = output address
  1347.           mov     cx,fout_len     ; CX = output length
  1348.           mov     bx,stdout       ; BX = standard output
  1349.           mov     ah,40h          ; function 40h = write
  1350.           int     21h             ; transfer to MS-DOS
  1351.           jmp     dump5           ; go get another record
  1352.  
  1353.   dump8:                          ; close input file
  1354.           mov     bx,fhandle      ; BX = file handle
  1355.           mov     ah,3eh          ; function 3eh = close
  1356.           int     21h             ; transfer to MS-DOS
  1357.  
  1358.           mov     ax,4c00h        ; function 4ch = terminate,
  1359.                                   ; return code = 0
  1360.           int     21h             ; transfer to MS-DOS
  1361.  
  1362.   dump9:                          ; display message on
  1363.                                   ; standard error device
  1364.                                   ; DS:DX = message address
  1365.                                   ; CX = message length
  1366.           mov     bx,stderr       ; standard error handle
  1367.           mov     ah,40h          ; function 40h = write
  1368.           int     21h             ; transfer to MS-DOS
  1369.  
  1370.           mov     ax,4c01h        ; function 4ch = terminate,
  1371.                                   ; return code = 1
  1372.           int     21h             ; transfer to MS-DOS
  1373.  
  1374.   dump    endp
  1375.   conv    proc    near            ; convert block of data
  1376.                                   ; from input file
  1377.  
  1378.           mov     di,offset fout  ; clear output format
  1379.           mov     cx,fout_len-2   ; area to blanks
  1380.           mov     al,blank
  1381.           rep stosb
  1382.  
  1383.           mov     di,offset fout  ; convert file offset
  1384.           mov     ax,fptr         ; to ASCII for output
  1385.           call    w2a
  1386.  
  1387.           mov     bx,0            ; init buffer pointer
  1388.  
  1389.   conv1:  mov     al,[fbuff+bx]   ; fetch byte from buffer
  1390.           mov     di,offset foutb ; point to output area
  1391.  
  1392.                                   ; format ASCII part...
  1393.                                   ; store '.' as default
  1394.           mov     byte ptr [di+bx],'.'
  1395.  
  1396.           cmp     al,blank        ; in range 20h-7eh?
  1397.           jb      conv2           ; jump, not alphanumeric
  1398.  
  1399.           cmp     al,7eh          ; in range 20h-7eh?
  1400.           ja      conv2           ; jump, not alphanumeric
  1401.  
  1402.           mov     [di+bx],al      ; store ASCII character
  1403.  
  1404.   conv2:                          ; format hex part...
  1405.           mov     di,offset fouta ; point to output area
  1406.           add     di,bx           ; base addr + (offset*3)
  1407.           add     di,bx
  1408.           add     di,bx
  1409.           call    b2a             ; convert byte to hex
  1410.  
  1411.           inc     bx              ; advance through record
  1412.           cmp     bx,flen         ; entire record converted?
  1413.           jne     conv1           ; no, get another byte
  1414.  
  1415.                                   ; update file pointer
  1416.           add     word ptr fptr,blksize
  1417.  
  1418.           ret
  1419.  
  1420.   conv    endp
  1421.   w2a     proc    near            ; convert word to hex ASCII
  1422.                                   ; call with AX = value
  1423.                                   ;           DI = addr for string
  1424.                                   ; returns AX, DI, CX destroyed
  1425.  
  1426.           push    ax              ; save copy of value
  1427.           mov     al,ah
  1428.           call    b2a             ; convert upper byte
  1429.  
  1430.           pop     ax              ; get back copy
  1431.           call    b2a             ; convert lower byte
  1432.           ret
  1433.  
  1434.   w2a     endp
  1435.  
  1436.   b2a     proc    near            ; convert byte to hex ASCII
  1437.                                   ; call with AL = binary value
  1438.                                   ;           DI = addr for string
  1439.                                   ; returns   AX, DI, CX modified
  1440.  
  1441.           sub     ah,ah           ; clear upper byte
  1442.           mov     cl,16
  1443.           div     cl              ; divide byte by 16
  1444.           call    ascii           ; quotient becomes the first
  1445.           stosb                   ; ASCII character
  1446.           mov     al,ah
  1447.           call    ascii           ; remainder becomes the
  1448.           stosb                   ; second ASCII character
  1449.           ret
  1450.  
  1451.   b2a     endp
  1452.  
  1453.   ascii   proc    near            ; convert value 0-0fh in AL
  1454.                                   ; into "hex ASCII" character
  1455.  
  1456.           add     al,'0'          ; offset to range 0-9
  1457.           cmp     al,'9'          ; is it > 9?
  1458.           jle     ascii2          ; no, jump
  1459.           add     al,'A'-'9'-1    ; offset to range A-F,
  1460.  
  1461.   ascii2: ret                     ; return AL = ASCII char
  1462.  
  1463.   ascii   endp
  1464.  
  1465.   argc    proc    near            ; count command-line arguments
  1466.                                   ; call with ES:BX = command line
  1467.                                   ; returns   AX = argument count
  1468.           push    bx              ; save original BX and CX
  1469.           push    cx              ; for later
  1470.           mov     ax,1            ; force count >= 1
  1471.  
  1472.   argc1:  mov     cx,-1           ; set flag = outside argument
  1473.  
  1474.   argc2:  inc     bx              ; point to next character
  1475.           cmp     byte ptr es:[bx],cr
  1476.           je      argc3           ; exit if carriage return
  1477.           cmp     byte ptr es:[bx],blank
  1478.           je      argc1           ; outside argument if ASCII blank
  1479.           cmp     byte ptr es:[bx],tab
  1480.           je      argc1           ; outside argument if ASCII tab
  1481.  
  1482.                                   ; otherwise not blank or tab,
  1483.           jcxz    argc2           ; jump if already inside argument
  1484.  
  1485.           inc     ax              ; else found argument, count it
  1486.           not     cx              ; set flag = inside argument
  1487.           jmp     argc2           ; and look at next character
  1488.  
  1489.   argc3:  pop     cx              ; restore original BX and CX
  1490.           pop     bx
  1491.           ret                     ; return AX = argument count
  1492.  
  1493.   argc    endp
  1494.  
  1495.   argv    proc    near            ; get address & length of
  1496.                                   ; command line argument
  1497.                                   ; call with ES:BX = command line
  1498.                                   ;           AX    = argument #
  1499.                                   ; returns   ES:BX = address
  1500.                                   ;           AX    = length
  1501.  
  1502.           push    cx              ; save original CX and DI
  1503.           push    di
  1504.  
  1505.           or      ax,ax           ; is it argument 0?
  1506.           jz      argv8           ; yes, jump to get program name
  1507.  
  1508.           xor     ah,ah           ; initialize argument counter
  1509.  
  1510.   argv1:  mov     cx,-1           ; set flag = outside argument
  1511.   argv2:  inc     bx              ; point to next character
  1512.           cmp     byte ptr es:[bx],cr
  1513.           je      argv7           ; exit if carriage return
  1514.           cmp     byte ptr es:[bx],blank
  1515.           je      argv1           ; outside argument if ASCII blank
  1516.           cmp     byte ptr es:[bx],tab
  1517.           je      argv1           ; outside argument if ASCII tab
  1518.  
  1519.                                   ; if not blank or tab...
  1520.           jcxz    argv2           ; jump if already inside argument
  1521.  
  1522.           inc     ah              ; else count arguments found
  1523.           cmp     ah,al           ; is this the one we're looking for?
  1524.           je      argv4           ; yes, go find its length
  1525.           not     cx              ; no, set flag = inside argument
  1526.           jmp     argv2           ; and look at next character
  1527.  
  1528.   argv4:                          ; found desired argument, now
  1529.                                   ; determine its length...
  1530.           mov     ax,bx           ; save param starting address
  1531.  
  1532.   argv5:  inc     bx              ; point to next character
  1533.           cmp     byte ptr es:[bx],cr
  1534.           je      argv6           ; found end if carriage return
  1535.           cmp     byte ptr es:[bx],blank
  1536.           je      argv6           ; found end if ASCII blank
  1537.           cmp     byte ptr es:[bx],tab
  1538.           jne     argv5           ; found end if ASCII tab
  1539.  
  1540.   argv6:  xchg    bx,ax           ; set ES:BX = argument address
  1541.           sub     ax,bx           ; and AX = argument length
  1542.           jmp     argvx           ; return to caller
  1543.  
  1544.   argv7:  xor     ax,ax           ; set AX = 0, argument not found
  1545.           jmp     argvx           ; return to caller
  1546.  
  1547.   argv8:                          ; special handling for argv = 0
  1548.           mov     ax,3000h        ; check if DOS 3.0 or later
  1549.           int     21h             ; (force AL = 0 in case DOS 1)
  1550.           cmp     al,3
  1551.           jb      argv7           ; DOS 1 or 2, return null param
  1552.           mov     es,es:[2ch]     ; get environment segment from PSP
  1553.           xor     di,di           ; find the program name by
  1554.           xor     al,al           ; first skipping over all the
  1555.           mov     cx,-1           ; environment variables...
  1556.           cld
  1557.   argv9:  repne scasb             ; scan for double null (can't use
  1558.           scasb                   ; SCASW since might be odd addr)
  1559.           jne     argv9           ; loop if it was a single null
  1560.           add     di,2            ; skip count word in environment
  1561.           mov     bx,di           ; save program name address
  1562.           mov     cx,-1           ; now find its length...
  1563.           repne scasb             ; scan for another null byte
  1564.           not     cx              ; convert CX to length
  1565.           dec     cx
  1566.           mov     ax,cx           ; return length in AX
  1567.  
  1568.   argvx:                          ; common exit point
  1569.           pop     di              ; restore original CX and DI
  1570.           pop     cx
  1571.           ret                     ; return to caller
  1572.  
  1573.   argv    endp
  1574.  
  1575.   _TEXT    ends
  1576.  
  1577.   _DATA   segment word public 'DATA'
  1578.  
  1579.   fname   db      64 dup (0)      ; buffer for input filespec
  1580.  
  1581.   fhandle dw      0               ; token from PCDOS for input file
  1582.  
  1583.   flen    dw      0               ; actual length read
  1584.  
  1585.   fptr    dw      0               ; relative address in file
  1586.  
  1587.   fbuff   db      blksize dup (?) ; data from input file
  1588.  
  1589.   fout    db      'nnnn'          ; formatted output area
  1590.           db      blank,blank
  1591.   fouta   db      16 dup ('nn',blank)
  1592.           db      blank
  1593.   foutb   db      16 dup (blank),cr,lf
  1594.   fout_len equ    $-fout
  1595.  
  1596.   hdg     db      cr,lf           ; heading for each 128 bytes
  1597.           db      7 dup (blank)   ; of formatted output
  1598.           db      '0  1  2  3  4  5  6  7  '
  1599.           db      '8  9  A  B  C  D  E  F',cr,lf
  1600.   hdg_len equ     $-hdg
  1601.   msg1    db      cr,lf
  1602.           db      'dump: file not found'
  1603.           db      cr,lf
  1604.   msg1_len equ    $-msg1
  1605.  
  1606.   msg2    db      cr,lf
  1607.           db      'dump: missing file name'
  1608.           db      cr,lf
  1609.   msg2_len equ    $-msg2
  1610.  
  1611.   msg3    db      cr,lf
  1612.           db      'dump: wrong MS-DOS version'
  1613.           db      cr,lf,'$'
  1614.  
  1615.   msg4    db      cr,lf
  1616.           db      'dump: empty file'
  1617.           db      cr,lf
  1618.   msg4_len equ    $-msg4
  1619.  
  1620.   _DATA   ends
  1621.  
  1622.   STACK   segment para stack 'STACK'
  1623.  
  1624.           db      64 dup (?)
  1625.  
  1626.   STACK   ends
  1627.  
  1628.           end     dump
  1629.   ──────────────────────────────────────────────────────────────────────────
  1630.  
  1631.   Figure 8-10.  The assembly-language version: DUMP.ASM.
  1632.  
  1633.   ──────────────────────────────────────────────────────────────────────────
  1634.   /*
  1635.       DUMP.C      Displays the binary contents of a file in
  1636.                   hex and ASCII on the standard output device.
  1637.  
  1638.       Compile:    C>CL DUMP.C
  1639.  
  1640.       Usage:      C>DUMP unit:path\filename.ext
  1641.  
  1642.       Copyright (C) 1988 Ray Duncan
  1643.   */
  1644.  
  1645.   #include <stdio.h>
  1646.   #include <io.h>
  1647.   #include <fcntl.h>
  1648.   #define REC_SIZE 16               /* input file record size    */
  1649.  
  1650.   main(int argc, char *argv[])
  1651.   {
  1652.       int fd;                       /* input file handle         */
  1653.         int status = 0;             /* status from file read     */
  1654.       long fileptr = 0L;            /* current file byte offset  */
  1655.       char filebuf[REC_SIZE];       /* data from file            */
  1656.  
  1657.       if(argc != 2)                 /* abort if missing filename */
  1658.       {   fprintf(stderr,"\ndump: wrong number of parameters\n");
  1659.           exit(1);
  1660.       }
  1661.  
  1662.                                     /* open file in binary mode,
  1663.                                        abort if open fails       */
  1664.       if((fd = open(argv[1],O_RDONLY | O_BINARY) ) == -1)
  1665.       {   fprintf(stderr, "\ndump: can't find file %s \n", argv[1]);
  1666.           exit(1);
  1667.       }
  1668.  
  1669.                                     /* read and dump records
  1670.                                        until end of file         */
  1671.       while((status = read(fd,filebuf,REC_SIZE) ) != 0)
  1672.       {   dump_rec(filebuf, fileptr, status);
  1673.           fileptr += REC_SIZE;
  1674.       }
  1675.  
  1676.       close(fd);                    /* close input file          */
  1677.       exit(0);                      /* return success code       */
  1678.   }
  1679.  
  1680.   /*
  1681.       Display record (16 bytes) in hex and ASCII on standard output
  1682.   */
  1683.  
  1684.   dump_rec(char *filebuf, long fileptr, int length)
  1685.   {
  1686.       int i;                        /* index to current record   */
  1687.  
  1688.       if(fileptr % 128 == 0)        /* display heading if needed */
  1689.           printf("\n\n       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
  1690.  
  1691.       printf("\n%04lX ",fileptr);   /* display file offset       */
  1692.  
  1693.                                     /* display hex equivalent of
  1694.                                        each byte from file       */
  1695.       for(i = 0; i < length; i++)
  1696.           printf(" %02X", (unsigned char) filebuf[i]);
  1697.  
  1698.       if(length != 16)              /* spaces if partial record  */
  1699.           for (i=0; i<(16-length); i++) printf("   ");
  1700.  
  1701.                                     /* display ASCII equivalent of
  1702.                                        each byte from file       */
  1703.       printf("  ");
  1704.       for(i = 0; i < length; i++)
  1705.       {   if(filebuf[i] < 32 || filebuf[i] > 126) putchar('.');
  1706.           else putchar(filebuf[i]);
  1707.       }
  1708.   }
  1709.   ──────────────────────────────────────────────────────────────────────────
  1710.  
  1711.   Figure 8-11.  The C version: DUMP.C.
  1712.  
  1713.   The assembly-language version of the DUMP program contains a number of
  1714.   subroutines that you may find useful in your own programming efforts.
  1715.   These include the following:
  1716.  
  1717.   Subroutine  Action
  1718.   ──────────────────────────────────────────────────────────────────────────
  1719.   argc        Returns the number of command-line arguments.
  1720.   argv        Returns the address and length of a particular command-line
  1721.               argument.
  1722.   w2a         Converts a binary word (16 bits) into hex ASCII for output.
  1723.   b2a         Converts a binary byte (8 bits) into hex ASCII for output.
  1724.   ascii       Converts 4 bits into a single hex ASCII character.
  1725.   ──────────────────────────────────────────────────────────────────────────
  1726.  
  1727.   It is interesting to compare these two equivalent programs. The C program
  1728.   contains only 77 lines, whereas the assembly-language program has 436
  1729.   lines. Clearly, the C source code is less complex and easier to maintain.
  1730.   On the other hand, if size and efficiency are important, the DUMP.EXE file
  1731.   generated by the C compiler is 8563 bytes, whereas the assembly-language
  1732.   DUMP.EXE file is only 1294 bytes and runs twice as fast as the C program.
  1733.  
  1734.  
  1735.  
  1736.