ASM3.TXT


                  - ASMVLA01 - File I/O - 04/14/93 -

    Lately we have been quite busy with school, so this second issue is a 
little behind schedule.  But that's life... This little issue will quickly
show off the DOS file functions: read, write, open, close, create & others.  
They are all pretty much the same, so there isn't a whole lot to go over.
But, as a bonus, I'm going to throw in a bit about how to do a subroutine.
Let's do the subroutine stuff first.

`Procedures' as they are called, are declared like this:

────────────────────────────────────────────────────────────────────────────

PROC TheProcedure

    ...             ;do whatever..
    
    ret             ;MUST have a RET statement!
ENDP TheProcedure

────────────────────────────────────────────────────────────────────────────

    In the procedure, you can do basically anything you want, just at the 
end of it, you say ret.  You can also specify how to call the PROC by putting
a NEAR or FAR after the procedure name.  This tells the compiler whether
to change segment AND offset, or just offset when the procedure is called.
Note that if you don't specify, it compiles into whatever the default is for
the current .MODEL (small = near, large = far)

────────────────────────────────────────────────────────────────────────────

PROC TheProc NEAR

    ...

    ret             ;this compiles to `retn' (return near- pops offset off
ENDP TheProc        ; stack only)

    OR

PROC TheProc FAR

    ...

    ret             ;compiles to `retf' pops both offset & segment off stack
ENDP TheProc        ; pops offset first

────────────────────────────────────────────────────────────────────────────

    That's basically all there is to that.  Note that if you REALLY wanted to
be tricky, you could do a far jump by doing this:

────────────────────────────────────────────────────────────────────────────
    push    seg TheProc
    push    offset TheProc
    retf
────────────────────────────────────────────────────────────────────────────

    This would "return" you to the beginning of the procedure "TheProc"...
This code is just to illustrate a point.  If you actually did something like
this and compiled and executed it, it would bomb.  Know why?  What happens 
when it hits the `ret' in the PROC?  Well it pops off the offset and puts 
it in IP and then pops the segment and puts it in CS.  Who knows what was
on the stack... will return to an unknown address and probably crash.  (It
DEFINATELY will not continue executing your code.)

    Of course, the only stack operations are PUSH and POP.  All they do is 
push or pop off the stack a word sized or a Dword sized piece of data.  NEVER
under ANY circumstance try to push a byte sized piece of data!  The results 
are unpredictable.  Well, not really, but just don't do it, ok?

    There are also two commands that'll save you some time and code space:

PUSHA and POPA (push all and Pop all)

    PUSHA pushes the general registers in this order:

AX, CX, DX, BX, SP, BP, SI, DI

    POPA pops the general registers in this order:

DI, SI, BP, (sp), BX, DX, CX, AX

    SP is different because popa does NOT restore the value of SP.  It merely 
pops it off and throws it away.

    For the 386+, pushad and popad push and pop all extended registers in
