1 dBASE III Anomalies and Workarounds 1.1 How to use this Section 1.2 @..SAY...GET to @...SAY...PICTURE >>> & 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. 1.3 APPEND to CONFIG.DB >>> 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. 1.4 COPY [STRUCTURE] to DO WHILE >>> 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 ENDDO the 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." For example: * ---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 in the program as it does in dBASE II. Therefore, the 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. 1.5 EJECT to LABEL limitations 1.6 MODIFY STRUCTURE to PCOL() and SET MARGIN TO >>> 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. Futhermore, 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. 1.7 RECNO() to ROUND() >>> 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 beacuse 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. 1.8 SET to SORT >>>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 boundry 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.). 1.9 STORE to ZAP 2 dBASE III Programming 3 dBASE III Frequently Asked Questions 3.1 Installation 3.2 Commands 3.3 New data types 3.4 Memory variables 3.5 Printing 3.6 Data transfer 4 dBASE III Reference 4.1 @...GET to Boolean Operators 4.2 CHR() to FILE() function 4.3 FIND to MODIFY STRUCTURE >>> 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 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 .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 4.4 Numeric fields to PARAMETERS 4.5 PRIVATE to PROCEDURE 4.6 PUBLIC to REPORT FORM >>> 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. 4.7 RELEASE to SET PROCEDURE 4.8 SET RELATION to warnings >>>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 respresentation 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" 4.9 dBase III File Structure 4.10 dBASE III Memo File Structure 4.11 Installation and Configuration 5 dBASE III Sample Programs 6 dBASE III Technical Notes 6.1 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 appearence 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 is to be replaced with 2D80H. Be sure that before you attempt this procedure, you have made a backup of your database file. For dBASE II users on a 16-bit computer: C>DEBUG .DBF ;Read database file into memory. -RCX ;Get the value in the CX ;register. -S 309 00 ;Search for nulls in the file. . . ;A list appears here of one or . ;more addresses containing a . ;null. -E
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 .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 00 . . ;List of addresses containing . ;nulls. . -E
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. 6.2 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 an .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 6.3 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 .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-error. 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) 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 .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 the .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 ; 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 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 If you are creating a .COM file, you need to specify the full target filename: EXE2BIN .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 an 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 7 dBASE III Version 1.1 Change Summary