home *** CD-ROM | disk | FTP | other *** search
-
-
-
- The PC Assembler Tutor mmxviii
- ______________________
-
- If you now load the user library along with BASIC, you can look
- at the segments and offsets of the arrays. Here is a program with
- a couple of arrays:
-
- **********************************************
- k% = 12000
- DIM array1!(k%), array2!(k%), array3!(12000)
-
- value1! = VARPTR (array1!(0))
- value2! = VARPTR (array2!(0))
- value3! = VARPTR (array3!(0))
- PRINT value1!, value2!, value3!
- **********************************************
-
- Each array is 12001 * 4 (bytes) or 48004 bytes long. The first
- two have been defined as dynamic, so they can be anywhere in
- memory as long as they're after the start of DS. The third one is
- static, so it must be entirely in DS. Here's the output:
-
- 68032 116064 6252
-
- Remember, these offsets are not relative to the start of memory,
- they are relative to the start of the DS segment. The DS segment
- only goes up to offset 65535 so the first two are outside of DS
- while the third one is totally inside (6252 + 48004 = 54006 which
- is less than 65536). PTR86 is going to give us a SEGMENT:OFFSET
- pair relative to the start of memory.{1} The form for the call
- is:
-
- CALL PTR86 (segment%, offset%, value!)
-
- where value! is the number returned by VARPTR.
-
- We'll add the following code to the bottom of the above program:
-
- ***************************************
- CALL PTR86 ( seg1% , off1%, value1! )
- CALL PTR86 ( seg2% , off2%, value2! )
- CALL PTR86 ( seg3% , off3%, value3! )
-
- PRINT seg1%, off1%
- PRINT seg2%, off2%
- PRINT seg3%, off3%
- ***************************************
-
- What does the program print now?
-
- 68032 116064 6252
- ____________________
-
- 1. If you are using QuickBasic 4.0 or later this has become
- simplified. You can just use VARSEG and VARPTR. These will give
- you a segment offset pair which is usable in a subroutine call.
-
- ______________________
-
- The PC Assembler Tutor - Copyright (C) 1990 Chuck Nelson
-
-
-
-
- BASIC II - Interfacing BASIC With Assembler mmxix
- ___________________________________________
-
- 19125 0
- 22127 0
- 15263 12
-
- PTR86 has calculated the segments. The first two offsets are 0
- and the third one is 12. Even though the third array is in the DS
- segment, PTR86 has recalculated the segment to find the highest
- segment which contains the first byte of the data. If you want to
- do a little calculation, you can figure out that while this
- program was running, DS was set to segment 14873.
-
- VARPTR and PTR86 can be used to do this calculation for any array
- element, not just array(0). Here's VARPTR:
-
- value1! = VARPTR (array1!(0))
- value2! = VARPTR (array1!(196))
- value3! = VARPTR (array1!(2781))
- PRINT value1!, value2!, value3!
-
-
- And here's the output:
-
- 68032 68816 79156
-
- From now on, we will have VARPTR inside of the PTR86 call:
-
- CALL PTR86 ( seg1% , off1%, VARPTR (array1!(0)) )
- CALL PTR86 ( seg2% , off2%, VARPTR (array2!(0)) )
- CALL PTR86 ( seg3% , off3%, VARPTR (array3!(0)) )
-
-
- It is clearer and uses less space. Of course, we can use this for
- any element in the array, not just the beginning of the array:
-
- CALL PTR86 ( seg1% , off1%, VARPTR (array1!(0)) )
- CALL PTR86 ( seg2% , off2%, VARPTR (array1!(5076)) )
- CALL PTR86 ( seg3% , off3%, VARPTR (array1!(1983)) )
-
-
-
- We now have all the ammunition we need to do a quicker disk
- write. We can pass the offset address of any numeric data in the
- DS segment, we can pass the string descriptor address of any
- string, and we can pass the SEGMENT:OFFSET pair of any array
- anywhere.
-
- Before doing our disk write program, I need to say something
- about subroutine calls. You notice that when I show a subroutine
- call I am always showing what numeric type I am passing. There is
- a reason for this. Let's step back from assembler for a minute
- and do a BASIC program with a subroutine. Here's the program:
-
- **********************************************************
- floatA! = 27.925
- floatB! = 16.96
- integerA% = 300
- integerB% = 140
-
-
-
-
- The PC Assembler Tutor mmxx
- ______________________
-
- CALL CheckTheNumbers (integerA%, integerB%, floatA!, floatB!)
- PRINT floatA!, floatB!, integerA%, integerB%
- END
-
- SUB CheckTheNumbers ( int1%, int2%, flt1!, flt2!) STATIC
- int1% = int1% + 7
- int2% = int2% * 45
- flt1! = flt1! + 19.0
- flt2! = flt2! * 43.0
- END SUB
- ***********************************************************
-
- This gives the following output:
-
- 46.925 729.28 307 6300
-
- This is nothing earthshaking. Now let's change one line in the
- program:
-
- CALL CheckTheNumbers (floatA!, floatB!, integerA%, integerB%)
-
- In the call statement I have put single precision numbers where
- the integers were and integers where the single precision numbers
- were. Now let's look at the output:
-
- Overflow.
- ENTER to debug, SPACEBAR to edit
-
- We had an overflow. Here's where the debugger said the error was:
-
- int2% = int2% * 45
-
- But int2% received the floating point number for floatB!, and
- that is 16.96. Since 16.96 * 45 = 763.2, where was the overflow?
- What the subroutine saw was not 16.96 but the first two binary
- bytes of floatB! (since we passed the address of floatB!). It
- thought these were an integer, performed a multiplication and got
- an error because the result was too big for an integer. Now,
- change the value in floatB! to:
-
- floatB! = 1.696E-29
-
- and run the program again. Your results should be:
-
- 27.92501 1.693756E-29 0 16792
-
- These numbers have no relation to either what we started out with
- or what we did. Why? Because the subroutine mixed binary
- information from single precision numbers with binary information
- from integers and came up with unmitigated garbage. It was not
- only doing that, it was also writing 4 bytes of information into
- a 2 byte integer. Both flt1! and flt2! were writing past the end
- of the data and overwriting something else.
-
- There is NEVER any checking between a subroutine and the calling
- program to see that the correct numeric types are being passed
- (integers, long integers, single precision, double precision). In
-
-
-
-
- BASIC II - Interfacing BASIC With Assembler mmxxi
- ___________________________________________
-
- C and Pascal this checking is done by the compiler at compile
- time and the compiler will howl if you try to do something like
- this. In BASIC (my BASIC at least), this checking is not being
- done. Therefore, it is IMPERATIVE that you make sure that you
- pass the correct data types.
-
- Having given that warning, we are going to build an assembler
- subprogram that opens a file for writing, then writes a block of
- data from memory to disk. The form of the call will be:
-
- CALL BlockToDisk ("filename$"+CHR$(0), seg%, offset%, # of bytes)
-
- Notice that there MUST be a 0 after the filename. When you use
- this function, you must always have:
-
- filename$ = "my.file.name" + CHR$(0)
-
- The disk interrupt that is used in this subroutine expects a 'C'
- string (terminated by a number 0, not an ASCII character '0'). If
- you don't do it, you will almost certainly get an error.
-
- This is followed by the segment of the first byte of data, the
- offset of the first byte of data, and the number of BYTES (not
- array elements) to write.
-
- Which way does BASIC load the arguments to a subroutine? From
- left to right, just like PASCAL. Also, in BASIC, ALL subroutine
- calls are far calls. Therefore, BASIC will do the following when
- it calls BlockToDisk:
-
- PUSH address of file_descriptor
- PUSH address of block segment
- PUSH address of block offset
- PUSH address of length
- CALL FAR PTR BlockToDisk
-
- There are two things to notice here. First, these are all
- ADDRESSES of the data, not the data items themselves. Upon entry
- to the subroutine and initialization of BP, the stack will look
- like this:
-
- address of file_descriptor bp+12
- address of block segment bp+10
- address of block offset bp+8
- address of length bp+6
- old CS bp+4
- old IP bp+2
- BP-> old BP bp
-
- Secondly, the name of the subroutine does not have any periods.
- BASIC allows periods '.' but does not allow underscores '_' while
- assembler allows underscores but doesn't allow periods. (Periods
- have a special meaning in assembler; they are used in
- structures).
-
- In order to go on from here, you need a book about interrupts.
- This information is from "DOS Programmer's Reference" by Terry
-
-
-
-
- The PC Assembler Tutor mmxxii
- ______________________
-
- Dettmann, but if you have "The Peter Norton Programmer's Guide to
- The IBM PC", that's fine too. I'm going to give only partial
- information about these interrupts and you should have complete
- information.
-
- Here's the program. The explaination will come afterwards.
-
- *******************************************************
- ; BASOUT.ASM
- include \pushregs.mac
- PUBLIC BLOCKTODISK
- DGROUP GROUP _DATA
- ; - - - - - - - - - - - - - - - - - - - - -
- _DATA SEGMENT PUBLIC 'DATA'
- file_handle dw ?
- error_message db "Disk i/o error #"
- error_byte db " ", 13, 10, "$"
- _DATA ENDS
- ; - - - - - - - - - - - - - - - - - - - - -
- _TEXT SEGMENT 'CODE'
- ASSUME cs:_TEXT, ds:DGROUP
- ; - - - - - - - - - - - - - - - - - - - - -
- print_error proc far
- mov ah, 9 ; print error message
- mov dx, offset DGROUP:error_message
- int 21h
- ret
- print_error endp
- ; - - - - - - - - - - - - - - - - - - - - - - - - -
- ; BlockToDisk ( filename , array SEG, array OFF, # of bytes)
- ; this is for BASIC
-
- DESCRIPTOR_LOCATION EQU [bp + 12]
- SEGMENT_LOCATION EQU [bp + 10]
- OFFSET_LOCATION EQU [bp + 8]
- LENGTH_LOCATION EQU [bp + 6]
-
- BLOCKTODISK proc far
- push bp
- mov bp, sp
- PUSHREGS ax, bx, cx, dx, si, ds
-
- ; open a new file or truncate an old one
- mov si, DESCRIPTOR_LOCATION
- mov ah, 3Ch ; open new or truncate old
- mov cx, 0 ; normal file attribute
- mov dx, [si+2] ; [si] =length, [si+2] =location
- int 21h
- jnc write_the_file ; ok if CF=0, error if CF=1
-
- mov error_byte, '1' ; cannot open
- call print_error
- jmp exit
-
- write_the_file:
- mov file_handle, ax ; store handle for later use
- mov bx, ax ; file_handle to bx
-
-
-
-
- BASIC II - Interfacing BASIC With Assembler mmxxiii
- ___________________________________________
-
- mov ah, 40h ; int 21h ah = 40h, write block
- mov si, LENGTH_LOCATION
- mov cx, [si] ; # of bytes into CX
- push ds ; save BASIC's DS
- mov si, OFFSET_LOCATION
- mov dx, [si] ; offset to DX
- mov si, SEGMENT_LOCATION
- mov ds, [si] ; segment to DS
- int 21h
- pop ds ; restore BASIC's DS
- jnc normal_exit ; ok if CF=0, error if CF=1
- mov error_byte, '2' ; bad file write
- call print_error
-
- normal_exit:
- mov ah, 3Eh ; close the file
- mov bx, file_handle
- int 21h
-
- exit:
- POPREGS ax, bx, cx, dx, si, ds
- mov sp, bp
- pop bp
- ret (8) ; pop 4 words (8 bytes off the stack)
-
- BLOCKTODISK endp
- ; - - - - - - - - - - - - - - - - - - - - - - - - -
- _TEXT ENDS
- END
- **************************************************************
-
- This is using the standardized segment names so the data will be
- in the DS segment. Notice the DGROUP GROUP declaration. Also
- notice that in the 'print_error' subroutine, we have done an
- offset override with 'offset DGROUP:error_message'. You need to
- do this every time to avoid errors with the offset addressing
- whenever you are using the DGROUP GROUP directive. Go back to the
- discussion of simplified segment directives if you don't remember
- this.
-
- The main program has 3 interrupts. The first one opens a new file
- or truncates an old one to zero length. The file will be usable
- for reading and/or writing:
-
- Int 21h function 3Ch
- AH = 3Ch
- CX = 0 0 indicates a normal file
- DS:DX address of ASCII filename (terminated by 0)
-
- Returns:
- AX = file handle if CF = 0
- or: AX = error code if CF = 1
-
- This filename can be any legitimate pathname specification, and
- must be terminated by a zero. Like all the disk interrupts we
- will see in this chapter, if there is an error, the interrupt
- will set CF = 1. Otherwise it will clear CF = 0. If CF = 0 you
-
-
-
-
- The PC Assembler Tutor mmxxiv
- ______________________
-
- can go on; if CF = 1, there was an error and you need to
- terminate the subroutine and do some error reporting. The file
- handle is a number from 0 to 65535 which the operating system
- gives your program to uniquely identify that open file. There is
- no other open file in the system which has that number. Guard it
- carefully because it is your ONLY access to the file.
-
- The second interrupt writes a block of data to disk.
-
- Int 21h function 40h
- AH = 40h
- BX = file handle
- CX = number of bytes to write
- DS:DX = address of first byte of data
-
- Returns:
- AX = actual number of bytes written if CF = 0
- AX = error code if CF = 1
-
- This too sets the carry flag if there was an error and clears it
- if there wasn't. It is limited to writing 65535 bytes at a time,
- but the largest array we can have is 65535 bytes (actually 65534
- since all data types have an even number of bytes), so this is no
- problem.
-
- The third interrupt closes the file.
-
- Int 21h function 3Eh
- AH = 3Eh
- BX = File handle
-
- Also, the print-error subroutine has an interrupt
-
- Int 21h function 09h
- AH = 9
- DS:DX = first byte of string.
-
- This string must be terminated by a dollar sign '$' (of all
- things). The message is on two lines so we can insert an error
- number into the middle of the message. This is a quick and dirty
- interrupt for string printing.
-
- All interrupt numbers and function numbers are hex. This is
- standard for interrupts. If things go wierd, always check first
- to make sure that you have a hex number and not a decimal number.
-
- The data has an 'ASSUME ds:DGROUP' statement.
-
- Like Pascal, BASIC requires that the CALLED subroutine pop the
- arguments off the stack, so we pop 4 extra words (8 extra bytes)
- with:
-
- ret (8)
-
- Assemble this program and put the object file in a library with
- the other object files by using BUILDLIB.EXE. Now all we need is
- a BASIC program to use this. Here it is:
-
-
-
-
- BASIC II - Interfacing BASIC With Assembler mmxxv
- ___________________________________________
-
-
- ************************************************************
- DIM large.array! (10000)
-
- FOR i% = 1 to 10000
- large.array! (i%) = 2.167832E+19
- NEXT i%
-
- filename$ = "blocktxt.doc" + CHR$ (0)
- length% = 40000 - 65536
- PRINT time$
- CALL PTR86 (segment%, offset%, VARPTR (large.array!(1)) )
- CALL BlockToDisk ( filename$ , segment% , offset%, length% )
- PRINT
- PRINT time$
- ************************************************************
-
- There is an extra PRINT statement there which I will explain
- later. We are starting at large.array!(1) because that is where
- we started with the other programs. why are we subtracting 65536?
- Because BASIC has a limit of -32768 to +32767, so we store 40000
- as its modular equivalent (mod 65536).
-
- How long does the disk write take? From 2 to 3 seconds, and much
- of that time was spent opening and truncating the file. This is
- significantly better than the other ways of doing i/o. In fact,
- the limits of this routine are the limits of your system. It is
- literally impossible to do disk i/o any faster than this.
-
- Try using a filename that doesn't have a CHR$(0) at the end. You
- should get an error message. In my BASIC, here is the output:
-
- 19:50:23
- Disk i/o error #1
- 19:50:23
-
- Now remove that lone PRINT statement (the next to the last line).
- Here's my output:
-
- 19:50:00
- D9:50:00 error #1
- 1
-
- For the QuickBASIC 3.0 environment, BASIC thinks that it has
- complete control of screen i/o, so it is not doing its i/o in a
- standard way and is overwriting the error message. If you are
- going to do any screen i/o from assembler, you will have to think
- of a way to live in harmony with BASIC.{2} We simply trick BASIC
- into writing an empty line where the message was. This may not
- always work correctly, especially if the window is scrolling up.
-
- ____________________
-
- 2. The easiest way to do this is to save the whole screen
- image and cursor location, do what you want using the whole
- screen, and then restore the screen and the cursor before
- returning.
-
-
-
-
- The PC Assembler Tutor mmxxvi
- ______________________
-
- This whole program only involved interrupts. There is nothing
- intrinsically assembler-like in its capabilities. In fact, we'll
- do its disk read counterpart entirely in BASIC.
-
- Let's do something that requires assembler language. The BASIC
- program:
- *******************************************
- FOR i% = 1 to 10000
- to.array!(i%) = from.array!(i%)
- NEXT i%
- *******************************************
-
- get's the job done, but is isn't all that fast. It requires about
- 5.5 seconds. This is a natural for assembler. Dive down into the
- assembler level, move the string, and come back up for air. Our
- BASIC program will be:
-
- *******************************************
- n% = 10000
- DIM from.array! (n%), to.array! (n%)
-
- PRINT time$
- FOR i% = 1 to 10000
- to.array!(i%) = from.array!(i%)
- NEXT i%
- PRINT time$
- FOR j% = 1 to 50
- cnt% = 40000 - 65536 'count
- CALL PTR86 (from.seg%, from.off%, VARPTR( from.array!(1)) )
- CALL PTR86 (to.seg%, to.off%, VARPTR ( to.array!(1)) )
- CALL BlockMove(from.seg%, from.off%, to.seg%, to.off%, cnt%)
- NEXT j%
- PRINT time$
- ********************************************
-
- We are doing 50 repeats of the bottom section of code so you will
- be able to average the time. Here's the assembler program:
-
- ; - - - - - - - - - -
- include /pushregs.mac
- _TEXT SEGMENT PUBLIC 'CODE'
- ASSUME cs:_TEXT
- PUBLIC BlockMove
- ; - - - - - - - - - -
- ; BlockMove ( from.seg, from.off, to.seg, to.off, byte.count)
- ; for BASIC
- ; MOVSW is from DS:[SI] to ES:[DI]
-
- FROM_SEG_ADDRESS EQU [bp+14]
- FROM_OFFSET_ADDRESS EQU [bp+12]
- TO_SEG_ADDRESS EQU [bp+10]
- TO_OFFSET_ADDRESS EQU [bp+8]
- BYTE_COUNT_ADDRESS EQU [bp+6]
- ; - - - - - - - - - -
- BlockMove proc far
- push bp
- mov bp, sp
-
-
-
-
- BASIC II - Interfacing BASIC With Assembler mmxxvii
- ___________________________________________
-
- PUSHREGS ax, bx, cx, dx, si, di, es, ds
-
- mov si, TO_SEG_ADDRESS
- mov es, [si] ; to_seg to ES
- mov si, TO_OFFSET_ADDRESS
- mov di, [si] ; to_offset to DI
- mov si, BYTE_COUNT_ADDRESS
- mov cx, [si] ; byte count to CX
- mov si, FROM_SEG_ADDRESS
- mov ax, [si] ; temporary storage for new DS
- mov si, FROM_OFFSET_ADDRESS
- mov si, [si] ; from_offset to SI
- mov ds, ax ; now move from_seg to DS
- sub bx, bx ; clear BX
- shr cx, 1 ; divide by 2, remainder in CF
- rcl bx, 1 ; move CF to low bit of BX
- cld ; clear DF (go up)
- rep movsw ; the block move (count in CX)
- and bx, bx ; one extra byte?
- jz exit
- movsb ; move one last byte
-
- exit:
- POPREGS ax, bx, cx, dx, si, di, es, ds
- mov sp, bp
- pop bp
- ret (10)
-
- BlockMove endp
- ; - - - - - - - - - -
- _TEXT ENDS
- END
- ; - - - - - - - - - -
-
- This is a string block move using MOVSW. The count is the number
- of BYTES, not the number of array elements. CX contains the byte
- count. It is divided by 2 so we can move words, and if there is a
- remainder (i.e. if the number was odd), BX is set to 1. We move
- words instead of bytes, and afterwards we check BX to see if we
- need to move 1 byte more. This routine takes about 1/8 second
- instead of 5.5 seconds. This is a considerable savings in time.
- There is a small problem, however. If the FROM block and the TO
- block overlap (e.g. move 400 bytes from array!(11) to
- array!(26)), then the data may be compromised. To be exact, if
- the start of the FROM data is below the start of the TO data, the
- data will be screwed up. The general solution of this for BASIC
- is in BLKMOVE.ASM, which is in a file called MISHMASH.DOC which
- is in \XTRAFILE.
-
- Finally, here's the disk read done entirely in BASIC. Once again
- you need your DOS interrupt book.
-
- ********************************************************
- ' READBLK.BAS
- ' reads a block from the disk into memory
-
- DIM in.regs%(9), out.regs%(9)
-
-
-
-
- The PC Assembler Tutor mmxxviii
- ______________________
-
- DIM big.array! (10000)
-
- AX% = 0
- BX% = 1
- CX% = 2
- DX% = 3
- BP% = 4
- SI% = 5
- DI% = 6
- FLGS% = 7
- DS% = 8
- ES% = 9
-
- filename$ = "blocktxt.doc" + CHR$(0)
-
- PRINT time$
- ' open an existing file for reading
- in.regs%(AX%) = &H3D00
- in.regs%(DX%) = SADD (filename$)
- CALL INT86 (&H21,VARPTR (in.regs%(0)),VARPTR (out.regs%(0)))
-
- IF (out.regs%(FLGS%) AND &H0001) <> 0 THEN
- PRINT "Can't open the file."
- GOTO ExitProgram
- END IF
-
- ' set the i/o pointer to 0
- file.handle% = out.regs%(AX%)
- in.regs%(AX%) = &H4200
- in.regs%(BX%) = file.handle%
- in.regs%(CX%) = 0
- in.regs%(DX%) = 0
- CALL INT86 (&H21, VARPTR(in.regs%(0)), VARPTR(out.regs%(0)))
-
- IF (out.regs%(FLGS%) AND &H0001) <> 0 THEN
- PRINT "File pointer error"
- GOTO CloseFile
- END IF
-
- in.regs%(AX%) = &H3F00
- in.regs%(BX%) = file.handle%
- in.regs%(CX%) = 40000 - 65536
- CALL PTR86 ( segment%, offset%, VARPTR (big.array!(1) ))
- in.regs%(DX%) = offset%
- in.regs%(DS%) = segment%
- CALL INT86X (&H21,VARPTR(in.regs%(0)),VARPTR(out.regs%(0)))
- IF (out.regs%(FLGS%) AND &H0001) <> 0 THEN
- PRINT "Disk read error"
- END IF
-
-
- CloseFile:
- in.regs%(AX%) = &H3E00
- in.regs%(BX%) = file.handle%
- CALL INT86 (&H21, VARPTR(in.regs%(0)), VARPTR(out.regs%(0)))
-
- PRINT time$
-
-
-
-
- BASIC II - Interfacing BASIC With Assembler mmxxix
- ___________________________________________
-
-
- ExitProgram:
- END
- ******************************************************
-
- This shows the use of INT86 in BASIC. We have two 10 element
- integer arrays (0 - 9). One is used for putting the data into the
- registers before the interrupt call and the other is used for
- getting the data out of the registers after the interrupt. At the
- top we have substituted variable names for the array elements
- they represent. This is the only way to make sense of things in
- BASIC. What is the difference between INT86 and INT86X? INT86X
- also changes ES and DS.
-
- We need a different file opening call because the last one
- TRUNCATED the file. This one just opens a pre-existing file and
- we put 00 in AL to signal a file read:
-
- INT 21h Function 3Dh
- AH = 3dh ; open a pre-existing file
- AL = 0 ; file read
- DS:DX ; pointer to a 00h terminated string
-
- Returns
- AX = file handle if CF = 0
- AX = error code if CF = 1
-
- We use SADD to get the filename offset in DS because this is
- exactly what DOS wants. SADD is a function that gives the offset
- of a string (the string itself) relative to the DS segment. It
- gives no length information.
-
- At every step along the way we check CF to make sure it is 0 and
- not 1. We need to make sure that the file pointer is at the
- beginning of the file. This is:
-
- INT 21h Function 42h
- AH = 42h ; move file pointer
- AL = 0 ; count from beginning of the file
- CX:DX = 0 ; 4 byte offset from beginning of file
-
- Returns
- DX:AX = new file-pointer location if CF = 0
- AX = error code if CF = 1
-
- Then we do the block read:
-
- INT 21h Function 3Fh
- AH = 3Fh ; read a block from disk
- BX = file handle
- CX = byte count
- DS:DX = pointer to first byte of block
-
- Returns
- AX = # of bytes read (can be less than CX) if CF = 0
- AX = error code if CF = 1
-
-
-
-
-
- The PC Assembler Tutor mmxxx
- ______________________
-
- Finally, we close the file:
-
- INT 21h Function 3Eh
- AH = 3Eh ; close a file
- BX = file handle
-
- Returns
- nothing if CF = 0 (close was successful)
- AX = error code if CF = 1
-
- All you need to know about CF is that when the flags are
- represented as a word (2 bytes) CF = &H0001.
-
- Is this faster than our other access methods? Our worst case
- before took 79 seconds and this one takes 1 second. This is
- certainly worth using for large disk reads. We don't need to go
- down to the assembler level, either.
-
- What's the difference between this and bload? Bload requires that
- the file has already been stored from memory. When you use BSAVE,
- the binary information is written to disk, but the first seven
- bytes is BLOAD information. The first byte (0FDh) is a signature
- byte. The next six bytes are three words. (1) the segment where
- the data came from, (2) the offset where the data came from, and
- (3) the length of the data. Of course, having these seven bytes
- in the front makes the file incompatible with everything else in
- the world. It even makes it difficult to load the information
- into BASIC the first time, since this seven byte header is
- missing in the original data unless the data came from a BASIC
- file.
-
-
-
- So what sorts of things are candidates for assembler subroutines?
- Things that are cumbersome in BASIC. If you want the top byte of
- a number, that's difficult. If you want to rotate the bits of a
- number, that's extremely hard. Shifting bits is hard. Practically
- everything involving unsigned numbers is problematic. For every
- assembler instruction, if you can't do it easily in BASIC you
- should make a subroutine that does it in assembler. How about one
- that does unsigned division? Another subroutine that you might
- want to make is one that returns both the quotient and the
- remainder from signed division. This cuts the work in half if you
- need both of them.
-
- Well, use BASIC any way you want, but most of all, have fun!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- BASIC II - Interfacing BASIC With Assembler mmxxxi
- ___________________________________________
-
-
- SUMMARY
-
- BASIC strings are defined by STRING DESCRIPTORS. A string
- descriptor is a 4 byte block that contains the LENGTH of the
- string and its LOCATION in the DS segment. Though you may modify
- individual bytes of a string from the assembler level, you may
- not alter the length without interfering with BASIC's memory
- management system.
-
- BASIC passes all arguments by reference. That is it sends the
- offset address of the data instead of the data itself. The ruiles
- are:
-
- 1) If it is a single piece of numeric data, the offset is
- relative to the DS segment.
-
- 2) If it is a string, the address is the address of the
- STRING DESCRIPTOR which contains both the length and
- location of the string.
-
- 3) To reference an array, use VARPTR (array(0)) to get the
- offset and then PTR86 to convert this to a SEGMENT:OFFSET
- pair which is usable by the assembler subroutine.
-
- 4) If you pass a single array element array(x) instead of
- using VARPTR, BASIC will pass the location of that element,
- but the element might be separated from the rest of the
- array, so only pass an individual element if you want the
- element itself and not the array.{3}
-
- SADD (stringname$)
- SADD [Microsoft's string address function] is used to pass
- the offset address of stringname$ relative to BASIC's DS
- segment. It should only be used with 00h terminated strings
- since this gives no length information. It can, however, be
- used in conjunction with LEN (stringname$).
-
- number! = VARPTR (variable)
- In older BASICs, VARPTR gives the offset address of
- "variable" relative to the first byte of the DS segment.
- This variable can be anywhere in memory from DS:0000 to the
- end of memory, and the number returned will be a single
- precision number in the range 0 to 1,048,576.
-
- VARSEG and VARPTR
- In more recent BASICs, using a combination of VARSEG and
- ____________________
-
- 3. The rule here is that if the array itself is outside of the
- DS segment (if it is a dynamic array), BASIC will make a copy of
- the element inside of DS before the CALL, give you the address of
- the COPY, and return the copy to its appropriate place in the
- array after the CALL. This copy can be hundreds of thousands of
- bytes away from the actual array. If you want the element itself
- this works properly, but if you want the array, the address will
- be the wrong address.
-
-
-
-
- The PC Assembler Tutor mmxxxii
- ______________________
-
- VARPTR has supplanted the use of PTR86.
-
- PTR86 ( segment%, offset%, VARPTR (variable) )
- PTR86 [Microsoft's segmentation scheme] takes the result
- provided by VARPTR and adds it to DS to come up with a total
- address. It then converts this absolute address into a
- SEGMENT:OFFSET pair where the segment is the highest segment
- that contains the first byte of the variable from VARPTR and
- the offset is a number from 0 to 15 which is the offset of
- this variable in this segment.
-
- RET (x)
- When executing a return, all called subroutines must pop the
- arguments passed to them by BASIC. The number of BYTES
- popped is twice the number of arguments (as long as you are
- passing addresses and not actual data).
-
- CALL MY_ROUTINE (arg1, arg2, arg3, etc)
- Arguments are always PUSHed on the stack from left to right,
- so this call will:
-
- PUSH address of arg1
- PUSH address of arg2
- PUSH address of arg3
- etc.
-
- in that order.
-
- INT86 ( interrupt.number%, in.reg.array%(9), out.reg.array%(9) )
- INT86 executes a DOS interrupt (interrupt.number%). The
- integers in in.reg.array% are put into the arithmetic
- registers before the call and the arithmetic registers are
- put into out.reg.array after the call. INT86X does the same
- thing but also changes the DS and ES segment registers.
- Consult your BASIC manual for the proper ordering of the
- registers in the array. Neither of these changes CS, SS or
- SP.
-
-