the same order.  You don't need to memorize the order, because you don't
need to know the order until you go and get tricky. (hint: the location of
AX on the stack is [sp + 14] - useful if you want to change what AX returns,
but you did a pusha cause you wanted to save all the registers (except AX)
Then you'd do a popa, and AX= whatever value you put in there.

    ────

    Alright, now a slightly different topic: memory management

    Ok, this isn't true by-the-book memory management, but you need to know
one thing:  Upon execution of a program, DOS gives it ALL memory up to the
address A000:0000. This happens to be the beginning of the VGA buffer...
Another thing you must know is that, if you used DOSSEG at the top of your
file, the segment is the last piece of your program.  The size of the segment
is derived from the little command `STACK 200h' or whatever the value was
that you put up there.  The 200h is the number of bytes in the stack.  To get
the number of paragraphs, you'd divide by 16.  Here's an example of how I can
get a pointer to the first valid available segment that I can use for data:

────────────────────────────────────────────────────────────────────────────
    mov     ax,ss       ;grab the stack segment
    add     ax,200h/16  ;add the size of the stack 200h/16 = 20h

    ;AX now contains the value of the first available segment the you can
    ; use.
────────────────────────────────────────────────────────────────────────────

    This is very nice, because you can just plop your data right there
and you have a 64k buffer you can use for anything you want.

    Ok, say you want to find out how much memory is available to use.  This
would be done like this:  (no suprises, I hope.)

────────────────────────────────────────────────────────────────────────────
    mov     ax,ss       ;grab the stack segment
    add     ax,200h/16  ;add the size of the stack 200h/16 = 20h
    mov     bx,0A000h   ;upper limit of the free memory
    sub     bx,ax       ;bx= # of paragraphs available
────────────────────────────────────────────────────────────────────────────

    Pretty darn simple.  That's enough of the overhead that you must know
to understand the included ANSI viewer (asm3.asm)  

        Now to the FILE I/O stuff...

    Files can be opened, read from, written to, created, and closed.  To open
a file, all you need to do is give the DOS interrupt a name & path.  All
references to that file are done through what's known as a file handle. A
file handle is simply a 16bit integer that DOS uses to identify the file.
It's used more or less like an index into chart of pointers that point to 
a big structure that holds all the info about the file- like current position
in the file, file type, etc.. all the data needed to maintain a file.
The `FILES= 20' thing in your autoexec simply tells DOS how much memory to
grab for those structures. ( Files=20 grabs enough room for 20 open files. )

    ANYway, here's each of the important function calls and a rundown on what
they do and how to work them.

────────────────────────────────────────────────────────────────────────────
FILE OPEN: Function 3Dh

 IN:
    ah= 3Dh
    al= open mode

        bits 7-3: Stuff that doesn't matter to us
        bits 2-0: Access code
            000 read only access
            001 write only access
            010 read and write access

    DS:DX= pointer to the ASCIIZ filename
        ASCIIZ means that its an ASCII string with a Zero on the end.

 Returns:
        CF=1 error occured
            AX= error code- don't worry about what they are, if the carry
                is set, you didn't open the file.

        CF=0 no error
            AX= File Handle ;you need to keep this- it's your only way to
                            ; reference your file!

  ──── EXAMPLE ────

    [...]   ;header stuff

    .CODE           ;this stuff is used for all the examples

  FileName  db "TextFile.TXT",0
  FileHandle dw 0
  Buffer    db  300 dup (0)
  BytesRead dw  0
  FileSize  dd  0

    [...]   ;more stuff

    mov     ax,3d00h    ; open file for read only
    mov     ax,cs
    mov     ds,ax       ;we use CS, cause it's pointing to the CODE segment
                        ; and our file name is in the code segment
    mov     dx,offset FileName
    int     21h
    jc      FileError_Open

    mov     [FileHandle],ax

    [...]   ;etc...

────────────────────────────────────────────────────────────────────────────
FILE CLOSE: Function 3Eh

  IN:
    AH= 3Eh
    BX= File Handle

  RETURN:
    CF=1 error occured, but who cares?
  
  ──── EXAMPLE ────

    mov     bx,[FileHandle]
    mov     ah,3eh
    int     21h

────────────────────────────────────────────────────────────────────────────
FILE READ: Function 3Fh

  IN:
    AH= 3Fh
    BX= File Handle
    CX= Number of bytes to read
    DS:DX= where to put data that is read from the file (in memory)
    
  RETURN:
    AX= number of bytes actually read- if 0, then you tried to read from
        the end of the file.

  ──── EXAMPLE ────

    mov     bx,[FileHandle]
    mov     ax,cs
    mov     ds,ax
    mov     dx,offset buffer
    mov     ah,3Fh
    mov     cx,300
    int     21h

    mov     [BytesRead],ax

────────────────────────────────────────────────────────────────────────────
FILE WRITE: Function 40h

  IN:
    AH= 40h
    BX= File Handle
    CX= Number of bytes to write
    DS:DX= where to read data from (in memory) to put on disk
    
  RETURN:
    AX= number of bytes actually written- if not equal to the number of bytes
        that you wanted to write, you have an error.
        
  ──── EXAMPLE ────

    mov     bx,[FileHandle]
    mov     ax,cs
    mov     ds,ax
    mov     dx,offset buffer
    mov     ah,40h
    mov     cx,[BytesRead]
    int     21h

    cmp     cx,ax
    jne     FileError_Write

────────────────────────────────────────────────────────────────────────────
FILE CREATE: Function 3Ch

 IN:
    ah= 3Ch
    cl= file attribute

        bit 0: read-only
        bit 1: hidden
        bit 2: system
        bit 3: volume label
        bit 4: sub directory
        bit 5: Archive
        bit 6&7: reserved

    DS:DX= pointer to the ASCIIZ filename
        ASCIIZ means that its an ASCII string with a Zero on the end.

 Returns:
        CF=1 error occured
            AX= error code- don't worry about what they are, if CF
                is set, you didn't create the file.

        CF=0 no error
            AX= File Handle ;you need to keep this- it's your only way to
                            ; reference your file!
  ──── EXAMPLE ────

    mov     ah,3ch
    mov     ax,cs
    mov     ds,ax       ;we use CS, cause it's pointing to the CODE segment
                        ; and our file name is in the code segment
    mov     dx,offset FileName
    mov     cx,0        ;no attributes
    int     21h
    jc      FileError_Create

    mov     [FileHandle],ax

────────────────────────────────────────────────────────────────────────────
FILE DELETE: Function 41h

 IN:
    ah= 41h
    DS:DX= pointer to the ASCIIZ filename
    
 Returns:
        CF=1 error occured
            AX= error code- 2= file not found, 3= path not found
                    5= access denied

        CF=0 no error

  ──── EXAMPLE ────

    mov     ah,41h      ;kill the sucker
    mov     ax,cs
    mov     ds,ax
    mov     dx,offset FileName
    int     21h
    jc      FileError_Delete

────────────────────────────────────────────────────────────────────────────
FILE MOVE POINTER: Function 42h

 IN:
    ah= 42h
    BX= File Handle
    CX:DX= 32 bit pointer to location in file to move to    
    AL= 0  offset from beginning of file
      = 1  offset from curent position
      = 2  offset from the end of the file
      
 Returns:
        CF=1 error occured
            AX= error code- no move occured

        CF=0 no error
            DX:AX 32 bit pointer to indicate current location in file
            
  ──── EXAMPLE ────

    mov     ah,42h      ;find out the size of the file
    mov     bx,[FileHandle]
    xor     cx,cx
    xor     dx,dx
    mov     al,2
    int     21h
    
    mov     [word low FileSize],ax
    mov     [word high FileSize],dx ;load data into filesize

    (or in MASM mode, 

            mov word ptr [FileSize],ax
            mov word ptr [FileSize+2],dx

    need I say why I like Ideal mode? )

────────────────────────────────────────────────────────────────────────────
FILE CHANGE MODE: Function 43h

 IN:
    ah= 43h
    DS:DX= pointer to the ASCIIZ filename
    al= 0
        returns file attributes in CX
    al= 1 
        sets file attributes to what's in CX
    
 Returns:
        CF=1 error occured
            AX= error code- 2= file not found, 3= path not found.
                    5= access denied

        CF=0 no error

  ──── EXAMPLE ──── Lets erase a hidden file in your root directory...

  FileName db   "C:\msdos.sys",0

    [...]

    mov     ah,43h          ;change attribute to that of a normal file
    mov     ax,cs
    mov     ds,ax
    mov     dx,offset FileName
    mov     al,1            ;set to whats in CX
    mov     cx,0            ;attribute = 0
    int     21h

    mov     ah,41h          ;Nuke it with the delete command
    int     21h

────────────────────────────────────────────────────────────────────────────

    Well, that's all for now.  I hope this info is enough for you to do some 
SERIOUS damage... :)  I just don't want to see any 'bombs' running around
erasing the hidden files in the root directory, ok?

    Anyway, go take a look at asm3.asm- it's a SIMPLE ansi/text displayer.
