home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Overload
/
ShartewareOverload.cdr
/
database
/
db3tips2.zip
/
DB3NTS.TXT
< prev
next >
Wrap
Text File
|
1986-07-01
|
45KB
|
1,162 lines
dBASE III Tips (DB3N8509.TXT)
1. dBASE III Anomalies and Workarounds
>>> & Function
The macro (&) function will not expand properly if it is followed
by a space and parentheses. For example:
STORE 'LIST FOR' TO x
STORE 10 TO memvar
&x Field1 = memvar
will execute properly, but,
&x (Field1 - memvar) * memvar = 0
will return the "*** Unrecognized command verb" error message. This
problem can be avoided by terminating the macro-substituted memory
variable with a period. For example,
&x. (Field1 - memvar) * memvar = 0
will work. It is always a good idea to terminate a macro with a period.
>>> CONFIG.DB with TEDIT or WP
Config.db will not accept more than eight characters for WP or
TEDIT. Any more than eight will be truncated. Attempting to use
MODIFY COMMAND (or to edit a MEMO field) will briefly display the
operating system message "Bad command or file name" and drop the user
to the dBASE dot prompt (or to the edit screen). dBASE III warns the
user that the filename is truncated when dBASE is initialized.
For example:
TEDIT=B:DFORMAT
^--- truncated
To work around this problem, place the word processor in the same drive
and directory or rename the wordprocessor with fewer characters.
>>> DO WHILE with RESTORE
If a memory variable tested in a DO WHILE loop is recreated in the
loop by RESTOREing the variable FROM a memory file, the loop will
continue to run even after the condition no longer evaluates as true
(.T.). The program below will run endlessly as long as the control
variable is not the first entry in the memory file:
var = .T.
DO WHILE var
RESTORE FROM Memfile <--- This overwrites var at the same
memory location.
var = .F. <--- This change is ignored if the
ENDDO previous assignment statement
changed the memory location of the
variable.
RESTOREing ADDITIVE ameliorates the problem.
>>> DO WHILE with semicolon
When a DO WHILE conditional statement is continued to a second
line with a semicolon, dBASE III tries to execute this second line the
second time through the loop. When the ENDDO is encountered and the
condition evaluates as true, program flow proceeds to this second line,
resulting in the error message, "*** Unrecognized command verb."
* ---This program will give an error message
* ---when "Y" is entered at the WAIT prompt.
answer = 'Y'
number = 1
DO WHILE number < 10;
.AND. answer = 'Y'
? number
WAIT '"Y" to continue ' TO answer
number = number + 1
ENDDO
If "Y" is entered at the WAIT prompt, dBASE III tries to execute ".AND.
answer = 'Y'." However, if the semicolon is deleted and the line is
allowed to wrap at column 67 in MODIFY COMMAND, execution flows
correctly.
>>> DO WHILE with RETURN
A RETURN statement inside a DO WHILE...ENDDO construction will not
close the DO WHILE <condition> in the program as it does in dBASE II.
Therefore, the <condition> will continue to be evaluated. For example:
* A.PRG
expA = "1"
DO WHILE expA = "1"
? "Hi!"
DO B ---------------> * B.PRG
ENDDO expB = "2"
* EOF: A.PRG DO WHILE expB = "2"
? "How are you?"
expA = "X"
RETURN
ENDDO
* EOF: B.PRG
Executing A.PRG will cause an infinite loop. It appears dBASE III
continues to test the condition of expB. In order to work around this,
B.PRG should be written as follows:
* B.PRG (revised).
expB = "2"
DO WHILE expB = "2"
? "How are you?"
expA = "X"
EXIT <----- Notice, the EXIT here,
ENDDO
RETURN <----- and the RETURN here.
* EOF: B.PRG
>>> DO WHILE with an extra ENDDO
If an extra ENDDO is added to a command file, an infinite loop
will result. For example, the following program will execute until Esc
is pressed:
number = 0
DO WHILE number < 10
@ 10,10 SAY "Now at loop " + STR(number,2)
STORE number + 1 TO number
ENDDO
ENDDO <--- This ENDDO has no matching DO WHILE.
RETURN
We recommend you use some method of indentation for the control
structures: DO CASE...ENDCASE, DO WHILE...ENDDO, and IF...ENDIF to
avoid this problem. This practice will make command files more
readable, and will allow for quick visual checking for accuracy of
nested control structures. In our example, two consecutive ENDDO
statements with the same left margin is a definite indication that
something is wrong.
>>> Memo fields, listing or printing after a previous field
A problem displaying memo fields has been found in dBASE III by
one of our users. The problem occurs when you try to list or print a
memo field preceded by a field whose length will force the first line
of the memo field to wrap around one or more times. Assume a file
structure consisting of two fields:
Structure for database: Example.DBF
Field Field Name Type Width Dec
1 Field1 Character 100
2 Field2 Character 70
3 Comments Memo 10
** Total ** 181
These display commands
? SPACE( 1 ), Comments
? Field1, Comments
? Field2, Comments
? Field1, Field2, Comments
will all produce different results.
If the total length of the fields preceding the memo field is 29
or above, the first line of the memo field will wrap around. The
entire memo field will then be displayed in double space. Furthermore,
if the total length of the fields preceding the memo field is long
enough to force the first line of the memo field to be displayed
starting on the second line of the display, then the entire memo field
will be displayed in triple space.
>>> REPORT FORM with right margin=report width
CREATEing a REPORT FORM with a right margin value equal to the
report width (the page width minus the left margin) will display
garbage to the screen or printer. This happens because there is no
space to print the report.
There is a general misconception about the meaning of the right
margin in the REPORT FORM. Some users have the impression that its
value is the number of characters from the left margin, much the same
way a typewriter works. The value actually refers the the number of
characters the right margin is offset from page width. For example,
page width |------------------------------------->|
right margin |<----------|
>>> REPORT FORM with stacked columns
CREATEing a REPORT FORM field that consists of stacked columns
built with the result of the STR() function and numeric fields may
cause numbers to display incorrectly. Specifically, entering:
STR( Field1, 5 ) + STR( Field2, 5 )
in the field contents and specifying a column width of five will result
in a correct display only when the fields have five-digit numbers
stored in them. If the number has less than five digits, the display
will be misplaced by the number of digits missing. The example above
will produce:
11111
11111
if the fields are full, but:
1111
1111
1111
if there are only four digits in the fields.
>>>SET FILTER TO with GO BOTTOM
If a SET FILTER TO condition is not satisfied by any records in
the database file and a GO BOTTOM is issued, both the EOF() and BOF()
will return a true (.T.). Removing the filter by issuing a SET FILTER
TO does not reset EOF() or BOF(). The record pointer must be
repositioned to reset EOF() and BOF(). SKIP or SKIP -1, however, will
return a file boundary error message, because EOF() and BOF() are true.
To move the record pointer appropriately issue a GO TOP and the BOF()
and EOF() values will be reset to false (.F.).
>>> Get Current Directory
dBASE III has no facility to get the name of the current directory.
To get it you must RUN the PC/MS-DOS command CD and import the results
into dBASE III. The basic algorithm is as follows:
1. Create or have available a general-purpose database file called
Util.DBF. Util.DBF has one field called Util_line which is character
type and has a length of 80.
2. RUN the PC/MS-DOS command CD, piping the result into a text
file entitled Util.TXT.
3. APPEND the text file Util.TXT into the database file, Util.DBF.
4. Assign to a memory variable the name of the current DOS
directory from Util.DBF.
The code that will execute this algorithm is as follows:
SET SAFETY OFF
RUN CD > Util.TXT
USE Util
ZAP
APPEND FROM Util SDF
currdir = TRIM( Util_line )
SET SAFETY ON
RETURN
>>> Get Diskspace
In the Developer's Release use the DISKSPACE() function to get the
amount of space left on the currently logged drive. The DISKSPACE()
will return the number of free bytes on the default drive as a numeric
value.
dBASE III versions 1.0 and 1.1 do not have a function to return
the amount of space left on the default drive. So, to get the amount
of diskspace in these versions, use the PC/MS-DOS utility CHKDSK and
import the results into dBASE III. The basic algorithm is as follows:
1. Create or have available a general purpose database file called
Util.DBF. Util.DBF has one field called Util_line which is character
type and has a length of 80. This database file will be useful for any
of these kinds of survey operations into PC/MS-DOS.
2. RUN CHKDSK including the designator of the drive for which you
want the space statistic for, and pipe the result into a text file
entitled Util.TXT. Piping is a PC/MS-DOS capability that allows the
results of a program to be sent into a text file. It is very useful
for passing parameters between programs when there is no formalized
interface. The syntax is:
<DOS commmand> > <result text file>
^_____ DOS piping symbol
For more information on this capability, consult your PC/MS-DOS
reference manual.
3. APPEND the text file, Util.TXT, into the database file,
Util.DBF.
4. Assign to a memory variable the number of free bytes on the
specified drive from Util.DBF. This operation requires that you GOTO
the record that contains the free disk space information and then
extract the number of bytes from the field using the SUBSTR() function.
The following is a LIST of Util.DBF with the results of a CHKDSK
report. When APPENDed into a database file, the first and last records
are always blank. Records 2 through 6 contain statistics about the
currently logged disk drive. Note that this is the currently logged
DOS drive and not the DEFAULT drive SET in dBASE III. Records 8 and 9
contain statistics about the memory configuration of your computer.
The number of bytes for each attribute of the drive and memory occupy
positions 1 through 9 in the database field, Util_line.
Record#
1
2 9965568 bytes total disk space
3 155648 bytes in 4 hidden files
4 90112 bytes in 22 directories
5 6000640 bytes in 397 user files
6 3719168 bytes available on disk
7
8 524288 bytes total memory
9 122480 bytes free
10
The code that will get the the number of free bytes on the
specified disk drive is as follows:
SET SAFETY OFF
RUN CHKDSK > Util.TXT
USE Util
ZAP
APPEND FROM Util SDF
GO 6
diskspace = STR( SUBSTR( Util_line, 1, 9 ), 9 )
USE
SET SAFETY ON
RETURN
>>> Get Last Update and Time
To get the date of last update for the currently SELECTed database
file in the Developer's Release of dBASE III, use the LUPDATE()
function. LUPDATE() returns the date of last update as a value of date
type.
dBASE III versions 1.0 and 1.1 currently do not have a function
that returns the date of last update for the SELECTed database file.
To get the date of last update in these versions of dBASE III, use the
PC/MS-DOS command DIR, and import the results into dBASE III. The
basic algorithm is as follows:
1. Create or have available a general purpose database file
called Util.DBF. Util.DBF has one field called Util_line which is
character type and has a length of 80.
2. RUN the PC/MS-DOS command DIR with the name of your database
file, piping the result into a text file entitled Util.TXT.
3. APPEND the text file Util.TXT into the database file Util.DBF.
4. Assign to a memory variable the date of last update from
Util.DBF for your database file.
The code that will get the last update of the currently SELECTed
database file is as follows:
SET SAFETY OFF
RUN DIR <Yourfile>.DBF > Util.TXT
USE Util
ZAP
APPEND FROM Util SDF
lupdate = SUBSTR( Util_line, 25, 8 )
luptime = SUBSTR( Util_line, 34, 6 )
USE
SET SAFETY ON
RETURN
>>>Installation
If the message "Insert System Disk #2 or press Ctrl-Break" appears
when dBASE III is being loaded from a hard disk and is installed, the
overlay file (DBASE.OVL) is corrupted. One possibility is that the
copy on the hard disk is corrupted and can no longer be used. Another,
and more likely, possibility is that the copy on System Disk #2 is bad,
and you are now trying to run dBASE III after just having installed to
the hard disk. If this occurs, contact the Ashton-Tate Customer
Service Department.
>>> MEMO fields
(1) MEMO fields are used to contain up to 5,000 characters of text
information that is to be associated with a database record.
Information may be read into a MEMO field using Ctrl-K-R and written to
text files using Ctrl-K-W. Information from MEMO fields can be
displayed or printed by using LIST, DISPLAY, ?. The field must be
specified with these commands. However, these commands cause the MEMO
field to wrap at 50 columns. The REPORT FORM may be used to output
MEMO fields with line widths of more or less than 50 characters.
(2) PACKing a database file with memo fields will not decrease the
amount of disk space used by the .DBT file. The command file below
demonstrates how to remove the deleted records and free the unused disk
space.
SET DELETED ON
USE Filea
COPY TO Temp
CLOSE DATABASE
ERASE Filea.dbf
ERASE Filea.dbt
RENAME Temp.dbf TO Filea.dbf
RENAME Temp.dbt TO Filea.dbt
SET DELETED OFF
>>> Ramdisk
There are several options for users who wish to use a ramdisk in
combination with dBASE III.
1. For faster operation of applications that utilize routines
stored in the DBASE.OVL file, you may wish to put the .OVL in a ramdisk.
(a) The minimum drive size will have to be in excess of
181,000 bytes. The DBASE.OVL file for version 1.1 is 180,736 bytes.
The total amount of RAM in your machine must be more than 440,000 bytes
in order to do this. Additionally, if you are using a CONFIG.DB, it
must be present on the drive where .OVL resides.
(b) Boot dBASE III from the ramdisk by calling for the
DBASE.COM from the drive on which it resides. For example: drive D:
is the ramdisk and the DBASE.COM is on the C: drive.
C> D:
D> C:DBASE
2. It may be a very useful area for procedure or command files to
be run from, increasing the speed of processing. Prior to entering
dBASE III, copy the appropriate files to the ramdisk. Once in dBASE
III, SET the DEFAULT TO the ramdisk drive and proceed.
3. It is also useful as a small work area to manipulate utility
and temporary files. The useage tips on getting the current directory
or diskspace are good examples of where a small ramdisk would be
extremely useful and time efficient.
>>> REPORT FORM
The semicolon is not documented as functioning as a Carriage
Return/Line-Feed in certain parts of REPORT FORMs.
>>> REPORT FORM grouped by week
If you have a date-oriented report and you need to have it grouped
by week, the following discussion will assist you.
The grouping of dates into weeks has two requirements. First, the
database file you are reporting from must be INDEXed on the date field
that is being grouped on. Second, as the group expression in your
REPORT FORM, you must have an expression that returns as its value the
first day of the week for each date field. The expression is as
follows:
Yourdate - ( DOW( Yourdate ) - 1 )
When given any date value, this expression returns the date of the
previous Sunday. It does this by subtracting from your date field the
number of days that have passed since the last Sunday, the first day of
the week in the dBASE III calendar. This value is obtained by
subtracting 1 from the result of the DOW() function.
If you wish to have the week you are grouping on start on a later
day such as Monday, subtract more from the result of the DOW() function.
For example, Monday would be DOW() - 2, Tuesday DOW() - 3, and so on.
>>>WAIT TO
When a function key is SET to a literal character string, the WAIT
command will not accept the assigned string, although ACCEPT TO will.
Instead, the WAIT command will take the ASCII code representation of
the function key itself. For example:
SET FUNCTION 10 TO "A"
WAIT TO var
* ---Press F10.
DISPLAY MEMORY
VAR pub C "v"
1 variables defined, 3 bytes used
255 variables available, 5997 bytes available
* ---Test to see what ASCII character F10 sent.
? ASC(var)
246 <------------------------- Code for F10
? ASC("A")
65 <-------------------------- Code for "A"
>>> Shifted Data Displays by Oliver Biggerstaff
A database file may become corrupted for any number of reasons.
Often the corruption may be the form of shifted data in full-screen
edit screen displays. This is caused by an embedded null character in
a record. A null character is represented as a 00 hex and is used by
dBASE II and dBASE III as a string terminator. A string terminator is
a character that can be thought of as a delimiter, much like double
quotes surrounding a character string, or as a carriage return and
linefeed at the end of a record.
The dBASE APPEND, EDIT, BROWSE, and other full-screen commands
work on the principle that the cursor's positioning on the screen
depends on certain attributes, such as the length of a field and its
current position. The appearance of shifted data is caused by the
embedded string terminator forcing dBASE to stop the display of a field
prematurely, placing the cursor at an incorrect location. If a null
character is encountered before all the characters of a field have been
displayed, dBASE will stop listing that particular field and will
produce the shift effect by displaying the next field at an incorrect
screen location.
The data is actually not shifted physically in the database file.
It is simple, therefore, to correct the database by replacing any null
character with another character that does not force dBASE to display
incorrectly. A good choice for this character is the ASCII character
zero (0) or 30 hex. Replacing a null with this character is
advantageous for two reasons:
1. Since dBASE can display it, you can locate the corrupted data.
2. A zero character will have the least disruptive effect on the
contents of the database file. Additionally, logical fields that
contain a zero character will be displayed as (.F.)
Once all the null characters have been replaced, the BROWSE or
EDIT commands can be used to retype the original data in place of the
characters that replaced the nulls.
The following is one of many methods that can be used to replace
null characters with other characters. In this example we use the
PC/MS-DOS utility DEBUG.COM, since most of you have this program on
your supplemental PC/MS-DOS disk. These examples assume that the
database file is 64K or less in size. Refer to the PC/MS-DOS manual
for more information on DEBUG and how to use data segments if the
database file is larger than 64K.
The contents contained in the < and > symbols must be calculated
by you, and entered without the symbols. For example, if the value of
the CX register is 2C80H, then <CX+100H> is to be replaced with 2D80H.
Be sure that before you attempt this procedure, you have make a backup
of your database file.
For dBASE II users on a 16-bit computer:
C>DEBUG <database>.DBF ;Read database file into memory.
-RCX ;Get the value in the CX register
-S 309 <CX+100H> 00 ;Search for nulls in the file.
.
. ;A list appears here of one or more
. ;addresses containing a null.
.
-E <address> 30 ;Replace each individual address that contains
;a null with a zero.
-W ;Save the modifications to disk.
-Q ;Quit DEBUG.
For dBASE III users:
C>DEBUG <database>.DBF ;Read database file into memory.
-RCX ;Get the value of the CX register.
-S 100H 1121H 0DH ;Search for the end of the header for address
;containing 0DH.
xxxx:yyyy ;for address containing 0D.
;Search for null characters.
-S <yyyy+2> <CX+100H> 00
.
. ;List of addresses containing
. ;nulls.
.
-E <address> 30H ;Replace each null with a zero.
-W ;Save the modifications to disk.
-Q ;Quit DEBUG.
Unfortunately, this method of replacing null characters can be
very tedious if many characters must be replaced. For those of you
with a large amount of corrupted data, it is suggested that you use
other well-known utility programs such as NIBBLER, NORTON UTILITIES,
JAZ, or PATCH. These programs will allow you to look at very large
database files directly from the hard disk. Some of these programs
may also have a global search and replace option.
>>> Swapping Printer Ports on the IBM PC by Robert Boies
Last month in the dBASE II section of TechNotes we presented an
assembler routine to swap printer ports on an IBM PC. This routine
allows the user or programmer to toggle the LPT1 and LPT2 ports, making
it easy to redirect output to several printers. This month we present
essentially the same routine configured for dBASE III.
If you have versions 1.0 or 1.1 of dBASE III, you will have to
create a .COM file from this routine and at runtime use the RUN command
to call it. For example from within dBASE III,
* ---Print a first report to the default port, LPT1.
REPORT FORM One TO PRINT
* ---Print a second report to LPT2.
RUN Portswap <---------------------------- Redirect to LPT2.
REPORT FORM Two TO PRINT
RUN Portswap <---------------------------- Restore LPT1.
If you have the Developer's Release of dBASE III, you will be able
to use the assembly language interface implemented in that version.
For example:
* ---LOAD into memory.
LOAD Portswap
* ---Print a first report to to the default port, LPT1.
REPORT FORM One TO PRINT
* ---Print a second report to LPT2.
CALL Portswap <---------------------------- Redirect to LPT2.
REPORT FORM Two TO PRINT
CALL Portswap <---------------------------- Restore LPT1.
* ---RELEASE the memory space.
RELEASE MODULE Portswap
The listing below, Portswap.ASM, uses the conditional directives
discussed earlier to allow you to specify whether you wish to create
a .BIN or .COM file.
Assembly language source code:
; Program ..: Portswap.ASM
; Author ...: Robert Boies
; Date .....: September 1, 1985
; Note .....: Swaps the LPT1 and LPT2 ports in PC/MS-DOS 2.x
.LFCOND ; List false conditionals.
PAGE 60,132 ; Page length 60, line 132.
COM EQU 0 ; Assemble as .BIN file
D3 EQU 1 ; for Developer's Release.
;
CODESEG SEGMENT BYTE PUBLIC 'CODE'
ASSUME CS:CODESEG
;
PORTSWAP PROC FAR
;
IF COM
ORG 0100H ; Originate at 0100H.
ENDIF
;
START: PUSH ES ; Save environment.
PUSH AX
PUSH BX
PUSH DI
;
MOV AX,40H ; System stores critical operating
; parameters at segment 40H (absolute
; address 400H).
; Load this segment address into AX.
PUSH AX ; Put it on the stack.
POP ES ; Pop the segment id from the
; stack into extra segment register.
MOV DI,8H ; Port address of LPT1 and LPT2 are
; stored at offsets 8H and 0AH in
; this segment. Load DI with offset.
MOV AX,ES:[DI] ; Put the port address of the
; first printer into the accumulator.
INC DI ; Increment the destination index
; twice to point to port address
INC DI ; of the second printer.
MOV BX,ES:[DI] ; Put the port address of the
; second printer into BX.
MOV ES:[DI],AX ; Poke the address of the first
; printer into the location of the
; second printer.
DEC DI ; Decrement DI twice to point to the
DEC DI ; location of the first printer
; port address.
MOV ES:[DI],BX ; Poke the address of the second
; printer into the location of the
; first.
POP DI ; Restore environment.
POP BX
POP AX
POP ES
IF COM
INT 20H ; INT 20H if .COM file
ELSE
RET ; far return to dBASE III
ENDIF
;
PORTSWAP ENDP
CODESEG ENDS
END START
>>> Interfacing Assembly Language Routines with dBASE by Ralph Davis
Creating Assembler Programs with DEBUG
DEBUG is the assembly language programmer's best friend. It is a
powerful tool for exploring the computer's memory, testing assembly
language programs, studying program listings and creating new programs.
Additionally, it can be used to rebuild corrupted data files, convert
hidden files to accessible files, or simply analyze file structures.
Our main interest in DEBUG here is to create assembly language routines
for use with dBASE II and dBASE III.
It is tempting to use DEBUG because of its interpreter-like
qualities. You can quickly enter code and then see if it works. If it
does, you call it <PROGRAM>.COM and write it to disk. If it doesn't,
you trace through the old code, enter new code, and try again.
Eventually, you come up with a program that works through trial-and-
rror. However, this can lead to sloppy programming habits and
inefficient code, so it is important to bear in mind what you want a
particular program to accomplish.
DEBUG has some limitations. Most importantly, it only recognizes
absolute addresses. When you write a program for submission to an
assembler, you label the instructions and data you will need to refer
to, then refer to them with the label. You don't need to know the
actual addresses. DEBUG, on the other hand, obliges you to look
through your program listing and find addresses whenever you refer to
them. For instance, instead of entering JMP EXIT, you must enter JMP
02FC. Instead of CALL HEXPRINT, you use CALL 05AE. Instead of MOV BX,
OFFSET DATA, you need MOV BX, 0105. If your routine is small, this
does not present a problem. But as you add features and it becomes
larger, this becomes a serious impediment. If you add or alter
instructions, thereby changing an absolute address, you have to change
every reference to it. And the only way to find the references is to
page through the entire program, line by line. For this reason, DEBUG
is best for creating short utility programs.
Most often, programs created with DEBUG use BIOS or DOS interrupts
to manipulate the hardware. Some typical functions that appear in this
issue are setting the cursor (see the example on page 4-72C of the
Developer's Release Reference Manual and the program listed in this
issue), manipulating the shift keys, or swapping printer ports.
Programs of this type should not contain any subroutines.
DEBUG has another important limitation: it only understands
hexadecimal numbers. There is simply nothing you can do to make it
accept decimal numbers. This is not a problem when entering addresses
or interrupt numbers, as most assembly language programmers think these
values in hexadecimal anyway. But very few programmers think in hex
when doing calculations. DEBUG is therefore not a good tool for doing
number-crunching of even intermediate complexity. Although there are
utilities available to assist in this process, such as Sidekick, this
is still a major obstacle to doing extensive calculations within DEBUG.
Another problem with DEBUG is that code produced with it can be
extremely obscure. Trying to decipher the flow of a program where you
have only absolute addresses and hexadecimal numbers to guide you can
be very frustrating. In addition, DEBUG does not support comments. So
when you read a DEBUG listing, you are, for all intents and purposes,
reading "machine English." The machine expresses its own language in
cryptic English-like symbols, making a few grudging concessions to your
desire to understand it. All of this reinforces what we suggested
earlier: keep DEBUG routines short.
The program from the Developer's Release Reference Manual
mentioned above is a good example of a program appropriate for DEBUG.
The listing on page 4-72C is as follows:
_PROG SEGMENT BYTE
ASSUME CS:_PROG
;
CURSOR PROC FAR ; Force a far return.
;
MOV CX,[BX] ; Get two HEX digits.
MOV AH,1 ; Set cursor type.
INT 10H ; Video interrupt.
RET ; Do a far return.
;
CURSOR ENDP
;
_PROG ENDS
END
This is a terse routine that converts the dBASE III cursor to a
full-sized box when CHR(18) is passed as a parameter to it. Notice one
thing about this code: it has six lines of assembler directives (the
first three and the last three), and only four lines of machine
instructions. In a short program like this one, there is no advantage
to assembling, linking, and converting it using MASM, LINK and EXE2BIN.
DEBUG is faster and easier.
Here is a DEBUG session that enters this program as a .COM file.
(The DEBUG commands are explained in Chapter 8 of the PC/MS-DOS manual.
Page numbers which follow refer to it.)
D>debug
First give DEBUG the 'A' (assemble) command (page 8-15) and enter
the program.
-A
6257:0100 MOV CX,[BX]
6257:0102 MOV AH,1
6257:0104 INT 10
6257:0106 INT 20
6257:0108
Notice that 'INT 20' is our last instruction, not 'RET' as the
manual indicates. We will explain this shortly.
The address following the last instruction is 108. Therefore,
enter eight into CX using the 'R' (register) command [page 8-41]. This
tells DEBUG the number of bytes to write to disk.
-RCX
CX 0000
:8
Name the program CURSOR.COM using the 'N' command [page 8-37], and
write it to disk using 'W' [page 8-55].
-NCURSOR.COM
-W
Writing 0008 bytes
This is the basic procedure for creating a .COM file from DEBUG.
CURSOR.COM will yield unpredictable results executed from PC/MS-DOS,
since the registers are not preserved, and we have no way of knowing
what is being passed in DS:BX. (When we tested it, the cursor simply
vanished.) Nor, in its present form, will it work in dBASE III. It
needs a couple of changes to make it work, but this point deserves some
attention.
PC/MS-DOS .COM files and dBASE LOAD modules require slightly
different specifications. A .COM file must be ORGed (originated) at
address 100H, and it must end with a command like INT 20H (terminate)
or INT 27H (terminate and stay resident); a simple RET will not return
correctly. dBASE III, on the other hand, requires LOAD modules to be
ORGed at address 0 and to return to dBASE III with a far return, RETF.
If you load a conventional .COM file, ORGed at 100H and terminated with
INT 20H, into dBASE III, and then call it, you will lock the system,
even if it works from PC/MS-DOS. When DEBUG writes a program to disk,
it writes a binary file -- that is, a file which contains nothing but
the machine instructions you have given it. Therefore, we need not
concern ourselves with ORGing programs correctly at this stage. We do
have to terminate LOAD modules with RETF, however. Here is a DEBUG
session that enters this program as a .BIN file which will execute from
dBASE III.
D>debug
Type 'A' for assemble. Terminate with a RETF.
-A
6346:0100 MOV CX,[BX]
6346:0102 MOV AH,1
6346:0104 INT 10
6346:0106 RETF
6346:0107
Place the number 7 in the CX register to save 7 bytes to disk.
-RCX
CX 0000
:7
Name the file, and write it.
-NCURSOR.BIN
-W
Writing 0007 bytes
Quit DEBUG.
-Q
The page of the Developer's Release Manual referred to above gives
the following example of how to use Cursor:
LOAD Cursor
STORE CHR(18) TO shape
CALL Cursor WITH shape
The commands to convert the cursor back to its normal format are:
LOAD Cursor
STORE CHR(12) + CHR(11) to shape
CALL Cursor WITH shape
>>> On .COM Files vs. .EXE Files
When creating programs with a full-featured assembler, we have two
options: .COM files and .EXE files. Each has advantages and
disadvantages.
.COM files are an inheritance from the world of 8-bit CP/M. They
are your only option if you have a CP/M machine. .COM files must
adhere to a strictly defined structure.
1. They must fit entirely within one segment. All segment
registers must point to the same address, and cannot be changed during
the execution of the program. This means that all of our main program,
subroutines, and data must fit in 64K. A 64K .COM file is a very large
program -- each line of code assembles to between 1 and 6 bytes, so a
64K .COM file could have as many as 30,000 lines of source code.
2. They must be ORGed at 100H. When PC/MS-DOS loads a .COM file,
it jumps to CS:100H and begins executing.
3. They must return control to their calling routine with either
INT 20H or INT 27H, or the equivalent INT 21H function calls, 4CH and
31H.
.COM files load more quickly than .EXE files, since no addresses
need to be calculated at load time.
The assembly language programs that dBASE II and dBASE III can
execute as subroutines (with the CALL command) are variations of a .COM
file. We will discuss the specifics of their formats later.
.EXE files are less limited structurally. The segment registers
can be freely manipulated, and each one can point to an entirely
different 64K segment. .EXE files can therefore be much larger than
.COM files. .EXE files were designed to take better advantage of the
actual architecture of 16-bit 8086-based microprocessors. Having data
in one segment, code in another, and the stack in a third allows much
greater utilization of the memory space available in today's machines.
It also provides us the semblance of structured programming in assembly
language. The SEGMENT, PROC, ENDS, and ENDP operators give a program
listing a much more organized appearance than it has with JMP and DB
statements interspersed throughout the code.
.EXE files take longer to load than .COM files, as many of the
absolute addresses are not computed until load time. They also take up
more disk space than .COM files. However, since they use much more of
the 8086 family's capabilities, they can be much more powerful programs.
The commercial programs which were handed down from the CP/M world are
all .COM files, whereas those which were created since the advent of
16-bit machines are mostly .EXE files.
Having said this, we will leave .EXE files behind. You cannot
LOAD .EXE files from dBASE II or dBASE III. You can execute them with
QUIT TO in dBASE II or RUN(!) in dBASE III. If you want to pass
parameters to and from .EXE files, you must pass them in text files
(the SDF format is recommended).
>>> Adapting Assembly Language Programs to dBASE II or III
As mentioned earlier, the format of a dBASE II or III assembly
language subroutine most closely resembles that of a .COM file. Most
importantly, it must reside in one segment. Since it is intended as a
subroutine, not as a stand-alone program, it will differ somewhat from
a standard .COM file.
For one thing, a .COM file must be ORGed at 100H. However, ORGing
a dBASE (II or III) subroutine at 100H will cause it to fail. A
program intended for use in dBASE II must be ORGed high in the code
segment -- the exact address depends on the version of dBASE II, the
later the version, the higher the address. In version 2.43*, the ORG
address should be above 61440 decimal. (See Robert Boies' article on
swapping printer ports in the August issue of TechNotes for a good
example of a dBASE II assembly language program.) A program intended
for dBASE III must be ORGed at 0 (that is, it need not have an ORG
statement). Secondly, .COM files return to their caller with interrupts
(usually 20H or 27H), whereas dBASE II and dBASE III routines require
RET (return) -- near for dBASE II, far for dBASE III.
The procedure for converting assembly language source code into
programs dBASE II or III can execute are as follows:
1. For dBASE II, you must assemble your program with an assembler
that produces a file in Intel .HEX format. Intel's assemblers, ASM
(for CP/M) and ASM86 (for CP/M-86), create such a file. For PC/MS-DOS,
the Seattle Computer Products assembler generates a .HEX file. Refer
to their manuals, as their assembly language syntax differs somewhat
from Microsoft's and IBM's.
2. For dBASE III, use the IBM or Microsoft Macro-Assembler
(MASM.EXE) to produce a .OBJ (object) file. Enter the command as
follows:
MASM <filename> <filename> <filename>;
The third parameter will cause MASM to produce a listing file with
a .LST extension, which is very useful for debugging.
3. Use the linker utility (LINK.EXE) that comes both with
PC/MS-DOS and with the assembler. This will create an .EXE file. The
command is:
LINK <filename>
Press Return three times in response to the prompts.
4. Use EXE2BIN.EXE to convert the program to .COM or .BIN format.
If you are creating a .BIN file, you need only enter one parameter in
the command line:
EXE2BIN <filename>
If you are creating a .COM file, you need to specify the full target
filename:
EXE2BIN <filename> <filename>.COM
>>> Using Conditional Assembler Directives
Because the differences between .COM files and .BIN files are
minor, it is possible to generate both using the same source code. The
following program skeleton shows how to set this up. The EQU statements
at the top inform the assembler whether we are assembling a program for
PC/MS-DOS or dBASE III. In the present example, we have set COM equal
to 0 (meaning false) and D3 equal to 1 (non-zero, meaning true). We
then use conditional directives to tell the assembler how we want the
program created. Conditional directives are statements in your
assembly program to direct the assembler to assemble a block of
instructions based on a variable value. For example, IF COM (if COM is
not zero), ORG the program at offset 100H. Then at the end of the
program, IF COM, exit with INT 20H; otherwise, exit with a far RET.
.LFCOND ; List false conditionals,
PAGE 60,132 ; page length 60, line 132.
COM EQU 0 ; Assemble program as .BIN
D3 EQU 1 ; file for dBASE III.
CODESEG SEGMENT BYTE PUBLIC 'CODE'
ROUTINE PROC FAR
ASSUME CS:CODESEG,DS:CODESEG
IF COM
ORG 100H
ENDIF
PUSH DS ; Make sure DS points to
PUSH CS ; the current
POP DS ; segment.
.
. (program goes here)
.
.
POP DS ; Restore caller's DS.
IF COM
INT 20H ; INT 20H if .COM file.
ELSE
RET ; Far return if dBASE III
ENDIF
ROUTINE ENDP
CODESEG ENDS
END
It is very important to load the DS register with the segment
address contained in CS. PC/MS-DOS does this automatically for
a .COM file, but dBASE III does not. Therefore, if your routine needs
to access its own data, it will need to set DS correctly.
Sample Program With Conditional Assembly
Here is a program built on the skeletal structure which sets
condensed print on an EPSON printer.
; Program ...: Printer.ASM
; Author ....: Ralph Davis
; Date ......: September 1, 1985
TITLEPRINTER.ASM -- sets condensed print
.LFCOND
PAGE60,132
COMEQU0
D3EQU1
CODESEGSEGMENTBYTE PUBLIC 'CODE'
PRINTER PROCFAR
ASSUME CS:CODESEG,DS:CODESEG
IFCOM
ORG100H
ENDIF
START:JMPSHORT ENTRY; Jump past data.
CODESDB27,64,27,15; Printer control codes.
CODELEN EQU$-CODES; Length of string.
ENTRY: PUSHAX; Save registers.
PUSHBX
PUSHDS
PUSHCS; Set up DS
POPDS; with current segment.
PUSHCX; Save CX
PUSHDX; and DX.
MOV BX,OFFSET CODES; Point BX to codes.
MOVCX,CODELEN; Length of string.
; Controls the loop.
GET_CODE:
MOV DL,BYTE PTR [BX] ; Get code to send.
MOV AH,5H ; PC/MS-DOS function 5H,
INT 21H ; (send char to printer).
INC BX ; Point to next code
LOOP GET_CODE ; and print it.
POPDX; Restore registers.
POPCX
POPDS
POPBX
POPAX
IFCOM
INT 20H ; INT 20H if .COM file.
ELSE
RET; Far return to dBASE III.
ENDIF
PRINTER ENDP
CODESEG ENDS
ENDSTART; End assembly.
Assemble this program according to the instructions given earlier.
To run it from dBASE II or dBASE III versions 1.0 and 1.1, assemble it
as a .COM file, and enter the following commands:
dBASE II:
QUIT TO 'Printer'
dBASE III:
RUN Printer
To run it from the Developer's Release of dBASE III, assemble it
as a .BIN file, and use these commands:
LOAD Printer
CALL Printer