home *** CD-ROM | disk | FTP | other *** search
/ Shareware Overload / ShartewareOverload.cdr / database / db3tips2.zip / DB3NTS.TXT < prev    next >
Text File  |  1986-07-01  |  45KB  |  1,162 lines

  1.                   dBASE III Tips (DB3N8509.TXT)
  2.  
  3. 1.  dBASE III Anomalies and Workarounds
  4.  
  5. >>> & Function
  6.  
  7.      The macro (&) function will not expand properly if it is followed
  8. by a space and parentheses.  For example:
  9.  
  10.      STORE 'LIST FOR' TO x
  11.      STORE 10 TO memvar
  12.      &x Field1 = memvar
  13.  
  14. will execute properly, but,
  15.  
  16.      &x (Field1 - memvar) * memvar = 0
  17.  
  18. will return the "*** Unrecognized command verb" error message.  This
  19. problem can be avoided by terminating the macro-substituted memory
  20. variable with a period.  For example,
  21.  
  22.      &x. (Field1 - memvar) * memvar = 0
  23.  
  24. will work.  It is always a good idea to terminate a macro with a period.
  25.  
  26.  
  27. >>> CONFIG.DB with TEDIT or WP
  28.  
  29.      Config.db will not accept more than eight characters for WP or
  30. TEDIT.  Any more than eight will be truncated.  Attempting to use
  31. MODIFY COMMAND (or to edit a MEMO field) will briefly display the
  32. operating system message "Bad command or file name" and drop the user
  33. to the dBASE dot prompt (or to the edit screen).  dBASE III warns the
  34. user that the filename is truncated when dBASE is initialized.
  35. For example:
  36.  
  37.      TEDIT=B:DFORMAT
  38.                    ^--- truncated
  39.  
  40. To work around this problem, place the word processor in the same drive
  41. and directory or rename the wordprocessor with fewer characters.
  42.  
  43.  
  44. >>> DO WHILE with RESTORE
  45.  
  46.      If a memory variable tested in a DO WHILE loop is recreated in the
  47. loop by RESTOREing the variable FROM a memory file, the loop will
  48. continue to run even after the condition no longer evaluates as true
  49. (.T.).  The program below will run endlessly as long as the control
  50. variable is not the first entry in the memory file:
  51.  
  52.  
  53.  
  54.  
  55.  
  56.      var = .T.
  57.      DO WHILE var
  58.         RESTORE FROM Memfile <--- This overwrites var at the same
  59.                                   memory location.
  60.         var = .F.            <--- This change is ignored if the
  61.      ENDDO                        previous assignment statement
  62.                                   changed the memory location of the
  63.                                   variable.
  64.  
  65.      RESTOREing ADDITIVE ameliorates the problem.
  66.  
  67.  
  68. >>> DO WHILE with semicolon
  69.  
  70.      When a DO WHILE conditional statement is continued to a second
  71. line with a semicolon, dBASE III tries to execute this second line the
  72. second time through the loop.  When the ENDDO is encountered and the
  73. condition evaluates as true, program flow proceeds to this second line,
  74. resulting in the error message, "*** Unrecognized command verb."
  75.  
  76.           * ---This program will give an error message
  77.           * ---when "Y" is entered at the WAIT prompt.
  78.           answer = 'Y'
  79.           number = 1
  80.           DO WHILE number < 10;
  81.              .AND. answer = 'Y'
  82.              ? number
  83.              WAIT '"Y" to continue ' TO answer
  84.              number = number + 1
  85.           ENDDO
  86.  
  87. If "Y" is entered at the WAIT prompt, dBASE III tries to execute ".AND.
  88. answer = 'Y'."  However, if the semicolon is deleted and the line is
  89. allowed to wrap at column 67 in MODIFY COMMAND, execution flows
  90. correctly.
  91.  
  92.  
  93. >>> DO WHILE with RETURN
  94.  
  95.      A RETURN statement inside a DO WHILE...ENDDO construction will not
  96. close the DO WHILE <condition> in the program as it does in dBASE II.
  97. Therefore, the <condition> will continue to be evaluated.  For example:
  98.  
  99.      * A.PRG
  100.      expA = "1"
  101.      DO WHILE expA = "1"
  102.         ? "Hi!"
  103.         DO B     --------------->  * B.PRG
  104.      ENDDO                         expB = "2"
  105.      * EOF: A.PRG                  DO WHILE expB = "2"
  106.                                       ? "How are you?"
  107.                                       expA = "X"
  108.                                       RETURN
  109.                                    ENDDO
  110.                                    * EOF: B.PRG
  111.  
  112.      Executing A.PRG will cause an infinite loop.  It appears dBASE III
  113. continues to test the condition of expB.  In order to work around this,
  114. B.PRG should be written as follows:
  115.  
  116.           * B.PRG (revised).
  117.           expB = "2"
  118.           DO WHILE expB = "2"
  119.              ? "How are you?"
  120.              expA = "X"
  121.              EXIT             <----- Notice, the EXIT here,
  122.           ENDDO
  123.           RETURN              <----- and the RETURN here.
  124.           * EOF: B.PRG
  125.  
  126.  
  127. >>> DO WHILE with an extra ENDDO
  128.  
  129.      If an extra ENDDO is added to a command file, an infinite loop
  130. will result.  For example, the following program will execute until Esc
  131. is pressed:
  132.  
  133.         number = 0
  134.         DO WHILE number < 10
  135.            @ 10,10 SAY "Now at loop " + STR(number,2)
  136.            STORE number + 1 TO number
  137.         ENDDO
  138.         ENDDO           <--- This ENDDO has no matching DO WHILE.
  139.         RETURN
  140.  
  141.      We recommend you use some method of indentation for the control
  142. structures:  DO CASE...ENDCASE, DO WHILE...ENDDO, and IF...ENDIF to
  143. avoid this problem.  This practice will make command files more
  144. readable, and will allow for quick visual checking for accuracy of
  145. nested control structures.  In our example, two consecutive ENDDO
  146. statements with the same left margin is a definite indication that
  147. something is wrong.
  148.  
  149.  
  150. >>> Memo fields, listing or printing after a previous field
  151.  
  152.      A problem displaying memo fields has been found in dBASE III by
  153. one of our users.  The problem occurs when you try to list or print a
  154. memo field preceded by a field whose length will force the first line
  155. of the memo field to wrap around one or more times.  Assume a file
  156. structure consisting of two fields:
  157.  
  158.      Structure for database: Example.DBF
  159.      Field  Field Name  Type       Width    Dec
  160.          1  Field1      Character    100
  161.          2  Field2      Character     70
  162.          3  Comments    Memo          10
  163.      ** Total **                     181
  164.  
  165.  
  166. These display commands
  167.  
  168.      ? SPACE( 1 ), Comments
  169.      ? Field1, Comments
  170.      ? Field2, Comments
  171.      ? Field1, Field2, Comments
  172.  
  173. will all produce different results.
  174.  
  175.      If the total length of the fields preceding the memo field is 29
  176. or above, the first line of the memo field will wrap around.  The
  177. entire memo field will then be displayed in double space.  Furthermore,
  178. if the total length of the fields preceding the memo field is long
  179. enough to force the  first line of the memo field to be displayed
  180. starting on the second line of the display, then the entire memo field
  181. will be displayed in triple space.
  182.  
  183.  
  184. >>> REPORT FORM with right margin=report width
  185.  
  186.      CREATEing a REPORT FORM with a right margin value equal to the
  187. report width (the page width minus the left margin) will display
  188. garbage to the screen or printer.  This happens because there is no
  189. space to print the report.
  190.  
  191.      There is a general misconception about the meaning of the right
  192. margin in the REPORT FORM.  Some users have the impression that its
  193. value is the number of characters from the left margin, much the same
  194. way a typewriter works.  The value actually refers the the number of
  195. characters the right margin is offset from page width.  For example,
  196.  
  197.      page width    |------------------------------------->|
  198.      right margin                             |<----------|
  199.  
  200.  
  201. >>> REPORT FORM with stacked columns
  202.  
  203.      CREATEing a REPORT FORM field that consists of stacked columns
  204. built with the result of the STR() function and numeric fields may
  205. cause numbers to display incorrectly.  Specifically, entering:
  206.  
  207.      STR( Field1, 5 ) + STR( Field2, 5 )
  208.  
  209. in the field contents and specifying a column width of five will result
  210. in a correct display only when the fields have five-digit numbers
  211. stored in them.  If the number has less than five digits, the display
  212. will be misplaced by the number of digits missing.  The example above
  213. will produce:
  214.  
  215.         11111
  216.         11111
  217.  
  218.  
  219.  
  220.  
  221. if the fields are full, but:
  222.  
  223.          1111
  224.         1111
  225.         1111
  226.  
  227. if there are only four digits in the fields.
  228.  
  229.  
  230. >>>SET FILTER TO with GO BOTTOM
  231.  
  232.      If a SET FILTER TO condition is not satisfied by any records in
  233. the database file and a GO BOTTOM is issued, both the EOF() and BOF()
  234. will return a true (.T.).  Removing the filter by issuing a SET FILTER
  235. TO does not reset EOF() or BOF().  The record pointer must be
  236. repositioned to reset EOF() and BOF().  SKIP or SKIP -1, however, will
  237. return a file boundary error message, because EOF() and BOF() are true.
  238. To move the record pointer appropriately issue a GO TOP and the BOF()
  239. and EOF() values will be reset to false (.F.).  
  240.  
  241.  
  242. >>> Get Current Directory
  243.  
  244.      dBASE III has no facility to get the name of the current directory.
  245. To get it you must RUN the PC/MS-DOS command CD and import the results
  246. into dBASE III.  The basic algorithm is as follows:
  247.  
  248.      1. Create or have available a general-purpose database file called
  249. Util.DBF.  Util.DBF has one field called Util_line which is character
  250. type and has a length of 80.
  251.      2. RUN the PC/MS-DOS command CD, piping the result into a text
  252. file entitled Util.TXT.
  253.      3. APPEND the text file Util.TXT into the database file, Util.DBF.
  254.      4. Assign to a memory variable the name of the current DOS
  255. directory from Util.DBF.
  256.  
  257. The code that will execute this algorithm is as follows:
  258.  
  259.      SET SAFETY OFF
  260.      RUN CD > Util.TXT
  261.      USE Util
  262.      ZAP
  263.      APPEND FROM Util SDF
  264.      currdir = TRIM( Util_line )
  265.      SET SAFETY ON
  266.      RETURN
  267.  
  268.  
  269. >>> Get Diskspace
  270.  
  271.      In the Developer's Release use the DISKSPACE() function to get the
  272. amount of space left on the currently logged drive.  The DISKSPACE()
  273. will return the number of free bytes on the default drive as a numeric
  274. value.
  275.  
  276.      dBASE III versions 1.0 and 1.1 do not have a function to return
  277. the amount of space left on the default drive.  So, to get the amount
  278. of diskspace in these versions, use the PC/MS-DOS utility CHKDSK and
  279. import the results into dBASE III.  The basic algorithm is as follows:
  280.  
  281.      1. Create or have available a general purpose database file called
  282. Util.DBF.  Util.DBF has one field called Util_line which is character
  283. type and has a length of 80.  This database file will be useful for any
  284. of these kinds of survey operations into PC/MS-DOS.
  285.  
  286.      2. RUN CHKDSK including the designator of the drive for which you
  287. want the space statistic for, and pipe the result into a text file
  288. entitled Util.TXT.  Piping is a PC/MS-DOS capability that allows the
  289. results of a program to be sent into a text file.  It is very useful
  290. for passing parameters between programs when there is no formalized
  291. interface.  The syntax is:
  292.  
  293.                  <DOS commmand>  >  <result text file>
  294.  
  295.                                 ^_____ DOS piping symbol
  296.  
  297. For more information on this capability, consult your PC/MS-DOS
  298. reference manual.
  299.  
  300.      3. APPEND the text file, Util.TXT, into the database file,
  301. Util.DBF.
  302.  
  303.      4. Assign to a memory variable the number of free bytes on the
  304. specified drive from Util.DBF.  This operation requires that you GOTO
  305. the record that contains the free disk space information and then
  306. extract the number of bytes from the field using the SUBSTR() function.
  307.  
  308.      The following is a LIST of Util.DBF with the results of a CHKDSK
  309. report.  When APPENDed into a database file, the first and last records
  310. are always blank.  Records 2 through 6 contain statistics about the
  311. currently logged disk drive.  Note that this is the currently logged
  312. DOS drive and not the DEFAULT drive SET in dBASE III.  Records 8 and 9
  313. contain statistics about the memory configuration of your computer.
  314. The number of bytes for each attribute of the drive and memory occupy
  315. positions 1 through 9 in the database field, Util_line.
  316.  
  317.         Record#
  318.               1
  319.               2    9965568 bytes total disk space
  320.               3     155648 bytes in 4 hidden files
  321.               4      90112 bytes in 22 directories
  322.               5    6000640 bytes in 397 user files
  323.               6    3719168 bytes available on disk
  324.               7
  325.               8     524288 bytes total memory
  326.               9     122480 bytes free
  327.              10
  328.  
  329.      The code that will get the the number of free bytes on the
  330. specified disk drive is as follows:
  331.  
  332.      SET SAFETY OFF
  333.      RUN CHKDSK > Util.TXT
  334.      USE Util
  335.      ZAP
  336.      APPEND FROM Util SDF
  337.      GO 6
  338.      diskspace = STR( SUBSTR( Util_line, 1, 9 ), 9 )
  339.      USE
  340.      SET SAFETY ON
  341.      RETURN
  342.  
  343.  
  344. >>> Get Last Update and Time
  345.  
  346.      To get the date of last update for the currently SELECTed database
  347. file in the Developer's Release of dBASE III, use the LUPDATE()
  348. function.  LUPDATE() returns the date of last update as a value of date
  349. type.
  350.  
  351.      dBASE III versions 1.0 and 1.1 currently do not have a function
  352. that returns the date of last update for the SELECTed database file.
  353. To get the date of last update in these versions of dBASE III, use the
  354. PC/MS-DOS command DIR, and import the results into dBASE III.  The
  355. basic algorithm is as follows:
  356.  
  357.      1. Create or have available a general purpose database file
  358. called Util.DBF.  Util.DBF has one field called Util_line which is
  359. character type and has a length of 80.
  360.  
  361.      2. RUN the PC/MS-DOS command DIR with the name of your database
  362. file, piping the result into a text file entitled Util.TXT.
  363.  
  364.      3. APPEND the text file Util.TXT into the database file Util.DBF.
  365.  
  366.      4. Assign to a memory variable the date of last update from
  367. Util.DBF for your database file.
  368.  
  369.      The code that will get the last update of the currently SELECTed
  370. database file is as follows:
  371.  
  372.      SET SAFETY OFF
  373.      RUN DIR <Yourfile>.DBF > Util.TXT
  374.      USE Util
  375.      ZAP
  376.      APPEND FROM Util SDF
  377.      lupdate = SUBSTR( Util_line, 25, 8 )
  378.      luptime = SUBSTR( Util_line, 34, 6 )
  379.      USE
  380.      SET SAFETY ON
  381.      RETURN
  382.  
  383.  
  384.  
  385.  
  386. >>>Installation
  387.  
  388.      If the message "Insert System Disk #2 or press Ctrl-Break" appears
  389. when dBASE III is being loaded from a hard disk and is installed, the
  390. overlay file (DBASE.OVL) is corrupted.  One possibility is that the
  391. copy on the hard disk is corrupted and can no longer be used.  Another,
  392. and more likely, possibility is that the copy on System Disk #2 is bad,
  393. and you are now trying to run dBASE III after just having installed to
  394. the hard disk.  If this occurs, contact the Ashton-Tate Customer
  395. Service Department.
  396.  
  397.  
  398. >>> MEMO fields
  399.  
  400.      (1) MEMO fields are used to contain up to 5,000 characters of text
  401. information that is to be associated with a database record.
  402. Information may be read into a MEMO field using Ctrl-K-R and written to
  403. text files using Ctrl-K-W.  Information from MEMO fields can be
  404. displayed or printed by using LIST, DISPLAY, ?.  The field must be
  405. specified with these commands.  However, these commands cause the MEMO
  406. field to wrap at 50 columns.  The REPORT FORM may be used to output
  407. MEMO fields with line widths of more or less than 50 characters.
  408.  
  409.      (2) PACKing a database file with memo fields will not decrease the
  410. amount of disk space used by the .DBT file.  The command file below
  411. demonstrates how to remove the deleted records and free the unused disk
  412. space.
  413.  
  414.           SET DELETED ON
  415.           USE Filea
  416.           COPY TO Temp
  417.           CLOSE DATABASE
  418.           ERASE Filea.dbf
  419.           ERASE Filea.dbt
  420.           RENAME Temp.dbf TO Filea.dbf
  421.           RENAME Temp.dbt TO Filea.dbt
  422.           SET DELETED OFF
  423.  
  424.  
  425. >>> Ramdisk
  426.  
  427.      There are several options for users who wish to use a ramdisk in
  428. combination with dBASE III.
  429.  
  430.      1. For faster operation of applications that utilize routines
  431. stored in the DBASE.OVL file, you may wish to put the .OVL in a ramdisk.
  432.  
  433.           (a) The minimum drive size will have to be in excess of
  434. 181,000 bytes.  The DBASE.OVL file for version 1.1 is 180,736 bytes.
  435. The total amount of RAM in your machine must be more than 440,000 bytes
  436. in order to do this.  Additionally, if you are using a CONFIG.DB, it
  437. must be present on the drive where .OVL resides.
  438.           (b) Boot dBASE III from the ramdisk by calling for the
  439. DBASE.COM from the drive on which it resides.  For example:  drive D:
  440. is the ramdisk and the DBASE.COM is on the C: drive.
  441.  
  442.             C> D:
  443.             D> C:DBASE
  444.  
  445.      2. It may be a very useful area for procedure or command files to
  446. be run from, increasing the speed of processing.  Prior to entering
  447. dBASE III, copy the appropriate files to the ramdisk.  Once in dBASE
  448. III, SET the DEFAULT TO the ramdisk drive and proceed.
  449.  
  450.      3. It is also useful as a small work area to manipulate utility
  451. and temporary files.  The useage tips on getting the current directory
  452. or diskspace are good examples of where a small ramdisk would be
  453. extremely useful and time efficient.
  454.  
  455.  
  456. >>> REPORT FORM
  457.  
  458.      The semicolon is not documented as functioning as a Carriage
  459. Return/Line-Feed in certain parts of REPORT FORMs.
  460.  
  461.  
  462. >>> REPORT FORM grouped by week
  463.  
  464.      If you have a date-oriented report and you need to have it grouped
  465. by week, the following discussion will assist you.
  466.  
  467.      The grouping of dates into weeks has two requirements.  First, the
  468. database file you are reporting from must be INDEXed on the date field
  469. that is being grouped on.  Second, as the group expression in your
  470. REPORT FORM, you must have an expression that returns as its value the
  471. first day of the week for each date field.  The expression is as
  472. follows:
  473.  
  474.           Yourdate - ( DOW( Yourdate ) - 1 )
  475.  
  476.      When given any date value, this expression returns the date of the
  477. previous Sunday.  It does this by subtracting from your date field the
  478. number of days that have passed since the last Sunday, the first day of
  479. the week in the dBASE III calendar.  This value is obtained by
  480. subtracting 1 from the result of the DOW() function.
  481.  
  482.      If you wish to have the week you are grouping on start on a later
  483. day such as Monday, subtract more from the result of the DOW() function.
  484. For example, Monday would be DOW() - 2, Tuesday DOW() - 3, and so on.
  485.  
  486.  
  487. >>>WAIT TO
  488.  
  489.      When a function key is SET to a literal character string, the WAIT
  490. command will not accept the assigned string, although ACCEPT TO will.
  491. Instead, the WAIT command will take the ASCII code representation of
  492. the function key itself.  For example:
  493.  
  494.    SET FUNCTION 10 TO "A"
  495.    WAIT TO var
  496.  
  497.    * ---Press F10.
  498.    DISPLAY MEMORY
  499.  
  500.    VAR        pub   C  "v"
  501.        1 variables defined,        3 bytes used
  502.      255 variables available,   5997 bytes available
  503.  
  504.    * ---Test to see what ASCII character F10 sent.
  505.    ? ASC(var)
  506.    246 <------------------------- Code for F10
  507.    ? ASC("A")
  508.    65 <-------------------------- Code for "A"
  509.  
  510.  
  511. >>> Shifted Data Displays by Oliver Biggerstaff
  512.  
  513.      A database file may become corrupted for any number of reasons.
  514. Often the corruption may be the form of shifted data in full-screen
  515. edit screen displays.  This is caused by an embedded null character in
  516. a record.  A null character is represented as a 00 hex and is used by
  517. dBASE II and dBASE III as a string terminator.  A string terminator is
  518. a character that can be thought of as a delimiter, much like double
  519. quotes surrounding a character string, or as a carriage return and
  520. linefeed at the end of a record.
  521.  
  522.      The dBASE APPEND, EDIT, BROWSE, and other full-screen commands
  523. work on the principle that the cursor's positioning on the screen
  524. depends on certain attributes, such as the length of a field and its
  525. current position.  The appearance of shifted data is caused by the
  526. embedded string terminator forcing dBASE to stop the display of a field
  527. prematurely, placing the cursor at an incorrect location.  If a null
  528. character is encountered before all the characters of a field have been
  529. displayed, dBASE will stop listing that particular field and will
  530. produce the shift effect by displaying the next field at an incorrect
  531. screen location.
  532.  
  533.      The data is actually not shifted physically in the database file.
  534. It is simple, therefore, to correct the database by replacing any null
  535. character with another character that does not force dBASE to display
  536. incorrectly.  A good choice for this character is the ASCII character
  537. zero (0) or 30 hex.  Replacing a null with this character is
  538. advantageous for two reasons:
  539.  
  540.      1. Since dBASE can display it, you can locate the corrupted data.
  541.  
  542.      2. A zero character will have the least disruptive effect on the
  543. contents of the database file.  Additionally, logical fields that
  544. contain a zero character will be displayed as (.F.)
  545.  
  546.      Once all the null characters have been replaced, the BROWSE or
  547. EDIT commands can be used to retype the original data in place of the
  548. characters that replaced the nulls. 
  549.  
  550.  
  551.      The following is one of many methods that can be used to replace
  552. null characters with other characters.  In this example we use the
  553. PC/MS-DOS utility DEBUG.COM, since most of you have this program on
  554. your supplemental PC/MS-DOS disk.  These examples assume that the
  555. database file is 64K or less in size.  Refer to the PC/MS-DOS manual
  556. for more information on DEBUG and how to use data segments if the
  557. database file is larger than 64K.
  558.  
  559.      The contents contained in the < and > symbols must be calculated
  560. by you, and entered without the symbols.  For example, if the value of
  561. the CX register is 2C80H, then <CX+100H> is to be replaced with 2D80H.
  562. Be sure that before you attempt this procedure, you have make a backup
  563. of your database file.
  564.  
  565. For dBASE II users on a 16-bit computer:
  566.  
  567. C>DEBUG <database>.DBF ;Read database file into memory.
  568. -RCX                   ;Get the value in the CX register
  569. -S 309 <CX+100H> 00    ;Search for nulls in the file.
  570.   .
  571.   .                    ;A list appears here of one or more
  572.   .                    ;addresses containing a null.
  573.   .
  574. -E <address> 30        ;Replace each individual address that contains
  575.                        ;a null with a zero.
  576. -W                     ;Save the modifications to disk.
  577. -Q                     ;Quit DEBUG.
  578.  
  579. For dBASE III users:
  580.  
  581. C>DEBUG <database>.DBF ;Read database file into memory.
  582. -RCX                   ;Get the value of the CX register.
  583. -S 100H 1121H 0DH      ;Search for the end of the header for address
  584.                        ;containing 0DH.
  585. xxxx:yyyy              ;for address containing 0D.
  586.                        ;Search for null characters.
  587.  
  588. -S <yyyy+2> <CX+100H> 00
  589.   .
  590.   .                     ;List of addresses containing
  591.   .                     ;nulls.
  592.   .
  593. -E <address> 30H        ;Replace each null with a zero.
  594. -W                      ;Save the modifications to disk.
  595. -Q                      ;Quit DEBUG.
  596.  
  597.      Unfortunately, this method of replacing null characters can be
  598. very tedious if many characters must be replaced.  For those of you
  599. with a large amount of corrupted data, it is suggested that you use
  600. other well-known utility programs such as NIBBLER, NORTON UTILITIES,
  601. JAZ, or PATCH.  These programs will allow you to look at very large
  602. database files directly from the hard disk.  Some of these programs
  603. may also have a global search and replace option.
  604.  
  605.  
  606. >>> Swapping Printer Ports on the IBM PC by Robert Boies
  607.  
  608.      Last month in the dBASE II section of TechNotes we presented an
  609. assembler routine to swap printer ports on an IBM PC.  This routine
  610. allows the user or programmer to toggle the LPT1 and LPT2 ports, making
  611. it easy to redirect output to several printers.  This month we present
  612. essentially the same routine configured for dBASE III.
  613.  
  614.      If you have versions 1.0 or 1.1 of dBASE III, you will have to
  615. create a .COM file from this routine and at runtime use the RUN command
  616. to call it.  For example from within dBASE III,
  617.  
  618.       * ---Print a first report to the default port, LPT1.
  619.       REPORT FORM One TO PRINT
  620.  
  621.       * ---Print a second report to LPT2.
  622.       RUN Portswap <---------------------------- Redirect to LPT2.
  623.       REPORT FORM Two TO PRINT
  624.       RUN Portswap <---------------------------- Restore LPT1.
  625.  
  626.      If you have the Developer's Release of dBASE III, you will be able
  627. to use the assembly language interface implemented in that version.
  628. For example:
  629.  
  630.       * ---LOAD into memory.
  631.       LOAD Portswap
  632.  
  633.       * ---Print a first report to to the default port, LPT1.
  634.       REPORT FORM One TO PRINT
  635.  
  636.       * ---Print a second report to LPT2.
  637.       CALL Portswap <---------------------------- Redirect to LPT2.
  638.       REPORT FORM Two TO PRINT
  639.       CALL Portswap <---------------------------- Restore LPT1.
  640.  
  641.       * ---RELEASE the memory space.
  642.       RELEASE MODULE Portswap
  643.  
  644.      The listing below, Portswap.ASM, uses the conditional directives
  645. discussed earlier to allow you to specify whether you wish to create
  646. a .BIN or .COM file.
  647.  
  648. Assembly language source code:
  649.  
  650. ; Program ..: Portswap.ASM
  651. ; Author ...: Robert Boies
  652. ; Date .....: September 1, 1985
  653. ; Note .....: Swaps the LPT1 and LPT2 ports in PC/MS-DOS 2.x
  654.  
  655.         .LFCOND               ; List false conditionals.
  656.         PAGE 60,132           ; Page length 60, line 132.
  657.  
  658. COM     EQU      0            ; Assemble as .BIN file
  659. D3      EQU      1            ; for Developer's Release.
  660. ;
  661. CODESEG SEGMENT BYTE PUBLIC 'CODE'
  662.         ASSUME    CS:CODESEG
  663. ;
  664. PORTSWAP PROC   FAR
  665. ;
  666.         IF COM
  667.           ORG    0100H       ; Originate at 0100H.
  668.         ENDIF
  669. ;
  670. START:  PUSH     ES          ; Save environment.
  671.         PUSH     AX
  672.         PUSH     BX
  673.         PUSH     DI
  674. ;
  675.          MOV     AX,40H      ; System stores critical operating
  676.                              ; parameters at segment 40H (absolute
  677.                              ; address 400H).
  678.                              ; Load this segment address into AX.
  679.         PUSH     AX          ; Put it on the stack.
  680.         POP      ES          ; Pop the segment id from the
  681.                              ; stack into extra segment register.
  682.         MOV      DI,8H       ; Port address of LPT1 and LPT2 are
  683.                              ; stored at offsets 8H and 0AH in
  684.                              ; this segment.  Load DI with offset.
  685.         MOV      AX,ES:[DI]  ; Put the port address of the
  686.                              ; first printer into the accumulator.
  687.         INC      DI          ; Increment the destination index
  688.                              ; twice to point to port address
  689.         INC     DI           ; of the second printer.
  690.         MOV     BX,ES:[DI]   ; Put the port address of the
  691.                              ; second printer into BX.
  692.         MOV    ES:[DI],AX    ; Poke the address of the first
  693.                              ; printer into the location of the
  694.                              ; second printer.
  695.         DEC    DI            ; Decrement DI twice to point to the
  696.         DEC    DI            ; location of the first printer
  697.                              ; port address.
  698.         MOV    ES:[DI],BX    ; Poke the address of the second
  699.                              ; printer into the location of the
  700.                              ; first.
  701.         POP    DI            ; Restore environment.
  702.         POP    BX
  703.         POP    AX
  704.         POP    ES
  705.  
  706.         IF     COM
  707.           INT  20H           ; INT 20H if .COM file
  708.         ELSE
  709.           RET                ; far return to dBASE III
  710.         ENDIF
  711. ;
  712. PORTSWAP ENDP
  713. CODESEG  ENDS
  714.          END      START
  715.  
  716. >>> Interfacing Assembly Language Routines with dBASE by Ralph Davis
  717.  
  718. Creating Assembler Programs with DEBUG
  719.  
  720.      DEBUG is the assembly language programmer's best friend.  It is a
  721. powerful tool for exploring the computer's memory, testing assembly
  722. language programs, studying program listings and creating new programs.
  723. Additionally, it can be used to rebuild corrupted data files, convert
  724. hidden files to accessible files, or simply analyze file structures.
  725. Our main interest in DEBUG here is to create assembly language routines
  726. for use with dBASE II and dBASE III.
  727.  
  728.      It is tempting to use DEBUG because of its interpreter-like
  729. qualities.  You can quickly enter code and then see if it works.  If it
  730. does, you call it <PROGRAM>.COM and write it to disk.  If it doesn't,
  731. you trace through the old code, enter new code, and try again.
  732. Eventually, you come up with a program that works through trial-and-
  733. rror.  However, this can lead to sloppy programming habits and
  734. inefficient code, so it is important to bear in mind what you want a
  735. particular program to accomplish.
  736.  
  737.      DEBUG has some limitations.  Most importantly, it only recognizes
  738. absolute addresses.  When you write a program for submission to an
  739. assembler, you label the instructions and data you will need to refer
  740. to, then refer to them with the label.  You don't need to know the
  741. actual addresses.  DEBUG, on the other hand, obliges you to look
  742. through your program listing and find addresses whenever you refer to
  743. them.  For instance, instead of entering JMP EXIT, you must enter JMP
  744. 02FC.  Instead of CALL HEXPRINT, you use CALL 05AE.  Instead of MOV BX,
  745. OFFSET DATA, you need MOV BX, 0105.  If your routine is small, this
  746. does not present a problem.  But as you add features and it becomes
  747. larger, this becomes a serious impediment.  If you add or alter
  748. instructions, thereby changing an absolute address, you have to change
  749. every reference to it.  And the only way to find the references is to
  750. page through the entire program, line by line.  For this reason, DEBUG
  751. is best for creating short utility programs.
  752.  
  753.      Most often, programs created with DEBUG use BIOS or DOS interrupts
  754. to manipulate the hardware.  Some typical functions that appear in this
  755. issue are setting the cursor (see the example on page 4-72C of the
  756. Developer's Release Reference Manual and the program listed in this
  757. issue), manipulating the shift keys, or swapping printer ports.
  758. Programs of this type should not contain any subroutines.
  759.  
  760.      DEBUG has another important limitation: it only understands
  761. hexadecimal numbers.  There is simply nothing you can do to make it
  762. accept decimal numbers.  This is not a problem when entering addresses
  763. or interrupt numbers, as most assembly language programmers think these
  764. values in hexadecimal anyway.  But very few programmers think in hex
  765. when doing calculations.  DEBUG is therefore not a good tool for doing
  766. number-crunching of even intermediate complexity.  Although there are
  767. utilities available to assist in this process, such as Sidekick, this
  768. is still a major obstacle to doing extensive calculations within DEBUG.
  769.  
  770.  
  771.      Another problem with DEBUG is that code produced with it can be
  772. extremely obscure.  Trying to decipher the flow of a program where you
  773. have only absolute addresses and hexadecimal numbers to guide you can
  774. be very frustrating.  In addition, DEBUG does not support comments.  So
  775. when you read a DEBUG listing, you are, for all intents and purposes,
  776. reading "machine English."  The machine expresses its own language in
  777. cryptic English-like symbols, making a few grudging concessions to your
  778. desire to understand it.  All of this reinforces what we suggested
  779. earlier: keep DEBUG routines short.
  780.  
  781.      The program from the Developer's Release Reference Manual
  782. mentioned above is a good example of a program appropriate for DEBUG.
  783. The listing on page 4-72C is as follows: 
  784.  
  785.                _PROG SEGMENT BYTE
  786.                ASSUME    CS:_PROG
  787.      ;
  788.      CURSOR    PROC       FAR       ; Force a far return.
  789.      ;
  790.                MOV        CX,[BX]    ; Get two HEX digits.
  791.                MOV        AH,1       ; Set cursor type.
  792.                INT        10H        ; Video interrupt.
  793.                RET                   ; Do a far return.
  794.      ;
  795.      CURSOR    ENDP
  796.      ;
  797.      _PROG     ENDS
  798.                END
  799.  
  800.      This is a terse routine that converts the dBASE III cursor to a
  801. full-sized box when CHR(18) is passed as a parameter to it.  Notice one
  802. thing about this code: it has six lines of assembler directives (the
  803. first three and the last three), and only four lines of machine
  804. instructions.  In a short program like this one, there is no advantage
  805. to assembling, linking, and converting it using MASM, LINK and EXE2BIN.
  806. DEBUG is faster and easier.
  807.  
  808.      Here is a DEBUG session that enters this program as a .COM file.
  809. (The DEBUG commands are explained in Chapter 8 of the PC/MS-DOS manual.
  810. Page numbers which follow refer to it.)
  811.  
  812. D>debug
  813.  
  814.      First give DEBUG the 'A' (assemble) command (page 8-15) and enter
  815. the program.
  816.  
  817. -A
  818. 6257:0100  MOV CX,[BX]
  819. 6257:0102  MOV AH,1
  820. 6257:0104  INT 10
  821. 6257:0106  INT 20
  822. 6257:0108
  823.  
  824.  
  825.  
  826.      Notice that 'INT 20' is our last instruction, not 'RET' as the
  827. manual indicates.  We will explain this shortly.
  828.      The address following the last instruction is 108.  Therefore,
  829. enter eight into CX using the 'R' (register) command [page 8-41].  This
  830. tells DEBUG the number of bytes to write to disk.
  831.  
  832. -RCX
  833. CX 0000
  834. :8
  835.  
  836.      Name the program CURSOR.COM using the 'N' command [page 8-37], and
  837. write it to disk using 'W' [page 8-55].
  838.  
  839. -NCURSOR.COM
  840. -W
  841. Writing 0008 bytes
  842.  
  843.      This is the basic procedure for creating a .COM file from DEBUG.
  844. CURSOR.COM will yield unpredictable results executed from PC/MS-DOS,
  845. since the registers are not preserved, and we have no way of knowing
  846. what is being passed in DS:BX.  (When we tested it, the cursor simply
  847. vanished.)  Nor, in its present form, will it work in dBASE III.  It
  848. needs a couple of changes to make it work, but this point deserves some
  849. attention.
  850.  
  851.      PC/MS-DOS .COM files and dBASE LOAD modules require slightly
  852. different specifications.  A .COM file must be ORGed (originated) at
  853. address 100H, and it must end with a command like INT 20H (terminate)
  854. or INT 27H (terminate and stay resident); a simple RET will not return
  855. correctly.  dBASE III, on the other hand, requires LOAD modules to be
  856. ORGed at address 0 and to return to dBASE III with a far return, RETF.
  857. If you load a conventional .COM file, ORGed at 100H and terminated with
  858. INT 20H, into dBASE III, and then call it, you will lock the system,
  859. even if it works from PC/MS-DOS.  When DEBUG writes a program to disk,
  860. it writes a binary file -- that is, a file which contains nothing but
  861. the machine instructions you have given it.  Therefore, we need not
  862. concern ourselves with ORGing programs correctly at this stage.  We do
  863. have to terminate LOAD modules with RETF, however.  Here is a DEBUG
  864. session that enters this program as a .BIN file which will execute from
  865. dBASE III.
  866.  
  867. D>debug
  868.  
  869. Type 'A' for assemble.  Terminate with a RETF.
  870.  
  871. -A
  872. 6346:0100  MOV CX,[BX]
  873. 6346:0102  MOV AH,1
  874. 6346:0104  INT 10
  875. 6346:0106  RETF
  876. 6346:0107
  877.  
  878. Place the number 7 in the CX register to save 7 bytes to disk.
  879.  
  880.  
  881.      -RCX
  882.      CX 0000
  883.      :7
  884.  
  885. Name the file, and write it.
  886.  
  887. -NCURSOR.BIN
  888. -W
  889. Writing 0007 bytes
  890.  
  891. Quit DEBUG.
  892.  
  893. -Q
  894.  
  895.      The page of the Developer's Release Manual referred to above gives
  896. the following example of how to use Cursor:
  897.  
  898.      LOAD Cursor
  899.      STORE CHR(18) TO shape
  900.      CALL Cursor WITH shape
  901.  
  902. The commands to convert the cursor back to its normal format are:
  903.  
  904.      LOAD Cursor
  905.      STORE CHR(12) + CHR(11) to shape
  906.      CALL Cursor WITH shape
  907.  
  908.  
  909. >>> On .COM Files vs. .EXE Files
  910.  
  911.      When creating programs with a full-featured assembler, we have two
  912. options: .COM files and .EXE files.  Each has advantages and
  913. disadvantages.
  914.  
  915.      .COM files are an inheritance from the world of 8-bit CP/M.  They
  916. are your only option if you have a CP/M machine.  .COM files must
  917. adhere to a strictly defined structure.
  918.  
  919.      1. They must fit entirely within one segment.  All segment
  920. registers must point to the same address, and cannot be changed during
  921. the execution of the program.  This means that all of our main program,
  922. subroutines, and data must fit in 64K.  A 64K .COM file is a very large
  923. program -- each line of code assembles to between 1 and 6 bytes, so a
  924. 64K .COM file could have as many as 30,000 lines of source code.
  925.  
  926.      2. They must be ORGed at 100H.  When PC/MS-DOS loads a .COM file,
  927. it jumps to CS:100H and begins executing.
  928.  
  929.      3. They must return control to their calling routine with either
  930. INT 20H or INT 27H, or the equivalent INT 21H function calls, 4CH and
  931. 31H.
  932.  
  933.      .COM files load more quickly than .EXE files, since no addresses
  934. need to be calculated at load time.
  935.  
  936.      The assembly language programs that dBASE II and dBASE III can
  937. execute as subroutines (with the CALL command) are variations of a .COM
  938. file.  We will discuss the specifics of their formats later.
  939.  
  940.      .EXE files are less limited structurally.  The segment registers
  941. can be freely manipulated, and each one can point to an entirely
  942. different 64K segment.  .EXE files can therefore be much larger than
  943.  .COM files.  .EXE files were designed to take better advantage of the
  944. actual architecture of 16-bit 8086-based microprocessors.  Having data
  945. in one segment, code in another, and the stack in a third allows much
  946. greater utilization of the memory space available in today's machines.
  947. It also provides us the semblance of structured programming in assembly
  948. language.  The SEGMENT, PROC, ENDS, and ENDP operators give a program
  949. listing a much more organized appearance than it has with JMP and DB
  950. statements interspersed throughout the code.
  951.  
  952.      .EXE files take longer to load than .COM files, as many of the
  953. absolute addresses are not computed until load time.  They also take up
  954. more disk space than .COM files.  However, since they use much more of
  955. the 8086 family's capabilities, they can be much more powerful programs.
  956. The commercial programs which were handed down from the CP/M world are
  957. all .COM files, whereas those which were created since the advent of
  958. 16-bit machines are mostly .EXE files.
  959.  
  960.      Having said this, we will leave .EXE files behind.  You cannot
  961. LOAD .EXE files from dBASE II or dBASE III.  You can execute them with
  962. QUIT TO in dBASE II or RUN(!) in dBASE III.  If you want to pass
  963. parameters to and from .EXE files, you must pass them in text files
  964. (the SDF format is recommended).
  965.  
  966.  
  967. >>> Adapting Assembly Language Programs to dBASE II or III
  968.  
  969.      As mentioned earlier, the format of a dBASE II or III assembly
  970. language subroutine most closely resembles that of a .COM file.  Most
  971. importantly, it must reside in one segment.  Since it is intended as a
  972. subroutine, not as a stand-alone program, it will differ somewhat from
  973. a standard .COM file.
  974.  
  975.      For one thing, a .COM file must be ORGed at 100H.  However, ORGing
  976. a dBASE (II or III) subroutine at 100H will cause it to fail.  A
  977. program intended for use in dBASE II must be ORGed high in the code
  978. segment -- the exact address depends on the version of dBASE II, the
  979. later the version, the higher the address.  In version 2.43*, the ORG
  980. address should be above 61440 decimal.  (See Robert Boies' article on
  981. swapping printer ports in the August issue of TechNotes for a good
  982. example of a dBASE II assembly language program.)  A program intended
  983. for dBASE III must be ORGed at 0 (that is, it need not have an ORG
  984. statement). Secondly, .COM files return to their caller with interrupts
  985. (usually 20H or 27H), whereas dBASE II and dBASE III routines require
  986. RET (return) -- near for dBASE II, far for dBASE III.
  987.  
  988.      The procedure for converting assembly language source code into
  989. programs dBASE II or III can execute are as follows:
  990.  
  991.      1. For dBASE II, you must assemble your program with an assembler
  992. that produces a file in Intel .HEX format.  Intel's assemblers, ASM
  993. (for CP/M) and ASM86 (for CP/M-86), create such a file.  For PC/MS-DOS,
  994. the Seattle Computer Products assembler generates a .HEX  file.  Refer
  995. to their manuals, as their assembly language syntax differs somewhat
  996. from Microsoft's and IBM's.
  997.  
  998.      2. For dBASE III, use the IBM or Microsoft Macro-Assembler
  999. (MASM.EXE) to produce a .OBJ (object) file.  Enter the command as
  1000. follows:
  1001.  
  1002.           MASM <filename> <filename> <filename>;
  1003.  
  1004. The third parameter will cause MASM to produce a listing file with
  1005. a .LST extension, which is very useful for debugging.
  1006.  
  1007.      3. Use the linker utility (LINK.EXE) that comes both with
  1008. PC/MS-DOS and with the assembler.  This will create an .EXE file.  The
  1009. command is:
  1010.  
  1011.           LINK <filename>
  1012.  
  1013. Press Return three times in response to the prompts.
  1014.  
  1015.      4. Use EXE2BIN.EXE to convert the program to .COM or .BIN format.
  1016. If you are creating a .BIN file, you need only enter one parameter in
  1017. the command line:
  1018.  
  1019.           EXE2BIN <filename>
  1020.  
  1021. If you are creating a .COM file, you need to specify the full target
  1022. filename:
  1023.  
  1024.           EXE2BIN <filename> <filename>.COM
  1025.  
  1026.  
  1027. >>> Using Conditional Assembler Directives
  1028.  
  1029.      Because the differences between .COM files and .BIN files are
  1030. minor, it is possible to generate both using the same source code.  The
  1031. following program skeleton shows how to set this up. The EQU statements
  1032. at the top inform the assembler whether we are assembling a program for
  1033. PC/MS-DOS or dBASE III.  In the present example, we have set COM equal
  1034. to 0 (meaning false) and D3 equal to 1 (non-zero, meaning true).  We
  1035. then use conditional directives to tell the assembler how we want the
  1036. program created.  Conditional directives are statements in your
  1037. assembly program to direct the assembler to assemble a block of
  1038. instructions based on a variable value.  For example, IF COM (if COM is
  1039. not zero), ORG the program at offset 100H.  Then at the end of the
  1040. program, IF COM, exit with INT 20H; otherwise, exit with a far RET.
  1041.  
  1042.  
  1043.         .LFCOND            ; List false conditionals,
  1044.          PAGE    60,132    ; page length 60, line 132.
  1045.  
  1046.     COM  EQU    0          ; Assemble program as .BIN
  1047.     D3   EQU    1          ; file for dBASE III.
  1048.  
  1049.     CODESEG    SEGMENT    BYTE PUBLIC 'CODE'
  1050.     ROUTINE PROC    FAR
  1051.          ASSUME CS:CODESEG,DS:CODESEG
  1052.  
  1053.          IF    COM
  1054.             ORG    100H
  1055.          ENDIF
  1056.  
  1057.          PUSH        DS        ; Make sure DS points to
  1058.          PUSH        CS        ; the current
  1059.          POP         DS        ; segment.
  1060.         .
  1061.         .    (program goes here)
  1062.         .
  1063.         .
  1064.  
  1065.          POP         DS        ; Restore caller's DS.
  1066.          IF    COM
  1067.            INT       20H       ; INT 20H if .COM file.
  1068.          ELSE
  1069.           RET                  ; Far return if dBASE III
  1070.          ENDIF
  1071.  
  1072.     ROUTINE  ENDP
  1073.     CODESEG  ENDS
  1074.               END
  1075.  
  1076.      It is very important to load the DS register with the segment
  1077. address contained in CS.  PC/MS-DOS does this automatically for
  1078. a .COM file, but dBASE III does not.  Therefore, if your routine needs
  1079. to access its own data, it will need to set DS correctly.
  1080.  
  1081.  
  1082. Sample Program With Conditional Assembly
  1083.  
  1084.      Here is a program built on the skeletal structure which sets
  1085. condensed print on an EPSON printer.
  1086.  
  1087.  ; Program ...: Printer.ASM
  1088.  ; Author ....: Ralph Davis
  1089.  ; Date ......: September 1, 1985
  1090.  
  1091.  TITLEPRINTER.ASM -- sets condensed print
  1092.  
  1093.   .LFCOND
  1094.  PAGE60,132
  1095.  
  1096.  COMEQU0
  1097.  D3EQU1
  1098.  
  1099.  CODESEGSEGMENTBYTE PUBLIC 'CODE'
  1100.  PRINTER PROCFAR
  1101.  ASSUME CS:CODESEG,DS:CODESEG
  1102.  
  1103.   IFCOM
  1104.      ORG100H
  1105.   ENDIF
  1106.  
  1107.  START:JMPSHORT ENTRY; Jump past data.
  1108.  CODESDB27,64,27,15; Printer control codes.
  1109.  CODELEN EQU$-CODES; Length of string.
  1110.  ENTRY: PUSHAX; Save registers.
  1111.  PUSHBX
  1112.  PUSHDS
  1113.  PUSHCS; Set up DS
  1114.  POPDS; with current segment.
  1115.  PUSHCX; Save CX
  1116.  PUSHDX; and DX.
  1117.   MOV     BX,OFFSET CODES; Point BX to codes.
  1118.  MOVCX,CODELEN; Length of string.
  1119.  ; Controls the loop.
  1120.  GET_CODE:
  1121.  MOV     DL,BYTE PTR [BX]       ; Get code to send.
  1122.  MOV     AH,5H                  ; PC/MS-DOS function 5H,
  1123.  INT     21H                    ; (send char to printer).
  1124.   INC     BX                     ; Point to next code
  1125.   LOOP    GET_CODE               ; and print it.
  1126.  
  1127.  POPDX; Restore registers.
  1128.  POPCX
  1129.  POPDS
  1130.  POPBX
  1131.  POPAX
  1132.  
  1133.   IFCOM
  1134.           INT     20H           ; INT 20H if .COM file.
  1135.   ELSE
  1136.    RET; Far return to dBASE III.
  1137.   ENDIF
  1138.  
  1139.  PRINTER  ENDP
  1140.  CODESEG  ENDS
  1141.   ENDSTART; End assembly.
  1142.  
  1143.  
  1144.      Assemble this program according to the instructions given earlier.
  1145. To run it from dBASE II or dBASE III versions 1.0  and 1.1, assemble it
  1146. as a .COM file, and enter the following commands:
  1147.  
  1148.  dBASE II:
  1149.  
  1150.       QUIT TO 'Printer'
  1151.  
  1152.  dBASE III:
  1153.  
  1154.       RUN Printer
  1155.  
  1156.      To run it from the Developer's Release of dBASE III, assemble it
  1157. as a .BIN file, and use these commands:
  1158.  
  1159.       LOAD Printer
  1160.       CALL Printer
  1161.  
  1162.