It just opens the file, reads it all into a "buffer" that was "allocated"
immediatly after the stack & reads in the entire file (if it's < 64k) and 
prints out the file character by character via DOS's print char (fn# 2).
Very simple and very slow.  You'd need a better print routine to go faster...
The quickest display programs would decode the ANSI on its own... But that's
kinda a chore...  Oh, well.  Enjoy.

    Draeden/VLA


    Suggested projects:

    1)  Write a program that will try to open a file, but if it does not
        find it, the program creates the file and fills it with a simple
        text message.

    2)  Write a program that will input your keystrokes and write them
        directly to a text file.

    3)  The write & read routines actually can be used for a file or device.
        Try to figure out what the FileHandle for the text screen is by
        writing to the device with various file handles.  This same channel,
        when read from, takes it's data from the keyboard.  Try to read data
        from the keyboard.  Maybe read like 20 characters...  CTRL-Z is the 
        end of file marker.

    4)  Try to use a file as `virtual memory'- open it for read/write access
        and write stuff to it and then read it back again after moving the 
        cursor position.



┌──────────┬───────────────────────────────────────────────────────────────
│ ASM3.ASM │
└──────────┘

;   VERY, VERY simple ANSI/text viewer
;
;   Coded by Draeden [VLA]
;

    DOSSEG
    .MODEL SMALL
    .STACK 200h
    .CODE
    Ideal

;===- Data -===

BufferSeg   dw  0

ErrMsgOpen  db  "Error opening `"
FileName    db  "ANSI.TXT",0,8,"'$"     ;8 is a delete character
                                        ;0 is required for filename 
                                        ;(displays a space)
FileLength dw 0

;===- Subroutines -===

PROC DisplayFile NEAR
    push    ds

    mov     ax,cs
    mov     ds,ax
    mov     ax,3d00h    ;open file (ah=3dh)
    mov     dx,offset FileName
    int     21h
    jc      OpenError
    mov     bx,ax       ;move the file handle into bx

    mov     ds,[BufferSeg]
    mov     dx,0            ;load to [BufferSeg]:0000
    mov     ah,3fh
    mov     cx,0FFFFh       ;try to read an entire segments worth
    int     21h

    mov     [cs:FileLength],ax

    mov     ah,3eh
    int     21h             ;close the file

    cld
    mov     si,0
    mov     cx,[cs:FileLength]
PrintLoop:
    mov     ah,2
    lodsb
    mov     dl,al
    int     21h         ;print a character

    dec     cx
    jne     PrintLoop
    
    pop     ds
    ret

OpenError:
    mov     ah,9
    mov     dx,offset ErrMsgOpen
    int     21h

    pop     ds
    ret
ENDP DisplayFile

;===- Main Program -===

START:
    mov     ax,cs
    mov     ds,ax
    mov     bx,ss
    add     bx,200h/10h     ;get past the end of the file
    mov     [BufferSeg],bx  ;store the buffer segment

    call    DisplayFile

    mov     ax,4c00h
    int     21h
END START