home *** CD-ROM | disk | FTP | other *** search
/ Microsoft Programmer's Library 1.3 / Microsoft_Programmers_Library.7z / MPL / basic / b71pgrmr.txt < prev    next >
Encoding:
Text File  |  2013-11-08  |  1.2 MB  |  30,600 lines

Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
  1.  Microsoft BASIC Programmer's Reference
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  Chapter 1:  Control-Flow Structures
  11.  
  12.  This chapter shows you how to use control-flow structures --
  13.  specifically, loops and decision statements -- to control the flow
  14.  of your program's execution. Loops make a program execute a
  15.  sequence of statements as many times as you want. Decision statements let
  16.  the program decide which of several alternative paths to take.
  17.  
  18.  When you are finished with this chapter, you will know how to do the
  19.  following tasks related to using loops and decision statements in your
  20.  Microsoft BASIC programs:
  21.  
  22.    ■   Compare expressions using relational operators.
  23.  
  24.    ■   Combine string or numeric expressions with logical operators and
  25.        determine whether the resulting expression is true or false.
  26.  
  27.    ■   Create branches in the flow of the program with the statements  IF...
  28.        THEN... ELSE and  SELECT CASE.
  29.  
  30.    ■   Write loops that repeat statements a specific number of times.
  31.  
  32.    ■   Write loops that repeat statements while or until a certain condition
  33.        is true.
  34.  
  35.  
  36.  
  37.  Changing Statement Execution Order
  38.  
  39.  Left unchecked by control-flow statements, a program's logic flows through
  40.  statements from left to right, top to bottom. While some very simple
  41.  programs can be written with only this unidirectional flow, most of the
  42.  power and utility of any programming language comes from its ability to
  43.  change statement execution order with decision structures and loops.
  44.  
  45.  With a decision structure, a program can evaluate an expression, then branch
  46.  to one of several different groups of related statements (statement blocks)
  47.  depending on the result of the evaluation. With a loop, a program can
  48.  repeatedly execute statement blocks.
  49.  
  50.  If you are coming to Microsoft BASIC after programming in BASICA, you will
  51.  appreciate the added versatility of these additional control-flow
  52.  structures:
  53.  
  54.    ■   The block  IF... THEN... ELSE statement
  55.  
  56.    ■   The  SELECT CASE statement
  57.  
  58.    ■   The  DO... LOOP and  EXIT DO statements
  59.  
  60.    ■   The  EXIT FOR statement, which provides an alternative way to exit
  61.        FOR... NEXT loops
  62.  
  63.  
  64.  
  65.  Boolean Expressions
  66.  
  67.  A Boolean expression is any expression that returns the value "true" or
  68.  "false." BASIC uses Boolean expressions in certain kinds of decision
  69.  structures and loops. The following  IF... THEN... ELSE statement contains a
  70.  Boolean expression, X < Y :
  71.  
  72.  IF X < Y THEN CALL Procedure1 ELSE CALL Procedure2
  73.  
  74.  In the previous example, if the Boolean expression is true (if the value of
  75.  the variable X is in fact less than the value of the variable Y), then
  76.  Procedure1 is executed; otherwise (if X is greater than or equal to Y),
  77.  Procedure2 is executed.
  78.  
  79.  The preceding example also demonstrates a common use of Boolean expressions:
  80.  comparing two other expressions (in this case, X and Y) to determine the
  81.  relationship between them. These comparisons are made with the relational
  82.  operators shown in Table 1.1.
  83.  
  84.  
  85.   =       Equal
  86.   <>      Not equal
  87.   <       Less than
  88.   <=      Less than or equal to
  89.   >       Greater than
  90.   >=      Greater than or equal to
  91.  
  92.  
  93.  You can use these relational operators to compare string expressions. In
  94.  this case "greater than," "less than," and so on refer to alphabetical
  95.  order. For example, the following expression is true, since the word deduce
  96.  comes alphabetically before the word deduct:
  97.  
  98.  "deduce" < "deduct"
  99.  
  100.  Boolean expressions also frequently use the "logical operators"  AND,  OR,
  101.  NOT,  XOR,  IMP, and  EQV. These operators allow you to construct compound
  102.  tests from one or more Boolean expressions. For example:
  103.  
  104.   expression1 AND  expression2
  105.  
  106.  The preceding example is true only if expression1 and expression2 are both
  107.  true. Thus, in the following example, the message All sorted is printed only
  108.  if both the Boolean expressions X <= Y and Y <= Z are true:
  109.  
  110.  IF (X <= Y) AND (Y <= Z) THEN PRINT "All sorted"
  111.  
  112.  The parentheses around the Boolean expressions in the last example are not
  113.  really necessary, since relational operators such as  <= are evaluated
  114.  before logical operators such as  AND.
  115.  
  116.  
  117.  However, parentheses make a complex Boolean expression more readable and
  118.  ensure that its components are evaluated in the order that you intend.
  119.  
  120.  BASIC uses the numeric values -1 and 0 to represent true and false,
  121.  respectively. You can see this by asking BASIC to print a true expression
  122.  and a false expression, as in the next example:
  123.  
  124.   X = 5
  125.   Y = 10
  126.   PRINT X < Y  ' Evaluate, print a "true"  Boolean expression.
  127.   PRINT X > Y  ' Evaluate, print a "false" Boolean expression.
  128.  
  129.  Output
  130.   -1
  131.    0
  132.  
  133.  The value -1 for true makes more sense when you consider how BASIC's
  134.  NOT operator works:  NOT inverts each bit in the binary representation of
  135.  its operand, changing one bits to zero bits, and zero bits to one bits.
  136.  Therefore, since the integer value 0 (false) is stored internally as a
  137.  sequence of 16 zero bits, NOT 0 (true) is stored internally as 16 one bits,
  138.  as follows:
  139.  
  140.   0000000000000000
  141.  
  142.   TRUE = NOT FALSE = 1111111111111111
  143.  
  144.  In the two's-complement method that BASIC uses to store integers, 16 one
  145.  bits represent the value -1.
  146.  
  147.  Note that BASIC returns -1 when it evaluates a Boolean expression as
  148.  true; however, BASIC considers any nonzero value to be true, as shown by the
  149.  output from the following example:
  150.  
  151.  INPUT "Enter a value: ", X
  152.  
  153.   IF X THEN PRINT X;"is true."
  154.  
  155.  Output
  156.   Enter a value:  2
  157.    2 is true.
  158.  
  159.  The  NOT operator in BASIC is a "bitwise" operator. Some programming
  160.  languages, such as C and Pascal, have both a bitwise  NOT operator and a
  161.  "logical"  NOT operator. The distinction is as follows:
  162.  
  163.    ■   A bitwise  NOT returns false (0) only for the value -1.
  164.  
  165.    ■   A logical  NOT returns false (0) for any true (nonzero) value.
  166.  
  167.  
  168.  
  169.  In BASIC, for any true  expression not equal to -1,  NOT  expression
  170.  returns another true value, as shown in the following table:
  171.  
  172. ╓┌───────────────────────────┌───────────────────────────────────────────────╖
  173.  ────────────────────────────────────────────────────────────────────────────
  174.   1                          -2
  175.   2                          -3
  176.  -2                           1
  177.  -1                           0
  178.  
  179.  
  180.  
  181.  
  182.  So beware:  NOT  expression is false only if  expression evaluates to a
  183.  value of -1. If you define Boolean constants or variables for use in
  184.  your programs, use -1 for true.
  185.  
  186.  You can use the values 0 and -1 to define helpful mnemonic Boolean
  187.  constants for use in loops or decisions. This technique is used in many of
  188.  the examples in this manual, as shown in the following program fragment,
  189.  which sorts the elements of an array named Amount in ascending order:
  190.  
  191.   ' Define symbolic constants to use in program:
  192.   CONST FALSE = 0, TRUE = NOT FALSE
  193.   .
  194.  .
  195.   .
  196.   DO
  197.      Swaps% = FALSE
  198.      FOR I% = 1 TO TransacNum - 1
  199.         IF Amount(I%) < Amount(I%+1) THEN
  200.            SWAP Amount(I%), Amount(I%+1)
  201.            Swaps% = TRUE
  202.         END IF
  203.      NEXT I%
  204.   LOOP WHILE Swaps%   ' Keep looping while Swaps is TRUE.
  205.   .
  206.  .
  207.   .
  208.  
  209.  Decision Structures
  210.  
  211.  Based on the value of an expression, decision structures cause a program to
  212.  take one of the following two actions:
  213.  
  214.    *  Execute one of several alternative statements within the decision
  215.        structure itself.
  216.  
  217.    *  Branch to another part of the program outside the decision structure.
  218.  
  219.  
  220.  In BASICA, decision-making is handled solely by the single-line  IF...
  221.  THEN... ELSE statement. In its simplest form ( IF... THEN), the expression
  222.  following the  IF keyword is evaluated. If the expression is true, the
  223.  program executes the statements following the  THEN keyword; if the
  224.  expression is false, the program continues with the next line after the
  225.  
  226.  
  227.   IF... THEN statement. Lines 50 and 70 from the following BASICA program
  228.  fragment show examples of  IF... THEN:
  229.  
  230.   30  INPUT A
  231.   40  ' If A is greater than 100, print a message and branch
  232.   45  ' back to line 30; otherwise, go on to line 60:
  233.   50  IF A > 100 THEN PRINT "Too big": GOTO 30
  234.   60  ' If A is equal to 100, branch to line 300;
  235.   65  ' otherwise, go on to line 80:
  236.   70  IF A = 100 THEN GOTO 300
  237.   80  PRINT A/100: GOTO 30
  238.   .
  239.   .
  240.   .
  241.  
  242.  By adding the  ELSE clause to an  IF... THEN statement, you can have your
  243.  program take one set of actions (those following the  THEN keyword) if an
  244.  expression is true, and another set of actions (those following the  ELSE
  245.  keyword) if it is false. The next program fragment shows how  ELSE works in
  246.  an  IF... THEN... ELSE statement:
  247.  
  248.   10 INPUT "What is your password"; Pass$
  249.   15 ' If user enters "sword," branch to line 50;
  250.   20 ' otherwise, print a message and branch back to line 10:
  251.   30 IF Pass$="sword" THEN 50 ELSE PRINT "Try again": GOTO 10
  252.  
  253.  While BASICA's single-line  IF... THEN... ELSE is adequate for simple
  254.  decisions, it can lead to virtually unreadable code in cases of more
  255.  complicated decisions. This is especially true if you write your programs so
  256.  all alternative actions take place within the  IF... THEN... ELSE statement
  257.  itself or if you nest  IF... THEN... ELSE statements (that is, if you put
  258.  one  IF... THEN... ELSE inside another, a perfectly legal construction). As
  259.  an example of how difficult it is to follow even a simple test, consider the
  260.  next fragment from a BASICA program:
  261.  
  262.  10 ' The following nested IF...THEN...ELSE statements print
  263.  15 ' different output for each of the following four cases:
  264.  20 '  1) A <= 50, B <= 50     3)  A > 50, B <= 50
  265.  25 '  2) A <= 50, B > 50      4)  A > 50, B > 50
  266.  30
  267.  35 INPUT A, B
  268.  40
  269.  45 IF A <= 50 THEN IF B <= 50 THEN PRINT "A <= 50, B <= 50" ELSE PRINT
  270.  "A <= 50, B > 50" ELSE IF B <= 50 THEN PRINT "A > 50, B <= 50;" ELSE
  271.  PRINT "A > 50, B > 50"
  272.  
  273.  Even though line 45 extends over several physical lines on the screen, it is
  274.  just one logical line (everything typed before the Enter key was pressed).
  275.  BASICA wraps long lines on the screen. To avoid the kind of complicated
  276.  statement shown by the preceding example, BASIC now includes the block form
  277.  of the  IF... THEN... ELSE statement, so that a decision is no longer
  278.  restricted to one logical line. The following example shows the same BASICA
  279.  program rewritten to use block IF...THEN...ELSE:
  280.  
  281.  
  282.  
  283.   INPUT A, B
  284.   IF A <= 50 THEN
  285.      IF B <= 50 THEN
  286.         PRINT "A <= 50, B <= 50"
  287.      ELSE
  288.         PRINT "A <= 50, B > 50"
  289.      END IF
  290.   ELSE
  291.      IF B <= 50 THEN
  292.         PRINT "A > 50, B <= 50"
  293.      ELSE
  294.         PRINT "A > 50, B > 50"
  295.      END IF
  296.   END IF
  297.  
  298.  Microsoft BASIC also provides the  SELECT CASE... END SELECT (referred to as
  299.   SELECT CASE) statement for structured decisions.
  300.  
  301.  The block  IF... THEN... ELSE statement and the  SELECT CASE statement allow
  302.  the appearance of your code to be based on program logic, rather than
  303.  requiring many statements to be crowded onto one line. This gives you
  304.  increased flexibility while you are programming, as well as improved program
  305.  readability and ease of maintenance when you are done.
  306.  
  307.  
  308.  Block IF...THEN...ELSE
  309.  
  310.  Table 1.2 shows the syntax of the block  IF... THEN... ELSE statement and
  311.  gives an example of its use.
  312.  
  313.  The arguments  condition1,  condition2, and so on are expressions. They can
  314.  be any numeric expression (in which case true becomes any nonzero value, and
  315.  false is 0), or they can be Boolean expressions (in which case true is
  316.  -1 and false is 0). As explained previously, Boolean expressions
  317.  typically compare two numeric or string expressions using one of the
  318.  relational operators, such as  < or  >=.
  319.  
  320.  Each  IF,  ELSEIF, and  ELSE clause is followed by a block of statements.
  321.  None of the statements in the block can be on the same line as the  IF,
  322.  ELSEIF, or  ELSE clause; otherwise, BASIC considers it a single-line  IF...
  323.  THEN statement.
  324.  
  325.  BASIC evaluates each of the expressions in the  IF and  ELSEIF clauses from
  326.  top to bottom, skipping over statement blocks until it finds the first true
  327.  expression. When it finds a true expression, it executes the statements
  328.  corresponding to the expression, then branches out of the block to the
  329.  statement following the  END IF clause.
  330.  
  331.  If none of the expressions in the  IF or  ELSEIF clauses is true, BASIC
  332.  skips to the  ELSE clause, if there is one, and executes its statements.
  333.  Otherwise, if there is no  ELSE clause, the program continues with the next
  334.  statement after the  END IF clause.
  335.  
  336.  The  ELSE and  ELSEIF clauses are optional, as shown in the following
  337.  example:
  338.  
  339.   ' If the value of X is less than 100, execute the two statements
  340.   ' before END IF; otherwise, go to the INPUT statement
  341.   ' following END IF:
  342.  
  343.   IF X < 100 THEN
  344.      PRINT X
  345.      Number = Number + 1
  346.   END IF
  347.   INPUT "New value"; Response$
  348.   .
  349.   .
  350.   .
  351.  
  352.  A single block  IF... THEN... ELSE can contain multiple  ELSEIF statements,
  353.  as follows:
  354.  
  355.   IF C$ >= "A" AND C$ <= "Z" THEN
  356.      PRINT "Capital letter"
  357.   ELSEIF C$ >= "a" AND C$ <= "z" THEN
  358.      PRINT "Lowercase letter"
  359.   ELSEIF C$ >= "0" AND C$ <= "9" THEN
  360.      PRINT "Number"
  361.   ELSE
  362.      PRINT "Not alphanumeric"
  363.   END IF
  364.  
  365.  
  366.  At most, only one block of statements is executed, even if more than one
  367.  condition is true. For example, if you enter the word ace as input to the
  368.  next example, it prints the message Input too short but does not print the
  369.  message Can't start with an a.
  370.  
  371.   INPUT Check$
  372.   IF LEN(Check$) > 6 THEN
  373.      PRINT "Input too long"
  374.   ELSEIF LEN(Check$) < 6 THEN
  375.      PRINT "Input too short"
  376.   ELSEIF LEFT$(Check$, 1) = "a" THEN
  377.      PRINT "Can't start with an a"
  378.   END IF
  379.  
  380.   IF... THEN... ELSE statements can be nested; in other words, you can put an
  381.   IF... THEN... ELSE statement inside another  IF... THEN... ELSE statement,
  382.  as shown here:
  383.  
  384.   IF X > 0 THEN
  385.      IF Y > 0 THEN
  386.         IF Z > 0 THEN
  387.      PRINT "All are greater than zero."
  388.         ELSE
  389.      PRINT "Only X and Y greater than zero."
  390.         END IF
  391.      END IF
  392.   ELSEIF X = 0 THEN
  393.      IF Y = 0 THEN
  394.         IF Z = 0 THEN
  395.      PRINT "All equal zero."
  396.         ELSE
  397.      PRINT "Only X and Y equal zero."
  398.         END IF
  399.      END IF
  400.   ELSE
  401.      PRINT "X is less than zero."
  402.   END IF
  403.  
  404.  SELECT CASE
  405.  
  406.  The  SELECT CASE statement is a multiple-choice decision structure similar
  407.  to the block  IF... THEN... ELSE statement.  SELECT CASE can be used
  408.  anywhere block  IF... THEN... ELSE can be used.
  409.  
  410.  The difference between the two is that  SELECT CASE evaluates a single
  411.  expression, then executes different statements or branches to different
  412.  parts of the program based on the result. In contrast, a block  IF...
  413.  THEN... ELSE can evaluate completely different expressions.
  414.  
  415.  
  416.  Examples
  417.  
  418.  The following examples illustrate the similarities and differences between
  419.  the  SELECT CASE and  IF... THEN... ELSE statements. Here is an example of
  420.  using block  IF... THEN... ELSE for a multiple-choice decision:
  421.  
  422.   INPUT X
  423.   IF X = 1 THEN
  424.      PRINT "One"
  425.   ELSEIF X = 2 THEN
  426.      PRINT "Two"
  427.   ELSEIF X = 3 THEN
  428.      PRINT "Three"
  429.   ELSE
  430.      PRINT "Must be integer from 1 to 3."
  431.   END IF
  432.  
  433.  The previous decision is rewritten using  SELECT CASE as follows:
  434.  
  435.   INPUT X
  436.   SELECT CASE X
  437.      CASE 1
  438.         PRINT "One"
  439.      CASE 2
  440.         PRINT "Two"
  441.      CASE 3
  442.         PRINT "Three"
  443.      CASE ELSE
  444.         PRINT "Must be integer from 1 to 3."
  445.   END SELECT
  446.  
  447.  The following decision can be made either with the  SELECT CASE or the block
  448.   IF... THEN... ELSE statement. The comparison is more efficient with the
  449.  IF... THEN... ELSE statement because different expressions are being
  450.  evaluated in the  IF and  ELSEIF clauses.
  451.  
  452.   INPUT X, Y
  453.   IF X = 0 AND Y = 0 THEN
  454.      PRINT "Both are zero."
  455.   ELSEIF X = 0 THEN
  456.      PRINT "Only X is zero."
  457.   ELSEIF Y = 0 THEN
  458.      PRINT "Only Y is zero."
  459.   ELSE
  460.      PRINT "Neither is zero."
  461.   END IF
  462.  
  463.  Using the SELECT CASE Statement
  464.  
  465.  Table 1.3 shows the syntax of a  SELECT CASE statement and an example.
  466.  
  467.  The  expressionlist arguments following a  CASE clause can be one or more of
  468.  the following, separated by commas:
  469.  
  470.    ■   A numeric expression or a range of numeric expressions
  471.  
  472.    ■   A string expression or a range of string expressions
  473.  
  474.  
  475.  To specify a range of expressions, use the following syntax for the  CASE
  476.  statement:
  477.  
  478.   CASE  expression  TO  expression
  479.    CASE  IS  relational-operator expression
  480.  
  481.  The  relational-operator is any of the operators shown in Table 1.1. For
  482.  example, if you use  CASE 1 TO 4, the statements associated with this case
  483.  are executed when the  testexpression in the  SELECT CASE statement is
  484.  greater than or equal to 1 and less than or equal to 4. If you use  CASE IS
  485.  < 5, the associated statements are executed only if  testexpression is less
  486.  than 5.
  487.  
  488.  If you are expressing a range with the  TO keyword, be sure to put the
  489.  lesser value first. For example, if you want to test for a value from
  490.  -5 to -1, write the  CASE statement as follows:
  491.  
  492.  CASE -5 TO -1
  493.  
  494.  
  495.  However, the following statement is not a valid way to specify the range
  496.  from -5 to -1, so the statements associated with this case are
  497.  never executed:
  498.  
  499.  CASE -1 TO -5
  500.  
  501.  Similarly, a range of string constants should list the string that comes
  502.  first alphabetically:
  503.  
  504.  CASE "aardvark" TO "bear"
  505.  
  506.  Multiple expressions or ranges of expressions can be listed for each  CASE
  507.  clause, as in the following lines:
  508.  
  509.   CASE 1 TO 4, 7 TO 9, WildCard1%, WildCard2%
  510.   CASE IS = Test$, IS = "end of data"
  511.   CASE IS < LowerBound, 5, 6, 12, IS > UpperBound
  512.   CASE IS < "HAN", "MAO" TO "TAO"
  513.  
  514.  If the value of the  SELECT CASE expression appears in the list following a
  515.  CASE clause, only the statements associated with that  CASE clause are
  516.  executed. Control then jumps to the first executable statement following
  517.  END SELECT, not the next block of statements inside the  SELECT CASE
  518.  structure, as shown by the output from the next example:
  519.  
  520.   INPUT X
  521.   SELECT CASE X
  522.      CASE 1
  523.         PRINT "One, ";
  524.      CASE 2
  525.         PRINT "Two, ";
  526.      CASE 3
  527.         PRINT "Three, ";
  528.   END SELECT
  529.   PRINT "that's all"
  530.  
  531.  Output
  532.   ?  1
  533.   One, that's all
  534.  
  535.  If the same value or range of values appears in more than one  CASE clause,
  536.  only the statements associated with the first occurrence are executed, as
  537.  shown by the next example's output:
  538.  
  539.   INPUT Test$
  540.   SELECT CASE Test$
  541.      CASE "A" TO "AZZZZZZZZZZZZZZZZZ"
  542.         PRINT "An uppercase word beginning with A"
  543.      CASE IS < "A"
  544.         PRINT "Some sequence of nonalphabetic characters"
  545.  
  546.      CASE "ABORIGINE"
  547.         ' This case is never executed since ABORIGINE
  548.         ' falls within the range in the first CASE clause:
  549.         PRINT "A special case"
  550.   END SELECT
  551.  
  552.  Output
  553.   ?  ABORIGINE
  554.   An uppercase word beginning with A
  555.  
  556.  If you use a  CASE ELSE clause, it must be the last  CASE clause listed in
  557.  the  SELECT CASE statement. The statements between a  CASE ELSE clause and
  558.  an  END SELECT clause are executed only if the  testexpression argument does
  559.  not match any of the other  CASE selections in the  SELECT CASE statement.
  560.  In fact, it is a good idea to have a  CASE ELSE statement in your  SELECT
  561.  CASE block to handle unforeseen values for  testexpression. However, if
  562.  there is no  CASE ELSE statement and no match is found in any  CASE
  563.  statement for  testexpression, the program continues execution.
  564.  
  565.  Example
  566.  
  567.  The following program fragment demonstrates a common use of the  SELECT CASE
  568.  statement. It prints a menu on the screen, then branches to different
  569.  subprograms based on the number typed by the user.
  570.  
  571.   DO' Start menu loop.
  572.  
  573.      CLS' Clear screen.
  574.  
  575.      ' Print five menu choices on the screen:
  576.      PRINT "MAIN MENU" : PRINT
  577.      PRINT "1)  Add New Names"
  578.      PRINT "2)  Delete Names"
  579.      PRINT "3)  Change Information"
  580.      PRINT "4)  List Names"
  581.      PRINT
  582.      PRINT "5)  EXIT"
  583.  
  584.      ' Print input prompt:
  585.      PRINT : PRINT "Type your selection (1 to 5):"
  586.  
  587.      ' Wait for the user to press a key. INPUT$(1)
  588.      ' reads one character input from the keyboard:
  589.      Ch$ = INPUT$(1)
  590.  
  591.      ' Use SELECT CASE to process the response:
  592.      SELECT CASE Ch$
  593.  
  594.         CASE "1"
  595.            CALL AddData
  596.         CASE "2"
  597.            CALL DeleteData
  598.         CASE "3"
  599.            CALL ChangeData
  600.         CASE "4"
  601.            CALL ListData
  602.         CASE "5"
  603.            EXIT DO' The only way to exit the menu loop.
  604.         CASE ELSE
  605.            BEEP
  606.      END SELECT
  607.  
  608.   LOOP' End menu loop.
  609.  
  610.   END
  611.  
  612.   ' Subprograms AddData, DeleteData, ChangeData, and ListData:
  613.   .
  614.   .
  615.   .
  616.  
  617.  SELECT CASE Vs. ON...GOSUB
  618.  
  619.  You can use the more versatile  SELECT CASE statement as a replacement for
  620.  the old  ON... GOSUB statement. The  SELECT CASE statement has many
  621.  advantages over the  ON... GOSUB statement, summarized as follows:
  622.  
  623.    ■   The  testexpression in  SELECT CASE can evaluate to either a string or
  624.        numeric value. The  expression given in the statement  ON  GOSUB must
  625.        evaluate to a number within the range 0 to 255.
  626.  
  627.    ■   The  SELECT CASE statement branches to a statement block immediately
  628.        following the matching  CASE clause. In contrast,  ON  GOSUB branches
  629.        to a subroutine in another part of the program.
  630.  
  631.    ■    CASE clauses can be used to test  expression against a range of
  632.        values. When the range is quite large, this is not easily done with
  633.        ON  GOSUB, especially in cases such as those shown in the code
  634.        fragments in the rest of this section.
  635.  
  636.  
  637.  In the following fragment, the  ON... GOSUB statement branches to one of the
  638.  subroutines 50, 100, or 150, depending on the value entered by the user:
  639.  
  640.   X% = -1
  641.   WHILE X%
  642.      INPUT "Enter choice (0 to quit): ", X%
  643.      IF X% = 0 THEN END
  644.  
  645.  
  646.   WHILE X% < 1 OR X% > 12
  647.         PRINT "Must be value from 1 to 12"
  648.         INPUT "Enter choice (0 to quit): ", X%
  649.      WEND
  650.      ON X% GOSUB 50,50,50,50,50,50,50,50,100,100,100,150
  651.   WEND
  652.   .
  653.   .
  654.   .
  655.  
  656.  Contrast the preceding example with the next one, which uses a  SELECT CASE
  657.  statement with ranges of values in each  CASE clause:
  658.  
  659.   DO
  660.      INPUT "Enter choice (0 to quit): ", X%
  661.      SELECT CASE X%
  662.         CASE 0
  663.            END
  664.         CASE 1 TO 8' Replaces "subroutine 50"
  665.   ' in preceding example
  666.         CASE 9 TO 11 ' Replaces "subroutine 100"
  667.   ' in preceding example
  668.         CASE 12' Replaces "subroutine 150"
  669.         ' in preceding example
  670.         CASE ELSE' Input was out of range.
  671.            PRINT "Must be value from 1 to 12"
  672.         END SELECT
  673.   LOOP
  674.  
  675.  Looping Structures
  676.  
  677.  Looping structures repeat a block of statements (the loop), either for a
  678.  specified number of times or until a certain expression (the loop condition)
  679.  is true or false.
  680.  
  681.  Users of BASICA are familiar with two looping structures,  FOR... NEXT and
  682.  WHILE... WEND, which are discussed in the following sections "FOR...NEXT
  683.  Loops" and "WHILE...WEND  Loops." Microsoft BASIC has extended the available
  684.  loop structures with  DO... LOOP.
  685.  
  686.  
  687.  FOR...NEXT Loops
  688.  
  689.  A  FOR... NEXT loop repeats the statements enclosed in the loop a specified
  690.  number of times, counting from a starting value to an ending value by
  691.  increasing or decreasing a loop counter. As long as the loop counter has not
  692.  reached the ending value, the loop continues to execute. Table 1.4 shows the
  693.  syntax of the  FOR... NEXT statement and gives an example of its use.
  694.  
  695.  In a  FOR... NEXT loop, the  counter variable initially has the value
  696.  of the expression  start. After each repetition of the loop, the value of
  697.  counter is adjusted. If you leave off the optional  STEP keyword, the
  698.  default value for this adjustment is 1; that is, 1 is added to  counter each
  699.  time the loop executes. If you use  STEP, then  counter is adjusted by the
  700.  amount  increment. The  increment argument can be any numeric value; if it
  701.  is negative, the loop counts down from  start to  end. After the  counter
  702.  variable is increased or decreased, its value is compared with  end. At this
  703.  point, if either of the following is true, the loop is completed:
  704.  
  705.    ■   The loop is counting up ( increment is positive) and  counter is
  706.        greater than  end.
  707.  
  708.    ■   The loop is counting down ( increment is negative) and  counter is
  709.        less than  end.
  710.  
  711.  
  712.  Figure 1.1 shows the logic of a  FOR... NEXT loop when the value of
  713.  increment is positive.
  714.  
  715.  Figure 1.2 shows the logic of a  FOR... NEXT loop when the value of
  716.  increment is negative.
  717.  
  718.  A  FOR... NEXT statement always "tests at the top," so if one of the
  719.  following conditions is true, the loop is never executed:
  720.  
  721.    ■   The  increment is positive, and the initial value of  start is greater
  722.        than the value of  end:
  723.  
  724.  
  725.  ' Loop never executes, because I% starts out greater
  726.  ' than 9:
  727.     FOR I% = 10 TO 9
  728.       .
  729.       .
  730.       .
  731.     NEXT I%
  732.  
  733.    ■   The  increment is negative, and the initial value of  start is less
  734.        than the value of  end:
  735.  
  736.  ' Loop never executes, because I% starts out less than 9:
  737.      FOR I% =  -10 TO -9 STEP -1
  738.      NEXT I%
  739.  
  740.  
  741.  
  742.  You don't have to use the  counter argument in the  NEXT clause; however, if
  743.  you have several nested  FOR... NEXT loops (one loop inside another),
  744.  listing the  counter arguments can be a helpful way to keep track of what
  745.  loop you are in.
  746.  
  747.  Here are some general guidelines for nesting  FOR... NEXT loops:
  748.  
  749.    ■   If you use a loop counter variable in a  NEXT clause, the counter for
  750.        a nested loop must appear before the counter for any enclosing loop.
  751.        In other words, the following is a legal nesting:
  752.  
  753.  
  754.  FOR I = 1 TO 10
  755.      FOR J = -5 TO 0
  756.      .
  757.      .
  758.      .
  759.      NEXT J
  760.  NEXT I
  761.  
  762.        However, the following is not a legal nesting:
  763.  
  764.  FOR I = 1 TO 10
  765.      FOR J = -5 TO 0
  766.      .
  767.      .
  768.      .
  769.      NEXT I
  770.  NEXT J
  771.  
  772.      *  For faster loops that generate smaller code, use integer variables
  773.         for counters in the loops whenever possible.
  774.  
  775.      *  If you use a separate  NEXT clause to end each loop, then the
  776.         number of  NEXT clauses must always be the same as the number of
  777.         FOR clauses.
  778.  
  779.      *  If you use a single  NEXT clause to terminate several levels of
  780.         FOR... NEXT loops, then the loop-counter variables must appear
  781.         after the  NEXT clause in "inside-out" order:
  782.  
  783.         NEXT  innermost-loopcounter, ... ,  outermost-loopcounter
  784.  
  785.  
  786.         In this case, the number of loop-counter variables in the  NEXT
  787.         clause must be the same as the number of  FOR clauses.
  788.  
  789.  
  790.  Examples
  791.  
  792.  The following three program fragments illustrate different ways of nesting
  793.  FOR... NEXT loops to produce the identical output. The first example shows
  794.  nested  FOR... NEXT loops with loop counters and separate  NEXT clauses for
  795.  each loop:
  796.  
  797.   FOR I = 1 TO 2
  798.      FOR J = 4 TO 5
  799.         FOR K = 7 TO 8
  800.            PRINT I, J, K
  801.         NEXT K
  802.      NEXT J
  803.   NEXT I
  804.  
  805.  
  806.  The following example also uses loop counters but only one  NEXT clause for
  807.  all three loops:
  808.  
  809.   FOR I = 1 TO 2
  810.      FOR J = 4 TO 5
  811.         FOR K = 7 TO 8
  812.            PRINT I, J, K
  813.   NEXT K, J, I
  814.  
  815.  The final example shows nested  FOR... NEXT loops without loop counters:
  816.  
  817.   FOR I = 1 TO 2
  818.      FOR J = 4 TO 5
  819.         FOR K = 7 TO 8
  820.            PRINT I, J, K
  821.         NEXT
  822.      NEXT
  823.   NEXT
  824.  
  825.  Output
  826.    1             4             7
  827.    1             4             8
  828.    1             5             7
  829.    1             5             8
  830.    2             4             7
  831.    2             4             8
  832.    2             5             7
  833.    2             5             8
  834.  
  835.  
  836.  Exiting a FOR...NEXT Loop with EXIT FOR
  837.  Sometimes you may want to exit a  FOR... NEXT loop before the counter
  838.  variable reaches the ending value of the loop. You can do this with the
  839.  EXIT FOR statement. A single  FOR... NEXT loop can have any number of  EXIT
  840.  FOR statements, and the  EXIT FOR statements can appear anywhere within the
  841.  loop. The following fragment shows one use for an  EXIT FOR statement:
  842.  
  843.   ' Print the square roots of the numbers from 1 to 30,000.
  844.   ' If the user presses any key while this loop is executing,
  845.   ' control exits from the loop:
  846.   FOR I% = 1 TO 30000
  847.      PRINT SQR(I%)
  848.      IF INKEY$ <> "" THEN EXIT FOR
  849.   NEXT
  850.   .
  851.   .
  852.   .
  853.  
  854.  
  855.   EXIT FOR exits only the smallest enclosing  FOR... NEXT loop in which it
  856.  appears. For example, if the user presses a key while the following nested
  857.  loops are executing, the program would simply exit the innermost loop. If
  858.  the outermost loop is still active (that is, if the value of I% is less than
  859.  or equal to 100), control passes right back to the innermost loop:
  860.  
  861.   FOR I% = 1 TO 100
  862.      FOR J% = 1 TO 100
  863.         PRINT I% / J%
  864.         IF INKEY$ <> "" THEN EXIT FOR
  865.      NEXT J%
  866.   NEXT I%
  867.  Suspending Program Execution with FOR...NEXT
  868.  Many BASICA programs use an empty  FOR... NEXT loop such as the following to
  869.  insert a pause in a program:
  870.  
  871.   .
  872.   .
  873.   .
  874.   ' There are no statements in the body of this loop;
  875.   ' all it does is count from 1 to 10,000
  876.   ' using integers (whole numbers).
  877.   FOR I% = 1 TO 10000: NEXT
  878.   .
  879.   .
  880.   .
  881.  
  882.  For very short pauses or pauses that do not have to be of some exact
  883.  interval, using  FOR... NEXT is fine. The problem with using an empty
  884.  FOR... NEXT loop in this way is that different computers, different versions
  885.  of BASIC, or different compile-time options can all affect how quickly the
  886.  arithmetic in a  FOR... NEXT loop is performed. So the length of a pause can
  887.  vary widely. BASIC's  SLEEP statement now provides a better alternative.
  888.  
  889.  
  890.  WHILE...WEND Loops
  891.  
  892.  The  FOR... NEXT statement is useful when you know ahead of time exactly how
  893.  many times a loop should be executed. When you cannot predict the precise
  894.  number of times a loop should be executed, but do know the condition that
  895.  will end the loop, the  WHILE... WEND statement is useful. Instead of
  896.  counting to determine if it should keep executing a loop,  WHILE... WEND
  897.  repeats the loop as long as the loop condition is true.
  898.  
  899.  
  900.  Table 1.5 shows the syntax of the  WHILE... WEND statement and an example.
  901.  
  902.  Example
  903.  
  904.  The following example assigns an initial value of ten to the variable X,
  905.  then successively halves that value and keeps halving it until the value of
  906.  X is less than .00001:
  907.  
  908.   X = 10
  909.  
  910.   WHILE X > .00001
  911.      PRINT X
  912.      X = X/2
  913.   WEND
  914.  
  915.  
  916.  Figure 1.3 illustrates the logic of a  WHILE... WEND loop.
  917.  
  918.  
  919.  DO...LOOP Loops
  920.  
  921.  Like the  WHILE... WEND statement, the  DO... LOOP statement executes a
  922.  block of statements an indeterminate number of times; that is, exiting from
  923.  the loop depends on the truth or falsehood of the loop condition. Unlike
  924.  WHILE... WEND,  DO... LOOP allows you to test for either a true or false
  925.  condition. You can also put the test at either the beginning or the end of
  926.  the loop.
  927.  
  928.  
  929.  Table 1.6 shows the syntax of a loop that tests at the loop's beginning.
  930.  
  931.  Figures 1.4 and 1.5 illustrate the two kinds of  DO... LOOP statements that
  932.  test at the beginning of the loop.
  933.  
  934.  Table 1.7 shows the syntax of a loop that tests for true or false
  935.  at the end of the loop.
  936.  
  937.  Figures 1.6 and 1.7 illustrate the two kinds of  DO... LOOP statements that
  938.  test at the end of the loop.
  939.  
  940.  Loop Tests: One Way to Exit DO...LOOP
  941.  You can use a loop test at the end of a  DO... LOOP
  942.  statement to create a loop in which the statements always execute
  943.  at least once. With the  WHILE... WEND statement, you sometimes have to
  944.  resort to the trick of presetting the loop variable to some value in order
  945.  to force the first pass through the loop. With  DO... LOOP, such tricks are
  946.  not necessary.
  947.  
  948.  
  949.  The following examples illustrate both approaches:
  950.  
  951.   ' WHILE...WEND loop tests at the top, so assigning "Y"
  952.   ' to Response$ is necessary to force execution of the
  953.   ' loop at least once:
  954.   Response$ = "Y"
  955.   WHILE UCASE$(Response$) = "Y"
  956.      .
  957.      .
  958.      .
  959.      INPUT "Do again"; Response$
  960.   WEND
  961.  ' The same loop using DO...LOOP to test after the
  962.   ' body of the loop:
  963.   DO
  964.      .
  965.      .
  966.      .
  967.      INPUT "Do again"; Response$
  968.   LOOP WHILE UCASE$(Response$) = "Y"
  969.  
  970.  You can also rewrite a condition expressed with  WHILE using  UNTIL instead,
  971.  as in the following:
  972.  
  973.  ' =======================================================
  974.   '  Using DO WHILE NOT...LOOP
  975.   ' =======================================================
  976.  
  977.   ' While the end of file 1 has not been reached, read
  978.   ' a line from the file and print it on the screen:
  979.   DO WHILE NOT EOF(1)
  980.      LINE INPUT #1, LineBuffer$
  981.      PRINT LineBuffer$
  982.   LOOP
  983.  
  984.   ' =======================================================
  985.   '  Using DO UNTIL...LOOP
  986.   ' =======================================================
  987.  
  988.   ' Until the end of file 1 has been reached, read
  989.   ' a line from the file and print it on the screen:
  990.   DO UNTIL EOF(1)
  991.      LINE INPUT #1, LineBuffer$
  992.      PRINT LineBuffer$
  993.   LOOP
  994.  
  995.  EXIT DO: An Alternative Way to Exit DO...LOOP
  996.  Inside a  DO... LOOP statement, other statements are executed that
  997.  eventually change the loop-test condition from true to false or false to
  998.  true, ending the loop. In the  DO... LOOP examples presented so far, the
  999.  test has occurred either at the beginning or the end of the loop. However,
  1000.  by using the  EXIT DO statement to exit from the loop, you can move the test
  1001.  elsewhere inside the loop. A single  DO... LOOP can contain any number of
  1002.  EXIT DO statements, and the  EXIT DO statements can appear anywhere within
  1003.  the loop.
  1004.  
  1005.  Example
  1006.  
  1007.  The following example opens a file and reads it, one line at a time, until
  1008.  the end of the file is reached or until it finds the pattern entered by the
  1009.  user. If it finds the pattern before getting to the end of the file, an
  1010.  EXIT DO statement exits the loop.
  1011.  
  1012.   INPUT "File to search: ", File$
  1013.   IF File$ = "" THEN END
  1014.  
  1015.   INPUT "Pattern to search for: ", Pattern$
  1016.   OPEN File$ FOR INPUT AS #1
  1017.  
  1018.   DO UNTIL EOF(1)    ' EOF(1) returns a true value if the
  1019.                      ' end of the file has been reached.
  1020.      LINE INPUT #1, TempLine$
  1021.      IF INSTR(TempLine$, Pattern$) > 0 THEN
  1022.  
  1023.         ' Print the first line containing the pattern and
  1024.         ' exit the loop:
  1025.         PRINT TempLine$
  1026.         EXIT DO
  1027.      END IF
  1028.   LOOP
  1029.  
  1030.  Sample Applications
  1031.  
  1032.  The sample applications for this chapter are a checkbook balancing program
  1033.  and a program that ensures that every line in a text file ends with a
  1034.  carriage-return line-feed sequence.
  1035.  
  1036.  
  1037.  Checkbook Balancing Program (CHECK.BAS)
  1038.  
  1039.  This program prompts the user for the starting checking account balance and
  1040.  all transactions --withdrawals or deposits -- that have occurred.
  1041.  It then prints a sorted list of the transactions and the final balance in
  1042.  the account.
  1043.  
  1044.  Statements UsedThe
  1045.  program demonstrates the following statements discussed in this chapter:
  1046.  
  1047.    ■    DO... LOOP WHILE
  1048.  
  1049.    ■    FOR... NEXT
  1050.  
  1051.    ■    EXIT FOR
  1052.  
  1053.    ■   Block  IF... THEN... ELSE
  1054.  
  1055.  Program Listing
  1056.   DIM Amount(1 TO 100) AS CURRENCY, Balance AS CURRENCY
  1057.   CONST FALSE = 0, TRUE = NOT FALSE
  1058.   CLS
  1059.   ' Get account's starting balance:
  1060.   INPUT "Type starting balance, then press <ENTER>: ", Balance
  1061.  ' Get transactions. Continue accepting input
  1062.   ' until the input is zero for a transaction,
  1063.   ' or until 100 transactions have been entered:
  1064.   FOR TransacNum% = 1 TO 100
  1065.      PRINT TransacNum%;
  1066.      PRINT ") Enter transaction amount (0 to end): ";
  1067.      INPUT "", Amount(TransacNum%)
  1068.      IF Amount(TransacNum%) = 0 THEN
  1069.         TransacNum% = TransacNum% - 1
  1070.         EXIT FOR
  1071.      END IF
  1072.   NEXT
  1073.  
  1074.  ' Sort transactions in ascending order,
  1075.   ' using a "bubble sort":
  1076.   Limit% = TransacNum%
  1077.   DO
  1078.      Swaps% = FALSE
  1079.      FOR I% = 1 TO (Limit% - 1)
  1080.        ' If two adjacent elements are out of order,
  1081.         ' switch those elements:
  1082.         IF Amount(I%) > Amount(I% + 1) THEN
  1083.            SWAP Amount(I%), Amount(I% + 1)
  1084.            Swaps% = I%
  1085.         END IF
  1086.      NEXT I%
  1087.     ' Sort on next pass only to where last switch was made:
  1088.      Limit% = Swaps%
  1089.   ' Sort until no elements are exchanged:
  1090.   LOOP WHILE Swaps%
  1091.  
  1092.  
  1093.  ' Print the sorted transaction array. If a transaction
  1094.   ' is greater than zero, print it as a "CREDIT"; if a
  1095.   ' transaction is less than zero, print it as a "DEBIT":
  1096.   FOR I% = 1 TO TransacNum%
  1097.      IF Amount(I%) > 0 THEN
  1098.         PRINT USING "CREDIT: $$#####.##"; Amount(I%)
  1099.      ELSEIF Amount(I%) < 0 THEN
  1100.         PRINT USING "DEBIT: $$#####.##"; Amount(I%)
  1101.      END IF
  1102.     ' Update balance:
  1103.      Balance = Balance + Amount(I%)
  1104.   NEXT I%
  1105.  ' Print the final balance:
  1106.   PRINT
  1107.   PRINT "--------------------------"
  1108.   PRINT USING "Final Balance: $$######.##"; Balance
  1109.   END
  1110.  
  1111.  Carriage-Return/Line-Feed Filter (CRLF.BAS)
  1112.  
  1113.  Some text files are saved in a format that uses only a carriage return
  1114.  (return to the beginning of the line) or a line feed (advance to the next
  1115.  line) to signify the end of a line. Many text editors expand this single
  1116.  carriage return (CR) or line feed (LF) to a carriage-return/line-feed
  1117.  (CR-LF) sequence whenever you load the file for editing. However, if you use
  1118.  a text editor that does not expand a single CR or LF to CR-LF, you may have
  1119.  to modify the file so it has the correct sequence at the end of each line.
  1120.  
  1121.  The following program is a filter that opens a file, expands a single CR or
  1122.  LF to a CR-LF combination, then writes the adjusted lines to a new file. The
  1123.  original contents of the file are saved in a file with a .BAK extension.
  1124.  
  1125.  Statements UsedThis program demonstrates the following statements discussed
  1126.  in this chapter:
  1127.  
  1128.    ■    DO... LOOP WHILE
  1129.  
  1130.    ■    DO UNTIL... LOOP
  1131.  
  1132.    ■   Block  IF... THEN... ELSE
  1133.  
  1134.    ■    SELECT CASE... END SELECT
  1135.  
  1136.  
  1137.  To make this program more useful, it contains the following constructions
  1138.  not discussed in this chapter:
  1139.  
  1140.    ■   A  FUNCTION procedure named Backup$ that creates the file with the
  1141.        .BAK extension.
  1142.  
  1143.        See Chapter 2, "SUB and FUNCTION Procedures," for more information on
  1144.        defining and using procedures.
  1145.  
  1146.  
  1147.  
  1148.    ■   An error-handling routine named ErrorHandler to deal with errors that
  1149.        could occur when the user enters a filename. For instance, if the user
  1150.        enters the name of a nonexistent file, this routine prompts for a new
  1151.        name. Without this routine, such an error would end the program.
  1152.  
  1153.        See Chapter 8, "Error Handling," for more information on trapping
  1154.        errors.
  1155.  
  1156.  Program Listing
  1157.   DEFINT A-Z             ' Default variable type is integer.
  1158.  
  1159.   ' The Backup$ function makes a backup file with
  1160.   ' the same base as FileName$ plus a .BAK extension:
  1161.   DECLARE FUNCTION Backup$ (FileName$)
  1162.  
  1163.   ' Initialize symbolic constants and variables:
  1164.   CONST FALSE = 0, TRUE = NOT FALSE
  1165.  
  1166.   CarReturn$ = CHR$(13)
  1167.   LineFeed$ = CHR$(10)
  1168.  
  1169.   DO
  1170.      CLS
  1171.  
  1172.      ' Input the name of the file to change:
  1173.      INPUT "Which file do you want to convert"; OutFile$
  1174.  
  1175.      InFile$ = Backup$(OutFile$)  ' Get backup file's name.
  1176.  
  1177.      ON ERROR GOTO ErrorHandler   ' Turn on error trapping.
  1178.  
  1179.      NAME OutFile$ AS InFile$     ' Rename input file as
  1180.                                   ' backup file.
  1181.  
  1182.      ON ERROR GOTO 0              ' Turn off error trapping.
  1183.  
  1184.      ' Open backup file for input and old file for output:
  1185.      OPEN InFile$ FOR INPUT AS #1
  1186.      OPEN OutFile$ FOR OUTPUT AS #2
  1187.  
  1188.      ' The PrevCarReturn variable is a flag set to TRUE
  1189.      ' whenever the program reads a carriage-return character:
  1190.      PrevCarReturn = FALSE
  1191.  ' Read from input file until reaching end of file:
  1192.      DO UNTIL EOF(1)
  1193.         ' This is not end of file, so read a character:
  1194.         FileChar$ = INPUT$(1, #1)
  1195.   CASE CarReturn$        ' The character is a CR.
  1196.  
  1197.   ' If the previous character was also a
  1198.       ' CR, put a LF before the character:
  1199.   IF PrevCarReturn THEN
  1200.   FileChar$ = LineFeed$ + FileChar$
  1201.   END IF
  1202.  
  1203.   ' In any case, set the PrevCarReturn
  1204.   ' variable to TRUE:
  1205.       PrevCarReturn = TRUE
  1206.  
  1207.   CASE LineFeed$         ' The character is a LF.
  1208.  
  1209.   ' If the previous character was not a
  1210.   ' CR, put a CR before the character:
  1211.   IF NOT PrevCarReturn THEN
  1212.   FileChar$ = CarReturn$ + FileChar$
  1213.   END IF
  1214.  
  1215.   ' Set the PrevCarReturn variable to FALSE:
  1216.   PrevCarReturn = FALSE
  1217.  
  1218.   CASE ELSE              ' Neither a CR nor a LF.
  1219.  
  1220.       ' If the previous character was a CR,
  1221.   ' set the PrevCarReturn variable to FALSE
  1222.   ' and put a LF before the current character:
  1223.   IF PrevCarReturn THEN
  1224.                  PrevCarReturn = FALSE
  1225.                  FileChar$ = LineFeed$ + FileChar$
  1226.   END IF
  1227.  
  1228.         END SELECT
  1229.  
  1230.         ' Write the character(s) to the new file:
  1231.         PRINT #2, FileChar$;
  1232.      LOOP
  1233.  
  1234.      ' Write a LF if the last character in the file was a CR:
  1235.      IF PrevCarReturn THEN PRINT #2, LineFeed$;
  1236.  CLOSE                   ' Close both files.
  1237.      PRINT "Another file (Y/N)?"  ' Prompt to continue.
  1238.  
  1239.      ' Change the input to uppercase (capital) letter:
  1240.      More$ = UCASE$(INPUT$(1))
  1241.  
  1242.   ' Continue the program if the user entered a "Y" or a "y":
  1243.   LOOP WHILE More$ = "Y"
  1244.   END
  1245.  
  1246.  ErrorHandler:           ' Error-handling routine
  1247.      CONST NOFILE = 53, FILEEXISTS = 58
  1248.  
  1249.      ' The ERR function returns the error code for last error:
  1250.      SELECT CASE ERR
  1251.         CASE NOFILE       ' Program couldn't find file
  1252.                           ' with input name.
  1253.            PRINT "No such file in current directory."
  1254.            INPUT "Enter new name: ", OutFile$
  1255.            InFile$ = Backup$(OutFile$)
  1256.            RESUME
  1257.         CASE FILEEXISTS   ' There is already a file named
  1258.                           ' <filename>.BAK in this directory:
  1259.                           ' remove it, then continue.
  1260.            KILL InFile$
  1261.            RESUME
  1262.         CASE ELSE         ' An unanticipated error occurred:
  1263.                           ' stop the program.
  1264.            ON ERROR GOTO 0
  1265.      END SELECT
  1266.  
  1267.   ' ======================== BACKUP$ =========================
  1268.   '   This procedure returns a filename that consists of the
  1269.   '   base name of the input file (everything before the ".")
  1270.   '   plus the extension ".BAK"
  1271.   ' ==========================================================
  1272.  
  1273.   FUNCTION Backup$ (FileName$) STATIC
  1274.  
  1275.      ' Look for a period:
  1276.      Extension = INSTR(FileName$, ".")
  1277.  
  1278.      ' If there is a period, add .BAK to the base:
  1279.      IF Extension > 0 THEN
  1280.         Backup$ = LEFT$(FileName$, Extension - 1) + ".BAK"
  1281.     ' Otherwise, add .BAK to the whole name:
  1282.      ELSE
  1283.         Backup$ = FileName$ + ".BAK"
  1284.      END IF
  1285.   END FUNCTION
  1286.  
  1287.   ───────────────────────────────────────────────────────────────────────────
  1288.  
  1289.  
  1290.  Chapter 2:  SUB and FUNCTION Procedures
  1291.  
  1292.  
  1293.  This chapter explains how to simplify your
  1294.  programming by breaking programs into smaller logical components. These
  1295.  components [mdash] known as "procedures" [mdash] can then become building
  1296.  blocks that let you enhance and extend the BASIC language itself.
  1297.  
  1298.  When you are finished with this chapter, you will know how to perform the
  1299.  following tasks with procedures:
  1300.  
  1301.    ■   Define and call BASIC procedures.
  1302.  
  1303.    ■   Use local and global variables in procedures.
  1304.  
  1305.    ■   Use procedures instead of  GOSUB subroutines and  DEF FN functions.
  1306.  
  1307.    ■   Pass arguments to procedures and return values from procedures.
  1308.  
  1309.    ■   Write recursive procedures (procedures that can call themselves).
  1310.  
  1311.  
  1312.  Although you can create a BASIC program with any text editor, the QuickBASIC
  1313.  Extended (QBX) development environment makes it especially easy to write
  1314.  programs that contain procedures. Also, in most cases QBX automatically
  1315.  generates a  DECLARE statement when you save your program. (The  DECLARE
  1316.  statement ensures that only the correct number and type of arguments are
  1317.  passed to a procedure and allows your program to call procedures defined in
  1318.  separate modules.)
  1319.  
  1320.  
  1321.  Procedures: Building Blocks for Programming
  1322.  
  1323.  As used in this chapter, the term "procedure" covers  SUB... END SUB and
  1324.  FUNCTION... END FUNCTION constructions. Procedures are useful for condensing
  1325.  repeated tasks. For example, suppose you are writing a program that you
  1326.  intend eventually to compile as a stand-alone application and you want the
  1327.  user of this application to be able to pass several arguments to the
  1328.  application from the command line. It then makes sense to turn this task
  1329.  [mdash] breaking the string returned by the  COMMAND$ function into two or
  1330.  more arguments [mdash] into a separate procedure. Once you have this
  1331.  procedure up and running, you can use it in other programs. In essence, you
  1332.  are extending BASIC to fit your individual needs when you use procedures.
  1333.  
  1334.  These are the two major benefits of programming with procedures:
  1335.  
  1336.    ■   Procedures allow you to break your programs into discrete logical
  1337.        units, each of which can be more easily debugged than can an entire
  1338.        program without procedures.
  1339.  
  1340.    ■   Procedures used in one program can be used as building blocks in other
  1341.        programs, usually with little or no modification.
  1342.  
  1343.  
  1344.  You can also put procedures in your own Quick library, which is a special
  1345.  file that you can load into memory when you start QBX. Once the contents of
  1346.  a Quick library are in memory with QBX, any program that you write has
  1347.  access to the procedures in the library. This makes it easier for all of
  1348.  your programs to share and save code. (See Chapter 19, "Creating and Using
  1349.  Quick Libraries," for more information on how to build Quick libraries.)
  1350.  
  1351.  
  1352.  Comparing Procedures with Subroutines
  1353.  
  1354.  If you are familiar with earlier versions of BASIC, you might think of a
  1355.  SUB... END SUB procedure as being roughly similar to a  GOSUB... RETURN
  1356.  subroutine. You will also notice some similarities between a  FUNCTION...
  1357.  END FUNCTION procedure and a  DEF FN... END DEF function. However,
  1358.  procedures have many advantages over these older constructions, as shown in
  1359.  the following sections.
  1360.  
  1361.  
  1362.  Note
  1363.  
  1364.  To avoid confusion between a  SUB procedure and the target of a  GOSUB
  1365.  statement,  SUB  procedures are referred to in this manual as "subprograms,"
  1366.  while statement blocks accessed by  GOSUB... RETURN  statements are referred
  1367.  to as "subroutines."
  1368.  
  1369.  
  1370.  Comparing SUB with GOSUB
  1371.  
  1372.  Although use of the  GOSUB subroutine does help break programs into
  1373.  manageable units,  SUB procedures have a number of advantages over
  1374.  subroutines, as discussed in the following sections.
  1375.  
  1376.  Local and Global Variables
  1377.  In  SUB procedures, all variables are local by default; that is,
  1378.  they exist only within the scope of the  SUB procedure's definition.
  1379.  To illustrate, the variable named I% in the following subprogram
  1380.  is local to the procedure, and has no connection with the variable named I%
  1381.  in the module-level code:
  1382.  
  1383.   I% = 1
  1384.   CALL Test
  1385.   PRINT I% ' I% still equals 1.
  1386.   END
  1387.  
  1388.   SUB Test STATIC
  1389.      I% = 50
  1390.   END SUB
  1391.  
  1392.  
  1393.  A  GOSUB has a major drawback as a building block in programs: it contains
  1394.  only "global variables." With global variables, if you have a variable named
  1395.  I% inside your subroutine, and another variable named I% outside the
  1396.  subroutine but in the same module, they are one and the same. Any changes to
  1397.  the value of I% in the subroutine affect I% everywhere it appears in the
  1398.  module. As a result, if you try to patch a subroutine from one module into
  1399.  another module, you may have to rename subroutine variables to avoid
  1400.  conflict with variable names in the new module.
  1401.  
  1402.  Use in Multiple-Module Programs
  1403.  A  SUB can be defined in one module and called from another. This
  1404.  significantly reduces the amount of code required for a program and
  1405.  increases the ease with which code can be shared among a number of
  1406.  programs.
  1407.  
  1408.  A  GOSUB subroutine, however, must be defined and used in the same module.
  1409.  
  1410.  Operating on Different Sets of Variables
  1411.  A  SUB procedure can be called any number of times within a
  1412.  program, with a different set of variables being passed to it each time.
  1413.  This is done by calling the  SUB procedure with an argument
  1414.  list. (See the section "Passing Arguments to Procedures" later in
  1415.  this chapter for more information on how to do this.) In the following
  1416.  example, the subprogram Compare is called twice, with different pairs of
  1417.  variables passed to it each time:
  1418.  
  1419.   X = 4: Y = 5
  1420.  
  1421.   CALL Compare (X, Y)
  1422.  
  1423.   Z = 7: W = 2
  1424.   CALL Compare (Z, W)
  1425.   END
  1426.  SUB Compare (A, B)
  1427.     IF A < B THEN SWAP A, B
  1428.   END SUB
  1429.  
  1430.  
  1431.  Calling a  GOSUB subroutine more than once in the same program and having it
  1432.  operate on a different set of variables each time is difficult. The process
  1433.  involves copying values to and from global variables, as shown in the next
  1434.  example:
  1435.  
  1436.   X = 4: Y = 5
  1437.   A = X: B = Y
  1438.   GOSUB Compare
  1439.   X = A: Y = B
  1440.  
  1441.   Z = 7 : W = 2
  1442.   A = Z : B = W
  1443.   GOSUB Compare
  1444.   Z = A : W = B
  1445.   END
  1446.  
  1447.   Compare:
  1448.     IF A < B THEN SWAP A, B
  1449.   RETURN
  1450.  
  1451.  
  1452.  Comparing FUNCTION with DEF FN
  1453.  
  1454.  While the multiline  DEF FN function definition answers the need for
  1455.  functions more complex than can be squeezed onto a single line,  FUNCTION
  1456.  procedures give you this capability plus the additional advantages discussed
  1457.  in the following sections.
  1458.  
  1459.  Local and Global Variables
  1460.  By default, all variables within a  FUNCTION procedure are
  1461.  local to it, although you do have the option of using global variables.
  1462.  (See the section "Sharing Variables with SHARED" later in this
  1463.  chapter for more information on procedures and global variables.)
  1464.  
  1465.  In a  DEF FN function, variables used within the function's body are global
  1466.  to the current module by default (this is also true for  GOSUB subroutines).
  1467.  However, you can make a variable in a  DEF FN function local by putting it
  1468.  in a  STATIC statement.
  1469.  
  1470.  Changing Variables Passed to the Procedure
  1471.  Variables are passed to  FUNCTION procedures by reference or
  1472.  by value. When you pass a variable by reference, you can change the
  1473.  variable by changing its corresponding parameter in the procedure.
  1474.  For example, after the call to GetRemainder in the next program,
  1475.  the value of X is 2, since the value of M is 2 at the end of the procedure:
  1476.  
  1477.   X = 89
  1478.   Y = 40
  1479.   PRINT GetRemainder(X, Y)
  1480.   PRINT X, Y        ' X is now 2.
  1481.   END
  1482.  
  1483.  
  1484.   FUNCTION GetRemainder (M, N)
  1485.     GetRemainder = M MOD N
  1486.     M = M \ N
  1487.   END FUNCTION
  1488.  
  1489.  Variables are passed to a  DEF FN function only by value, so in the next
  1490.  example, FNRemainder changes M without affecting X:
  1491.  
  1492.   DEF FNRemainder (M, N)
  1493.     FNRemainder = M MOD N
  1494.     M = M \ N
  1495.   END DEF
  1496.  
  1497.   X = 89
  1498.   Y = 40
  1499.   PRINT FNRemainder(X, Y)
  1500.  
  1501.   PRINT X,Y  ' X is still 89.
  1502.  
  1503.  See the sections "Passing Arguments by Reference" and "Passing Arguments by
  1504.  Value" later in this chapter for more information on the distinction between
  1505.  passing by reference and by value.
  1506.  
  1507.  Calling the Procedure Within Its Definition
  1508.  A  FUNCTION procedure can be "recursive"; in other words,
  1509.  it can call itself within its own definition. (See the section
  1510.  "Recursive Procedures" later in this chapter for more information on
  1511.  how procedures can be recursive.) A  DEF FN function cannot
  1512.  be recursive.
  1513.  
  1514.  Use in Multiple-Module Programs
  1515.  You can define a  FUNCTION procedure in one module and use
  1516.  it in another module. You need to put a  DECLARE statement in
  1517.  the module in which the procedure is used; otherwise, your program thinks
  1518.  the procedure  name refers to a variable. (See the section "Checking
  1519.  Arguments with DECLARE" later in this chapter for more information on using
  1520.  DECLARE this way.)
  1521.  
  1522.  A  DEF FN  function can only be used in the module in which it is defined.
  1523.  Unlike  SUB or  FUNCTION procedures, which can be called before they appear
  1524.  in the program, a  DEF FN function must always be defined before it is used
  1525.  in a module.
  1526.  
  1527.  
  1528.  Note
  1529.  
  1530.  The name of a  FUNCTION procedure can be any valid BASIC variable name,
  1531.  except one beginning with the letters "FN." The name of a  DEF   FN function
  1532.  must always be preceded
  1533.  
  1534.   by "FN."
  1535.  
  1536.  
  1537.  Defining Procedures
  1538.  
  1539.  BASIC procedure definitions have the following general syntax:
  1540.  
  1541.  { SUB |  FUNCTION}  procedurename ( parameterlist)  STATIC
  1542.    statementblock-1
  1543.    EXIT { SUB |  FUNCTION}
  1544.   statementblock-2
  1545.    END { SUB |  FUNCTION}
  1546.  
  1547.  The following table describes the parts of a procedure definition:
  1548.  
  1549. ╓┌──────────────────┌──────────────────┌──────────────────┌──────────────────╖
  1550.  ────────────────────────────────────────────────────────────────────────────
  1551.  { SUB |  FUNCTION}  Marks the
  1552.                     beginning of a
  1553.                     SUB or  FUNCTION
  1554.                     procedure,
  1555.                     respectively.
  1556.  ────────────────────────────────────────────────────────────────────────────
  1557.                    respectively.
  1558.  
  1559.   procedurename     Any valid
  1560.                     variable name up
  1561.                     to 40 characters
  1562.                     long. The same
  1563.                     name cannot be
  1564.                     used for a  SUB
  1565.                     and a  FUNCTION
  1566.                     procedure.
  1567.  
  1568.   parameterlist     A list of
  1569.                     variables,
  1570.                     separated by
  1571.                     commas, that
  1572.                     shows the number
  1573.                     and type of
  1574.                     arguments to be
  1575.                     passed to the
  1576.                     procedure. (The
  1577.  ────────────────────────────────────────────────────────────────────────────
  1578.                    procedure. (The
  1579.                     section
  1580.                     "Parameters and
  1581.                     Arguments" later
  1582.                     in this chapter
  1583.                     explains the
  1584.                     difference
  1585.                     between
  1586.                     parameters and
  1587.                     arguments.)
  1588.  
  1589.   STATIC            If you use the     If you omit the    See the section
  1590.                     STATIC attribute,  STATIC attribute,  "Automatic and
  1591.                     local variables    local variables    Static Variables"
  1592.                     are static; that   are "automatic"    later in this
  1593.                     is, they retain    by default; that   chapter for more
  1594.                     their values       is, they are       information.
  1595.                     between calls to   initialized to
  1596.                     the procedure.     zeros or null
  1597.                                        strings at the
  1598.  ────────────────────────────────────────────────────────────────────────────
  1599.                                       strings at the
  1600.                                        start of each
  1601.                                        procedure call.
  1602.  
  1603.  
  1604.  
  1605.  
  1606.  
  1607.  
  1608. ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
  1609.  ────────────────────────────────────────────────────────────────────────────
  1610.   END { SUB |  FUNCTION}  Ends a  SUB or           When your program
  1611.                           FUNCTION definition. To  encounters an  END SUB
  1612.                           run correctly, every     or  END FUNCTION, it
  1613.                           procedure must have      exits the procedure and
  1614.                           exactly one  END { SUB   returns to the statement
  1615.                           |  FUNCTION} statement.  immediately following
  1616.                                                    the one that called the
  1617.                                                    procedure. You can also
  1618.                                                    use one or more optional
  1619.  ────────────────────────────────────────────────────────────────────────────
  1620.                                                   use one or more optional
  1621.                                                     EXIT { SUB |  FUNCTION}
  1622.                                                    statements within the
  1623.                                                    body of a procedure
  1624.                                                    definition to exit from
  1625.                                                    the procedure.
  1626.  
  1627.  
  1628.  
  1629.  
  1630.  
  1631.  All valid BASIC expressions and statements except the following are allowed
  1632.  within a procedure definition.
  1633.  
  1634.      ■   DEF FN... END DEF,  FUNCTION... END FUNCTION, or  SUB... END SUB
  1635.  
  1636.      ■   COMMON.
  1637.  
  1638.      ■   DECLARE.
  1639.  
  1640.      ■   DIM SHARED.
  1641.  
  1642.      ■   OPTION BASE.
  1643.  
  1644.      ■   TYPE... END TYPE.
  1645.  
  1646.  
  1647.  Example
  1648.  
  1649.  The following example is a  FUNCTION procedure named IntegerPower:
  1650.  
  1651.   FUNCTION IntegerPower& (X&, Y&) STATIC
  1652.     PowerVal& = 1
  1653.     FOR I& = 1 TO Y&
  1654.      PowerVal& = PowerVal& * X&
  1655.     NEXT I&
  1656.     IntegerPower& = PowerVal&
  1657.   END FUNCTION
  1658.  
  1659.  
  1660.  Calling Procedures
  1661.  
  1662.  Calling a  FUNCTION procedure is different from calling a  SUB procedure, as
  1663.  shown in the next two sections.
  1664.  
  1665.  
  1666.  Calling a FUNCTION Procedure
  1667.  
  1668.  You call a  FUNCTION procedure the same way you use an intrinsic BASIC
  1669.  function such as  ABS, that is, by using its name in an expression. For
  1670.  example, any of the following statements would call a  FUNCTION named ToDec
  1671.  :
  1672.  
  1673.   PRINT 10 * ToDec
  1674.   X = ToDec
  1675.   IF ToDec = 10 THEN PRINT "Out of range."
  1676.  
  1677.  A  FUNCTION procedure can return values by changing variables passed to it
  1678.  as arguments. (See the section "Passing Arguments by Reference" later in
  1679.  this chapter for an explanation of how this is done.) Additionally, a
  1680.  FUNCTION procedure returns a single value in its name, so the name of the
  1681.  function must agree with the type it returns. For example, if the function
  1682.  returns a string value, either its name must have the string
  1683.  type-declaration character ( $) appended to it, or it must be declared as
  1684.  having the string type in a preceding  DEFSTR statement.
  1685.  
  1686.  Example
  1687.  
  1688.  The following program shows a  FUNCTION procedure that returns a string
  1689.  value. Note that the type-declaration suffix for strings  ($) is part of the
  1690.  procedure name.
  1691.  
  1692.  DECLARE FUNCTION GetInput$ ()
  1693.  Banner$ = GetInput$  ' Call the function and assign the
  1694.  ' return value to a string variable.
  1695.  PRINT Banner$        ' Print the string.
  1696.  END
  1697.  
  1698.  ' ======================= GetInput$ ========================
  1699.  '    The $ type-declaration character at the end of this
  1700.  '    function name means that it returns a string value.
  1701.  ' ==========================================================
  1702.  
  1703.  FUNCTION GetInput$ STATIC
  1704.  
  1705.     ' Return a string of 10 characters read from the
  1706.     ' keyboard, echoing each character as it is typed:
  1707.     FOR I% = 1 TO 10
  1708.        Char$ = INPUT$(1)     ' Get the character.
  1709.        PRINT Char$;          ' Echo the character on the
  1710.  ' screen.
  1711.        Temp$ = Temp$ + Char$ ' Add the character to the
  1712.  ' string.
  1713.   NEXT
  1714.     PRINT
  1715.     GetInput$ = Temp$    ' Assign the string to the procedure.
  1716.  END FUNCTION
  1717.  
  1718.  
  1719.  Calling a SUB Procedure
  1720.  
  1721.  A  SUB procedure differs from a  FUNCTION procedure in that a sub procedure
  1722.  cannot be called by using its name within an expression. A call to a sub
  1723.  procedure is a stand-alone statement, like BASIC's  CIRCLE statement. Also,
  1724.  a sub procedure does not return a value in its name as does a function.
  1725.  However, like a function, a sub procedure can modify the values of any
  1726.  variables passed to it. (The section "Passing Arguments by Reference" later
  1727.  in this chapter explains how this is done.)
  1728.  
  1729.  There are two ways to call a  SUB procedure:
  1730.  
  1731.    ■   Put its name in a  CALL statement:
  1732.  CALL PrintMessage  ■   n
  1733.  
  1734.        Use its name as a statement by itself:
  1735.  PrintMessage
  1736.  
  1737.  If you omit the  CALL keyword, don't put parentheses around arguments passed
  1738.  to the sub procedure:
  1739.  
  1740.   ' Call the ProcessInput subprogram with CALL and pass the
  1741.   ' three arguments First$, Second$, and NumArgs% to it:
  1742.   CALL ProcessInput (First$, Second$, NumArgs%)
  1743.  
  1744.   ' Call the ProcessInput subprogram without CALL and pass
  1745.   ' it the same arguments (note no parentheses around the
  1746.   ' argument list):
  1747.   ProcessInput First$, Second$, NumArgs%
  1748.  
  1749.  See the next section for more information on passing arguments to
  1750.  procedures.
  1751.  
  1752.  If your program calls  SUB procedures without using  CALL, and if you are
  1753.  not using QBX to write the program, you must put the name of the subprogram
  1754.  in a  DECLARE statement before it is called:
  1755.  
  1756.   DECLARE SUB CheckForKey
  1757.   .
  1758.   .
  1759.   .
  1760.   CheckForKey
  1761.  
  1762.  
  1763.  You need to be concerned about this only if you are developing programs
  1764.  outside QBX, since QBX automatically inserts  DECLARE statements wherever
  1765.  they are needed when it saves a program.
  1766.  
  1767.  
  1768.  Passing Arguments to Procedures
  1769.  
  1770.  The following sections explain how to tell the difference between parameters
  1771.  and arguments, how to pass arguments to procedures, and how to check
  1772.  arguments to make sure they are of the correct type and quantity.
  1773.  
  1774.  
  1775.  Parameters and Arguments
  1776.  
  1777.  The first step in learning about passing arguments to procedures is
  1778.  understanding the difference between the terms "parameter" and "argument":
  1779.  
  1780. ╓┌─────────────────────────────────────┌─────────────────────────────────────╖
  1781.  ────────────────────────────────────────────────────────────────────────────
  1782.  A variable name that appears in a     A constant, variable, or expression
  1783.  SUB,  FUNCTION, or  DECLARE           passed to a  SUB or  FUNCTION when
  1784.  statement                             the procedure is called
  1785.  
  1786.  
  1787.  
  1788.  
  1789.  
  1790.  In a procedure definition, parameters are placeholders for arguments. As
  1791.  shown in Figure 2.1, when a procedure is called, arguments are plugged into
  1792.  the variables in the parameter list, with the first parameter receiving the
  1793.  first argument, the second parameter receiving the second argument, and so
  1794.  on.
  1795.  
  1796.  Figure 2.1 also demonstrates another important rule: although the
  1797.  names of variables do not have to be the same in an argument list and a
  1798.  parameter list, the number of parameters and the number of arguments do.
  1799.  Furthermore, the type (string, integer numeric, single-precision numeric,
  1800.  and so on) should be the same for corresponding arguments and parameters.
  1801.  (See the section "Checking Arguments with DECLARE" later in this chapter for
  1802.  more information on how to ensure that arguments and parameters agree in
  1803.  number and type.)
  1804.  
  1805.  
  1806.  A parameter list consists of any of the following, all separated by commas:
  1807.  
  1808.     ■  Valid variable names, except for fixed-length strings.
  1809.  
  1810.        For example, X$ and X AS STRING are both legal in a parameter list,
  1811.        since they refer to variable-length strings. However, X AS STRING * 10
  1812.        refers to a fixed-length string 10 characters long and cannot appear
  1813.        in a parameter list. (Fixed-length strings are perfectly all right as
  1814.        arguments passed to procedures. See Chapter 4, "String Processing,"
  1815.        for more information on fixed-length and variable-length strings.)
  1816.  
  1817.     ■  Array names followed by a pair of left and right parentheses.
  1818.  
  1819.        An argument list consists of any of the following, all separated by
  1820.        commas:
  1821.  
  1822.     ■  Constants.
  1823.  
  1824.     ■  Expressions.
  1825.  
  1826.     ■  Valid variable names.
  1827.  
  1828.     ■  Array names followed by left and right parentheses.
  1829.  
  1830.  
  1831.  Examples
  1832.  
  1833.  The following example shows the first line of a subprogram definition with a
  1834.  parameter list:
  1835.  
  1836.  SUB TestSub (A%, Array(), RecVar AS RecType, Cs$)
  1837.  
  1838.  The first parameter, A%, is an integer; the second parameter, Array(), is a
  1839.  single-precision array, since untyped numeric variables are single precision
  1840.  by default; the third parameter, RecVar, is a record of type RecType; and
  1841.  the fourth parameter, Cs$, is a string.
  1842.  
  1843.  The CALL TestSub line in the next example calls the TestSub subprogram and
  1844.  passes it four arguments of the appropriate type:
  1845.  
  1846.   TYPE RecType
  1847.     Rank AS STRING * 12
  1848.     SerialNum AS LONG
  1849.   END TYPE
  1850.  
  1851.   DIM RecVar AS RecType
  1852.  
  1853.   CALL TestSub (X%, A(), RecVar, "Daphne")
  1854.  
  1855.  
  1856.  Passing Constants and Expressions
  1857.  
  1858.  Constants [mdash] whether string or numeric [mdash] can appear in the list
  1859.  of arguments passed to a procedure. Naturally, a string constant must be
  1860.  passed to a string parameter and a numeric constant to a numeric parameter,
  1861.  as shown in the next example:
  1862.  
  1863.  
  1864.   CONST SCREENWIDTH = 80
  1865.   CALL PrintBanner (SCREENWIDTH, "Monthly Status Report")
  1866.   .
  1867.   .
  1868.   .
  1869.   SUB PrintBanner (SW%, Title$)
  1870.     .
  1871.     .
  1872.     .
  1873.   END SUB
  1874.  
  1875.  If a numeric constant in an argument list does not have the same type as the
  1876.  corresponding parameter in the  SUB or  FUNCTION statement, then the
  1877.  constant is coerced to the type of the parameter, as you can see by the
  1878.  output from the next example:
  1879.  
  1880.   CALL test(4.6, 4.1)
  1881.   END
  1882.  
  1883.   SUB test (x%, y%)
  1884.     PRINT x%, y%
  1885.   END SUB
  1886.  Output5       4
  1887.  
  1888.  Expressions resulting from operations on variables and constants can also be
  1889.  passed to a procedure. As is the case with constants, numeric expressions
  1890.  that disagree in type with their parameters are coerced into agreement, as
  1891.  shown here:
  1892.  
  1893.   Checker A! + 25!, NOT BooleanVal%
  1894.  
  1895.   ' In the next call, putting parentheses around the
  1896.   ' long-integer variable Bval& makes it an expression.
  1897.   ' The (Bval&) expression is coerced to a short integer
  1898.   ' in the Checker procedure:
  1899.   Checker A! / 3.1, (Bval&)
  1900.   .
  1901.   .
  1902.   .
  1903.   END
  1904.  
  1905.   SUB Checker (Param1!, Param2%)
  1906.     .
  1907.     .
  1908.     .
  1909.   END SUB
  1910.  
  1911.  
  1912.  Passing Variables
  1913.  
  1914.  This section discusses how to pass simple variables, complete arrays,
  1915.  elements of arrays, records, and elements of records to procedures.
  1916.  
  1917.  Passing Simple Variables
  1918.  In argument and parameter lists, you can declare the type for a simple
  1919.  variable in one of the following three ways:
  1920.  
  1921.    ■   Append one of the type-declaration suffixes ( %,  &,  !,  #,  @ or  $)
  1922.        to the variable name.
  1923.  
  1924.    ■   Declare the variable in a  DIM,  COMMON,  REDIM,  SHARED, or  STATIC
  1925.        statement . For example:
  1926.  
  1927.  
  1928.  DIM A AS LONG  ■   n
  1929.  
  1930.        Use a  DEF type statement to set the default type.
  1931.  
  1932.  
  1933.  No matter which method you choose, corresponding variables must have the
  1934.  same type in the argument and parameter lists, as shown in the example
  1935.  below.
  1936.  
  1937.  
  1938.  Example
  1939.  
  1940.  In this example, two arguments are passed to the  FUNCTION procedure. The
  1941.  first is an integer giving the length of the string returned by CharString$,
  1942.  while the second is a character that is repeated to make the string.
  1943.  
  1944.   FUNCTION CharString$(A AS INTEGER, B$) STATIC
  1945.     CharString$ = STRING$(A%, B$)
  1946.   END FUNCTION
  1947.  
  1948.   DIM X AS INTEGER
  1949.   INPUT "Enter a number (1 to 80): ", X
  1950.   INPUT "Enter a character: ", Y$
  1951.  
  1952.   ' Print a string consisting of the Y$ character, repeated
  1953.   ' X number of times:
  1954.   PRINT CharString$(X, Y$)
  1955.   END
  1956.  OutputEnter a number (1 to 80):  21
  1957.   Enter a character:  #
  1958.   #####################
  1959.  Passing an Entire Array
  1960.  To pass all the elements of an array to a procedure, put the array's name,
  1961.  followed by left and right parentheses, in the argument and parameter lists.
  1962.  
  1963.  
  1964.  Example
  1965.  
  1966.  This example shows how to pass all the elements of an array to a procedure:
  1967.  
  1968.   DIM Values(1 TO 5) AS INTEGER
  1969.  
  1970.   ' Note empty parentheses after array name when calling
  1971.   ' procedure and passing array:
  1972.   CALL ChangeArray (1, 5, Values())
  1973.   CALL PrintArray (1, 5, Values())
  1974.   END
  1975.  
  1976.   ' Note empty parentheses after P parameter:
  1977.  
  1978.   SUB ChangeArray (Min%, Max%, P() AS INTEGER) STATIC
  1979.      FOR I% = Min% TO Max%
  1980.         P(I%) = I% ^ 3
  1981.      NEXT I%
  1982.   END SUB
  1983.  
  1984.   SUB PrintArray (Min%, Max%, P() AS INTEGER) STATIC
  1985.      FOR I% = Min% TO Max%
  1986.         PRINT P(I%)
  1987.      NEXT I%
  1988.      PRINT
  1989.   END SUB
  1990.  
  1991.  
  1992.  Passing Individual Array Elements
  1993.  
  1994.  If a procedure does not require an entire array, you can pass individual
  1995.  elements of the array instead. To pass an element of an array, use the array
  1996.  name followed by the appropriate subscripts inside parentheses.
  1997.  
  1998.  Example
  1999.  
  2000.  The SqrVal Array(4,2) statement in the following example passes the element
  2001.  in row 4, column 2 of the array to the SqrVal subprogram. (Note how the
  2002.  subprogram actually changes the value of this array element.)
  2003.  
  2004.   DIM Array(1 TO 5,1 TO 3)
  2005.  
  2006.   Array(4,2) = -36
  2007.   PRINT Array(4,2)
  2008.   SqrVal Array(4,2)
  2009.   PRINT Array(4,2)' The call to SqrVal has changed
  2010.   ' the value of Array(4,2).
  2011.   END
  2012.   SUB SqrVal(A) STATIC
  2013.     A = SQR(ABS(A))
  2014.   END SUB
  2015.  Output-36
  2016.    6
  2017.  
  2018.  
  2019.  Using Array-Bound Functions
  2020.  
  2021.  The  LBOUND and  UBOUND functions provide a useful way to determine the size
  2022.  of an array passed to a procedure. The  LBOUND function finds the smallest
  2023.  index value of an array subscript, while the  UBOUND function finds the
  2024.  largest one. These functions save you the trouble of having to pass the
  2025.  upper and lower bounds of each array dimension to a procedure.
  2026.  
  2027.  Example
  2028.  
  2029.  The subprogram in the following example uses the  LBOUND function to
  2030.  initialize the variables Row and Col to the lowest subscript values in each
  2031.  dimension of A. It also uses the  UBOUND function to limit the number of
  2032.  times the  FOR loop executes to the number of elements in the array.
  2033.  
  2034.   SUB PrintOut(A()) STATIC
  2035.     FOR Row = LBOUND(A,1) TO UBOUND(A,1)
  2036.      FOR Col = LBOUND(A,2) TO UBOUND(A,2)
  2037.      PRINT A(Row,Col)
  2038.      NEXT Col
  2039.     NEXT Row
  2040.   END SUB
  2041.  
  2042.  
  2043.  Passing an Entire Record
  2044.  
  2045.  To pass a complete record (a variable declared as having a user-defined
  2046.  type) to a procedure, complete the following steps:
  2047.  
  2048.     1. Define the type (StockItem in this example).
  2049.  TYPE StockItem
  2050.      PartNumber AS STRING * 6
  2051.      Description AS STRING * 20
  2052.      UnitPrice  AS SINGLE
  2053.      Quantity  AS INTEGER
  2054.  END TYPE
  2055.  
  2056.     2. Declare a variable (StockRecord) with that type.
  2057.  DIM StockRecord AS StockItem   3.
  2058.  
  2059.     3. Call a procedure (FindRecord) and pass it the variable
  2060.      you have declared.
  2061.  CALL FindRecord(StockRecord)   4.
  2062.  
  2063.     4. In the procedure definition, give the parameter the same
  2064.      type as the variable.
  2065.  
  2066.  SUB FindRecord (RecordVar AS StockItem) STATIC
  2067.  .
  2068.  .
  2069.  .
  2070.  END SUB
  2071.  
  2072.  
  2073.  Passing Individual Elements of a Record
  2074.  To pass an individual element in a record to a procedure, put the
  2075.  name of the element ( recordname . elementname)
  2076.  in the argument list. Be sure, as always, that the corresponding
  2077.  parameter in the procedure definition agrees with the type of that
  2078.  element.
  2079.  
  2080.  Example
  2081.  
  2082.  The following example shows how to pass the two elements in the record
  2083.  variable StockItem to the PrintTag  SUB procedure. Note how each parameter
  2084.  in the  SUB procedure agrees with the type of the individual record
  2085.  elements.
  2086.  
  2087.  TYPE StockType
  2088.     PartNumber AS STRING * 6
  2089.     Descrip AS STRING * 20
  2090.     UnitPrice AS CURRENCY
  2091.    Quantity AS INTEGER
  2092.   END TYPE
  2093.  
  2094.   DIM StockItem AS StockType
  2095.  
  2096.   CALL PrintTag (StockItem.Descrip, StockItem.UnitPrice)
  2097.   .
  2098.   .
  2099.   .
  2100.   END
  2101.  
  2102.   SUB PrintTag (Desc$, Price AS CURRENCY)
  2103.     .
  2104.     .
  2105.     .
  2106.   END SUB
  2107.  
  2108.  Checking Arguments with DECLARE
  2109.  
  2110.  If you are using QBX to write your program, you will notice that QBX
  2111.  automatically inserts a  DECLARE statement for each procedure whenever you
  2112.  save the program. Each  DECLARE statement consists of the word  DECLARE,
  2113.  followed by the words  SUB or  FUNCTION, the name of the procedure, and a
  2114.  set of parentheses. If the procedure has no parameters, then the parentheses
  2115.  are empty. If the procedure has parameters, then the parentheses enclose a
  2116.  parameter list that specifies the number and type of the arguments to be
  2117.  passed to the procedure. This parameter list has the same format as the list
  2118.  in the definition line found in the  SUB or  FUNCTION procedure.
  2119.  
  2120.  The purpose of the parameter list in a  DECLARE statement is to turn on
  2121.  "type checking" of arguments passed to the procedure. That is, every time
  2122.  the procedure is called with variable arguments, those variables are checked
  2123.  to be sure they agree with the number and type of the parameters in the
  2124.  DECLARE statement.
  2125.  
  2126.  
  2127.  QBX puts all procedure definitions at the end of a module when it saves a
  2128.  program. Therefore, if there are no  DECLARE statements, when you try to
  2129.  compile a program with the BC command you would run into a problem known as
  2130.  "forward reference" (calling a procedure before it is defined). By
  2131.  generating a prototype of the procedure definition,  DECLARE statements
  2132.  allow your program to call procedures that are defined later in a module, or
  2133.  in another module altogether.
  2134.  
  2135.  Examples
  2136.  
  2137.  The next example shows an empty parameter list in the  DECLARE statement,
  2138.  since no arguments are passed to GetInput$:
  2139.  
  2140.  DECLARE FUNCTION GetInput$ ()
  2141.   X$ = GetInput$
  2142.  
  2143.   FUNCTION GetInput$ STATIC
  2144.     GetInput$ = INPUT$(10)
  2145.   END FUNCTION
  2146.  
  2147.  The next example shows a parameter list in the  DECLARE statement, since an
  2148.  integer argument is passed to this version of GetInput$:
  2149.  
  2150.  DECLARE FUNCTION GetInput$ (X%)
  2151.   X$ = GetInput$ (5)
  2152.  
  2153.   FUNCTION GetInput$ (X%) STATIC
  2154.     GetInput$ = INPUT$(X%)
  2155.   END FUNCTION
  2156.  When QBX Does Not Generate a DECLARE Statement
  2157.  In certain instances, QBX does not generate  DECLARE statements in the
  2158.  module that calls a procedure.
  2159.  
  2160.  QBX cannot generate a  DECLARE statement in one module for a  SUB procedure
  2161.  defined in another module if the module containing the definition is not
  2162.  loaded.  However, the  DECLARE statement is not needed unless you want to
  2163.  call the  SUB procedure without using the keyword  CALL.
  2164.  
  2165.  QBX does not generate a  DECLARE statement for a  FUNCTION procedure,
  2166.  whether that module is loaded or not. In such a case, you must type the
  2167.  DECLARE statement yourself at the beginning of the module where the
  2168.  FUNCTION procedure is called; otherwise, QBX considers the call to the
  2169.  procedure to be a variable name.
  2170.  
  2171.  QBX also cannot generate a  DECLARE statement for any procedure in a Quick
  2172.  library. You must add one to the program yourself.
  2173.  
  2174.  Developing Programs Outside the QBX Environment
  2175.  If you are writing your programs with your own text editor and then
  2176.  compiling them outside the QBX environment with the BC and LINK
  2177.  commands, be sure to put  DECLARE statements in the
  2178.  following three locations:
  2179.  
  2180.    ■   At the beginning of any module that calls a  FUNCTION procedure before
  2181.        it is defined:
  2182.  
  2183.    ■   At the beginning of any module that calls a  SUB procedure before it
  2184.        is defined and does not use  CALL when calling the sub procedure:
  2185.  
  2186.  When you call a  SUB procedure with  CALL, you
  2187.  don't have to declare the procedure first:
  2188.  
  2189.    ■   At the beginning of any module that calls a  SUB or  FUNCTION
  2190.        procedure defined in another module (an "external procedure").
  2191.  
  2192.  If your procedure has no parameters, remember to put empty parentheses after
  2193.  the name of the procedure in the  DECLARE statement, as in the next example:
  2194.  
  2195.  DECLARE FUNCTION GetHour$ ()
  2196.   PRINT GetHour$
  2197.   END
  2198.  
  2199.   FUNCTION GetHour$ STATIC
  2200.     GetHour$ = LEFT$(TIME$,2)
  2201.   END FUNCTION
  2202.  
  2203.  Remember, a  DECLARE statement can appear only at the module level, not the
  2204.  procedure level. A  DECLARE statement affects the entire module in which it
  2205.  appears.
  2206.  
  2207.  Using Include Files for DeclarationsIf you have created a separate
  2208.  procedure-definition module that defines one or more  SUB or  FUNCTION
  2209.  procedures, it is a good idea to make an include file to go along with this
  2210.  module. This include file should contain the following:
  2211.  
  2212.    ■    DECLARE statements for all the module's procedures.
  2213.  
  2214.    ■    TYPE... END TYPE record definitions for any record parameters in this
  2215.        module's  SUB or  FUNCTION procedures.
  2216.  
  2217.    ■    COMMON statements listing variables shared between this module and
  2218.        other modules in the program. (See the section "Sharing Variables with
  2219.        Other Modules" later in this chapter for more information on using
  2220.        COMMON for this purpose.)
  2221.  
  2222.  
  2223.  Every time you use the definition module in one of your programs, insert a
  2224.  $INCLUDE metacommand at the beginning of any module that invokes procedures
  2225.  in the definition module. When your program is compiled, the actual contents
  2226.  of the include file are substituted for the  $INCLUDE metacommand.
  2227.  
  2228.  A simple rule of thumb is to make an include file for every module and then
  2229.  use the module and the include file together as outlined previously. The
  2230.  following list itemizes some of the benefits of this technique:
  2231.  
  2232.    ■   A module containing procedure definitions remains truly modular
  2233.        [mdash] that is, you don't have to copy all the  DECLARE statements
  2234.        for its procedures every time you call them from another module;
  2235.        instead, you can just substitute one  $INCLUDE metacommand.
  2236.  
  2237.    ■   In QBX, using an include file for procedure declarations suppresses
  2238.        automatic generation of  DECLARE statements when you save a program.
  2239.  
  2240.    ■   Using an include file for declarations avoids problems with getting
  2241.        one module to recognize a  FUNCTION procedure in another module. (See
  2242.        the section "When QBX Does Not Generate a DECLARE Statement" earlier
  2243.        in this chapter for more information.)
  2244.  
  2245.  
  2246.  
  2247.  You can take advantage of QBX's facility for generating  DECLARE statements
  2248.  when creating your include file. The following steps show you how to do
  2249.  this:
  2250.  
  2251.     1. Create your module.
  2252.  
  2253.     2. Within that module, call any  SUB or  FUNCTION procedures you have
  2254.        defined.
  2255.  
  2256.     3. Save the module to get automatic  DECLARE statements for all the
  2257.        procedures.
  2258.  
  2259.     4. Re-edit the module, removing the procedure calls and moving the
  2260.        DECLARE statements to a separate include file.
  2261.  
  2262.  
  2263.  See the  BASIC Language Reference for more information on the syntax and
  2264.  usage of the  $INCLUDE metacommand.
  2265.  
  2266.  Example
  2267.  
  2268.  The following fragments illustrate how to use a definition module and an
  2269.  include file together:
  2270.  
  2271.  ' =========================================================
  2272.   '                        MODDEF.BAS
  2273.   ' This module contains definitions for the Prompter and
  2274.   ' Max! procedures.
  2275.   ' =========================================================
  2276.   ' $INCLUDE: 'MODDEF.BI'
  2277.  FUNCTION Max! (X!, Y!) STATIC
  2278.      IF X! > Y! THEN Max! = X! ELSE Max! = Y!
  2279.   END FUNCTION
  2280.  SUB Prompter (Row%, Column%, RecVar AS RecType) STATIC
  2281.      LOCATE Row%, Column%
  2282.      INPUT "Description: ", RecVar.Description
  2283.      INPUT "Quantity:    ", RecVar.Quantity
  2284.   END SUB
  2285.  
  2286.   ' =========================================================
  2287.   '                        MODDEF.BI
  2288.   '  This is an include file that contains DECLARE statements
  2289.   '  for the Prompter and Max! procedures (as well as a TYPE
  2290.   '  statement defining the RecType user type). Use this file
  2291.   '  whenever you use the MODDEF.BAS module.
  2292.   ' =========================================================
  2293.   '
  2294.   TYPE RecType
  2295.      Description AS STRING * 15
  2296.      Quantity AS INTEGER
  2297.   END TYPE
  2298.  
  2299.   DECLARE FUNCTION Max! (X!, Y!)
  2300.   DECLARE SUB Prompter (Row%, Column%, RecVar AS RecType)
  2301.  
  2302.  
  2303.  ' ============================================================
  2304.   'SAMPLE.BAS
  2305.   '   This module is linked with the MODDEF.BAS module, and
  2306.   '   calls the Prompter and Max! procedures in MODDEF.BAS.
  2307.   ' ============================================================
  2308.   '
  2309.   ' The next line makes the contents of the MODDEF.BI include
  2310.   ' file part of this module as well:
  2311.   ' $INCLUDE: 'MODDEF.BI'
  2312.   .
  2313.   .
  2314.   .
  2315.   INPUT A, B
  2316.   PRINT Max!(A, B)' Call the Max! FUNCTION procedure in MODDEF.BAS.
  2317.   .
  2318.   .
  2319.   .
  2320.   Prompter 5, 5, RecVar' Call the Prompter SUB procedure in MODDEF.BAS
  2321.   .
  2322.   .
  2323.   .
  2324.  
  2325.  Important
  2326.  
  2327.  While it is good programming practice to put procedure declarations in an
  2328.  include file, do not put the procedures themselves ( SUB... END SUB or
  2329.  FUNCTION... END FUNCTION blocks) in an include file. Procedure definitions
  2330.  are not allowed inside include files in QBX. If you have used include files
  2331.  to define  SUB procedures in programs written with QuickBASIC versions 2.0
  2332.  or 3.0, either put these definitions in a separate module or incorporate
  2333.  them into the module where they are called.
  2334.  
  2335.  Declaring Procedures in Quick Libraries
  2336.  
  2337.  A convenient programming practice is to put all the declarations for
  2338.  procedures in a Quick library into one include file. With the  $INCLUDE
  2339.  metacommand you can then incorporate this include file into programs
  2340.  using the library. This saves you the trouble of copying all the
  2341.  relevant  DECLARE statements every time you use the library.
  2342.  
  2343.  
  2344.  Passing Arguments by Reference
  2345.  
  2346.  By default, variables [mdash] whether simple scalar variables, arrays and
  2347.  array elements, or records [mdash]are passed "by reference" to  FUNCTION and
  2348.   SUB procedures. Here is what is meant by passing variables by reference:
  2349.  
  2350.    ■   Each program variable has an address or a location in memory where its
  2351.        value is stored.
  2352.  
  2353.    ■   The process of calling a procedure and passing variables to it by
  2354.        reference calls the procedure and passes it the address of each
  2355.        variable. This means that the address of the variable and the address
  2356.        of its corresponding parameter in the procedure are one and the same.
  2357.  
  2358.    ■   Therefore, if the procedure modifies the value of the parameter, it
  2359.        also modifies the value of the variable that is passed.
  2360.  
  2361.  
  2362.  If you do not want a procedure to change the value of a variable, pass the
  2363.  procedure the value contained in the variable, not the address. This way,
  2364.  changes are made only to a copy of the variable, not the variable itself.
  2365.  See the next section for a discussion of this alternative way of passing
  2366.  variables.
  2367.  
  2368.  Example
  2369.  
  2370.  In the following program, changes made to the parameter A$ in the Replace
  2371.  procedure also change the argument Test$:
  2372.  
  2373.   Test$ = "a string with all lowercase letters."
  2374.   PRINT "Before subprogram call: "; Test$
  2375.   CALL Replace (Test$, "a")
  2376.   PRINT "After subprogram call: "; Test$
  2377.   END
  2378.  
  2379.   SUB Replace (A$, B$) STATIC
  2380.      Start = 1
  2381.      DO
  2382.  
  2383.         ' Look for B$ in A$, starting at the character
  2384.         ' with position "Start" in A$:
  2385.         Found = INSTR(Start, A$, B$)
  2386.       ' Make every occurrence of B$ in A$
  2387.        ' an uppercase letter:
  2388.        IF Found > 0 THEN
  2389.   MID$(A$,Found) = UCASE$(B$)
  2390.   Start = Start + 1
  2391.        END IF
  2392.     LOOP WHILE Found > 0
  2393.   END SUB
  2394.  
  2395.  Output
  2396.   Before subprogram call: a string with all lowercase letters.
  2397.   After subprogram call: A string with All lowercAse letters.
  2398.  
  2399.  
  2400.  Passing Arguments by Value
  2401.  
  2402.  Passing an argument "by value" means the value of the argument is passed,
  2403.  rather than its address. This prevents the original variable from being
  2404.  changed by the procedure that is called. In BASIC procedures, an actual
  2405.  value cannot be passed, but the same result is achieved by putting
  2406.  parentheses around the variable name. This causes BASIC to treat the
  2407.  variable as an expression. In this case, as with all expressions, the
  2408.  variable is copied to a temporary location, and the address of this
  2409.  temporary location is passed. Since the procedure does not have access to
  2410.  the address of the original variable, it cannot change the original
  2411.  variable; it makes all changes to the copy instead.
  2412.  
  2413.  
  2414.  Expressions are passed to procedures as in the following:
  2415.  
  2416.   ' A + B is an expression; the values of A and B
  2417.   ' are not affected by this procedure call:
  2418.   CALL Mult(A + B, C)
  2419.  
  2420.  Any variable enclosed in parentheses is treated by BASIC as an expression,
  2421.  as shown in the next example.
  2422.  
  2423.  Example
  2424.  
  2425.  In this example, a variable is enclosed in parentheses and passed to a
  2426.  procedure. This simulates actual passing by value because the variable data
  2427.  is copied to a temporary location whose address is passed. As you can see
  2428.  from the output that follows, changes to the  SUB procedure's local variable
  2429.  Y are passed back to the module-level code as changes to the variable B.
  2430.  However, changes to X in the procedure do not affect the value of A, since A
  2431.  is passed by value.
  2432.  
  2433.   A = 1
  2434.   B = 1
  2435.   PRINT "Before subprogram call, A ="; A; ", B ="; B
  2436.  
  2437.   ' A is passed by value, and B is passed by reference:
  2438.   CALL Mult((A), B)
  2439.   PRINT "After subprogram call, A ="; A; ", B ="; B
  2440.   END
  2441.  SUB Mult (X, Y) STATIC
  2442.     X = 2 * X
  2443.     Y = 3 * Y
  2444.     PRINT "In subprogram, X ="; X; ", Y ="; Y
  2445.   END SUB
  2446.  
  2447.  Output
  2448.   Before subprogram call, A = 1 , B = 1
  2449.   In subprogram, X = 2 , Y = 3
  2450.   After subprogram call, A = 1 , B = 3
  2451.  
  2452.  Sharing Variables with SHARED
  2453.  
  2454.  In addition to passing variables through argument and parameter lists,
  2455.  procedures can also share variables with other procedures and with code at
  2456.  the module level (that is, code within a module but outside of any
  2457.  procedure) in one of the following two ways:
  2458.  
  2459.    ■   Variables listed in a  SHARED statement within a procedure are shared
  2460.        only between that procedure and the module-level code. Use this method
  2461.        when different procedures in the same module need different
  2462.        combinations of module-level variables.
  2463.  
  2464.    ■   Variables listed in a module-level  COMMON SHARED,  DIM SHARED, or
  2465.        REDIM SHARED statement are shared between the module-level code and
  2466.        all procedures within that module. This method is most useful when all
  2467.        procedures in a module use a common set of variables.
  2468.  
  2469.  
  2470.  You can also use the  COMMON or  COMMON SHARED statement to share variables
  2471.  among two or more modules. The next three sections discuss these three ways
  2472.  to share variables.
  2473.  
  2474.  
  2475.  Sharing Variables with Specific Procedures
  2476.  
  2477.   in a Module
  2478.  
  2479.  If different procedures within a module need to share different variables
  2480.  with the module-level code, use the  SHARED statement within each procedure.
  2481.  
  2482.  Arrays in  SHARED statements consist of the array name followed by a set of
  2483.  empty parentheses:
  2484.  
  2485.   SUB JustAnotherSub STATIC
  2486.     SHARED ArrayName ()
  2487.     .
  2488.     .
  2489.     .
  2490.  
  2491.  If you give a variable its type in an  AS  type clause, then the variable
  2492.  must also be typed with the  AS  type clause in a  SHARED statement:
  2493.  
  2494.   DIM Buffer AS STRING * 10
  2495.   .
  2496.   .
  2497.   .
  2498.   END
  2499.  
  2500.   SUB ReadRecords STATIC
  2501.     SHARED Buffer AS STRING * 10
  2502.     .
  2503.     .
  2504.     .
  2505.   END SUB
  2506.  
  2507.  
  2508.  Example
  2509.  
  2510.  In this example, the  SHARED statements in the GetRecords and InventoryTotal
  2511.  procedures show the format of a shared variable list:
  2512.  
  2513.  DECLARE SUB GetRecords ()
  2514.  DECLARE FUNCTION InventoryTotal! ()
  2515.  ' =========================================================
  2516.  '                   MODULE-LEVEL CODE
  2517.  ' =========================================================
  2518.  TYPE RecType
  2519.  Price AS SINGLE
  2520.  Desc AS STRING * 35
  2521.  END TYPE
  2522.  
  2523.  DIM RecVar(1 TO 100) AS RecType    ' Array of records
  2524.  
  2525.  INPUT "File name: ", FileSpec$
  2526.  CALL GetRecords
  2527.  PRINT InventoryTotal
  2528.  END
  2529.  
  2530.  ' =========================================================
  2531.  '                   PROCEDURE-LEVEL CODE
  2532.  ' =========================================================
  2533.  SUB GetRecords STATIC
  2534.  
  2535.  ' Both FileSpec$ and the RecVar array of records
  2536.  ' are shared with the module-level code above:
  2537.  SHARED FileSpec$, RecVar() AS RecType
  2538.  OPEN FileSpec$ FOR RANDOM AS #1
  2539.     .
  2540.     .
  2541.     .
  2542.  END SUB
  2543.  
  2544.  FUNCTION InventoryTotal STATIC
  2545.  
  2546.  ' Only the RecVar array is shared with the module-level
  2547.  ' code:
  2548.  SHARED RecVar() AS RecType
  2549.     .
  2550.     .
  2551.     .
  2552.  END FUNCTION
  2553.  
  2554.  
  2555.  Sharing Variables with All Procedures in a Module
  2556.  
  2557.  If variables are declared at the module level with the  SHARED attribute in
  2558.  a  COMMON,  DIM, or  REDIM statement (for example, by using a statement of
  2559.  the form  COMMON SHARED  variablelist), then all procedures within that
  2560.  module have access to those variables; in other words, the  SHARED attribute
  2561.  makes variables global throughout a module.
  2562.  
  2563.  The  SHARED attribute is convenient when you need to share large numbers of
  2564.  variables among all procedures in a module.
  2565.  
  2566.  Examples
  2567.  
  2568.  These statements declare variables shared among all procedures in one
  2569.  module:
  2570.  
  2571.   COMMON SHARED A, B, C
  2572.   DIM SHARED Array(1 TO 10, 1 TO 10) AS UserType
  2573.   REDIM SHARED Alpha(N%)
  2574.  
  2575.  In the following example, the module-level code shares the string array
  2576.  StrArray and the integer variables Min and Max with the two  SUB procedures
  2577.  FillArray and PrintArray:
  2578.  
  2579.  ' =========================================================
  2580.   '                    MODULE-LEVEL CODE
  2581.   ' =========================================================
  2582.   '
  2583.  DECLARE SUB FillArray ()
  2584.   DECLARE SUB PrintArray ()
  2585.  
  2586.   ' The following DIM statements share the Min and Max
  2587.   ' integer variables and the StrArray string array
  2588.   ' with any SUB or FUNCTION procedure in this module:
  2589.   DIM SHARED StrArray (33 TO 126) AS STRING * 5
  2590.   DIM SHARED Min AS INTEGER, Max AS INTEGER
  2591.  
  2592.   Min = LBOUND(StrArray)
  2593.   Max = UBOUND(StrArray)
  2594.  
  2595.   FillArray' Note the absence of argument lists.
  2596.   PrintArray
  2597.   END
  2598.  
  2599.  ' =========================================================
  2600.   '                    PROCEDURE-LEVEL CODE
  2601.   ' =========================================================
  2602.   '
  2603.  SUB FillArray STATIC
  2604.  
  2605.      ' Load each element of the array from 33 to 126
  2606.      ' with a 5-character string, each character of which
  2607.      ' has the ASCII code I%:
  2608.      FOR I% = Min TO Max
  2609.         StrArray(I%) = STRING$(5, I%)
  2610.      NEXT
  2611.  
  2612.   END SUB
  2613.  
  2614.   SUB PrintArray STATIC
  2615.      FOR I% = Min TO Max
  2616.         PRINT StrArray(I%)
  2617.      NEXT
  2618.   END SUB
  2619.  Partial Output!!!!!
  2620.   """""
  2621.   #####
  2622.   $$$$$
  2623.   %%%%%
  2624.   &&&&&
  2625.   '''''
  2626.   .
  2627.   .
  2628.   .
  2629.  
  2630.  If you are using your own text editor to write your programs and directly
  2631.  compiling those programs outside the QBX development environment, note that
  2632.  variable declarations with the  SHARED attribute must precede the procedure
  2633.  definition. Otherwise, the value of any variable declared with  SHARED is
  2634.  not available to the procedure, as shown by the output from the next
  2635.  example. (If you are using QBX to create your programs, this sequence is not
  2636.  required, since QBX automatically saves programs in the correct order.)
  2637.  
  2638.   DEFINT A-Z
  2639.  
  2640.   FUNCTION Adder (X, Y) STATIC
  2641.     Adder = X + Y + Z
  2642.   END FUNCTION
  2643.  
  2644.   DIM SHARED Z
  2645.   Z = 2
  2646.   PRINT Adder (1, 3)
  2647.   END
  2648.  Output4
  2649.  
  2650.  
  2651.  The next example shows how you should save the module shown previously, with
  2652.  the definition of Adder following the DIM SHARED Z statement:
  2653.  
  2654.   DEFINT A-Z
  2655.  
  2656.   DECLARE FUNCTION Adder (X, Y)
  2657.  
  2658.   ' The variable Z is now shared with Adder:
  2659.   DIM SHARED Z
  2660.   Z = 2
  2661.   PRINT Adder (1, 3)
  2662.   END
  2663.  
  2664.   FUNCTION Adder (X, Y) STATIC
  2665.     Adder = X + Y + Z
  2666.   END FUNCTION
  2667.  Output6
  2668.  
  2669.  
  2670.  Sharing Variables with Other Modules
  2671.  
  2672.  If you want to share variables across modules in your program, list the
  2673.  variables in  COMMON or  COMMON SHARED statements at the module level in
  2674.  each module.
  2675.  
  2676.  Examples
  2677.  
  2678.  The following example shows how to share variables between modules by using
  2679.  a  COMMON statement in the module that calls the  SUB procedures, as well as
  2680.  a  COMMON SHARED statement in the module that defines the procedures. With
  2681.  COMMON SHARED, all procedures in the second module have access to the common
  2682.  variables.
  2683.  
  2684.  ' =========================================================
  2685.  '                      MAIN MODULE
  2686.  ' =========================================================
  2687.  
  2688.  COMMON A, B
  2689.  A = 2.5
  2690.  B = 1.2
  2691.  CALL Square
  2692.  CALL Cube
  2693.  END
  2694.  
  2695.  ' =========================================================
  2696.  '           Module with Cube and Square Procedures
  2697.  ' =========================================================
  2698.  
  2699.  ' NOTE: The names of the variables (X, Y) do not have to be
  2700.  ' the same as in the other module (A, B). Only the types
  2701.  ' have to be the same.
  2702.  
  2703.  COMMON SHARED X, Y  ' This statement is at the module level.
  2704.  ' Both X and Y are shared with the CUBE
  2705.  ' and SQUARE procedures below.
  2706.  SUB Cube STATIC
  2707.     PRINT "A cubed   ="; X ^ 3
  2708.     PRINT "B cubed   ="; Y ^ 3
  2709.  END SUB
  2710.  
  2711.  SUB Square STATIC
  2712.     PRINT "A squared ="; X ^ 2
  2713.     PRINT "B squared ="; Y ^ 2
  2714.  END SUB
  2715.  
  2716.  The following example uses named  COMMON blocks at the module levels and
  2717.  SHARED statements within procedures to share different sets of variables
  2718.  with each procedure:
  2719.  
  2720.  DECLARE SUB VolumeCalc ()
  2721.  DECLARE SUB DensityCalc ()
  2722.  ' =========================================================
  2723.  '                        MAIN MODULE
  2724.  ' Prints the volume and density of a filled cylinder given
  2725.  ' the input values.
  2726.  ' =========================================================
  2727.  
  2728.  COMMON /VolumeValues/ Height, Radius, Volume
  2729.  COMMON /DensityValues/ Weight, Density
  2730.  
  2731.  INPUT "Height of cylinder in centimeters: ", Height
  2732.  INPUT "Radius of cylinder in centimeters: ", Radius
  2733.  INPUT "Weight of filled cylinder in grams: ", Weight
  2734.  
  2735.  CALL VolumeCalc
  2736.  CALL DensityCalc
  2737.  
  2738.  PRINT "Volume is"; Volume; "cubic centimeters."
  2739.  PRINT "Density is"; Density; "grams/cubic centimeter."
  2740.  END
  2741.  
  2742.  
  2743.  ' =========================================================
  2744.  '     Module with DensityCalc and VolumeCalc Procedures
  2745.  ' =========================================================
  2746.  
  2747.  COMMON /VolumeValues/ H, R, V
  2748.  COMMON /DensityValues/ W, D
  2749.  
  2750.  SUB DensityCalc STATIC
  2751.  
  2752.     ' Share the Weight, Volume, and Density variables
  2753.     ' with this procedure:
  2754.     SHARED W, V, D
  2755.     D = W / V
  2756.  END SUB
  2757.  
  2758.  SUB VolumeCalc STATIC
  2759.  
  2760.     ' Share the Height, Radius, and Volume variables
  2761.     ' with this procedure:
  2762.     SHARED H, R, V
  2763.     CONST PI = 3.141592653589#
  2764.     V = PI * H * (R ^ 2)
  2765.  END SUB
  2766.  OutputHeight of cylinder in centimeters:  100
  2767.   Radius of cylinder in centimeters:  10
  2768.   Weight of filled cylinder in grams:  10000
  2769.   Volume is 31415.93 cubic centimeters.
  2770.   Density is .3183099 grams/cubic centimeter.
  2771.  
  2772.  The Problem of Variable Aliases
  2773.  
  2774.  "Variable aliases" can become a problem in long programs containing many
  2775.  variables and procedures. Variable aliases occur when two or more names
  2776.  refer to the same location in memory. Situations where it arises are:
  2777.  
  2778.    ■   When the same variable appears more than once in the list of arguments
  2779.        passed to a procedure.
  2780.  
  2781.    ■   When a variable passed in an argument list is also accessed by the
  2782.        procedure by means of the  SHARED statement or the  SHARED attribute.
  2783.  
  2784.  
  2785.  
  2786.  To avoid alias problems, double-check variables shared with a procedure to
  2787.  make sure they don't also appear in a procedure call's argument list. Also,
  2788.  don't pass the same variable twice, as in the next statement:
  2789.  
  2790.  ' X is passed twice; this will lead to alias problems
  2791.   ' in the Test procedure:
  2792.   CALL Test(X, X, Y)
  2793.  
  2794.  Example
  2795.  
  2796.  The following example illustrates how variable aliases can occur. Here the
  2797.  variable A is shared between the module-level code and the  SUB procedure
  2798.  with the DIM SHARED statement. However, A is also passed by reference to the
  2799.  subprogram as an argument. Therefore, in the subprogram, A and X both refer
  2800.  to the same location in memory. Thus, when the subprogram modifies X, it is
  2801.  also modifying A, and vice versa.
  2802.  
  2803.   DIM SHARED A
  2804.   A = 4
  2805.   CALL PrintHalf(A)
  2806.   END
  2807.  
  2808.   SUB PrintHalf (X) STATIC
  2809.     PRINT "Half of"; X; "plus half of"; A; "equals";
  2810.     X = X / 2      ' X and A now equal 2.
  2811.     A = A / 2      ' X and A now equal 1.
  2812.     PRINT A + X
  2813.   END SUB
  2814.  OutputHalf of 4 plus half of 4 equals 2
  2815.  
  2816.  Automatic and Static Variables
  2817.  
  2818.  When the  STATIC attribute appears on a procedure-definition line, it means
  2819.  that local variables within the procedure are "static"; that is, their
  2820.  values are preserved between calls to the procedure.
  2821.  
  2822.  Leaving off the  STATIC attribute makes local variables within the procedure
  2823.  "automatic" by default; that is, you get a fresh set of local variables each
  2824.  time the procedure is called.
  2825.  
  2826.  You can override the effect of leaving off the  STATIC attribute by using
  2827.  the  STATIC statement within the procedure, thus making some variables
  2828.  automatic and others static (see the next section for more information).
  2829.  
  2830.  Note
  2831.  
  2832.  The  SHARED statement also overrides the default for variables in a
  2833.  procedure (local static or local automatic), since any variable appearing in
  2834.  a  SHARED statement is known at the module level and thus is not local to
  2835.  the procedure.
  2836.  
  2837.  
  2838.  Preserving Values of Local Variables with STATIC
  2839.  
  2840.  Sometimes you may want to make some local variables in a procedure static
  2841.  while keeping the rest automatic. List those variables in a  STATIC
  2842.  statement within the procedure.
  2843.  
  2844.  Also, putting a variable name in a  STATIC statement is a way of making
  2845.  absolutely sure that the variable is local, since a  STATIC statement
  2846.  overrides the effect of a module-level  SHARED statement.
  2847.  
  2848.  Note
  2849.  
  2850.  If you give a variable its type in an  AS  type clause, then the  AS  type
  2851.  clause must appear along with the variable's name in the  STATIC and  DIM
  2852.  statements.
  2853.  
  2854.  A  STATIC statement can appear only within a procedure. An array name in a
  2855.  STATIC statement must be followed by a set of empty parentheses. Also, you
  2856.  must dimension any array that appears in a  STATIC statement before using
  2857.  the array, as shown in the next example:
  2858.  
  2859.   SUB SubProg2
  2860.     STATIC Array() AS INTEGER
  2861.     DIM Array(-5 TO 5, 1 TO 25) AS INTEGER
  2862.     .
  2863.     .
  2864.     .
  2865.   END SUB
  2866.  
  2867.  Example
  2868.  
  2869.  The following example shows how a  STATIC statement preserves the value of
  2870.  the string variable Y$ throughout successive calls to TestSub:
  2871.  
  2872.   DECLARE SUB TestSub ()
  2873.   FOR I% = 1 TO 5
  2874.     TestSub       ' Call TestSub five times.
  2875.   NEXT I%
  2876.   END
  2877.  
  2878.   SUB TestSub' Note: no STATIC attribute.
  2879.  
  2880.    ' Both X$ and Y$ are local variables in TestSub (that is,
  2881.    ' their values are not shared with the module-level code).
  2882.    ' However since X$ is an automatic variable, it is
  2883.    ' reinitialized to a null string every time TestSub is
  2884.    ' called. In contrast, Y$ is static, so it retains the
  2885.    ' value it had from the last call:
  2886.     STATIC Y$
  2887.     X$ = X$ + "*"
  2888.     Y$ = Y$ + "*"
  2889.     PRINT X$, Y$
  2890.   END SUB
  2891.  
  2892.  Output
  2893.   *       *
  2894.   *       **
  2895.   *       ***
  2896.   *       ****
  2897.   *       *****
  2898.  
  2899.  Recursive Procedures
  2900.  
  2901.  Procedures in BASIC can be recursive. A recursive procedure is one that can
  2902.  call itself or call other procedures that in turn call the first procedure.
  2903.  
  2904.  
  2905.  The Factorial Function
  2906.  
  2907.  A good way to illustrate recursive procedures is to consider the factorial
  2908.  function from mathematics. One way to define n! ("n factorial") is with the
  2909.  following formula:
  2910.  
  2911.    n! = n * (n[ndash]1) * (n[ndash]2) * ... * 2 * 1
  2912.  
  2913.  
  2914.  For example, 5 factorial is evaluated as follows:
  2915.  
  2916.    5! = 5 * 4 * 3 * 2 * 1 = 120
  2917.  
  2918.  
  2919.  Note
  2920.  
  2921.  Do not confuse the mathematical factorial symbol (!) used in this discussion
  2922.  with the single-precision type-declaration suffix used by BASIC.
  2923.  
  2924.  Factorials lend themselves to a recursive definition as well:
  2925.  
  2926.    n! = n * (n[ndash]1)!
  2927.  
  2928.  
  2929.  This leads to the following progression:
  2930.  
  2931.    5! = 5 * 4!
  2932.  
  2933.  
  2934.  
  2935.        4! = 4 * 3!
  2936.  
  2937.  
  2938.  
  2939.        3! = 3 * 2!
  2940.  
  2941.  
  2942.  
  2943.        2! = 2 * 1!
  2944.  
  2945.  
  2946.  
  2947.        1! = 1 * 0!
  2948.  
  2949.  
  2950.  Recursion must always have a terminating condition. With factorials, this
  2951.  terminating condition occurs when 0! is evaluated [mdash] by definition, 0!
  2952.  is equal to 1.
  2953.  
  2954.  Note
  2955.  
  2956.  Although a recursive procedure can have static variables by default (as in
  2957.  the next example), it is often preferable to let automatic variables be the
  2958.  default instead. In this way, recursive calls will not overwrite variable
  2959.  values from a preceding call.
  2960.  
  2961.  
  2962.  Example
  2963.  
  2964.  The following example uses a recursive  FUNCTION procedure to calculate
  2965.  factorials:
  2966.  
  2967.   DECLARE FUNCTION Factorial# (N%)
  2968.   DO
  2969.      INPUT "Enter number from 0 [ndash] 20 (or -1 to end): ", Num%
  2970.      IF Num% >= 0 AND Num% <= 20 THEN
  2971.         PRINT Num%; Factorial#(Num%)
  2972.      END IF
  2973.   LOOP WHILE Num% >= 0
  2974.   END
  2975.  
  2976.   FUNCTION Factorial# (N%) STATIC
  2977.  
  2978.      IF N% > 0 THEN' Call Factorial# again
  2979.   ' if N is greater than zero.
  2980.         Factorial# = N% * Factorial#(N% - 1)
  2981.  
  2982.      ELSE    ' Reached the end of recursive calls
  2983.   ' (N% = 0), so "climb back up the ladder."
  2984.         Factorial# = 1
  2985.      END IF
  2986.   END FUNCTION
  2987.  
  2988.  Adjusting the Size of the Stack
  2989.  
  2990.  Recursion can eat up a lot of memory, since each set of automatic variables
  2991.  in a  SUB or  FUNCTION procedure is saved on the stack. (Saving variables
  2992.  this way allows a procedure to continue with the correct variable values
  2993.  after control returns from a recursive call.)
  2994.  
  2995.  If you have a recursive procedure with many automatic variables, or a deeply
  2996.  nested recursive procedure, you may need to adjust the size of the stack
  2997.  before starting the procedure. Otherwise, you may get an Out of stack space
  2998.  error message.
  2999.  
  3000.  To make this adjustment you use the  FRE and  STACK functions, plus the
  3001.  STACK statement as explained in the following.
  3002.  
  3003.  Before actually adjusting the size of the stack, there are several facts
  3004.  that need to be determined. First, you must estimate the amount of memory
  3005.  your recursive procedure needs. Do this by following these steps:
  3006.  
  3007.     1. Make a test module consisting of the  DECLARE statement for the
  3008.        procedure, a single call to the procedure (using  CALL), and the
  3009.        procedure itself.
  3010.  
  3011.     2. Add a FRE([ndash]2) function (which returns the total unused stack
  3012.        space) just before you call the recursive procedure. Add a second
  3013.        FRE([ndash]2) function right at the end of the recursive procedure.
  3014.        Save the returned values in two long integers.
  3015.  
  3016.     3. Run the test module. The difference in values is the amount of stack
  3017.        space (in bytes) used by one call to the procedure.
  3018.  
  3019.     4. Estimate the maximum number of times the procedure is likely to be
  3020.        invoked, then multiply this value by the stack space consumed by one
  3021.        call to the procedure. The result is the amount of memory your
  3022.        recursive procedure needs.
  3023.  
  3024.  
  3025.  Once you know how many bytes of stack space the procedure needs, you then
  3026.  determine the currently allocated size of the stack. This is 3K for DOS and
  3027.  3.5K for OS/2 unless you have previously changed it with the  STACK
  3028.  statement. Assuming that you are running under DOS and using the default
  3029.  stack size, the following code adjusts the size of the stack (if space is
  3030.  available):
  3031.  
  3032.  ' Initialize a variable that contains the currently allocated stack size.
  3033.  CurrentSize = 3072
  3034.  ' Initialize a variable with the calculated recursion stack space
  3035.  ' requirements as explained above.
  3036.  RecursiveBytes = 6000
  3037.  ' Find out how many bytes are used up on the stack right now.
  3038.  BytesOnStack = CurrentSize - FRE(-2)
  3039.  ' Calculate the total required stack space.
  3040.  RequiredSpace = RecursiveBytes + BytesOnStack
  3041.  ' Request the space if there's room.
  3042.  IF RequiredSpace <= STACK THEN
  3043.  STACK RequiredSpace
  3044.  ELSE GOTO ReportError
  3045.  END IF
  3046.  
  3047.  Notice that in the preceding example, the  STACK statement and  STACK
  3048.  function were used. The  STACK function returns the maximum space that can
  3049.  be allocated. The  STACK statement allocates the space. See the  BASIC
  3050.  Language Reference for further information.
  3051.  
  3052.  
  3053.  Transferring Control to Another Program
  3054.  
  3055.   with CHAIN
  3056.  
  3057.  Unlike procedure calls, which occur within the same program, the  CHAIN
  3058.  statement simply starts a new program. When a program chains to another
  3059.  program, the following sequence occurs:
  3060.  
  3061.     1. The first program stops running.
  3062.  
  3063.     2. The second program is loaded into memory.
  3064.  
  3065.     3. The second program starts running.
  3066.  
  3067.  
  3068.  The advantage of using  CHAIN is that it enables you to split a program with
  3069.  large memory requirements into several smaller programs.
  3070.  
  3071.  
  3072.  The  COMMON statement allows you to pass variables from one program to
  3073.  another program in a chain. A prevalent programming practice is to put these
  3074.   COMMON statements in an include file, and then use the  $INCLUDE
  3075.  metacommand at the beginning of each program in the chain.
  3076.  
  3077.  Note
  3078.  
  3079.  Don't use a  COMMON  /blockname/ variablelist statement (a "named  COMMON
  3080.  block") to pass variables to a chained program, since variables listed in
  3081.  named  COMMON blocks are not preserved when chaining. Use a blank  COMMON
  3082.  block ( COMMON  variablelist) instead.
  3083.  
  3084.  Example
  3085.  
  3086.  This example, which shows a chain connecting three separate programs, uses
  3087.  an include file to declare variables passed in common among the programs:
  3088.  
  3089.  ' ============ CONTENTS OF INCLUDE FILE COMMONS.BI ========
  3090.   DIM Values(10)
  3091.   COMMON Values(), NumValues
  3092.  
  3093.   ' ======================= MAIN.BAS ========================
  3094.   '
  3095.   ' Read in the contents of the COMMONS.BI file:
  3096.   ' $INCLUDE: 'COMMONS.BI'
  3097.  
  3098.      ' Input the data:
  3099.      INPUT "Enter number of data values (<=10): ", NumValues
  3100.      FOR I = 1 TO NumValues
  3101.         Prompt$ = "Value ("+LTRIM$(STR$(I))+")? "
  3102.         PRINT Prompt$;
  3103.         INPUT "", Values(I)
  3104.      NEXT I
  3105.  
  3106.      ' Have the user specify the calculation to do:
  3107.      INPUT "Calculation (1=st. dev., 2=mean)? ", Choice
  3108.  
  3109.      ' Now, chain to the correct program:
  3110.      SELECT CASE Choice
  3111.  
  3112.         CASE 1:  ' Standard deviation
  3113.   CHAIN "STDEV"
  3114.  
  3115.         CASE 2:  ' Mean
  3116.   CHAIN "MEAN"
  3117.      END SELECT
  3118.   END
  3119.  
  3120.  
  3121.  ' ======================= STDEV.BAS =======================
  3122.   ' Calculates the standard deviation of a set of data
  3123.   ' =========================================================
  3124.   '
  3125.   ' $INCLUDE: 'COMMONS.BI'
  3126.  
  3127.      Sum   = 0   ' Normal sum
  3128.      SumSq = 0   ' Sum of values squared
  3129.  
  3130.      FOR I = 1 TO NumValues
  3131.         Sum   = Sum   + Values(I)
  3132.         SumSq = SumSq + Values(I) ^ 2
  3133.      NEXT I
  3134.  
  3135.      Stdev = SQR(SumSq / NumValues - (Sum / NumValues) ^ 2)
  3136.      PRINT "The Standard Deviation of the samples is: " Stdev
  3137.   END
  3138.  
  3139.   ' ======================== MEAN.BAS =======================
  3140.   ' Calculates the mean (average) of a set of data
  3141.   ' =========================================================
  3142.   '
  3143.   ' $INCLUDE: 'COMMONS.BI'
  3144.  
  3145.      Sum = 0
  3146.  
  3147.      FOR I = 1 TO NumValues
  3148.         Sum = Sum + Values(I)
  3149.      NEXT
  3150.  
  3151.      Mean = Sum / NumValues
  3152.      PRINT "The mean of the samples is: " Mean
  3153.   END
  3154.  
  3155.  Sample Application: Recursive Directory Search
  3156.  
  3157.   (WHEREIS.BAS)
  3158.  
  3159.  The following program uses a recursive  SUB procedure, ScanDir, to scan a
  3160.  disk for the filename entered by the user. Each time this program finds the
  3161.  given file, it prints the complete directory path to the file.
  3162.  
  3163.  
  3164.  Statements Used
  3165.  
  3166.  This program demonstrates the following statements discussed in this
  3167.  chapter:
  3168.  
  3169.    ■    DECLARE
  3170.  
  3171.    ■    FUNCTION... END FUNCTION
  3172.  
  3173.    ■    STATIC
  3174.  
  3175.    ■    SUB... END SUB
  3176.  
  3177.  
  3178.  
  3179.  Program Listing
  3180.  
  3181.  DEFINT A-Z
  3182.  
  3183.   ' Declare symbolic constants used in program:
  3184.   CONST EOFTYPE = 0, FILETYPE = 1, DIRTYPE = 2, ROOT = "TWH"
  3185.  
  3186.   DECLARE SUB ScanDir (PathSpec$, Level, FileSpec$, Row)
  3187.  
  3188.   DECLARE FUNCTION MakeFileName$ (Num)
  3189.   DECLARE FUNCTION GetEntry$ (FileNum, EntryType)
  3190.  CLS
  3191.   INPUT "File to look for"; FileSpec$
  3192.   PRINT
  3193.   PRINT "Enter the directory where the search should start"
  3194.   PRINT "(optional drive + directories). Press <ENTER> to "
  3195.   PRINT "begin search in root directory of current drive."
  3196.   PRINT
  3197.   INPUT "Starting directory"; PathSpec$
  3198.   CLS
  3199.  
  3200.   RightCh$ = RIGHT$(PathSpec$, 1)
  3201.  
  3202.   IF PathSpec$ = "" OR RightCh$ = ":" OR RightCh$ <> "\" THEN
  3203.      PathSpec$ = PathSpec$ + "\"
  3204.   END IF
  3205.  
  3206.  
  3207.  FileSpec$ = UCASE$(FileSpec$)
  3208.   PathSpec$ = UCASE$(PathSpec$)
  3209.   Level = 1
  3210.   Row = 3
  3211.  
  3212.   ' Make the top level call (level 1) to begin the search:
  3213.   ScanDir PathSpec$, Level, FileSpec$, Row
  3214.  
  3215.   KILL ROOT + ".*"        ' Delete all temporary files created
  3216.   ' by the program.
  3217.  
  3218.   LOCATE Row + 1, 1: PRINT "Search complete."
  3219.   END
  3220.  
  3221.  ' ======================= GetEntry ========================
  3222.   '    This procedure processes entry lines in a DIR listing
  3223.   '    saved to a file.
  3224.  
  3225.   '    This procedure returns the following values:
  3226.  
  3227.   'GetEntry$A valid file or directory name
  3228.   'EntryTypeIf equal to 1, then GetEntry$
  3229.   'is a file.
  3230.   'If equal to 2, then GetEntry$
  3231.   'is a directory.
  3232.   ' =========================================================
  3233.  '
  3234.  FUNCTION GetEntry$ (FileNum, EntryType) STATIC
  3235.  
  3236.      ' Loop until a valid entry or end-of-file (EOF) is read:
  3237.      DO UNTIL EOF(FileNum)
  3238.         LINE INPUT #FileNum, EntryLine$
  3239.         IF EntryLine$ <> "" THEN
  3240.  
  3241.            ' Get first character from the line for test:
  3242.   TestCh$ = LEFT$(EntryLine$, 1)
  3243.   IF TestCh$ <> " " AND TestCh$ <> "." THEN EXIT DO
  3244.         END IF
  3245.      LOOP
  3246.  
  3247.      ' Entry or EOF found, decide which:
  3248.      IF EOF(FileNum) THEN' EOF, so return EOFTYPE
  3249.         EntryType = EOFTYPE' in EntryType.
  3250.         GetEntry$ = ""
  3251.  
  3252.      ELSE           ' Not EOF, so it must be a
  3253.      ' file or a directory.
  3254.  
  3255.         ' Build and return the entry name:
  3256.         EntryName$ = RTRIM$(LEFT$(EntryLine$, 8))
  3257.  
  3258.         ' Test for extension and add to name if there is one:
  3259.         EntryExt$ = RTRIM$(MID$(EntryLine$, 10, 3))
  3260.         IF EntryExt$ <> "" THEN
  3261.            GetEntry$ = EntryName$ + "." + EntryExt$
  3262.         ELSE
  3263.   GetEntry$ = EntryName$
  3264.         END IF
  3265.  
  3266.         ' Determine the entry type, and return that value
  3267.         ' to the point where GetEntry$ was called:
  3268.         IF MID$(EntryLine$, 15, 3) = "DIR" THEN
  3269.   EntryType = DIRTYPE            ' Directory
  3270.         ELSE
  3271.   EntryType = FILETYPE           ' File
  3272.         END IF
  3273.  
  3274.      END IF
  3275.  
  3276.   END FUNCTION
  3277.  
  3278.  ' ===================== MakeFileName$ =====================
  3279.   '    This procedure makes a filename from a root string
  3280.   '    ("TWH," defined as a symbolic constant at the module
  3281.   '    level) and a number passed to it as an argument (Num).
  3282.   ' =========================================================
  3283.   '
  3284.   FUNCTION MakeFileName$ (Num) STATIC
  3285.  
  3286.      MakeFileName$ = ROOT + "." + LTRIM$(STR$(Num))
  3287.  
  3288.   END FUNCTION
  3289.  
  3290.   ' ======================= ScanDir =========================
  3291.   '   This procedure recursively scans a directory for the
  3292.   '   filename entered by the user.
  3293.  
  3294.   '   NOTE: The SUB header doesn't use the STATIC keyword
  3295.   '         since this procedure needs a new set of variables
  3296.   '         each time it is invoked.
  3297.   ' =========================================================
  3298.   '
  3299.   SUB ScanDir (PathSpec$, Level, FileSpec$, Row)
  3300.  
  3301.      LOCATE 1, 1: PRINT "Now searching"; SPACE$(50);
  3302.      LOCATE 1, 15: PRINT PathSpec$;
  3303.  
  3304.      ' Make a file specification for the temporary file:
  3305.      TempSpec$ = MakeFileName$(Level)
  3306.  
  3307.  
  3308.  ' Get a directory listing of the current directory,
  3309.      ' and save it in the temporary file:
  3310.      SHELL "DIR " + PathSpec$ + " > " + TempSpec$
  3311.  
  3312.      ' Get the next available file number:
  3313.      FileNum = FREEFILE
  3314.  
  3315.      ' Open the DIR listing file and scan it:
  3316.      OPEN TempSpec$ FOR INPUT AS #FileNum
  3317.  ' Process the file, one line at a time:
  3318.      DO
  3319.  
  3320.         ' Input an entry from the DIR listing file:
  3321.         DirEntry$ = GetEntry$(FileNum, EntryType)
  3322.  
  3323.         ' If entry is a file:
  3324.         IF EntryType = FILETYPE THEN
  3325.  
  3326.   ' If the FileSpec$ string matches,
  3327.   ' print entry and exit this loop:
  3328.   IF DirEntry$ = FileSpec$ THEN
  3329.   LOCATE Row, 1: PRINT PathSpec$; DirEntry$;
  3330.   Row = Row + 1
  3331.   EntryType = EOFTYPE
  3332.   END IF
  3333.  
  3334.         ' If the entry is a directory, then make a recursive
  3335.         ' call to ScanDir with the new directory:
  3336.         ELSEIF EntryType = DIRTYPE THEN
  3337.   NewPath$ = PathSpec$ + DirEntry$ + "\"
  3338.    ScanDir NewPath$, Level + 1, FileSpec$, Row
  3339.   LOCATE 1, 1: PRINT "Now searching"; SPACE$(50);
  3340.   LOCATE 1, 15: PRINT PathSpec$;
  3341.         END IF
  3342.  
  3343.      LOOP UNTIL EntryType = EOFTYPE
  3344.  
  3345.      ' Scan on this DIR listing file is finished, so close it:
  3346.      CLOSE FileNum
  3347.   END SUB
  3348.  
  3349.       ──────────────────────────────────────────────────────────────────────────
  3350.  
  3351.  Chapter 3:  File and Device I/O
  3352.  
  3353.  This chapter shows you how to use Microsoft BASIC input and output
  3354.  (I/O) functions and statements. These functions and statements
  3355.  permit your programs to access data stored in files and to communicate with
  3356.  devices attached to your system.
  3357.  
  3358.  The chapter includes material on a variety of programming tasks related to
  3359.  retrieving, storing, and formatting information. The relationship between
  3360.  data files and physical devices such as screens and keyboards is also
  3361.  covered.
  3362.  
  3363.  When you are finished with this chapter, you will know how to perform the
  3364.  following programming tasks:
  3365.  
  3366.    ■   Print text on the screen.
  3367.    ■   Get input from the keyboard for use in a program.
  3368.    ■   Create data files on disk.
  3369.    ■   Store records in data files.
  3370.    ■   Read records from data files.
  3371.    ■   Read or modify data in files that are not in ASCII format.
  3372.    ■   Communicate with other computers through the serial port.
  3373.  
  3374.  
  3375.  Note
  3376.  
  3377.  Creating and using ISAM files are discussed in Chapter 10, "Database
  3378.  Programming with ISAM."
  3379.  
  3380.  
  3381.  Printing Text on the Screen
  3382.  
  3383.  This section explains how to accomplish the following tasks:
  3384.  
  3385.    ■   Display text on the screen using  PRINT.
  3386.    ■   Display formatted text on the screen using  PRINT USING.
  3387.    ■   Skip spaces in a row of printed text using  SPC.
  3388.    ■   Skip to a given column in a row of printed text using  TAB.
  3389.    ■   Change the number of rows or columns appearing on the screen using
  3390.        WIDTH.
  3391.    ■   Open a text viewport using  VIEW PRINT.
  3392.  
  3393.  
  3394.  Note
  3395.  
  3396.  Output that appears on the screen is sometimes referred to as "standard
  3397.  output." You can redirect standard output by using the DOS command-line
  3398.  symbols > or >>, thus sending output that would have gone to the screen to a
  3399.  different output device (such as a printer) or to a disk file. (See your
  3400.  operating system documentation for more information on redirecting output.)
  3401.  
  3402.  
  3403.  Screen Rows and Columns
  3404.  
  3405.  To understand how text is printed on the screen, it helps to think of the
  3406.  screen as a grid of "rows" and "columns." The height of one row slightly
  3407.  exceeds the height of a line of printed output; the width of one column is
  3408.  just wider than the width of one character. A standard screen configuration
  3409.  in text mode (nongraphics) is 80 columns wide by 25 rows high. Figure 3.1
  3410.  shows how each character printed on the screen occupies a unique cell in the
  3411.  grid, a cell that can be identified by pairing a row argument with a column
  3412.  argument.
  3413.  
  3414.  The bottom row of the screen is not usually used for output, unless
  3415.  you use a  LOCATE statement to display text there. (See the section
  3416.  "Controlling the Text Cursor" later in the chapter for more information on
  3417.  LOCATE.)
  3418.  
  3419.  
  3420.  Displaying Text and Numbers with PRINT
  3421.  
  3422.  By far the most commonly used statement for output to the screen is the
  3423.  PRINT statement. With  PRINT, you can display numeric or string values, or a
  3424.  mixture of the two. In addition,  PRINT with no arguments prints a blank
  3425.  line.
  3426.  
  3427.  
  3428.  The following are some general comments about  PRINT:
  3429.  
  3430.    ■   PRINT always prints numbers with a trailing blank space. If the
  3431.         number is positive, the number is also preceded by a space; if the
  3432.         number is negative, the number is preceded by a minus sign (-).
  3433.  
  3434.    ■   The  PRINT statement can be used to print lists of expressions.
  3435.        Expressions in the list can be separated from other expressions by
  3436.        commas, semicolons, one or more blank spaces, or one or more tab
  3437.        characters. A comma causes  PRINT to skip to the beginning of the next
  3438.        "print zone," or block of 14 columns, on the screen. A semicolon (or
  3439.        any combination of spaces and/or tabs) between two expressions prints
  3440.        the expressions on the screen next to each other, with no spaces in
  3441.        between (except for the built-in spaces for numbers).
  3442.  
  3443.    ■   Ordinarily,  PRINT ends each line of output with a new-line sequence
  3444.        (a carriage return and line feed). However, a comma or semicolon at
  3445.        the end of the list of expressions suppresses this; the next printed
  3446.        output from the program appears on the same line unless it is too long
  3447.        to fit on that line.
  3448.  
  3449.    ■    PRINT wraps an output line that exceeds the width of the screen onto
  3450.        the next line. For example, if you try to print a line that is 100
  3451.        characters long on an 80-column screen, the first 80 characters of the
  3452.        line show up on one row, followed by the next 20 characters on the
  3453.        next row. If the 100-character line didn't start at the left edge of
  3454.        the screen (for example, if it followed a  PRINT statement ending in a
  3455.        comma or semicolon), then the line would print until it reached the
  3456.        80th column of one row and continue in the first column of the next
  3457.        row.
  3458.  
  3459.  
  3460.  Example
  3461.  
  3462.  The output from the following program shows some of the different ways you
  3463.  can use  PRINT:
  3464.  
  3465.   A = 2
  3466.   B = -1
  3467.   C = 3
  3468.   X$ = "over"
  3469.   Y$ = "there"
  3470.  
  3471.   PRINT A, B, C
  3472.   PRINT B, A, C
  3473.   PRINT A; B; C
  3474.   PRINT X$; Y$
  3475.   PRINT X$, Y$;
  3476.   PRINT A, B
  3477.   PRINT
  3478.   FOR I = 1 TO 8
  3479.      PRINT X$,
  3480.   NEXT
  3481.  
  3482.  Output
  3483.  
  3484.   2            -1             3
  3485.   -1             2             3
  3486.    2 -1  3
  3487.   overthere
  3488.   over          there 2       -1
  3489.  
  3490.   over          over          over          over          over
  3491.   over          over          over
  3492.  
  3493.  
  3494.  Displaying Formatted Output with PRINT USING
  3495.  
  3496.  The  PRINT USING statement gives greater control than  PRINT over the
  3497.  appearance of printed data, especially numeric data. Through the use of
  3498.  special characters embedded in a format string,  PRINT USING allows you to
  3499.  specify information such as how many digits from a number (or how many
  3500.  characters from a string) are displayed, whether or not a plus sign ( +) or
  3501.  a dollar sign ( $) appears in front of a number, and so forth.
  3502.  
  3503.  Example
  3504.  
  3505.  The example that follows shows what can be done with  PRINT USING. You can
  3506.  list more than one expression after the  PRINT USING format string. As is
  3507.  the case with  PRINT, the expressions in the list can be separated from one
  3508.  another by commas, semicolons, spaces, or tab characters.
  3509.  
  3510.   X = 441.2318
  3511.  
  3512.   PRINT USING "The number with 3 decimal places ###.###";X
  3513.   PRINT USING "The number with a dollar sign $$##.##";X
  3514.   PRINT USING "The number in exponential format #.###^^^^";X
  3515.   PRINT USING "Numbers with plus signs +###  "; X; 99.9
  3516.  
  3517.  Output
  3518.  
  3519.   The number with 3 decimal places 441.232
  3520.   The number with a dollar sign $441.23
  3521.   The number in exponential format 0.441E+03
  3522.   Numbers with plus signs +441  Numbers with plus signs +100
  3523.  
  3524.  Consult online Help for more on  PRINT USING.
  3525.  
  3526.  
  3527.  Skipping Spaces and Advancing to a Specific Column
  3528.  
  3529.  By using the  SPC( n) statement in a  PRINT statement, you can skip  n
  3530.  spaces in a row of printed output, as shown in the next example:
  3531.  
  3532.   PRINT "         1         2         3"
  3533.   PRINT "123456789012345678901234567890"
  3534.   PRINT "First Name"; SPC(10); "Last Name"
  3535.  
  3536.  Output
  3537.  
  3538.  1         2         3
  3539.   123456789012345678901234567890
  3540.   First Name          Last Name
  3541.  
  3542.  By using the TAB(n) statement in a PRINT statement, you can skip to the nth c
  3543.  output. In the following example,  TAB produces the same output shown in the
  3544.  preceding example:
  3545.  
  3546.   PRINT "         1         2         3"
  3547.   PRINT "123456789012345678901234567890"
  3548.   PRINT "First Name"; TAB(21); "Last Name"
  3549.  
  3550.  Neither  SPC nor  TAB can be used by itself to position printed output on
  3551.  the screen; they can only appear in  PRINT statements.
  3552.  
  3553.  
  3554.  Changing the Number of Columns or Rows
  3555.  
  3556.  You can control the maximum number of characters that appear in a single row
  3557.  of output by using the  WIDTH  columns statement. The  WIDTH  columns
  3558.  statement actually changes the size of characters that are printed on the
  3559.  screen, so that more or fewer characters can fit on a row. For example,
  3560.  WIDTH 40 makes characters wider, so the maximum row length is 40 characters.
  3561.  WIDTH 80 makes characters narrower, so the maximum row length is 80
  3562.  characters. The numbers 40 and 80 are the only valid values for the  columns
  3563.  argument.
  3564.  
  3565.  On machines equipped with an Enhanced Graphics Adapter (EGA) or Video
  3566.  Graphics Array (VGA), the  WIDTH statement can also control the number of
  3567.  rows that appear on the screen by using this syntax:
  3568.  
  3569.   WIDTH [ screenwidth%],[ screenheight%]
  3570.  
  3571.  The value for  screenheight% may be 25, 30, 43, 50, or 60,
  3572.  depending on the type of display adapter you use and the screen mode
  3573.  set in a preceding  SCREEN statement.
  3574.  
  3575.  
  3576.  Creating a Text Viewport
  3577.  
  3578.  So far, the entire screen has been used for text output. However, with the
  3579.  VIEW PRINT statement, you can restrict printed output to a "text viewport,"
  3580.  a horizontal slice of the screen. The syntax of the  VIEW PRINT statement
  3581.  is:
  3582.  
  3583.   VIEW PRINT  [topline% TO bottomline%]
  3584.  
  3585.  The values for  topline% and bottomline% specify the
  3586.  locations where the viewport will begin and end, respectively.
  3587.  
  3588.  
  3589.  A text viewport also gives you control over on-screen scrolling. Without
  3590.  a viewport, when printed output reaches the bottom of the screen, text
  3591.  or graphics output that was at the top of the screen scrolls off and
  3592.  is lost. However, after a  VIEW PRINT statement, scrolling
  3593.  takes place only between the top and bottom lines of the viewport. This
  3594.  means you can label the displayed output at the top and/or bottom of
  3595.  the screen without having to worry that the labeling will scroll it
  3596.  off if too many lines of data appear. You can also use CLS 2 to
  3597.  clear just the text viewport, leaving the contents of the rest of
  3598.  the screen intact. See the section "Defining a Graphics Viewport" in
  3599.  Chapter 5, "Graphics," to learn how to create a viewport for graphics
  3600.  output on the screen.
  3601.  
  3602.  
  3603.  Example
  3604.  
  3605.  You can see the effects of a  VIEW PRINT statement by examining the output
  3606.  from the next example:
  3607.  
  3608.   CLS
  3609.   LOCATE 3, 1
  3610.   PRINT "This is above the text viewport; it doesn't scroll."
  3611.  
  3612.   LOCATE 4, 1
  3613.   PRINT STRING$(60, "_")       ' Print horizontal lines above
  3614.   LOCATE 11, 1
  3615.   PRINT STRING$(60, "_")       ' and below the text viewport.
  3616.  
  3617.   PRINT "This is below the text viewport."
  3618.  
  3619.   VIEW PRINT 5 TO 10           ' Text viewport extends from
  3620.   ' lines 5 to 10.
  3621.  
  3622.   FOR I = 1 TO 20              ' Print numbers and text in
  3623.      PRINT I; "a line of text" ' the viewport.
  3624.   NEXT
  3625.  
  3626.   DO: LOOP WHILE INKEY$ = ""   ' Wait for a key press.
  3627.   CLS 2                        ' Clear just the viewport.
  3628.   END
  3629.  
  3630.  
  3631.  Getting Input from the Keyboard
  3632.  
  3633.  This section shows you how to use the following statements and functions to
  3634.  enable your BASIC programs to accept input entered from the keyboard:
  3635.  
  3636.    ■    INPUT
  3637.    ■    LINE INPUT
  3638.    ■    INPUT$
  3639.    ■    INKEY$
  3640.  
  3641.  
  3642.  Note
  3643.  
  3644.  Input typed at the keyboard is often referred to as "standard input." You
  3645.  can use the DOS redirection symbol (<) to direct standard input to your
  3646.  program from a file or other input device instead of from the keyboard. (See
  3647.  your operating system documentation for more information on redirecting
  3648.  input.)
  3649.  
  3650.  
  3651.  The INPUT Statement
  3652.  
  3653.  The  INPUT statement takes information typed by the user and stores it in a
  3654.  list of variables, as shown in the following example:
  3655.  
  3656.   INPUT A%, B, C$
  3657.   INPUT D$
  3658.   PRINT A%, B, C$, D$
  3659.  
  3660.  Output
  3661.  
  3662.   6.6,45,a string ?
  3663.   "two, three"
  3664.    7             45           a string      two, three
  3665.  
  3666.  Here are some general comments about  INPUT:
  3667.  
  3668.    ■   An  INPUT statement by itself prompts the user with a question mark
  3669.        (?) followed by a blinking cursor.
  3670.  
  3671.    ■   The  INPUT statement is followed by one or more variable names. When
  3672.        there are two or more variables, they are separated by commas.
  3673.  
  3674.    ■   The number of constants entered by the user after the  INPUT prompt
  3675.        must be the same as the number of variables in the  INPUT statement
  3676.        itself.
  3677.  
  3678.    ■   The values the user enters must agree in type with the variables in
  3679.        the list following  INPUT. In other words, enter a number if the
  3680.        variable is designated as having the type integer, long integer,
  3681.        single precision, or double precision. Enter a string if the variable
  3682.        is designated as having the type string.
  3683.  
  3684.    ■   Since constants in an input list must be separated by commas, an input
  3685.        string constant containing one or more commas should be enclosed in
  3686.        double quotation marks. The double quotation marks ensure that the
  3687.        string is treated as a unit and not broken into two or more parts.
  3688.  
  3689.  If the user breaks any of the last three rules, BASIC prints the error
  3690.  message Redo from start. This message reappears until the input agrees in
  3691.  number and type with the variable list.
  3692.  
  3693.  If you want your input prompt to be more informative than a simple question
  3694.  mark, you can make a prompt appear, as in the following example:
  3695.  
  3696.  INPUT "What is the correct time (hour, min)"; Hr$, Min$
  3697.  
  3698.  This prints the following prompt:
  3699.  What is the correct time (hour, min)?
  3700.  
  3701.  Note the semicolon between the prompt and the input variables. This
  3702.  semicolon causes a question mark to appear as part of the prompt. Sometimes
  3703.  you may want to eliminate the question mark altogether; in this case, put a
  3704.  comma between the prompt and the variable list:
  3705.  INPUT "Enter the time (hour, min): ", Hr$, Min$
  3706.  
  3707.  This prints the following prompt:
  3708.  Enter the time (hour, min):
  3709.  
  3710.  The LINE INPUT Statement
  3711.  
  3712.  If you want your program to accept lines of text with embedded commas,
  3713.  leading blanks, or trailing blanks, but you do not want to have to remind
  3714.  the user to enclose the input in double quotation marks, use the  LINE INPUT
  3715.  statement. The  LINE INPUT statement, as its name implies, accepts a line of
  3716.  input (terminated by pressing Enter) from the keyboard and stores it in a
  3717.  single string variable. Unlike  INPUT, the  LINE INPUT statement does not
  3718.  print a question mark by default to prompt for input; it does, however,
  3719.  allow you to display a prompt string.
  3720.  
  3721.  Example
  3722.  
  3723.  The following example shows the difference between  INPUT and  LINE INPUT:
  3724.  
  3725.   ' Assign the input to three separate variables:
  3726.   INPUT "Enter three values separated by commas: ", A$, B$, C$
  3727.  
  3728.   ' Assign the input to one variable (commas not treated
  3729.   ' as delimiters between input):
  3730.   LINE INPUT "Enter the same three values: ", D$
  3731.   PRINT "A$ = "; A$
  3732.   PRINT "B$ = "; B$
  3733.   PRINT "C$ = "; C$
  3734.   PRINT "D$ = "; D$
  3735.  
  3736.  Output
  3737.  
  3738.   Enter 3 values separated by commas:
  3739.    by land, air, and sea Enter the same three values:
  3740.    by land, air, and sea A$ = by land
  3741.  
  3742.   B$ = air
  3743.   C$ = and sea
  3744.   D$ = by land, air, and sea
  3745.  
  3746.  With  INPUT and  LINE INPUT, input is terminated when the user presses
  3747.  Enter, which also advances the cursor to the next line. As the next example
  3748.  shows, a semicolon between the  INPUT keyword and the prompt string keeps
  3749.  the cursor on the same line:
  3750.  
  3751.    INPUT "First value: ", A
  3752.   INPUT; "Second value: ", B
  3753.   INPUT "    Third value: ", C
  3754.  
  3755.  The following shows some sample input to the preceding program and the
  3756.  positions of the prompts:
  3757.  
  3758.  First value:
  3759.   5 Second value:
  3760.   4     Third value:  3
  3761.  The INPUT$ Function
  3762.  
  3763.   INPUT and  LINE INPUT wait for the user to press Enter before they store
  3764.  what is typed; that is, they read a line of input, then assign it to
  3765.  program variables. In contrast, the  INPUT$( number )
  3766.  function doesn't wait for Enter to be pressed; it just reads a specified
  3767.  number of characters. For example, the following line in a program reads
  3768.  three characters typed by the user, then stores the three-character
  3769.  string in the variable Test$:
  3770.  
  3771.  Test$ = INPUT$(3)
  3772.  
  3773.  Unlike the  INPUT statement, the  INPUT$ function does not prompt the user
  3774.  for data, nor does it echo input characters on the screen. Also, since
  3775.  INPUT$ is a function, it cannot stand by itself as a complete statement.
  3776.  INPUT$ must appear in an expression, as in the following:
  3777.  
  3778.  INPUT X              ' INPUT is a statement.
  3779.  
  3780.   PRINT INPUT$(1)      ' INPUT$ is a function, so it must
  3781.   Y$ = INPUT$(1)       ' appear in an expression.
  3782.  
  3783.  The  INPUT$ function reads input from the keyboard as an unformatted stream
  3784.  of characters. Unlike  INPUT or  LINE INPUT,  INPUT$ accepts any key
  3785.  pressed, including control keys like Esc or Backspace. For example, pressing
  3786.  Enter five times assigns five carriage-return characters to the Test$
  3787.  variable in the next line:
  3788.  
  3789.  Test$ = INPUT$(5)
  3790.  
  3791.  The INKEY$ Function
  3792.  
  3793.  The  INKEY$ function completes the list of BASIC's keyboard-input functions
  3794.  and statements. When BASIC encounters an expression containing the  INKEY$
  3795.  function, it checks to see if the user has pressed a key since one of the
  3796.  following:
  3797.  
  3798.    ■   The last time it found an expression with  INKEY$
  3799.  
  3800.    ■   The beginning of the program, if this is the first time  INKEY$
  3801.        appears
  3802.  
  3803.  If no key has been pressed since the last time the program checked,  INKEY$
  3804.  returns a null string (""). If a key has been pressed,  INKEY$ returns the
  3805.  character corresponding to that key.
  3806.  
  3807.  Example
  3808.  
  3809.  The most important difference between  INKEY$ and the other statements and
  3810.  functions discussed in this section is that  INKEY$ lets your program
  3811.  continue doing other things while it checks for input. In contrast,  LINE
  3812.  INPUT,  INPUT$, and  INPUT suspend program execution until there is input,
  3813.  as shown in this example:
  3814.  
  3815.   PRINT "Press any key to start. Press any key to end."
  3816.  
  3817.   ' Don't do anything else until the user presses a key:
  3818.   Begin$ = INPUT$(1)
  3819.  
  3820.   I& = 1
  3821.  
  3822.   ' Print the numbers from one to one million.
  3823.   ' Check for a key press while the loop is executing:
  3824.   DO
  3825.      PRINT I&
  3826.      I& = I& + 1
  3827.  
  3828.   ' Continue looping until the value of the variable I& is
  3829.   ' greater than one million or until a key is pressed:
  3830.   LOOP UNTIL I& > 1000000 OR INKEY$ <> ""
  3831.  
  3832.  Controlling the Text Cursor
  3833.  
  3834.  When you display printed text on the screen, the text cursor marks the place
  3835.  on the screen where output from the program--or input typed by the
  3836.  user--will appear next. In the following example, after the  INPUT statement
  3837.  displays its 12-character prompt, "First name: ", the cursor waits for input
  3838.  in row 1 at column 13:
  3839.  
  3840.  ' Clear the screen; start printing in row one, column one:
  3841.   CLS
  3842.   INPUT "First name: ", FirstName$
  3843.  
  3844.  In the next example, the semicolon at the end of the second  PRINT
  3845.  statement leaves the cursor in row 2 at column 27:
  3846.  
  3847.   CLS
  3848.   PRINT
  3849.  
  3850.   ' Twenty-six characters are in the next line:
  3851.   PRINT "Press any key to continue.";
  3852.   PRINT INPUT$(1)
  3853.  The following sections show how to control the location of the text cursor,
  3854.  change its shape, and get information about its location.
  3855.  
  3856.  Positioning the Cursor
  3857.  
  3858.  The input and output statements and functions discussed so far do not allow
  3859.  much control over where output is displayed or where the cursor is located
  3860.  after the output is displayed. Input prompts or output always start in the
  3861.  far left column of the screen and descend one row at a time from top to
  3862.  bottom unless a semicolon is used in the  PRINT or  INPUT statements to
  3863.  suppress the carriage-return-and-line-feed sequence.
  3864.  
  3865.  The  SPC and  TAB statements, discussed in the section "Skipping Spaces and
  3866.  Advancing to a Specific Column" later in this chapter give some control over
  3867.  the location of the cursor by allowing you to move it to any column within a
  3868.  given row.
  3869.  
  3870.  The  LOCATE statement extends this control one step further. The syntax for
  3871.  LOCATE is:
  3872.  
  3873.   LOCATE [row%],[ column%],[ cursor%],[ start%],[ stop%]
  3874.  
  3875.  
  3876.  Example
  3877.  
  3878.  Using the  LOCATE statement allows you to position the cursor in any row or
  3879.  column on the screen, as shown by the output in the next example:
  3880.  
  3881.   CLS
  3882.   FOR Row = 9 TO 1 STEP -2
  3883.      Column = 2 * Row
  3884.      LOCATE Row, Column
  3885.      PRINT "12345678901234567890";
  3886.   NEXT
  3887.  
  3888.  Output
  3889.  
  3890.   12345678901234567890
  3891.  
  3892.       12345678901234567890
  3893.  
  3894.           12345678901234567890
  3895.  
  3896.               12345678901234567890
  3897.  
  3898.                   12345678901234567890
  3899.  
  3900.  
  3901.  Changing the Cursor's Shape
  3902.  
  3903.  The optional  cursor%,  start%, and  stop% arguments shown in the syntax for
  3904.  the  LOCATE statement also allow you to change the shape of the cursor and
  3905.  make it visible or invisible. A value of 1 for  cursor% makes the cursor
  3906.  visible, while a value of 0 makes the cursor invisible. The  start% and
  3907.  stop% arguments control the height of the cursor, if it is on, by specifying
  3908.  the top and bottom "pixel" lines, respectively, for the cursor. (Any
  3909.  character on the screen is composed of lines of pixels, which are dots of
  3910.  light on the screen.) If a cursor spans the height of one row of text, then
  3911.  the line of pixels at the top of the cursor has the value 0, while the line
  3912.  of pixels at the bottom has a value of 7 or 13, depending whether your
  3913.  display adapter is monochrome (13) or color (7).
  3914.  
  3915.  You can turn the cursor on and change its shape without specifying a new
  3916.  location for it. For example, the following statement keeps the cursor
  3917.  wherever it is at the completion of the next  PRINT or  INPUT statement,
  3918.  then makes it half a character high:
  3919.  
  3920.  LOCATE , , 1, 2, 5  ' Row and column arguments both optional.
  3921.  
  3922.  The following examples show different cursor shapes produced using different
  3923.   start and  stop values on a color display. Each  LOCATE statement shown in
  3924.  the left column is followed by the statement:
  3925.  
  3926.  INPUT "PROMPT:", X$
  3927.  
  3928.  In the preceding examples, note that making the  start% argument bigger than
  3929.  the  stop% argument results in a two-piece cursor.
  3930.  
  3931.  
  3932.  Getting Information About the Cursor's Location
  3933.  
  3934.  You can think of the functions  CSRLIN and  POS( numeric-expression ) as the
  3935.  complements of the  LOCATE statement: whereas  LOCATE tells the cursor where
  3936.  to go,  CSRLIN and  POS( numeric-expression ) tell your program where the
  3937.  cursor is. The  CSRLIN function returns the current row and the  POS(
  3938.  numeric-expression ) function returns the current column of the cursor's
  3939.  position.
  3940.  
  3941.  The argument  n for  POS( numeric-expression ) is what is known as a "dummy"
  3942.  argument; that is,  numeric-expression is a placeholder that can be any
  3943.  numeric expression. For example, POS(0) and POS(1) return the same value.
  3944.  
  3945.  
  3946.  Example
  3947.  
  3948.  The following example uses the  POS( numeric-expression ) function to print
  3949.  50 asterisks in rows of 13 asterisks:
  3950.  
  3951.   FOR I% = 1 TO 50
  3952.      PRINT "*";' Print an asterisk and keep
  3953.      ' the cursor on the same line.
  3954.      IF POS(1) > 13 THEN PRINT ' If the cursor's position
  3955.                                ' is past column 13, advance
  3956.   ' to the next line.
  3957.   NEXT
  3958.  
  3959.  Output
  3960.   *************
  3961.   *************
  3962.   *************
  3963.   ***********
  3964.  
  3965.  Working with Data Files
  3966.  
  3967.  Data files are physical locations on your disk where information is
  3968.  permanently stored. The following tasks are greatly simplified by using data
  3969.  files in your BASIC programs:
  3970.  
  3971.    ■   Creating, manipulating, and storing large amounts of data
  3972.  
  3973.    ■   Accessing several sets of data with one program
  3974.  
  3975.    ■   Using the same set of data in several different programs
  3976.  
  3977.  
  3978.  The sections that follow introduce the concepts of records and fields and
  3979.  contrast different ways to access data files from BASIC. When you have
  3980.  completed those sections, you should know how to do the following:
  3981.  
  3982.    ■   Create new data files
  3983.  
  3984.    ■   Open existing files and read their contents
  3985.  
  3986.    ■   Add new information to an existing data file
  3987.  
  3988.    ■   Change the contents of an existing data file
  3989.  
  3990.  
  3991.  
  3992.  How Data Files Are Organized
  3993.  
  3994.  A data file is a collection of related blocks of information, or "records."
  3995.  Each record in a data file is further subdivided into "fields" or regularly
  3996.  recurring items of information within each record. If you compare a data
  3997.  file with a more old-fashioned way of storing information--for example, a
  3998.  folder containing application forms filled out by job applicants at a
  3999.  particular company--then a record is analogous to one application form in
  4000.  that folder. To carry the comparison one step further, a field is analogous
  4001.  to an item of information included on every application form, such as a
  4002.  Social Security number.
  4003.  
  4004.  Note
  4005.  
  4006.  If you do not want to access a file using records but instead want to treat
  4007.  it as an unformatted sequence of bytes, then read the section "Binary File
  4008.  I/O" later in this chapter.
  4009.  
  4010.  
  4011.  Sequential and Random-Access Files
  4012.  
  4013.  The terms "sequential file" and "random-access file" refer to two different
  4014.  ways to store and access data on disk from your BASIC programs. A simplified
  4015.  way to think of these two kinds of files is with the following analogy: a
  4016.  sequential file is like a cassette tape, while a random-access file is like
  4017.  an LP record. To find a song on a cassette tape, you have to start at the
  4018.  beginning and fast-forward through the tape sequentially until you find the
  4019.  song you are looking for--there is no way to jump right to the song you
  4020.  want. This is similar to the way you have to find information in a
  4021.  sequential file: to get to the 500th record, you first have to read records
  4022.  1 through 499.
  4023.  
  4024.  In contrast, if you want to play a certain song on an LP, all you have to do
  4025.  is lift the tone arm of the record player and put the needle down right on
  4026.  the song: you can randomly access anything on the LP without having to play
  4027.  all the songs before the one you want. Similarly, you can call up any record
  4028.  in a random-access file just by specifying its number, greatly reducing
  4029.  access time.
  4030.  
  4031.  
  4032.  Note
  4033.  
  4034.  Although there is no way to jump directly to a specific  record in a
  4035.  sequential file, the  SEEK statement lets you jump directly to a specific
  4036.  byte in the file. See the section "Binary File I/O" later in this chapter
  4037.  for more information on how to do this.
  4038.  
  4039.  
  4040.  Opening a Data File
  4041.  
  4042.  Before your program can read, modify, or add to a data file, it must first
  4043.  open the file. BASIC does this with the  OPEN statement. The  OPEN statement
  4044.  can be used to create a new file. The following list describes the various
  4045.  uses of the  OPEN statement:
  4046.  
  4047.    ■   Create a new data file and open it so records can be added to it. For
  4048.        example:
  4049.  
  4050.  
  4051.  ' No file named PRICE.DAT is in the current directory:
  4052.  OPEN "PRICE.DAT" FOR OUTPUT AS #1
  4053.  
  4054.    ■   Open an existing data file so new records overwrite any data already
  4055.        in the file. For example:
  4056.  
  4057.   ' A file named PRICE.DAT is already in the current
  4058.   ' directory; new records can be written to it, but all
  4059.   ' old records are lost:
  4060.   OPEN "PRICE.DAT" FOR OUTPUT AS #1
  4061.  
  4062.    ■   Open an existing data file so new records are added to the end of the
  4063.        file, preserving data already in the file. For example:
  4064.  
  4065.  OPEN "PRICE.DAT" FOR APPEND AS #1
  4066.  
  4067.  The  APPEND mode will also create a new file if a file
  4068.  with the given name does not already appear in the current directory.
  4069.  
  4070.    ■   Open an existing data file so old records can be read from it. For
  4071.        example:
  4072.  
  4073.  OPEN "PRICE.DAT" FOR INPUT AS #1
  4074.  
  4075.  See the section "Using Sequential Files" for more information about the  INPU
  4076.   OUTPUT, and  APPEND modes.
  4077.  
  4078.    ■   Open an existing data file (or create a new one if a file with
  4079.        that name doesn't exist), then read or write fixed-length records
  4080.        to and from the file. For example:
  4081.  
  4082.  OPEN "PRICE.DAT" FOR RANDOM AS #1
  4083.  
  4084.  See the section "Using Random-Access Files" for more information about
  4085.  this mode.
  4086.  
  4087.    ■   Open an existing data file (or create a new one if a file with
  4088.        that name doesn't exist), then read data from the file or add new
  4089.        data to the file, starting at any byte position in the file. For
  4090.        example:
  4091.  
  4092.  OPEN "PRICE.DAT" FOR BINARY AS #1
  4093.  
  4094.  See the section "Binary File I/O" for more information about this mode.
  4095.  
  4096.  
  4097.  File Numbers in BASIC
  4098.  
  4099.  The  OPEN statement does more than just specify a mode for data I/O for a
  4100.  particular file ( OUTPUT,  INPUT,  APPEND,  RANDOM, or  BINARY); it also
  4101.  associates a unique file number with that file. This file number, which can
  4102.  be any integer from 1 to 255, is then used by subsequent file I/O statements
  4103.  in the program as a shorthand way to refer to the file. As long as the file
  4104.  is open, this number remains associated with the file. When the file is
  4105.  closed, the file number is freed for use with another file. Your BASIC
  4106.  programs can open more than one file at a time.
  4107.  
  4108.  The  FREEFILE function can help you find an unused file number. This
  4109.  function returns the next available number that can be associated with a
  4110.  file in an  OPEN statement. For example,  FREEFILE might return the value 3
  4111.  after the following  OPEN statements:
  4112.  
  4113.   OPEN "Test1.Dat" FOR RANDOM AS #1
  4114.   OPEN "Test2.Dat" FOR RANDOM AS #2
  4115.   FileNum = FREEFILE
  4116.   OPEN "Test3.Dat" FOR RANDOM AS #FileNum
  4117.  The  FREEFILE function
  4118.  is particularly useful when you create your own library procedures that open
  4119.  files. With  FREEFILE, you don't have to pass information about the number
  4120.  of open files to these procedures.
  4121.  
  4122.  
  4123.  Filenames in BASIC
  4124.  
  4125.  Filenames in  OPEN statements can be any string expression, composed of any
  4126.  combination of the following characters:
  4127.  
  4128.    ■   The letters a-z and A-Z
  4129.  
  4130.    ■   The numbers 0-9
  4131.  
  4132.    ■   The following special characters:
  4133.  
  4134.        (  )  @  #  $  %  ^  &  !  -  _  '  ~
  4135.  
  4136.  
  4137.  The string expression can also contain an optional drive, as well as a
  4138.  complete or partial path specification. This means your BASIC program can
  4139.  work with data files on another drive or in a directory other than the one
  4140.  where the program is running. For example, the following  OPEN statements
  4141.  are all valid:
  4142.  
  4143.   OPEN "..\Grades.Qtr" FOR INPUT AS #1
  4144.  
  4145.   OPEN "A:\SALARIES\1990.MAN" FOR INPUT AS #2
  4146.  
  4147.   FileName$ = "TempFile"
  4148.   OPEN FileName$ FOR OUTPUT AS #3
  4149.  
  4150.   BaseName$ = "Invent"
  4151.   OPEN BaseName$ + ".DAT" FOR OUTPUT AS #4
  4152.  
  4153.  DOS also imposes its own restrictions on filenames: you can use no more than
  4154.  eight characters for the filename (everything to the left of an optional
  4155.  period) and no more than three characters for the extension (everything to
  4156.  the right of an optional period).
  4157.  
  4158.  
  4159.  Long filenames in BASIC
  4160.  programs are truncated in the following fashion:
  4161.  
  4162.  
  4163. ╓┌──────────────────┌───────────────────────────┌────────────────────────────╖
  4164.  ────────────────────────────────────────────────────────────────────────────
  4165.  Prog@Data@File     PROG@DAT.A@F                The BASIC name is more than
  4166.                                                 11 characters long, so
  4167.                                                 BASIC takes the first eight
  4168.                                                 characters for the base
  4169.                                                 name, inserts a period (.),
  4170.                                                 and uses the next three
  4171.                                                 characters as the extension.
  4172.                                                 Everything else is
  4173.                                                 discarded.
  4174.  
  4175.  Mail#.Version1     MAIL#.VER                   The filename (Mail#) is
  4176.                                                 shorter than eight
  4177.                                                 characters, but the
  4178.                                                 extension (Version1) is
  4179.                                                 longer than three, so the
  4180.                                                 extension is shortened to
  4181.  ────────────────────────────────────────────────────────────────────────────
  4182.                                                extension is shortened to
  4183.                                                 three characters.
  4184.  
  4185.  RELEASE_NoteS.BAK  Gives the run-time error
  4186.                     message Bad file name. The
  4187.                     base name must be shorter
  4188.                     than eight characters if
  4189.                     you are going to include
  4190.                     an explicit extension
  4191.                     (.BAK in this case).
  4192.  
  4193.  
  4194.  
  4195.  
  4196.  
  4197.  DOS is not case sensitive, so lowercase letters in filenames are converted
  4198.  to all uppercase (capital) letters. Therefore, you should not rely on the
  4199.  mixing of lowercase and uppercase to distinguish between files. For example,
  4200.  if you already had a file on the disk named INVESTOR.DAT, the following
  4201.  OPEN statement would overwrite that file, destroying any information already
  4202.  stored in it:
  4203.  
  4204.  OPEN "Investor.Dat" FOR OUTPUT AS #1
  4205.  
  4206.  Closing a Data File
  4207.  
  4208.  Closing a data file has two important results: first, it writes any data
  4209.  currently in the file's buffer (a temporary holding area in memory) to the
  4210.  file; second, it frees the file number associated with that file for use by
  4211.  another  OPEN statement.
  4212.  
  4213.  Use the  CLOSE statement following a program to close a file. For example,
  4214.  consider a file  PRICE.DAT that is opened with this statement:
  4215.  
  4216.  OPEN "PRICE.DAT" FOR OUTPUT AS #1
  4217.  
  4218.  The statement CLOSE #1 then ends output to PRICE.DAT. Next, PRICE.DAT is
  4219.  opened with the following:
  4220.  
  4221.  OPEN "PRICE.DAT" FOR OUTPUT AS #2
  4222.  
  4223.  Then the appropriate statement for ending output is CLOSE #2. A  CLOSE
  4224.  statement with no file number arguments closes all open files.
  4225.  
  4226.  A data file is also
  4227.  closed when either of the following occurs:
  4228.  
  4229.  
  4230.    ■   The BASIC program performing I/O ends. (Program termination always
  4231.        closes all open data files.)
  4232.  
  4233.    ■   The program performing I/O transfers control to another program with
  4234.        the  RUN statement (or with the  CHAIN statements if compiled with the
  4235.        /O option).
  4236.  
  4237.  
  4238.  Using Sequential Files
  4239.  
  4240.  This section discusses how records are organized in sequential data files
  4241.  and then shows how to read data from, or write data to, an open sequential
  4242.  file.
  4243.  
  4244.  
  4245.  Records in Sequential Files
  4246.  
  4247.  Sequential files are ASCII (text) files. This means you can use any word
  4248.  processor to view or modify a sequential file. Records are stored in
  4249.  sequential files as a single line of text, terminated by a
  4250.  carriage-return-and-line-feed (CR-LF) sequence. Each record is divided into
  4251.  fields, or repeated chunks of data that occur in the same order in every
  4252.  record. Figure 3.2 shows how three records might appear in a sequential
  4253.  file.
  4254.  
  4255.  Note that each record in a sequential file can be a different
  4256.  length; moreover, fields can be different lengths in different records.
  4257.  
  4258.  
  4259.  The kind of variable in which a field is stored determines where that
  4260.  field begins and ends. (See the following sections for examples of
  4261.  reading and storing fields from records.) For example, if your program
  4262.  reads a field into a string variable, then any of the following can
  4263.  signal the end of that field:
  4264.  
  4265.    ■   Double quotation marks (") if the string begins with double quotation
  4266.        marks
  4267.  
  4268.    ■   Comma (,) if the string does not begin with double quotation marks
  4269.  
  4270.    ■   CR-LF if the field is at the end of the record
  4271.  
  4272.  
  4273.  On the other hand, if your program reads a field into a numeric variable,
  4274.  then any of the following can signal the end of that field:
  4275.  
  4276.    ■   Comma
  4277.  
  4278.    ■   One or more spaces
  4279.  
  4280.    ■   CR-LF
  4281.  
  4282.  
  4283.  Putting Data in a New Sequential File
  4284.  
  4285.  You can add data to a new sequential file after first opening it to receive
  4286.  records with an  OPEN  filename  FOR OUTPUT statement. Use the  WRITE #
  4287.  statement to write records to the file.
  4288.  
  4289.  You can open sequential files for reading or for writing but not for both at
  4290.  the same time. If you are writing to a sequential file and want to read back
  4291.  the data you stored, you must first close the file, then reopen it for
  4292.  input.
  4293.  
  4294.  
  4295.  Example
  4296.  
  4297.  The following short program creates a sequential file named PRICE.DAT, then
  4298.  adds data entered at the keyboard to the file. The  OPEN statement in this
  4299.  program creates the file and readies it to receive records. The  WRITE #
  4300.  statement then writes each record to the file. Note that the number used in
  4301.  the  WRITE # statement is the same number given to the filename PRICE.DAT in
  4302.  the  OPEN statement.
  4303.  
  4304.   ' Create a file named Price.Dat
  4305.   ' and open it to receive new data:
  4306.  
  4307.   OPEN "Price.Dat" FOR OUTPUT AS #1
  4308.  
  4309.   DO
  4310.      ' Continue putting new records in Price.Dat until the
  4311.      ' user presses Enter without entering a company name:
  4312.      INPUT "Company (press <ENTER> to quit): ", Company$
  4313.  
  4314.  
  4315.  IF Company$ <> "" THEN
  4316.  
  4317.  
  4318.         ' Enter the other fields of the record:
  4319.         INPUT "Style: ", Style$
  4320.         INPUT "Size: ", Size$
  4321.         INPUT "Color: ", Clr$
  4322.         INPUT "Quantity: ", Qty
  4323.  
  4324.         ' Put the new record in the file
  4325.         ' with the WRITE # statement:
  4326.         WRITE #1, Company$, Style$, Size$, Clr$, Qty
  4327.      END IF
  4328.   LOOP UNTIL Company$ = ""
  4329.  
  4330.   ' Close Price.Dat (this ends output to the file):
  4331.   CLOSE #1
  4332.   END
  4333.  
  4334.  
  4335.  Warning
  4336.  
  4337.  If, in the case of the preceding example, you already had a file named
  4338.  PRICE.DAT on the disk, the  OUTPUT mode given in the  OPEN statement would
  4339.  erase the existing contents of  PRICE.DAT before writing any new data to it.
  4340.  If you want to add new data to the end of an existing file without erasing
  4341.  what is already in it, use the  APPEND mode of  OPEN. See the section
  4342.  "Adding Data to a Sequential File" later in this chapter for more
  4343.  information on this mode.
  4344.  
  4345.  
  4346.  Reading Data from a Sequential File
  4347.  
  4348.  You can read data from a sequential file after first opening it with the
  4349.  statement  OPEN  filename  FOR INPUT. Use the  INPUT# statement to read
  4350.  records from the file one field at a time. (See the section "Other Ways to
  4351.  Read Data from a Sequential File" later in this chapter for information on
  4352.  other file-input statements and functions you can use with a sequential
  4353.  file.)
  4354.  
  4355.  
  4356.  Example
  4357.  
  4358.  The following program opens the PRICE.DAT data file created in the previous
  4359.  example and reads the records from the file, displaying the complete record
  4360.  on the screen if the quantity for the item is less than the input amount.
  4361.  
  4362.  The INPUT #1 statement reads one record at a time from PRICE.DAT, assigning
  4363.  the fields in the record to the variables Company$, Style$, Size$, Clr$, and
  4364.  Qty. Since this is a sequential file, the records are read in order from the
  4365.  first to the last entered.
  4366.  
  4367.  The  EOF (end-of-file) function tests whether the last record has been read
  4368.  by  INPUT#. If the last record has been read,  EOF returns the value -1
  4369.  (true), and the loop for getting data ends; if the last record has not been
  4370.  read,  EOF returns the value 0 (false), and the next record is read from the
  4371.  file.
  4372.  
  4373.   OPEN "PRICE.DAT" FOR INPUT AS #1
  4374.  
  4375.  
  4376.   INPUT "Display all items below what level"; Reorder
  4377.  
  4378.   DO UNTIL EOF(1)
  4379.      INPUT #1, Company$, Style$, Size$, Clr$, Qty
  4380.      IF Qty < Reorder THEN
  4381.         PRINT  Company$, Style$, Size$, Clr$, Qty
  4382.      END IF
  4383.   LOOP
  4384.   CLOSE #1
  4385.   END
  4386.  
  4387.  Adding Data to a Sequential File
  4388.  
  4389.  As mentioned earlier, if you have a sequential file on disk and want to add
  4390.  more data to the end of it, you cannot simply open the file in output mode
  4391.  and start writing data. As soon as you open a sequential file in output
  4392.  mode, you destroy its current contents. You must use the  APPEND mode
  4393.  instead, as shown in the next example:
  4394.  
  4395.  OPEN "PRICE.DAT" FOR APPEND AS #1
  4396.  
  4397.   APPEND is always a safe alternative to  OUTPUT, since  APPEND creates a new
  4398.  file if one with the name specified doesn't already exist. For example, if a
  4399.  file named PRICE.DAT did not reside on disk, the example statement would
  4400.  make a new file with that name.
  4401.  
  4402.  
  4403.  Other Ways to Write Data to a Sequential File
  4404.  
  4405.  The preceding examples all use the  WRITE # statement to write records to a
  4406.  sequential file. There is, however, another statement you can use to write
  4407.  sequential file records:  PRINT #.
  4408.  
  4409.  The best way to show the difference between these two data-storage
  4410.  statements is to examine the contents of a file created with both. The
  4411.  following short fragment opens a file named TEST.DAT, then places the same
  4412.  record in it twice, once with  WRITE # and once with  PRINT #. After running
  4413.  this program you can examine the contents of TEST.DAT with the DOS TYPE
  4414.  command.
  4415.  
  4416.   OPEN "TEST.DAT" FOR OUTPUT AS #1
  4417.   Nm$ = "Penn, Will"
  4418.   Dept$ = "User Education"
  4419.   Level = 4
  4420.   Age = 25
  4421.   WRITE #1, Nm$, Dept$, Level, Age
  4422.   PRINT #1, Nm$, Dept$, Level, Age
  4423.   CLOSE #1
  4424.  
  4425.  Output
  4426.  
  4427.   "Penn, Will","User Education",4,25
  4428.   Penn, Will    User Education               4             25
  4429.  
  4430.  The record stored with WRITE # has commas that explicitly
  4431.  separate each field of the record, as well as double quotation marks
  4432.  enclosing each string expression. On the other hand,  PRINT # has
  4433.  written an image of the record to the file exactly as it would appear on
  4434.  screen with a simple  PRINT statement. The commas in the  PRINT #
  4435.  statement are interpreted as meaning "advance to the next print zone"
  4436.  (a new print zone occurs every 14 spaces, starting at the beginning of
  4437.  a line), and quotation marks are not placed around the string expressions.
  4438.  
  4439.  
  4440.  At this point, you may be wondering what difference these output statements
  4441.  make, except in the appearance of the data within the file. The answer lies
  4442.  in what happens when your program reads the data back from the file with an
  4443.  INPUT # statement. In the following example, the program reads the record
  4444.  stored with  WRITE # and prints the values of its fields without any
  4445.  problem:
  4446.  
  4447.   OPEN "TEST.DAT" FOR INPUT AS #1
  4448.  
  4449.   ' Input the first record,
  4450.   ' and display the contents of each field:
  4451.   INPUT #1, Nm$, Dept$, Level, Age
  4452.   PRINT Nm$, Dept$, Level, Age
  4453.  
  4454.  ' Input the second record,
  4455.   ' and display the contents of each field:
  4456.   INPUT #1, Nm$, Dept$, Level, Age
  4457.   PRINT Nm$, Dept$, Level, Age
  4458.  
  4459.   CLOSE #1
  4460.  
  4461.  
  4462.  Output
  4463.  
  4464.  Penn, Will    User Education               4             25
  4465.  
  4466.  However, when the program tries to input the next record stored with  PRINT
  4467.  #, the attempt produces the error message Input past end of file. Without
  4468.  double quotation marks enclosing the first field, the  INPUT # statement
  4469.  sees the comma between Penn and Will as a field delimiter, so it assigns
  4470.  only the last name Penn to the variable Nm$.  INPUT # then reads the rest of
  4471.  the line into the variable Dept$. Since all of the record has now been read,
  4472.  there is nothing left to put in the variables Level and Age. The result is
  4473.  the error message Input past end of file.
  4474.  
  4475.  If you are storing records that have string expressions and you want to read
  4476.  these records later with the  INPUT # statement, follow one of these two
  4477.  rules of thumb:
  4478.  
  4479.    ■   Use the  WRITE # statement to store the records.
  4480.  
  4481.    ■   If you want to use the  PRINT # statement, remember it does not put
  4482.        commas in the record to separate fields, nor does it put double
  4483.        quotation marks around strings. You have to put these field separators
  4484.        in the  PRINT # statement yourself.
  4485.  
  4486.  
  4487.  Example
  4488.  
  4489.  For example, you can avoid the problems shown in the preceding example by
  4490.  using  PRINT # with double quotation marks surrounding each field containing
  4491.  a string expression, as in the following example:
  4492.  
  4493.   ' 34 is ASCII value for double-quotation-mark character:
  4494.  
  4495.   Q$ = CHR$(34)
  4496.  
  4497.   ' The next four statements all write the record to the
  4498.   ' file with double quotation marks around each string field:
  4499.  
  4500.   PRINT #1, Q$ Nm$ Q$ Q$ Dept$ Q$ Level Age
  4501.   PRINT #1, Q$ Nm$ Q$;Q$ Dept$ Q$;Level;Age
  4502.   PRINT #1, Q$ Nm$ Q$,Q$ Dept$ Q$,Level,Age
  4503.   WRITE #1, Nm$, Dept$, Level, Age
  4504.  
  4505.  Output to File
  4506.  
  4507.   "Penn, Will""User Education" 4  25
  4508.   "Penn, Will""User Education" 4  25
  4509.   "Penn, Will"  "User Education"      4             25
  4510.   "Penn, Will","User Education",4,25
  4511.  
  4512.  Other Ways to Read Data from a Sequential File
  4513.  
  4514.  In the preceding sections,  INPUT # is used to read a record (one line of
  4515.  data from a file), assigning different fields in the record to the variables
  4516.  listed after  INPUT #. This section explores alternative ways to read data
  4517.  from sequential files, as records ( LINE INPUT #) and as unformatted
  4518.  sequences of bytes ( INPUT$).
  4519.  
  4520.  
  4521.  The LINE INPUT # Statement
  4522.  
  4523.  With the  LINE INPUT# statement, your program can read a line of text
  4524.  exactly as it appears in a file without interpreting commas or double
  4525.  quotation marks as field delimiters. This is particularly useful in programs
  4526.  that work with ASCII text files.
  4527.  
  4528.  The  LINE INPUT # statement reads an entire line from a sequential file (up
  4529.  to a carriage-return-and-line-feed sequence) into a single string variable.
  4530.  
  4531.  Examples
  4532.  
  4533.  The following short program reads each line from the file CHAP1.TXT and then
  4534.  echoes that line on the screen:
  4535.  
  4536.   ' Open CHAP1.TXT for sequential input:
  4537.   OPEN "CHAP1.TXT" FOR INPUT AS #1
  4538.  
  4539.   ' Keep reading lines sequentially from the file until
  4540.   ' there are none left in the file:
  4541.  DO UNTIL EOF(1)
  4542.  
  4543.  
  4544.  ' Read a line from the
  4545.  file and store it
  4546.  
  4547.      ' in the variable LineBuffer$:
  4548.      LINE INPUT #1, LineBuffer$
  4549.  
  4550.      ' Print the line on the screen:
  4551.      PRINT LineBuffer$
  4552.   LOOP
  4553.  
  4554.  The preceding program is easily modified to a file-copying utility that
  4555.  prints each line read from the specified input file to another file, instead
  4556.  of to the screen:
  4557.  
  4558.   ' Input names of input and output files:
  4559.  
  4560.   INPUT "File to copy: ", FileName1$
  4561.   IF FileName1$ = "" THEN END
  4562.   INPUT "Name of new file: ", FileName2$
  4563.   IF FileName2$ = "" THEN END
  4564.  
  4565.   ' Open first file for sequential input:
  4566.   OPEN FileName1$ FOR INPUT AS #1
  4567.  
  4568.   ' Open second file for sequential output:
  4569.   OPEN FileName2$ FOR OUTPUT AS #2
  4570.  
  4571.   ' Keep reading lines sequentially from first file
  4572.   ' until there are none left in the file:
  4573.   DO UNTIL EOF(1)
  4574.  
  4575.      ' Read a line from first file and store it in the
  4576.      ' variable LineBuffer$:
  4577.      LINE INPUT #1, LineBuffer$
  4578.  
  4579.      ' Write LineBuffer$ to the second file:
  4580.      PRINT #2, LineBuffer$
  4581.  
  4582.   LOOP
  4583.  
  4584.  The INPUT$ Function
  4585.  
  4586.  Another way to read data from sequential files (and, in fact, from any file)
  4587.  is to use the  INPUT$ function. Whereas  INPUT # and  LINE INPUT # read one
  4588.  line at a time from a sequential file,  INPUT$ reads a specified number of
  4589.  characters from a file, as shown in the following examples:
  4590.  
  4591. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  4592.  ────────────────────────────────────────────────────────────────────────────
  4593.  X$ = INPUT$(100, #1)                    Reads 100 characters from file
  4594.                                          number 1 and assigns all of them
  4595.                                          to the string variable X$.
  4596.  
  4597.  Test$ = INPUT$(1, #2)                   Reads one character from file
  4598.                                          number 2 and assigns it to the
  4599.                                          string variable Test$.
  4600.  
  4601.  ────────────────────────────────────────────────────────────────────────────
  4602. 
  4603.  
  4604.  
  4605.  
  4606.  
  4607.  The  INPUT$ function
  4608.  without a file number always reads input from standard input (usually the
  4609.  keyboard).
  4610.  
  4611.  
  4612.  The  INPUT$ function does what is known as "binary input"; that is, it reads
  4613.  a file as an unformatted stream of characters. For example, it does not see
  4614.  a carriage-return-and-line-feed sequence as signalling the end of an input
  4615.  operation. Therefore,  INPUT$ is the best choice when you want your program
  4616.  to read every single character from a file or when you want it to read a
  4617.  binary, or non-ASCII, file.
  4618.  
  4619.  Example
  4620.  
  4621.  The following program copies the named binary file to the screen, printing
  4622.  only alphanumeric and punctuation characters in the ASCII range 32 to 126,
  4623.  as well as tabs, carriage returns, and line feeds:
  4624.  
  4625.   ' 9 is ASCII value for horizontal tab, 10 is ASCII value
  4626.   ' for line feed, and 13 is ASCII value for carriage return:
  4627.   CONST LINEFEED = 10, CARRETURN = 13, TABCHAR = 9
  4628.  
  4629.   INPUT "Print which file: ", FileName$
  4630.   IF FileName$ = "" THEN END
  4631.  
  4632.   OPEN FileName$ FOR INPUT AS #1
  4633.  
  4634.   DO UNTIL EOF(1)
  4635.      Character$ = INPUT$(1, #1)
  4636.      CharVal = ASC(Character$)
  4637.      SELECT CASE CharVal
  4638.         CASE 32 TO 126
  4639.   PRINT Character$;
  4640.         CASE TABCHAR, CARRETURN
  4641.   PRINT Character$;
  4642.         CASE LINEFEED
  4643.            IF OldCharVal <> CARRETURN THEN PRINT Character$;
  4644.         CASE ELSE
  4645.   ' This is not one of the characters this program
  4646.   ' is interested in, so don't print anything.
  4647.       END SELECT
  4648.       OldCharVal = CharVal
  4649.   LOOP
  4650.  
  4651.  Using Random-Access Files
  4652.  
  4653.  This section discusses how records are organized in random-access data
  4654.  files, then shows you how to read data from, and write data to, a file
  4655.  opened for random access.
  4656.  
  4657.  
  4658.  Records in Random-Access Files
  4659.  
  4660.  Random-access records are stored quite differently from sequential records.
  4661.  Each random-access record is defined with a fixed length, as is each field
  4662.  within the record. These fixed lengths determine where a record or field
  4663.  begins and ends, as there are no commas separating fields, and no
  4664.  carriage-return-and-line-feed sequences between records. Figure 3.3 shows
  4665.  how three records might appear in a random-access file.
  4666.  
  4667.  If you are storing records containing numbers, using random-access
  4668.  files saves disk space when compared with using sequential files. This is
  4669.  because sequential files save numbers as a sequence of ASCII characters
  4670.  representing each digit, whereas random-access files save numbers in binary
  4671.  format.
  4672.  
  4673.  
  4674.  For example, the number 17,000 is represented in a sequential file using 5
  4675.  bytes, one for each digit. However, if 17,000 is stored in an integer field
  4676.  of a random-access record, it takes only 2 bytes of disk space.
  4677.  
  4678.  Integers in random-access files take 2 bytes, long integers and
  4679.  single-precision numbers take 4 bytes, and double-precision numbers and
  4680.  currency data types take 8 bytes.
  4681.  
  4682.  
  4683.  Adding Data to a Random-Access File
  4684.  
  4685.  To write a program that adds data to a random-access file, follow these
  4686.  steps:
  4687.  
  4688.     1. Define the fields of each record.
  4689.  
  4690.     2. Open the file in random-access mode and specify the length of each
  4691.        record.
  4692.  
  4693.     3. Get input for a new record and store the record in the file.
  4694.  
  4695.  Each of these steps is now considerably easier than it was in BASICA, as you
  4696.  can see from the examples that follow.
  4697.  
  4698.  
  4699.  Defining Records
  4700.  
  4701.  You can define your own record with a  TYPE... END TYPE statement,  which
  4702.  allows you to create a composite data type that mixes string and numeric
  4703.  elements. This is a big advantage over the earlier method of setting up
  4704.  records with a  FIELD statement, which required that each field be defined
  4705.  as a string. By defining a record with  TYPE... END TYPE,  you eliminate the
  4706.  need to use the functions that convert numeric data to strings ( MK
  4707.   type $,  MK$,  MKSMBF$, and
  4708.  MKDMBF$)  and strings to numeric data ( CV type,  CVSMBF, andCVDMBF).
  4709.  
  4710.  
  4711.  The following examples contrast these two methods of defining records.
  4712.  
  4713.    ■   Record defined with  TYPE... END TYPE:
  4714.  
  4715.  
  4716.   ' Define the RecordType structure:
  4717.  
  4718.  TYPE RecordType
  4719.      Name AS STRING * 30
  4720.      Age AS INTEGER
  4721.      Salary AS SINGLE
  4722.  END TYPE
  4723.  
  4724.  ' Declare the variable RecordVar
  4725.  ' as having the type RecordType:
  4726.  DIM RecordVar AS RecordType
  4727.  
  4728.  
  4729.    ■   Record defined with  FIELD:
  4730.  
  4731.  ' Define the lengths of the fields
  4732.  ' in the temporary storage buffer:
  4733.  FIELD #1,30 AS Name$,2 AS Age$,4 AS Salary$
  4734.  
  4735.  
  4736.  Opening the File and Specifying Record Length
  4737.  
  4738.  Since the length of a random-access record is fixed, you should let your
  4739.  program know how long you want each record to be; otherwise, record length
  4740.  defaults to 128 bytes.
  4741.  
  4742.  To specify record length, use the  LEN = clause in the  OPEN statement. The
  4743.  next two fragments, which continue the contrasting examples started in the
  4744.  preceding section, show how to use  LEN =.
  4745.  
  4746.    ■   Specify the record length for a record that is defined with the
  4747.        statement  TYPE... END TYPE:
  4748.  
  4749.  ' Open the random-access file and specify the length
  4750.  ' of one record as being equal to the length of the
  4751.  ' RecordVar variable:
  4752.  OPEN "EMPLOYEE.DAT" FOR RANDOM AS #1 LEN = LEN(RecordVar)
  4753.  
  4754.  
  4755.  
  4756.    ■   Specify record length for a record defined with  FIELD:
  4757.  ' Open the random-access file and specify the length
  4758.  ' of a record:
  4759.  OPEN "EMPLOYEE.DAT" FOR RANDOM AS #1 LEN = 36
  4760.  
  4761.  As you can see, when you use  FIELD, you have to add the lengths of each
  4762.  field yourself (Name$ is 30 bytes, Age$ is 2 bytes, Salary$ is 4 bytes, so
  4763.  the record is 30+2+4 or 36 bytes). With  TYPE... END TYPE, you no longer
  4764.  have to do these calculations. Instead, just use the  LEN function to
  4765.  calculate the length of the variable you have created to hold your records
  4766.  (RecordVar, in this case).
  4767.  
  4768.  
  4769.  Entering Data and Storing the Record
  4770.  
  4771.  You can enter data directly into the elements of a user-defined record
  4772.  without having to worry about left or right justification of input within a
  4773.  field with  LSET or  RSET.  Compare the following two fragments, which
  4774.  continue the examples started in the preceding section, to see the amount of
  4775.  code this approach saves you.
  4776.  
  4777.    ■   Enter data for a random-access record and storing the record using
  4778.        TYPE... END TYPE:
  4779.  ' Enter the data:
  4780.  INPUT "Name"; RecordVar.Name
  4781.  INPUT "Age"; RecordVar.Age
  4782.  INPUT "Salary"; RecordVar.Salary
  4783.  ' Store the record:
  4784.  PUT #1, , RecordVar
  4785.  
  4786.    ■   Enter data for a random-access record and store the record using
  4787.        FIELD:
  4788.  ' Enter the data:
  4789.  INPUT "Name"; Nm$
  4790.  INPUT "Age"; AgeVal%
  4791.  INPUT "Salary"; SalaryVal!
  4792.  
  4793.  ' Left justify the data in the storage-buffer fields,
  4794.  ' using the MKI$ and MKS$ functions to convert numbers
  4795.  ' to file strings:
  4796.  LSET Name$ = Nm$
  4797.  LSET Age$ = MKI$(AgeVal%)
  4798.  LSET Salary$ = MKS$(SalaryVal!)
  4799.  
  4800.  ' Store the record:
  4801.  PUT #1
  4802.  
  4803.  
  4804.  
  4805.  
  4806.  Putting It All Together
  4807.  
  4808.  The following program puts together all the steps outlined in the preceding
  4809.  section -- defining fields, specifying record length, entering data, and
  4810.  storing the input data -- to open a random-access data file named STOCK.DAT
  4811.  and add records to it:
  4812.  
  4813.   DEFINT A-Z
  4814.  
  4815.   ' Define structure of a single record in the random-access
  4816.   ' file. Each record will consist of four fixed-length fields
  4817.   ' ("PartNumber", "Description", "UnitPrice", "Quantity"):
  4818.   TYPE StockItem
  4819.      PartNumber AS STRING *  6
  4820.      Description AS STRING * 20
  4821.      UnitPrice AS SINGLE
  4822.      Quantity AS INTEGER
  4823.   END TYPE
  4824.  ' Declare a variable (StockRecord) using the above type:
  4825.   DIM StockRecord AS StockItem
  4826.  
  4827.   ' Open the random-access file, specifying the length of one
  4828.   ' record as the length of the StockRecord variable:
  4829.   OPEN "STOCK.DAT" FOR RANDOM AS #1 LEN=LEN(StockRecord)
  4830.  ' Use LOF() to
  4831.  calculate the number of records already in
  4832.  
  4833.   ' the file, so new records will be added after them:
  4834.   RecordNumber = LOF(1) \ LEN(StockRecord)
  4835.  
  4836.   ' Now, add new records:
  4837.   DO
  4838.  
  4839.      ' Input data for a stock record from keyboard and store
  4840.      ' in the different elements of the StockRecord variable:
  4841.      INPUT "Part Number? ", StockRecord.PartNumber
  4842.      INPUT "Description? ", StockRecord.Description
  4843.      INPUT "Unit Price ? ", StockRecord.UnitPrice
  4844.      INPUT "Quantity   ? ", StockRecord.Quantity
  4845.  
  4846.      RecordNumber = RecordNumber + 1
  4847.  
  4848.      ' Write data in StockRecord to a new record in the file:
  4849.      PUT #1, RecordNumber, StockRecord
  4850.  
  4851.      ' Check to see if more data are to be read:
  4852.      INPUT "More (Y/N)? ", Resp$
  4853.   LOOP UNTIL UCASE$(Resp$) = "N"
  4854.  
  4855.   ' All done; close the file and end:
  4856.   CLOSE  #1
  4857.   END
  4858.  If the STOCK.DAT file already existed, this program would add more records
  4859.  to the file without overwriting any that were already in the file. The
  4860.  following key statement makes this work:
  4861.  
  4862.  RecordNumber = LOF(1) \ LEN(StockRecord)
  4863.  Here is what happens:
  4864.  
  4865.     1. The LOF(1) function calculates the total number of bytes in the file
  4866.        STOCK.DAT. If STOCK.DAT is new or has no records in it, LOF(1) returns
  4867.        the value 0.
  4868.  
  4869.     2. The LEN(StockRecord) function calculates the number of bytes in one
  4870.        record. (StockRecord is defined as having the same structure as the
  4871.        user-defined type StockItem.)
  4872.  
  4873.     3. Therefore, the number of records is equal to the total bytes in the
  4874.        file divided by the bytes in one record. This is another advantage of
  4875.        having a fixed-length record. Since each record is the same size, you
  4876.        can always use a formula to calculate the number of records in the
  4877.        file. Obviously, this would not work with a sequential file, since
  4878.        sequential records can have different lengths.
  4879.  
  4880.  
  4881.  
  4882.  Reading Data Sequentially
  4883.  
  4884.  Using the technique outlined in the preceding section for calculating the
  4885.  number of records in a random-access file, you can write a program that
  4886.  reads all the records in that file.
  4887.  
  4888.  Example
  4889.  
  4890.  The following program reads records sequentially (from the first record
  4891.  stored to the last) from the STOCK.DAT file created in the previous section:
  4892.  
  4893.   ' Define a record structure (type) for random-access
  4894.   ' file records:
  4895.   TYPE StockItem
  4896.      PartNumber AS STRING *  6
  4897.      Description AS STRING * 20
  4898.      UnitPrice AS SINGLE
  4899.      Quantity AS INTEGER
  4900.   END TYPE
  4901.  
  4902.   ' Declare a variable (StockRecord) using the above type:
  4903.   DIM StockRecord AS StockItem
  4904.  
  4905.   ' Open the random-access file:
  4906.   OPEN "STOCK.DAT" FOR RANDOM AS #1 LEN = LEN(StockRecord)
  4907.  
  4908.   ' Calculate number of records in the file:
  4909.   NumberOfRecords = LOF(1) \ LEN(StockRecord)
  4910.  
  4911.   ' Read the records and write the data to the screen:
  4912.   FOR RecordNumber = 1 TO NumberOfRecords
  4913.  
  4914.      ' Read the data from a new record in the file:
  4915.      GET #1, RecordNumber, StockRecord
  4916.  
  4917.      ' Print the data to the screen:
  4918.      PRINT "Part Number: ", StockRecord.PartNumber
  4919.      PRINT "Description: ", StockRecord.Description
  4920.      PRINT "Unit Price : ", StockRecord.UnitPrice
  4921.      PRINT "Quantity   : ", StockRecord.Quantity
  4922.  
  4923.   NEXT
  4924.  
  4925.   ' All done; close the file and end:
  4926.   CLOSE #1
  4927.   END
  4928.  
  4929.  It is not necessary to close STOCK.DAT before reading from it. Opening a
  4930.  file for random access lets you write to or read from the file with a single
  4931.   OPEN statement.
  4932.  
  4933.  
  4934.  Using Record Numbers to Retrieve Records
  4935.  
  4936.  You can read any record from a random-access file by specifying the record's
  4937.  number in a  GET statement. You can write to any record in a random-access
  4938.  file by specifying the record's number in a  PUT statement. This is one of
  4939.  the major advantages that random-access files have over sequential files,
  4940.  since sequential files do not permit direct access to a specific record.
  4941.  
  4942.  The sample application program, INDEX.BAS, listed in the section "Indexing a
  4943.  Random-Access File" later in this chapter shows a technique that quickly
  4944.  finds a particular record by searching an index of record numbers.
  4945.  
  4946.  Example
  4947.  
  4948.  The following fragment shows how to use  GET with a record number:
  4949.  
  4950.   DEFINT A-Z' Default variable type is integer.
  4951.   CONST FALSE = 0, TRUE = NOT FALSE
  4952.  
  4953.   TYPE StockItem
  4954.      PartNumber AS STRING *  6
  4955.      Description AS STRING * 20
  4956.      UnitPrice AS SINGLE
  4957.      Quantity AS INTEGER
  4958.   END TYPE
  4959.  
  4960.   DIM StockRecord AS StockItem
  4961.  
  4962.   OPEN "STOCK.DAT" FOR RANDOM AS #1 LEN=LEN(StockRecord)
  4963.  
  4964.   NumberOfRecords = LOF(1) \ LEN(StockRecord)
  4965.   GetMoreRecords = TRUE
  4966.  
  4967.   DO
  4968.      PRINT "Enter record number for part you want to see ";
  4969.      PRINT "(0 to end): ";
  4970.      INPUT "", RecordNumber
  4971.  
  4972.      IF RecordNumber>0 AND RecordNumber<NumberOfRecords THEN
  4973.  
  4974.         ' Get the record whose number was entered and store
  4975.         ' it in StockRecord:
  4976.         GET #1, RecordNumber, StockRecord
  4977.  
  4978.         ' Display the record:
  4979.         .
  4980.         .
  4981.         .
  4982.  ELSEIF RecordNumber = 0
  4983.  THEN
  4984.  
  4985.         GetMoreRecords = FALSE
  4986.      ELSE
  4987.         PRINT "Input value out of range."
  4988.      END IF
  4989.   LOOP WHILE GetMoreRecords
  4990.   END
  4991.  
  4992.  Binary File I/O
  4993.  
  4994.  Binary access is a third way -- in addition to random access and sequential
  4995.  access -- to read or write a file's data. Use the following statement to
  4996.  open a file for binary I/O:
  4997.  
  4998.   OPEN  file$  FOR BINARY AS  # filenumber%Binary access
  4999.  is a way to get at the raw bytes of any file, not just an ASCII file.
  5000.  This makes it very useful for reading or modifying files saved in a
  5001.  non-ASCII format, such as Microsoft Word files or executable files.
  5002.  
  5003.  
  5004.  Files opened for binary access are treated as an unformatted sequence of
  5005.  bytes. Although you can read or write a record (a variable declared as
  5006.  having a user-defined type) to a file opened in the binary mode, the file
  5007.  itself does not have to be organized into fixed-length records. In fact,
  5008.  binary I/O does not have to deal with records at all, unless you consider
  5009.  each byte in a file as a separate record.
  5010.  
  5011.  
  5012.  Comparing Binary Access and Random Access
  5013.  
  5014.  The  BINARY mode is similar to the  RANDOM mode in that you can both read
  5015.  from and write to a file after a single  OPEN statement. (Binary thus
  5016.  differs from sequential access, where you must first close a file and then
  5017.  reopen it if you want to switch between reading and writing.) Also, like
  5018.  random access, binary access lets you move backward and forward within an
  5019.  open file. Binary access even supports the same statements used for reading
  5020.  and writing random-access files using this syntax:
  5021.  
  5022.   GET |  PUT}  [#] filenumber% [, [position%]],[ variable]]
  5023.   Here,  variable can have any type, including a variable-length
  5024.  string or a user-defined type, and  position% points to the
  5025.  place in the file where the next  GET or  PUT
  5026.  operation will take place. (The  position% value is relative to the
  5027.  beginning of the file; that is, the first byte in the file has position one,
  5028.  the second byte has position two, and so on.) If you leave off the
  5029.  position% argument, successive  GET and  PUT operations move the file
  5030.  pointer sequentially through the file from the first byte to the last.
  5031.  
  5032.  
  5033.  The  GET statement reads a number of bytes from the file equal to the length
  5034.   variable. Similarly, the  PUT statement writes a number of bytes to the
  5035.  file equal to the length  variable.
  5036.  
  5037.  For example, if
  5038.  variable has integer type, then  GET reads 2 bytes into  variable; if
  5039.  variable has single-precision type,  GET reads 4 bytes. Therefore, if you
  5040.  don't specify a  position argument in a  GET or  PUT statement, the file
  5041.  pointer is moved a distance equal to the length  variable.
  5042.  
  5043.  
  5044.  The  GET statement and  INPUT$ function are the only ways to read data from
  5045.  a file opened in binary mode. The  PUT statement is the only way to write
  5046.  data to a file opened in binary mode.
  5047.  
  5048.  Binary access, unlike random access, enables you to move to any byte
  5049.  position in a file and then read or write any number of bytes you want. In
  5050.  contrast, random access can only move to a record boundary and read a fixed
  5051.  number of bytes (the length of a record) each time.
  5052.  
  5053.  
  5054.  Positioning the File Pointer with SEEK
  5055.  
  5056.  If you want to move the file pointer to a certain place in a file without
  5057.  actually performing any I/O, use the  SEEK statement. Its syntax is:
  5058.  
  5059.   SEEK [#] filenumber%,  position&After a  SEEK statement, the next read or
  5060.  write operation in the file opened with  filenumber% begins at the byte
  5061.  noted in  position&.
  5062.  
  5063.  
  5064.  The counterpart to the  SEEK statement is the  SEEK function, with this
  5065.  syntax:
  5066.  
  5067.   SEEK( filenumber%)
  5068.  
  5069.  The  SEEK function tells you the byte position where the
  5070.  very next read or write operation begins. (If you are using binary I/O to
  5071.  access a file, the  LOC and  SEEK functions give similar results, but  LOC
  5072.  returns the position of the last byte read or written, while  SEEK returns
  5073.  the position of the next byte to be read or written.)
  5074.  
  5075.  
  5076.  The  SEEK statement and function also work on files opened for sequential or
  5077.  random access. With sequential access, the statement and the function behave
  5078.  in the same way they do with binary access; that is, the  SEEK statement
  5079.  moves the file pointer to a specific byte position, and the  SEEK function
  5080.  returns information about the next byte to read or write.
  5081.  
  5082.  However, if a file is opened for random access, the  SEEK statement can move
  5083.  the file pointer only to the beginning of a record, not to a byte within a
  5084.  record. Also, when used with random-access files, the  SEEK function returns
  5085.  the number of the next record rather than the position of the next byte.
  5086.  
  5087.  Example
  5088.  
  5089.  The following program opens a BASIC Quick library, then reads and prints the
  5090.  names of BASIC procedures and other external symbols contained in the
  5091.  library. This program is in the file named QLBDUMP.BAS on the Microsoft
  5092.  BASIC distribution disks.
  5093.  
  5094.   ' This program prints the names of Quick library procedures.
  5095.  
  5096.   DECLARE SUB DumpSym (SymStart AS INTEGER, QHdrPos AS LONG)
  5097.  
  5098.   TYPE ExeHdr' Part of DOS .EXE header.
  5099.       other1    AS STRING* 8' Other header information.
  5100.       CParHdr   AS INTEGER ' Size of header in paragraphs.
  5101.       other2    AS STRING* 10' Other header information.
  5102.       IP        AS INTEGER ' Initial IP value.
  5103.       CS        AS INTEGER ' Initial (relative) CS value.
  5104.   END TYPE
  5105.  TYPE QBHdr' QLB header.
  5106.       QBHead    AS STRING* 6' QBX specific heading.
  5107.       Magic     AS INTEGER ' Magic word: identifies file as a Quick '
  5108.  library.
  5109.       SymStart  AS INTEGER ' Offset from header to first code symbol.
  5110.       DatStart  AS INTEGER ' Offset from header to first data symbol.
  5111.   END TYPE
  5112.  
  5113.   TYPE QbSym' QuickLib symbol entry.
  5114.       Flags     AS INTEGER ' Symbol flags.
  5115.       NameStart AS INTEGER ' Offset into name table.
  5116.       Other     AS STRING* 4' Other header information.
  5117.   END TYPE
  5118.  
  5119.   DIM EHdr AS ExeHdr, Qhdr AS QBHdr, QHdrPos AS LONG
  5120.  
  5121.   INPUT "Enter Quick library filename: ", FileName$
  5122.   FileName$ = UCASE$(FileName$)
  5123.   IF INSTR(FileName$,".QLB") = 0 THEN FileName$ = FileName$ + ".QLB"
  5124.   INPUT "Enter output filename or press Enter for screen: ", OutFile$
  5125.   OutFile$ = UCASE$(OutFile$)
  5126.   IF OutFile$ = "" THEN OutFile$ = "CONS:"
  5127.   OPEN FileName$ FOR BINARY AS #1
  5128.   OPEN OutFile$ FOR OUTPUT AS #2
  5129.  
  5130.   GET #1,, EHdr' Read the EXE format header.
  5131.   QHdrPos= (EHdr.CParHdr+ EHdr.CS) * 16 + EHdr.IP + 1
  5132.  
  5133.   GET #1,QHdrPos, Qhdr' Read the QuickLib format header.
  5134.   IF Qhdr.Magic <> &H6C75THEN PRINT "Not a QBX UserLibrary": END
  5135.  
  5136.   PRINT #2, "Code Symbols:": PRINT #2,
  5137.   DumpSym Qhdr.SymStart, QHdrPos' dump code symbols
  5138.   PRINT #2,
  5139.   PRINT #2, "Data Symbols:": PRINT #2, ""
  5140.   DumpSym Qhdr.DatStart, QHdrPos' dump data symbols
  5141.   PRINT #2,
  5142.  
  5143.   END
  5144.  SUB DumpSym (SymStart
  5145.  AS INTEGER, QHdrPos AS LONG)
  5146.  
  5147.   DIM QlbSym AS QbSym
  5148.   DIM NextSym AS LONG, CurrentSym AS LONG
  5149.  
  5150.   ' Calculate the location of the first symbol entry,
  5151.   ' then read that entry:
  5152.   NextSym = QHdrPos + SymStart
  5153.   GET #1, NextSym, QlbSym
  5154.  DO
  5155.   NextSym = SEEK(1)' Save the location of the next symbol.
  5156.   CurrentSym = QHdrPos + QlbSym.NameStart
  5157.   SEEK #1, CurrentSym' Use SEEK to move to the name
  5158.   ' for the current symbol entry.
  5159.   Prospect$ = INPUT$(40, 1)' Read the longest legal string,
  5160.   ' plus one additional byte for
  5161.   ' the final null character (CHR$(0)).
  5162.  
  5163.   ' Extract the null-terminated name:
  5164.   SName$ = LEFT$(Prospect$, INSTR(Prospect$, CHR$(0)))
  5165.  
  5166.  ' Print only those names that do not begin with "__", "$", or
  5167.   ' "b$" as these names are usually considered reserved:
  5168.   T$ = LEFT$(SName$, 2)
  5169.   IF T$ <> "__" AND LEFT$(SName$, 1) <> "$" AND UCASE$(T$) <> "B$" THEN
  5170.   PRINT #2, "  " + SName$
  5171.   END IF
  5172.  
  5173.        GET #1, NextSym, QlbSym ' Read a symbol entry.
  5174.  LOOP WHILE QlbSym.Flags' Flags=0 (false) means end of table.
  5175.  
  5176.   END SUB
  5177.  
  5178.  Working with Devices
  5179.  
  5180.  Microsoft BASIC supports device I/O. This means certain computer peripherals
  5181.  can be opened for I/O just like data files on disk. Exceptions to the
  5182.  statements and functions that can be used with these devices are noted in
  5183.  the following section,"Differences Between Device I/O and File I/O." Table
  5184.  3.1 lists the devices supported by BASIC.
  5185.  
  5186.  
  5187.  Differences Between Device I/O and File I/O
  5188.  
  5189.  Certain functions and statements used for file I/O are not allowed for
  5190.  device I/O, while other statements and functions have the same name but
  5191.  behave differently. These are some of the differences:
  5192.  
  5193.    ■   The CONS, SCRN, and LPT n devices cannot be opened in the input,
  5194.        append , or random-access modes.
  5195.  
  5196.    ■   The KYBD device cannot be opened in the output, append , or
  5197.        random-access modes.
  5198.  
  5199.    ■   The  EOF,  LOC, and  LOF functions cannot be used with the CONS, KYBD,
  5200.        LPT n, or SCRN devices.
  5201.  
  5202.  
  5203.    ■   The  EOF,  LOC, and  LOF functions can be used with the COM n serial
  5204.        device; however, the values these functions return have a different
  5205.        meaning than the values they return when used with data files. (See
  5206.        the next section for an explanation of what these functions do when
  5207.        used with COM n.)
  5208.  
  5209.  
  5210.  Example
  5211.  
  5212.  The following program shows how the devices LPT1 or SCRN can be opened for
  5213.  output using the same syntax as that for data files. This program reads all
  5214.  the lines from the file chosen by the user and then prints the lines on the
  5215.  screen or the printer according to the user's choice.
  5216.  
  5217.   CLS
  5218.   ' Input the name of the file to look at:
  5219.   INPUT "Name of file to display: ", FileNam$
  5220.  
  5221.   ' If no name is entered, end the program;
  5222.   ' otherwise, open the given file for reading (INPUT):
  5223.   IF FileNam$ = "" THEN END ELSE OPEN FileNam$ FOR INPUT AS #1
  5224.  
  5225.   ' Input choice for displaying file (Screen or Printer);
  5226.   ' continue prompting for input until either the "S" or "P"
  5227.   ' key is pressed:
  5228.   DO
  5229.      ' Go to row 2, column 1 on the screen and print prompt:
  5230.      LOCATE 2, 1, 1
  5231.      PRINT "Send output to screen (S), or to printer (P): ";
  5232.  
  5233.  
  5234.  ' Print over anything
  5235.  after the prompt:
  5236.  
  5237.      PRINT SPACE$(2);
  5238.  
  5239.      ' Relocate cursor after the prompt, and make it visible:
  5240.      LOCATE 2, 47, 1
  5241.      Choice$ = UCASE$(INPUT$(1))     ' Get input.
  5242.      PRINT Choice$
  5243.   LOOP WHILE Choice$ <> "S" AND Choice$ <> "P"
  5244.  
  5245.   ' Depending on the key pressed, open either the printer
  5246.   ' or the screen for output:
  5247.   SELECT CASE Choice$
  5248.      CASE "P"
  5249.         OPEN "LPT1:" FOR OUTPUT AS #2
  5250.         PRINT "Printing file on printer."
  5251.      CASE "S"
  5252.         CLS
  5253.         OPEN "SCRN:" FOR OUTPUT AS #2
  5254.   END SELECT
  5255.  
  5256.   ' Set the width of the chosen output device to 80 columns:
  5257.   WIDTH #2, 80
  5258.  
  5259.  END COLUMN BREAK' As
  5260.  long as there are lines in the file, read a line
  5261.  
  5262.   ' from the file and print it on the chosen device:
  5263.   DO UNTIL EOF(1)
  5264.      LINE INPUT #1, LineBuffer$
  5265.      PRINT #2, LineBuffer$
  5266.   LOOP
  5267.  
  5268.   CLOSE' End input from the file and output to the device.
  5269.   END
  5270.  
  5271.  Communications Through the Serial Port
  5272.  
  5273.  The  OPEN "COM n :" statement (where  n can be 1 or, if you have two serial
  5274.  ports, 2) allows you to open your computer's serial port(s) for serial
  5275.  (bit-by-bit) communication with other computers or with peripheral devices
  5276.  such as modems or serial printers.  The following are some of the parameters
  5277.  you can specify:
  5278.  
  5279.    ■   Rate of data transmission, measured in "baud" (bits per second)
  5280.  
  5281.    ■   Whether or not to detect transmission errors and how those errors will
  5282.        be detected
  5283.  
  5284.    ■   How many stop bits (1, 1.5, or 2) are to be used to signal the end of
  5285.        a transmitted byte
  5286.  
  5287.    ■   How many bits in each byte of data transmitted or received constitute
  5288.        actual data
  5289.  
  5290.  
  5291.  When the serial port is opened for communication, an input buffer is
  5292.  set aside to hold the bytes being read from another device. This is
  5293.  because, at high baud rates, characters arrive faster than they can
  5294.  be processed. The default size for this buffer is 512 bytes, and it
  5295.  can be modified with the  LEN =  reclen%
  5296.  option of the  OPEN "COM n :" statement. The values returned by the  EOF,
  5297.  
  5298.  LOC, and  LOF functions when used with a communications device return
  5299.  information about the size of this buffer, as shown in the following table:
  5300.  
  5301.  
  5302. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  5303.  Function                                Information returned
  5304.  ────────────────────────────────────────────────────────────────────────────
  5305.   EOF                                    Whether any characters are waiting
  5306.                                          to be read from the input buffer
  5307.  
  5308.   LOC                                    The number of characters waiting
  5309.                                          in the input buffer
  5310.  
  5311.   LOF                                    The amount of space remaining (in
  5312.                                          bytes) in the output buffer
  5313.  
  5314.  
  5315.  
  5316.  
  5317.  
  5318.  Since every character is potentially significant data,  INPUT # and  LINE
  5319.  INPUT # have serious drawbacks for getting input from another device. This
  5320.  is because  INPUT # stops reading data into a variable when it encounters a
  5321.  comma or new-line character (and, sometimes, a space or double quotation
  5322.  mark), and  LINE INPUT # stops reading data when it encounters a new-line
  5323.  character. This makes  INPUT$ the best function to use for input from a
  5324.  communications device, since it reads all characters.
  5325.  
  5326.  The following line uses the  LOC function to check the input buffer for the
  5327.  number of characters waiting there from the communications device opened as
  5328.  file #1; it then uses the  INPUT$ function to read those characters,
  5329.  assigning them to a string variable named ModemInput$:
  5330.  
  5331.  ModemInput$ = INPUT$(LOC(1), #1)
  5332.  
  5333.  Sample Applications
  5334.  
  5335.  The sample applications listed in this section include a screen-handling
  5336.  program that prints a calendar for any month in any year from 1899 to 2099,
  5337.  a file I/O program that builds and searches an index of record numbers from
  5338.  a random-access file, and a communications program that makes your PC behave
  5339.  like a terminal when connected to a modem.
  5340.  
  5341.  
  5342.  Perpetual Calendar (CAL.BAS)
  5343.  
  5344.  After prompting the user to input a month from 1 to 12 and a year from 1899
  5345.  to 2099, the following program prints the calendar for the given month and
  5346.  year. The IsLeapYear procedure makes appropriate adjustments to the calendar
  5347.  for months in a leap year.
  5348.  
  5349.  
  5350.  Statements and Functions Used
  5351.  
  5352.  This program demonstrates the following screen-handling statements and
  5353.  functions:
  5354.  
  5355.    ■    INPUT
  5356.  
  5357.    ■    INPUT$
  5358.  
  5359.    ■    LOCATE
  5360.  
  5361.    ■    POS(0)
  5362.  
  5363.    ■    PRINT
  5364.  
  5365.    ■    PRINT USING
  5366.  
  5367.    ■    TAB
  5368.  
  5369.  
  5370.  
  5371.  Program Listing
  5372.  
  5373.   DEFINT A-Z      ' Default variable type is integer.
  5374.  
  5375.  
  5376.   ' Define a data type for the names of the months and the
  5377.   ' number of days in each:
  5378.  TYPE MonthType
  5379.  Number AS INTEGER     ' Number of days in the month.
  5380.  MName AS STRING * 9   ' Name  of the month.
  5381.  END TYPE
  5382.  
  5383.  ' Declare procedures used:
  5384.  DECLARE FUNCTION IsLeapYear% (N%)
  5385.  DECLARE FUNCTION GetInput% (Prompt$, Row%, LowVal%, HighVal%)
  5386.  
  5387.  DECLARE SUB PrintCalendar (Year%, Month%)
  5388.  DECLARE SUB ComputeMonth (Year%, Month%, StartDay%, TotalDays%)
  5389.  
  5390.  DIM MonthData(1 TO 12)   AS MonthType
  5391.  
  5392.  ' Initialize month definitions from DATA statements below:
  5393.  FOR I = 1 TO 12
  5394.  READ MonthData(I).MName, MonthData(I).Number
  5395.  NEXT
  5396.  
  5397.  ' Main loop, repeat for as many months as desired:
  5398.  DO
  5399.  CLS
  5400.  
  5401.  ' Get year and month as
  5402.  input:
  5403.  
  5404.  Year = GetInput("Year (1899 to 2099): ", 1, 1899, 2099)
  5405.  Month = GetInput("Month (1 to 12): ", 2, 1, 12)
  5406.  
  5407.  ' Print the calendar:
  5408.  PrintCalendar Year, Month
  5409.  ' Another Date?
  5410.  LOCATE 13, 1         ' Locate in 13th row, 1st column.
  5411.  PRINT "New Date? ";  ' Keep cursor on same line.
  5412.  LOCATE , , 1, 0, 13  ' Turn cursor on and make it one
  5413.  ' character high.
  5414.  Resp$ = INPUT$(1)    ' Wait for a key press.
  5415.  PRINT Resp$          ' Print the key pressed.
  5416.  
  5417.  LOOP WHILE UCASE$(Resp$) = "Y"
  5418.  END
  5419.  
  5420.  ' Data for the months of a year:
  5421.  DATA January, 31, February, 28,  March, 31
  5422.  DATA April, 30,   May, 31, June, 30, July, 31, August, 31
  5423.  DATA September,   30, October, 31, November, 30, December, 31
  5424.  
  5425.  ' ====================== ComputeMonth =====================
  5426.  '  Computes the first day and the total days in a month
  5427.  ' =========================================================
  5428.  '
  5429.  SUB ComputeMonth (Year, Month, StartDay, TotalDays) STATIC
  5430.  SHARED MonthData() AS MonthType
  5431.  
  5432.  CONST LEAP = 366 MOD 7
  5433.  CONST NORMAL = 365 MOD 7
  5434.  
  5435.  ' Calculate total number of days (NumDays) since 1/1/1899:
  5436.  
  5437.  ' Start with whole years:
  5438.  NumDays = 0
  5439.  FOR I = 1899 TO Year - 1
  5440.  IF IsLeapYear(I) THEN          ' If leap year,
  5441.  NumDays = NumDays + LEAP    ' add 366 MOD 7.
  5442.  ELSE                           ' If normal year,
  5443.  NumDays = NumDays + NORMAL  ' add 365 MOD 7.
  5444.  END IF
  5445.  NEXT
  5446.  
  5447.  ' Next, add in days
  5448.  from whole months:
  5449.  
  5450.  FOR I = 1 TO Month - 1
  5451.  NumDays = NumDays + MonthData(I).Number
  5452.  NEXT
  5453.  
  5454.  ' Set the number of days in the requested month:
  5455.  TotalDays = MonthData(Month).Number
  5456.  
  5457.  ' Compensate if requested year is a leap year:
  5458.  IF IsLeapYear(Year) THEN
  5459.  
  5460.  ' If after February, add one to total days:
  5461.  IF Month > 2 THEN
  5462.  NumDays = NumDays + 1
  5463.  
  5464.  ' If February, add one to the month's days:
  5465.  ELSEIF Month = 2 THEN
  5466.  TotalDays = TotalDays + 1
  5467.  END IF
  5468.  END IF
  5469.  
  5470.  ' 1/1/1899 was a Sunday, so calculating "NumDays MOD 7"
  5471.  ' gives the day of week (Sunday = 0, Monday = 1, Tuesday
  5472.  ' = 2, and so on) for the first day of the input month:
  5473.  StartDay = NumDays MOD 7
  5474.  END SUB
  5475.  
  5476.  ' ======================== GetInput =======================
  5477.  '  Prompts for input, then tests for a valid range
  5478.  ' =========================================================
  5479.  '
  5480.  FUNCTION GetInput (Prompt$, Row, LowVal, HighVal) STATIC
  5481.  
  5482.  ' Locate prompt at specified row, turn cursor on and
  5483.  ' make it one character high:
  5484.  LOCATE Row, 1, 1, 0, 13
  5485.  PRINT Prompt$;
  5486.  
  5487.  ' Save column position:
  5488.  Column = POS(0)
  5489.  
  5490.  ' Input value until it's within range:
  5491.  DO
  5492.  
  5493.  LOCATE Row, Column   ' Locate cursor at end of prompt.
  5494.  PRINT SPACE$(10)     ' Erase anything already there.
  5495.  LOCATE Row, Column   ' Relocate cursor at end of prompt.
  5496.  INPUT "", Value      ' Input value with no prompt.
  5497.  LOOP WHILE (Value < LowVal OR Value > HighVal)
  5498.  
  5499.  ' Return valid input as value of function:
  5500.  GetInput = Value
  5501.  
  5502.  END FUNCTION
  5503.  
  5504.  ' ====================== IsLeapYear =======================
  5505.  '   Determines if a year is a leap year or not
  5506.  ' =========================================================
  5507.  '
  5508.  FUNCTION IsLeapYear (N) STATIC
  5509.  
  5510.  ' If the year is evenly divisible by 4 and not divisible
  5511.  ' by 100, or if the year is evenly divisible by 400,
  5512.  ' then it's a leap year:
  5513.  IsLeapYear = (N MOD 4 = 0 AND N MOD 100 <> 0) OR (N MOD 400 = 0)
  5514.  END FUNCTION
  5515.  
  5516.  ' ===================== PrintCalendar =====================
  5517.  '   Prints a formatted calendar given the year and month
  5518.  ' =========================================================
  5519.  '
  5520.  SUB PrintCalendar (Year, Month) STATIC
  5521.  SHARED MonthData() AS MonthType
  5522.  
  5523.  ' Compute starting day (Su M Tu ...)
  5524.  ' and total days for the month:
  5525.  ComputeMonth Year, Month, StartDay, TotalDays
  5526.  CLS
  5527.  Header$ = RTRIM$(MonthData(Month).MName) + "," + STR$(Year)
  5528.  
  5529.  ' Calculate location for centering month and year:
  5530.  LeftMargin = (35 - LEN(Header$)) \ 2
  5531.  ' Print header:
  5532.  PRINT TAB(LeftMargin); Header$
  5533.  PRINT
  5534.  PRINT "Su    M   Tu    W   Th    F   Sa"
  5535.  PRINT
  5536.  
  5537.  ' Recalculate and print
  5538.  tab
  5539.  
  5540.  ' to the first day of the month (Su M Tu ...):
  5541.  LeftMargin = 5 * StartDay + 1
  5542.  PRINT TAB(LeftMargin);
  5543.  
  5544.  ' Print out the days of the month:
  5545.  FOR I = 1 TO TotalDays
  5546.  PRINT USING "##_   "; I;
  5547.  
  5548.  ' Advance to the next line
  5549.  ' when the cursor is past column 32:
  5550.  IF POS(0) > 32 THEN PRINT
  5551.  NEXT
  5552.  
  5553.  END SUB
  5554.  
  5555.  Output
  5556.  
  5557.  6787bfff
  5558.  Indexing a Random-Access File (INDEX.BAS)
  5559.  
  5560.  The following program uses an indexing technique to store and retrieve
  5561.  records in a random-access file. Each element of the Index() array has two
  5562.  parts: a string field (PartNumber) and an integer field (RecordNumber). This
  5563.  array is sorted alphabetically on the PartNumber field, which allows the
  5564.  array to be rapidly searched for a specific part number using a binary
  5565.  search.
  5566.  
  5567.  The Index array
  5568.  functions much like the index to a book. When you want to find the pages in
  5569.  a book that deal with a particular topic, you look up an entry for that
  5570.  topic in the index. The entry then points to a page number in the book.
  5571.  Similarly, this program looks up a part number in the alphabetically sorted
  5572.  Index() array. Once it finds the part number, the associated record number
  5573.  in the RecordNumber field points to the record containing all the
  5574.  information for that part.
  5575.  
  5576.  
  5577.  Statements and Functions Used
  5578.  
  5579.  This program demonstrates the following statements and functions used in
  5580.  accessing random-access files:
  5581.  
  5582.    ■    TYPE... END TYPE
  5583.  
  5584.    ■    OPEN... FOR RANDOM
  5585.  
  5586.    ■    GET #
  5587.  
  5588.    ■    PUT #
  5589.  
  5590.    ■    LOF
  5591.  
  5592.  
  5593.  
  5594.  Program Listing
  5595.  
  5596.  
  5597.  DEFINT A-Z
  5598.  
  5599.  
  5600.   ' Define the symbolic constants used globally in the program:
  5601.   CONST FALSE = 0, TRUE = NOT FALSE
  5602.  
  5603.  ' Define a record structure for random-access records:
  5604.  TYPE StockItem
  5605.  PartNumber AS STRING * 6
  5606.  Description AS STRING * 20
  5607.  UnitPrice AS SINGLE
  5608.  Quantity AS INTEGER
  5609.  END TYPE
  5610.  
  5611.  ' Define a record structure for each element of the index:
  5612.  TYPE IndexType
  5613.  RecordNumber AS INTEGER
  5614.  PartNumber AS STRING * 6
  5615.  END TYPE
  5616.  
  5617.  ' Declare procedures that will be called:
  5618.  DECLARE FUNCTION Filter$ (Prompt$)
  5619.  DECLARE FUNCTION FindRecord% (PartNumber$, RecordVar AS StockItem)
  5620.  
  5621.  DECLARE SUB AddRecord
  5622.  (RecordVar AS StockItem)
  5623.  
  5624.  DECLARE SUB InputRecord (RecordVar AS StockItem)
  5625.  DECLARE SUB PrintRecord (RecordVar AS StockItem)
  5626.  DECLARE SUB SortIndex ()
  5627.  DECLARE SUB ShowPartNumbers ()
  5628.  ' Define a buffer (using the StockItem type)
  5629.  ' and define and dimension the index array:
  5630.  DIM StockRecord AS StockItem, Index(1 TO 100) AS IndexType
  5631.  
  5632.  ' Open the random-access file:
  5633.  OPEN "STOCK.DAT" FOR RANDOM AS #1 LEN = LEN(StockRecord)
  5634.  
  5635.  ' Calculate number of records in the file:
  5636.  NumberOfRecords = LOF(1) \ LEN(StockRecord)
  5637.  
  5638.  ' If there are records, read them and build the index:
  5639.  IF NumberOfRecords <> 0 THEN
  5640.  FOR RecordNumber = 1 TO NumberOfRecords
  5641.  
  5642.  ' Read the data from a new record in the file:
  5643.  GET #1, RecordNumber, StockRecord
  5644.  
  5645.  ' Place part number and record number in index:
  5646.  Index(RecordNumber).RecordNumber = RecordNumber
  5647.  Index(RecordNumber).PartNumber = StockRecord.PartNumber
  5648.  NEXT
  5649.  
  5650.  SortIndex            ' Sort index in part-number order.
  5651.  END IF
  5652.  
  5653.  DO                      ' Main-menu loop.
  5654.  CLS
  5655.  PRINT "(A)dd records."
  5656.  PRINT "(L)ook up records."
  5657.  PRINT "(Q)uit program."
  5658.  PRINT
  5659.  LOCATE , , 1
  5660.  PRINT "Type your choice (A, L, or Q) here: ";
  5661.  
  5662.  ' Loop until user presses, A, L, or Q:
  5663.  DO
  5664.  Choice$ = UCASE$(INPUT$(1))
  5665.  LOOP WHILE INSTR("ALQ", Choice$) = 0
  5666.  
  5667.  ' Branch according to
  5668.  choice:
  5669.  
  5670.  SELECT CASE Choice$
  5671.  CASE "A"
  5672.  AddRecord StockRecord
  5673.  CASE "L"
  5674.  IF NumberOfRecords = 0 THEN
  5675.  PRINT : PRINT "No records in file yet. ";
  5676.  PRINT "Press any key to continue.";
  5677.  Pause$ = INPUT$(1)
  5678.  ELSE
  5679.  InputRecord StockRecord
  5680.  END IF
  5681.  CASE "Q"          ' End program.
  5682.  END SELECT
  5683.  LOOP UNTIL Choice$ = "Q"
  5684.  
  5685.  CLOSE #1                ' All done, close file and end.
  5686.  END
  5687.  
  5688.  ' ======================== AddRecords ======================
  5689.  ' Adds records to the file from input typed at the keyboard
  5690.  ' =========================================================
  5691.  '
  5692.  SUB AddRecord (RecordVar AS StockItem) STATIC
  5693.  SHARED Index() AS IndexType, NumberOfRecords
  5694.  DO
  5695.  CLS
  5696.  INPUT "Part Number: ", RecordVar.PartNumber
  5697.  INPUT "Description: ", RecordVar.Description
  5698.  
  5699.  ' Call the Filter$ function to input price & quantity:
  5700.  RecordVar.UnitPrice = VAL(Filter$("Unit Price : "))
  5701.  RecordVar.Quantity = VAL(Filter$("Quantity   : "))
  5702.  
  5703.  NumberOfRecords = NumberOfRecords + 1
  5704.  
  5705.  PUT #1, NumberOfRecords, RecordVar
  5706.  
  5707.  Index(NumberOfRecords).RcrNme
  5708.  = NumberOfRecords
  5709.  
  5710.  Index(NumberOfRecords).PartNumber = RecordVar.PartNumber
  5711.  PRINT : PRINT "Add another? ";
  5712.  OK$ = UCASE$(INPUT$(1))
  5713.  LOOP WHILE OK$ = "Y"
  5714.  
  5715.  SortIndex            ' Sort index file again.
  5716.  END SUB
  5717.  
  5718.  ' ========================= Filter$ ========================
  5719.  ' Filters all non-numeric characters from a string
  5720.  ' and returns the filtered string
  5721.  ' =========================================================
  5722.  '
  5723.  FUNCTION Filter$ (Prompt$) STATIC
  5724.  ValTemp2$ = ""
  5725.  PRINT Prompt$;                    ' Print the prompt passed.
  5726.  INPUT "", ValTemp1$               ' Input a number as
  5727.  ' a string.
  5728.  StringLength = LEN(ValTemp1$)     ' Get the string's length.
  5729.  FOR I% = 1 TO StringLength        ' Go through the string,
  5730.  Char$ = MID$(ValTemp1$, I%, 1) ' one character at a time.
  5731.  
  5732.  ' Is the character a valid part of a number (i.e.,
  5733.  ' a digit or a decimal point)?  If yes, add it to
  5734.  ' the end of a new string:
  5735.  IF INSTR(".0123456789", Char$) > 0 THEN
  5736.  ValTemp2$ = ValTemp2$ + Char$
  5737.  
  5738.  ' Otherwise, check to see if it's a lowercase "l",
  5739.  ' since typewriter users may enter a one that way:
  5740.  ELSEIF Char$ = "l" THEN
  5741.  ValTemp2$ = ValTemp2$ + "1" ' Change the "l" to a "1."
  5742.  END IF
  5743.  NEXT I%
  5744.  
  5745.  Filter$ = ValTemp2$               ' Return filtered string.
  5746.  
  5747.  END FUNCTION
  5748.  
  5749.  '
  5750.  ======================= FindRecord% ===================
  5751.  
  5752.  '  Uses a binary search to locate a record in the index
  5753.  ' ======================================================
  5754.  '
  5755.  FUNCTION FindRecord% (Part$, RecordVar AS StockItem) STATIC
  5756.  SHARED Index() AS IndexType, NumberOfRecords
  5757.  
  5758.  ' Set top and bottom bounds of search:
  5759.  TopRecord = NumberOfRecords
  5760.  BottomRecord = 1
  5761.  
  5762.  ' Search until top of range is less than bottom:
  5763.  DO UNTIL (TopRecord < BottomRecord)
  5764.  
  5765.  ' Choose midpoint:
  5766.  Midpoint = (TopRecord + BottomRecord) \ 2
  5767.  
  5768.  ' Test to see if it's the one wanted (RTRIM$()
  5769.  ' trims trailing blanks from a fixed string):
  5770.  Test$ = RTRIM$(Index(Midpoint).PartNumber)
  5771.  
  5772.  ' If it is, exit loop:
  5773.  IF Test$ = Part$ THEN
  5774.  EXIT DO
  5775.  
  5776.  ' Otherwise, if what you're looking for is greater,
  5777.  ' move bottom up:
  5778.  ELSEIF Part$ > Test$ THEN
  5779.  BottomRecord = Midpoint + 1
  5780.  
  5781.  ' Otherwise, move the top down:
  5782.  ELSE
  5783.  TopRecord = Midpoint - 1
  5784.  END IF
  5785.  LOOP
  5786.  
  5787.  ' If part was found, input record from file using
  5788.  ' pointer in index and set FindRecord% to TRUE:
  5789.  IF Test$ = Part$ THEN
  5790.  GET #1, Index(Midpoint).RecordNumber, RecordVar
  5791.  FindRecord% = TRUE
  5792.  
  5793.  ' Otherwise, if part
  5794.  was not found, set FindRecord%
  5795.  
  5796.  ' to FALSE:
  5797.  ELSE
  5798.  FindRecord% = FALSE
  5799.  END IF
  5800.  END FUNCTION
  5801.  
  5802.  ' ======================= InputRecord =====================
  5803.  '    First, InputRecord calls ShowPartNumbers, which prints
  5804.  '    a menu of part numbers on the top of the screen. Next,
  5805.  '    InputRecord prompts the user to enter a part number.
  5806.  '    Finally, it calls the FindRecord and PrintRecord
  5807.  '    procedures to find and print the given record.
  5808.  ' =========================================================
  5809.  '
  5810.  SUB InputRecord (RecordVar AS StockItem) STATIC
  5811.  CLS
  5812.  ShowPartNumbers      ' Call the ShowPartNumbers SUB procedure.
  5813.  
  5814.  ' Print data from specified records
  5815.  ' on the bottom part of the screen:
  5816.  DO
  5817.  PRINT "Type a part number listed above ";
  5818.  INPUT "(or Q to quit) and press <ENTER>: ", Part$
  5819.  IF UCASE$(Part$) <> "Q" THEN
  5820.  IF FindRecord(Part$, RecordVar) THEN
  5821.  PrintRecord RecordVar
  5822.  ELSE
  5823.  PRINT "Part not found."
  5824.  END IF
  5825.  END IF
  5826.  PRINT STRING$(40, "_")
  5827.  LOOP WHILE UCASE$(Part$) <> "Q"
  5828.  
  5829.  VIEW PRINT   ' Restore the text viewport to entire screen.
  5830.  END SUB
  5831.  
  5832.  '
  5833.  ======================= PrintRecord =====================
  5834.  
  5835.  '                Prints a record on the screen
  5836.  ' =========================================================
  5837.  '
  5838.  SUB PrintRecord (RecordVar AS StockItem) STATIC
  5839.  PRINT "Part Number: "; RecordVar.PartNumber
  5840.  PRINT "Description: "; RecordVar.Description
  5841.  PRINT USING "Unit Price :$$###.##"; RecordVar.UnitPrice
  5842.  PRINT "Quantity   :"; RecordVar.Quantity
  5843.  END SUB
  5844.  
  5845.  ' ===================== ShowPartNumbers ===================
  5846.  ' Prints an index of all the part numbers in the upper part
  5847.  ' of the screen
  5848.  ' =========================================================
  5849.  '
  5850.  SUB ShowPartNumbers STATIC
  5851.  SHARED index() AS IndexType, NumberOfRecords
  5852.  
  5853.  CONST NUMCOLS = 8, COLWIDTH = 80 \ NUMCOLS
  5854.  
  5855.  ' At the top of the screen, print a menu indexing all
  5856.  ' the part numbers for records in the file. This menu is
  5857.  ' printed in columns of equal length (except possibly the
  5858.  ' last column, which may be shorter than the others):
  5859.  ColumnLength = NumberOfRecords
  5860.  DO WHILE ColumnLength MOD NUMCOLS
  5861.  ColumnLength = ColumnLength + 1
  5862.  LOOP
  5863.  ColumnLength = ColumnLength \ NUMCOLS
  5864.  Column = 1
  5865.  RecordNumber = 1
  5866.  DO UNTIL RecordNumber > NumberOfRecords
  5867.  FOR Row = 1 TO ColumnLength
  5868.  LOCATE Row, Column
  5869.  PRINT index(RecordNumber).PartNumber
  5870.  RecordNumber = RecordNumber + 1
  5871.  IF RecordNumber > NumberOfRecords THEN EXIT FOR
  5872.  NEXT Row
  5873.  Column = Column + COLWIDTH
  5874.  LOOP
  5875.  
  5876.  LOCATE ColumnLength +
  5877.  1, 1
  5878.  
  5879.  PRINT STRING$(80, "_")       ' Print separator line.
  5880.  
  5881.  ' Scroll information about records below the part-number
  5882.  ' menu (this way, the part numbers are not erased):
  5883.  VIEW PRINT ColumnLength + 2 TO 24
  5884.  END SUB
  5885.  
  5886.  ' ========================= SortIndex =====================
  5887.  '                Sorts the index by part number
  5888.  ' =========================================================
  5889.  '
  5890.  SUB SortIndex STATIC
  5891.  SHARED Index() AS IndexType, NumberOfRecords
  5892.  
  5893.  ' Set comparison offset to half the number of records
  5894.  ' in Index:
  5895.  Offset = NumberOfRecords \ 2
  5896.  
  5897.  ' Loop until offset gets to zero:
  5898.  DO WHILE Offset > 0
  5899.  Limit = NumberOfRecords - Offset
  5900.  DO
  5901.  
  5902.  ' Assume no switches at this offset:
  5903.  Switch = FALSE
  5904.  
  5905.  ' Compare elements and switch ones out of order:
  5906.  FOR I = 1 TO Limit
  5907.  IF Index(I).PartNumber > Index(I + Offset).PartNumber THEN
  5908.  SWAP Index(I), Index(I + Offset)
  5909.  Switch = I
  5910.  END IF
  5911.  NEXT I
  5912.  
  5913.  ' Sort on next pass only to where
  5914.  ' last switch was made:
  5915.  Limit = Switch
  5916.  LOOP WHILE Switch
  5917.  
  5918.  ' No switches at last offset, try one half as big:
  5919.  Offset = Offset \ 2
  5920.  LOOP
  5921.  END SUB
  5922.  
  5923.  Output
  5924.  
  5925.  b315bfff
  5926.  Terminal Emulator (TERMINAL.BAS)
  5927.  
  5928.  The following simple program turns your computer into a "dumb" terminal;
  5929.  that is, it makes your computer function solely as an I/O device in tandem
  5930.  with a modem. This program uses the  OPEN "COM1:" statement and associated
  5931.  device I/O functions to do the following things:
  5932.  
  5933.    ■   Send the characters you type to the modem
  5934.  
  5935.    ■   Print characters returned by the modem on the screen
  5936.  
  5937.  
  5938.  Note that typed characters displayed on the screen are first sent to the
  5939.  modem and then returned to your computer through the open communications
  5940.  channel. You can verify this if you have a modem with Receive Detect (RD)
  5941.  and Send Detect (SD) lights -- they will flash in sequence as you type.
  5942.  
  5943.  To dial a number, you would have to enter the Attention Dial Touch-Tone
  5944.  (ATDT) or Attention Dial Pulse (ATDP) commands at the keyboard (assuming you
  5945.  have a Hayes-compatible modem).
  5946.  
  5947.  Any other commands sent to the modem would also have to be entered at the
  5948.  keyboard.
  5949.  
  5950.  
  5951.  Statements and Functions Used
  5952.  
  5953.  This program demonstrates the following statements and functions used in
  5954.  communicating with a modem through your computer's serial port:
  5955.  
  5956.    ■    OPEN "COM1:"
  5957.  
  5958.    ■    EOF
  5959.  
  5960.    ■    INPUT$
  5961.  
  5962.    ■    LOC
  5963.  
  5964.  
  5965.  
  5966.  Program Listing
  5967.  
  5968.  DEFINT A-Z
  5969.  
  5970.  
  5971.   DECLARESUB Filter (InString$)
  5972.  
  5973.   COLOR 7, 1' Set screen color.
  5974.   CLS
  5975.  Quit$ = CHR$(0) + CHR$(16)' Value returned by INKEY$
  5976.  ' when Alt+Q is pressed.
  5977.  
  5978.  ' Set up prompt on bottom line of screen and turn cursor on:
  5979.  LOCATE 24, 1, 1
  5980.  PRINT STRING$(80, "_");
  5981.  LOCATE 25, 1
  5982.  PRINT TAB(30); "Press Alt+Q to quit";
  5983.  
  5984.  VIEW PRINT 1 TO23' Print between lines 1 & 23.
  5985.  
  5986.  ' Open communications (1200 baud, no parity, 8-bit data,
  5987.  ' 1 stop bit, 256-byte input buffer):
  5988.  OPEN "COM1:1200,N,8,1" FOR RANDOM AS #1LEN = 256
  5989.  
  5990.  DO                                ' Main communications loop.
  5991.  
  5992.     KeyInput$ = INKEY$            ' Check the keyboard.
  5993.  
  5994.     IF KeyInput$    = Quit$    THEN' Exit the loop if the user
  5995.        EXIT DO' pressed Alt+Q.
  5996.  
  5997.  ELSEIF KeyInput$ <> ""
  5998.  THEN' Otherwise, if the user has
  5999.  
  6000.        PRINT #1,KeyInput$;     ' pressed a key, send the
  6001.     END IF                       ' character typed to modem.
  6002.   ' Check the modem. If characters are waiting (EOF(1) is
  6003.   ' true), get them and print them to the screen:
  6004.   IF NOT EOF(1) THEN
  6005.  
  6006.        ' LOC(1) gives the number of characters waiting:
  6007.        ModemInput$ = INPUT$(LOC(1), #1)
  6008.  
  6009.        Filter ModemInput$        ' Filter out line feeds and
  6010.        PRINT ModemInput$;        ' backspaces, then print.
  6011.     END IF
  6012.  LOOP
  6013.  
  6014.  CLOSE                           ' End communications.
  6015.  CLS
  6016.  END
  6017.  '
  6018.  ' ========================= Filter ========================
  6019.  '               Filters characters in an input string
  6020.  ' =========================================================
  6021.  '
  6022.  SUB Filter (InString$) STATIC
  6023.  
  6024.     ' Look for Backspace characters and recode
  6025.     ' them to CHR$(29) (the Left direction key):
  6026.     DO
  6027.        BackSpace = INSTR(InString$, CHR$(8))
  6028.        IF BackSpace THEN
  6029.        MID$(InString$, BackSpace) = CHR$(29)
  6030.        END IF
  6031.     LOOP WHILE BackSpace
  6032.  
  6033.     ' Look for line-feed characters and
  6034.     ' remove any found:
  6035.     DO
  6036.        LnFd = INSTR(InString$, CHR$(10))
  6037.        IF LnFd THEN
  6038.     InString$=LEFT$(InString$,LnFd-1)+MID$(InString$,LnFd+1)
  6039.        END IF
  6040.     LOOP WHILE LnFd
  6041.  
  6042.  END SUB
  6043.  
  6044.   ────────────────────────────────────────────────────────────────────────────
  6045.  
  6046.  Chapter 4:  String Processing
  6047.  
  6048.  
  6049.  This chapter shows you how to manipulate sequences of ASCII characters,
  6050.  known as strings. String manipulation is indispensable when you are
  6051.  processing text files, sorting data, or modifying string-data input.
  6052.  
  6053.  When you are finished with this chapter, you will know how to perform the
  6054.  following string-processing tasks:
  6055.  
  6056.    ■   Declare fixed-length strings.
  6057.  
  6058.    ■   Compare strings and sort them alphabetically.
  6059.  
  6060.    ■   Search for one string inside another.
  6061.  
  6062.    ■   Get part of a string.
  6063.  
  6064.    ■   Trim spaces from the beginning or end of a string.
  6065.  
  6066.    ■   Generate a string by repeating a single character.
  6067.  
  6068.    ■   Change uppercase letters in a string to lowercase and vice versa.
  6069.  
  6070.    ■   Convert numeric expressions to string representations and vice versa.
  6071.  
  6072.  
  6073.  
  6074.  Strings Defined
  6075.  
  6076.  A string is a sequence of contiguous characters. Examples of characters are
  6077.  the letters of the alphabet (a - z and A - Z), punctuation symbols such as
  6078.  commas (,) or question marks (?), and other symbols from the fields of math
  6079.  and finance such as plus (+) or percent (%) signs.
  6080.  
  6081.  In this chapter, the term "string" can refer to any of the following:
  6082.  
  6083.    ■   A string constant ■   A string variable
  6084.  
  6085.    ■   A string expression
  6086.  String constants are
  6087.  declared in one of two ways:
  6088.  
  6089.  
  6090.  *     By enclosing a sequence of characters between double quotation marks,
  6091.        as in the following  PRINT statement:
  6092.  
  6093.        PRINT "Processing the file. Please wait."
  6094.  
  6095.        This is known as a "literal string constant."
  6096.  
  6097.  *     By setting a name equal to a literal string in a  CONST statement,
  6098.        as in the following:
  6099.  
  6100.        ' Define the string constant MESSAGE:
  6101.         CONST MESSAGE = "Drive door open."
  6102.  
  6103.        This is known as a "symbolic string constant."
  6104.  
  6105.  
  6106.  String variables can be declared in one of three ways:
  6107.  
  6108.    ■   By appending the string-type suffix ( $) to the variable name:
  6109.  LINE INPUT "Enter your name: "; Buffer$  ■   n
  6110.  
  6111.        By using the  DEFSTR statement:
  6112.  
  6113.  
  6114.        ' All variables beginning with the letter "B"
  6115.        ' are strings, unless they end with one of the
  6116.        ' numeric-type suffixes (%, &, !, @, or #):
  6117.        DEFSTR B
  6118.        . . .
  6119.        Buffer = "Your name here"  ' Buffer is a string variable.
  6120.  
  6121.    ■   By declaring the string name in an  AS STRING statement:
  6122.  
  6123.        DIM Buffer1 AS STRING      ' Buffer1 is a variable-length string
  6124.        DIM Buffer2 AS STRING*10   ' Buffer2 is a fixed-length
  6125.                                   ' string 10 bytes long.
  6126.  
  6127.  
  6128.  A string expression is a combination of string variables, constants, and/or
  6129.  string functions.
  6130.  
  6131.  Of course, the character components of strings are not stored in your
  6132.  computer's memory in a form generally recognizable. Instead, each character
  6133.  is translated to a number known as the American Standard Code for
  6134.  Information Interchange code for that character. For example, uppercase "A"
  6135.  is stored as decimal 65 (or hexadecimal 41H), while lowercase "a" is stored
  6136.  as decimal 97 (or hexadecimal 61H).
  6137.  
  6138.  You can also use the BASIC  ASC function to determine the ASCII code for a
  6139.  character; for example, ASC("A") returns the value 65. The inverse of the
  6140.  ASC function is the  CHR$ function.  CHR$ takes an ASCII code as its
  6141.  argument and returns the character with that code; for example, the
  6142.  statement PRINT CHR$(64) displays the character @.
  6143.  
  6144.  
  6145.  Variable- and Fixed-Length Strings
  6146.  
  6147.  In previous versions of BASIC, strings were always variable length. BASIC
  6148.  now supports variable-length strings and fixed-length strings.
  6149.  
  6150.  
  6151.  Variable-Length Strings
  6152.  
  6153.  Variable-length strings are "elastic"; that is, they expand and contract to
  6154.  store different numbers of characters (from none to a maximum of 32,767).
  6155.  
  6156.  Example
  6157.  
  6158.  The length of the variable Temp$ in the following example varies according
  6159.  to the size of what is stored in Temp$:
  6160.  
  6161.   Temp$ = "1234567"
  6162.  
  6163.   ' LEN function returns length of string
  6164.   ' (number of characters it contains):
  6165.   PRINT LEN(Temp$)
  6166.  
  6167.   Temp$ = "1234"
  6168.   PRINT LEN(Temp$)
  6169.  
  6170.  Output
  6171.  
  6172.  7
  6173.   4
  6174.  
  6175.  Fixed-Length Strings
  6176.  
  6177.  Fixed-length strings are commonly used as record elements in a  TYPE... END
  6178.  TYPE user-defined data type. However, they can also be declared by
  6179.  themselves in  COMMON,  DIM,  REDIM,  SHARED, or  STATIC statements, as in
  6180.  the following statement:
  6181.  
  6182.  DIM Buffer AS STRING * 10
  6183.  
  6184.  Examples
  6185.  
  6186.  As their name implies, fixed-length strings have a constant length,
  6187.  regardless of the length of the string stored in them. This is shown by the
  6188.  output from the following example:
  6189.  
  6190.   DIM LastName AS STRING * 12
  6191.   DIM FirstName AS STRING * 10
  6192.  
  6193.   LastName = "Huntington-Ashley"
  6194.   FirstName = "Claire"
  6195.  
  6196.   PRINT "123456789012345678901234567890"
  6197.   PRINT FirstName; LastName
  6198.   PRINT LEN(FirstName)
  6199.   PRINT LEN(LastName)
  6200.  
  6201.  Output
  6202.  
  6203.   123456789012345678901234567890
  6204.   Claire    Huntington-A
  6205.    10
  6206.    12
  6207.  
  6208.  Note that the lengths of the string variables FirstName and LastName did not
  6209.  change, as demonstrated by the values returned by the  LEN function (as well
  6210.  as the four spaces following the six-letter name, Claire).
  6211.  
  6212.  The output from the preceding example also illustrates how values assigned
  6213.  to fixed-length variables are left-justified and, if necessary, truncated on
  6214.  the right. It is not necessary to use the  LSET function to left-justify
  6215.  values in fixed-length strings; this is done automatically. If you want to
  6216.  right-justify a string inside a fixed-length string, use  RSET, as shown
  6217.  here:
  6218.  
  6219.   DIM NameBuffer AS STRING * 10
  6220.   RSET NameBuffer = "Claire"
  6221.   PRINT "1234567890"
  6222.   PRINT NameBuffer
  6223.  
  6224.  Output
  6225.  1234567890
  6226.       Claire
  6227.  
  6228.  Combining Strings
  6229.  
  6230.  Two strings can be combined with the addition operator ( +). The string
  6231.  following the plus operator is appended to the string preceding the plus
  6232.  operator.
  6233.  
  6234.  Examples
  6235.  
  6236.  The following example combines two strings:
  6237.  
  6238.   A$ = "first string"
  6239.   B$ = "second string"
  6240.   C$ = A$ + B$
  6241.   PRINT C$
  6242.  
  6243.  Output
  6244.  
  6245.  first stringsecond string
  6246.  
  6247.  The process of combining strings in this way is called "concatenation,"
  6248.  which means linking together.
  6249.  
  6250.  Note that in the previous example, the two strings are combined without any
  6251.  intervening spaces. If you want a space in the combined string, you could
  6252.  pad one of the strings A$ or B$, like this:
  6253.  
  6254.  B$ = " second string"          ' Leading blank in B$
  6255.  Because values are
  6256.  left-justified in fixed-length strings, you may find extra spaces when you
  6257.  concatenate them, as in this example:
  6258.  
  6259.  
  6260.   TYPE NameType
  6261.      First AS STRING * 10
  6262.      Last  AS STRING * 12
  6263.   END TYPE
  6264.  
  6265.   DIM NameRecord AS NameType
  6266.  
  6267.   ' The constant "Ed" is left-justified in the variable
  6268.   ' NameRecord.First, so there are eight trailing blanks:
  6269.   NameRecord.First = "Ed"
  6270.   NameRecord.Last  = "Feldstein"
  6271.  
  6272.   ' Print a line of numbers for reference:
  6273.   PRINT "123456789012345678901234567890"
  6274.  
  6275.   WholeName$ = NameRecord.First + NameRecord.Last
  6276.   PRINT WholeName$
  6277.  
  6278.  Output
  6279.  
  6280.  123456789012345678901234567890
  6281.   Ed        Feldstein
  6282.  
  6283.  The  LTRIM$ function returns a string with its leading spaces stripped away,
  6284.  while the  RTRIM$ function returns a string with its trailing spaces
  6285.  stripped away. The original string is unaltered. (See the section
  6286.  "Retrieving Parts of Strings" later in this chapter for more information on
  6287.  these functions.)
  6288.  
  6289.  
  6290.  Comparing Strings
  6291.  
  6292.  Strings are compared with the following relational operators:
  6293.  
  6294. ╓┌──────────────────┌────────────────────────────────────────────────────────╖
  6295.  ────────────────────────────────────────────────────────────────────────────
  6296.   <>                Not equal
  6297.   =                 Equal
  6298.   <                 Less than
  6299.   >                 Greater than
  6300.   <=                Less than or equal to
  6301.   >=                Greater than or equal to
  6302.  ────────────────────────────────────────────────────────────────────────────
  6303.  >=                Greater than or equal to
  6304.  
  6305.  
  6306.  
  6307.  
  6308.  A single character is greater than another character if its ASCII value is
  6309.  greater. For example, the ASCII value of the letter "B" is greater than the
  6310.  ASCII value of the letter "A," so the expression  "B" > "A" is true.
  6311.  
  6312.  When comparing two strings, BASIC looks at the ASCII values of
  6313.  corresponding characters. The first character where the two strings
  6314.  differ determines the alphabetical order of the strings.
  6315.  
  6316.  For instance, the strings "doorman" and "doormats" are
  6317.  the same up to the seventh character in each ("n"  and "t"). Since the ASCII
  6318.  value of "n" is less than the ASCII value of "t," the expression "doorman" <
  6319.  "doormats" is true. Note that the ASCII values for letters follow
  6320.  alphabetical order from A to Z and from a to z, so you can use the
  6321.  relational operators listed in the preceding table to alphabetize strings.
  6322.  Moreover, uppercase letters have smaller ASCII values than lowercase
  6323.  letters, so in a sorted list of strings, "ASCII"  would come before "ascii."
  6324.  
  6325.  If there is no difference between corresponding characters of two strings
  6326.  and they are the same length, then the two strings are equal. If there is no
  6327.  difference between corresponding characters of two strings, but one of the
  6328.  strings is longer, then the longer string is greater than the shorter
  6329.  string. For example abc =abc and aaaaaaa >aaa are true expressions.
  6330.  
  6331.  Leading and trailing blank spaces are significant in string comparisons. For
  6332.  example, the string "   test" is less than the string "test," since a blank
  6333.  space (" ") is less than a "t"; on the other hand, the string "test  " is
  6334.  greater than the string "test." Keep this in mind when comparing
  6335.  fixed-length and variable-length strings, since fixed-length strings may
  6336.  contain trailing spaces.
  6337.  
  6338.  
  6339.  Searching for Strings
  6340.  
  6341.  One of the most common string-processing tasks is searching for a string
  6342.  inside another string. The  INSTR( stringexpression1$,  stringexpression2$)
  6343.  function tells you whether or not  string2 is contained in
  6344.  stringexpression1$ by returning the position of the first character in
  6345.  stringexpression1$ (if any) where the match begins.
  6346.  
  6347.  Examples
  6348.  
  6349.  The following example finds the starting position of one string inside
  6350.  another string:
  6351.  
  6352.   String1$ = "A line of text with 37 letters in it."
  6353.   String2$ = "letters"
  6354.  
  6355.   PRINT "         1         2         3         4"
  6356.   PRINT "1234567890123456789012345678901234567890"
  6357.   PRINT String1$
  6358.   PRINT String2$
  6359.   PRINT INSTR(String1$, String2$)
  6360.  
  6361.  Output
  6362.  
  6363.   1         2         3         4
  6364.   1234567890123456789012345678901234567890
  6365.   A line of text with 37 letters in it.
  6366.   letters
  6367.    24
  6368.  
  6369.  If no match is found (that is,  stringexpression2$ is not a substring of
  6370.  stringexpression1$),  INSTR returns the value 0.
  6371.  
  6372.  A variation of the preceding syntax,  INSTR( start%,
  6373.     stringexpression1$,  stringexpression2$),
  6374.  allows you to specify where you want the search to start in  stringexpression
  6375.  To illustrate, making the following modification to the preceding example
  6376.  changes the output:
  6377.  
  6378.  
  6379.   ' Start looking for a match at the 30th
  6380.   ' character of String1$:
  6381.   PRINT INSTR(30, String1$, String2$)
  6382.  
  6383.  Output
  6384.  
  6385.   1         2         3         4
  6386.   1234567890123456789012345678901234567890
  6387.   A line of text with 37 letters in it.
  6388.   letters
  6389.    0
  6390.  
  6391.  In other words, the string letters does not appear after the 30th character
  6392.  of String1$.
  6393.  
  6394.  The  INSTR( position,   string1,   string2) variation is useful for finding
  6395.  every occurrence of  stringexpression2$ in  stringexpression1$ instead of
  6396.  just the first occurrence, as shown in the next example:
  6397.  
  6398.   String1$ = "the dog jumped over the broken saxophone."
  6399.   String2$ = "the"
  6400.   PRINT String1$
  6401.  
  6402.   Start      = 1
  6403.   NumMatches = 0
  6404.  
  6405.   DO
  6406.      Match = INSTR(Start, String1$, String2$)
  6407.      IF Match > 0 THEN
  6408.         PRINT TAB(Match); String2$
  6409.         Start      = Match + 1
  6410.         NumMatches = NumMatches + 1
  6411.      END IF
  6412.   LOOP WHILE MATCH
  6413.  
  6414.   PRINT "Number of matches ="; NumMatches
  6415.  
  6416.  Output
  6417.  
  6418.   the dog jumped over the broken saxophone.
  6419.   the
  6420.   the
  6421.   Number of matches = 2
  6422.  
  6423.  Retrieving Parts of Strings
  6424.  
  6425.  The section "Combining Strings" shows how to put strings together
  6426.  (concatenate them) by using the addition operator ( +). Several functions
  6427.  are available in BASIC that take strings apart, returning pieces of a
  6428.  string, either from the left side, the right side, or the middle of a target
  6429.  string.
  6430.  
  6431.  
  6432.  Retrieving Characters from the Left Side of a String
  6433.  
  6434.  The  LEFT$ and  RTRIM$ functions return characters from the left side of a
  6435.  string. The  LEFT$( stringexpression$,   n% ) function returns  number
  6436.  characters from the left side of  string.
  6437.  
  6438.  Examples
  6439.  
  6440.  The following example retrieves four characters from the left side of a
  6441.  string:
  6442.  
  6443.   Test$ = "Overtly"
  6444.  
  6445.   ' Print the leftmost 4 characters from Test$:
  6446.   PRINT LEFT$(Test$, 4)
  6447.  
  6448.  Output
  6449.  
  6450.  Over
  6451.  
  6452.  Note that  LEFT$, like the other functions described in this chapter, does
  6453.  not change the original string Test$; it merely returns a different string,
  6454.  a copy of part of Test$.
  6455.  
  6456.  The  RTRIM$ function returns the left part of a string after removing any
  6457.  blank spaces at the end. For example, contrast the output from the two
  6458.  PRINT statements in the following example:
  6459.  
  6460.   PRINT "a left-justified string      "; "next"
  6461.   PRINT RTRIM$("a left-justified string      "); "next"
  6462.  
  6463.  Output
  6464.  
  6465.   a left-justified string      next
  6466.   a left-justified stringnext
  6467.  
  6468.  The  RTRIM$ function is useful for comparing fixed-length and
  6469.  variable-length strings, as in the next example:
  6470.  
  6471.   DIM NameRecord AS STRING * 10
  6472.   NameRecord = "Ed"
  6473.  
  6474.   NameTest$ = "Ed"
  6475.  
  6476.  
  6477.  ' Use RTRIM$ to trim
  6478.  all the blank spaces from the right
  6479.  
  6480.   ' side of the fixed-length string NameRecord, then compare
  6481.   ' it with the variable-length string NameTest$:
  6482.   IF RTRIM$(NameRecord) = NameTest$ THEN
  6483.      PRINT "They're equal"
  6484.   ELSE
  6485.      PRINT "They're not equal"
  6486.   END IF
  6487.  
  6488.  Output
  6489.  
  6490.  They're equal
  6491.  
  6492.  
  6493.  Retrieving Characters from the Right Side of a String
  6494.  
  6495.  The  RIGHT$ and  LTRIM$ functions return characters from the right side of a
  6496.  string. The  RIGHT$( stringexpression$,  n%) function returns  n% characters
  6497.  from the right side of  stringexpression$.
  6498.  
  6499.  Examples
  6500.  
  6501.  The following example retrieves five characters from the right side of a
  6502.  string:
  6503.  
  6504.   Test$ = "1234567890"
  6505.  
  6506.   ' Print the rightmost 5 characters from Test$:
  6507.   PRINT RIGHT$(Test$,5)
  6508.  
  6509.  Output
  6510.  
  6511.  67890
  6512.  
  6513.  The  LTRIM$ function returns the right part of a string after removing any
  6514.  blank spaces at the beginning. For example, contrast the output from the
  6515.  next two  PRINT statements:
  6516.  
  6517.   PRINT "first"; "      a right-justified string"
  6518.   PRINT "first"; LTRIM$("      a right-justified string")
  6519.  
  6520.  Output
  6521.  
  6522.   first      a right-justified string
  6523.   firsta right-justified string
  6524.  
  6525.  
  6526.  Retrieving Characters from Anywhere in a String
  6527.  
  6528.  Use the  MID$ function to retrieve any number of characters from any point
  6529.  in a string. The  MID$( stringexpression$,   start%,   length% ) function
  6530.  returns  n% characters from  stringexpression$, starting at the character
  6531.  with position  start%. For example, the following statement starts at the
  6532.  12th character of the string ("h") and returns the next three characters
  6533.  ("hill"):
  6534.  
  6535.  MID$("**over the hill**", 12, 4)
  6536.  
  6537.  Example
  6538.  
  6539.  The following example shows how to use the  MID$ function to step through a
  6540.  line of text character by character:
  6541.  
  6542.   ' Get the number of characters in the string of text:
  6543.   Length = LEN(TextString$)
  6544.  
  6545.   FOR I = 1 TO Length
  6546.  
  6547.     ' Get the first character, then the second, third,
  6548.     ' and so forth, up to the end of the string:
  6549.     Char$ = MID$(TextString$, I, 1)
  6550.  
  6551.     ' Evaluate that character:
  6552.     .
  6553.     .
  6554.     .
  6555.   NEXT
  6556.  
  6557.  
  6558.  Generating Strings
  6559.  
  6560.  The easiest way to create a string of one character repeated over and over
  6561.  is to use the intrinsic function  STRING$. The  STRING$( m%,
  6562.  stringexpression$) function produces a new string with  m% characters, each
  6563.  character of which is the first character of  stringexpression$. For
  6564.  example, the following statement generates a string of 27 asterisks:
  6565.  
  6566.  Filler$ = STRING$(27, "*")
  6567.  
  6568.  
  6569.  Example
  6570.  
  6571.  For characters that cannot be produced by typing, such as characters whose
  6572.  ASCII values are greater than 127, use the alternative form of this
  6573.  function,  STRING$( m%,  n%). This form creates a string with  m%
  6574.  characters, each character of which has the ASCII code specified by  m%, as
  6575.  in the next example:
  6576.  
  6577.   ' Print a string of 10 "@" characters
  6578.   ' (64 is ASCII code for @):
  6579.   PRINT STRING$(10, 64)
  6580.  
  6581.  Output
  6582.  
  6583.  @@@@@@@@@@
  6584.  The  SPACE$( n)
  6585.  function generates a string consisting of  n blank spaces.
  6586.  
  6587.  
  6588.  Changing the Case of Letters
  6589.  
  6590.  You may want to convert uppercase (capital) letters in a string to lowercase
  6591.  or vice versa. This conversion is useful in a non-case-sensitive search for
  6592.  a particular string pattern in a large file (in other words, "help," "HELP,"
  6593.  and "Help" are all be considered the same). These functions are also handy
  6594.  when you are not sure whether a user will input text in uppercase or
  6595.  lowercase letters.
  6596.  
  6597.  The  UCASE$ and  LCASE$ functions make the following conversions to a
  6598.  string:
  6599.  
  6600.    ■    UCASE$ returns a copy of the string passed to it, with all the
  6601.        lowercase letters converted to uppercase.
  6602.  
  6603.    ■    LCASE$ returns a copy of the string passed to it, with all the
  6604.        uppercase letters converted to lowercase.
  6605.  
  6606.  
  6607.  Example
  6608.  
  6609.  The following example prints a string unchanged, in all uppercase letters,
  6610.  and in all lowercase letters:
  6611.  
  6612.   Sample$ = "* The ASCII Appendix: a table of useful codes *"
  6613.   PRINT Sample$
  6614.   PRINT UCASE$(Sample$)
  6615.   PRINT LCASE$(Sample$)
  6616.  
  6617.  Output
  6618.  
  6619.   * The ASCII Appendix: a table of useful codes *
  6620.   * THE ASCII APPENDIX: A TABLE OF USEFUL CODES *
  6621.   * the ascii appendix: a table of useful codes *
  6622.  
  6623.  Letters that are already uppercase, as well as characters that are not
  6624.  letters, remain unchanged by  UCASE$; similarly, lowercase letters and
  6625.  characters that are not letters are unaffected by  LCASE$.
  6626.  
  6627.  
  6628.  Strings and Numbers
  6629.  
  6630.  BASIC does not allow a string to be assigned to a numeric variable, nor does
  6631.  it allow a numeric expression to be assigned to a string variable. For
  6632.  example, if you use either of these statements, BASIC will generate the
  6633.  error message Type mismatch.
  6634.  
  6635.   TempBuffer$ = 45
  6636.   Counter% = "45"
  6637.  
  6638.  Instead, use the  STR$ function to return the string
  6639.  representation of a number or the  VAL function to return
  6640.  the numeric representation of a string:
  6641.  
  6642.   ' The following statements are both valid:
  6643.   TempBuffer$ = STR$(45)
  6644.   Counter%  = VAL("45")
  6645.  Example
  6646.  
  6647.  Note that  STR$ includes the leading blank space that BASIC prints for
  6648.  positive numbers, as this short example shows:
  6649.  
  6650.   FOR I = 0 TO 9
  6651.      PRINT STR$(I);
  6652.   NEXT
  6653.  
  6654.  Output
  6655.  
  6656.  0 1 2 3 4 5 6 7 8 9
  6657.  
  6658.  If you want to eliminate this space, you can use the  LTRIM$ function, as
  6659.  shown in the following example:
  6660.  
  6661.   FOR I = 0 TO 9
  6662.      PRINT LTRIM$(STR$(I));
  6663.   NEXT
  6664.  
  6665.  Output
  6666.  
  6667.  0123456789
  6668.  
  6669.  Another way to format numeric output is with the  PRINT USING statement (see
  6670.  the section "Printing Text on the Screen" in Chapter 3, "File and Device
  6671.  I/O").
  6672.  
  6673.  
  6674.  Changing Strings
  6675.  
  6676.  The functions mentioned in each of the preceding sections all leave their
  6677.  string arguments unchanged. Changes are made to a copy of the argument.
  6678.  
  6679.  In contrast, the  MID$ statement (unlike the  MID$ function discussed in the
  6680.  section "Retrieving Characters from Anywhere in a String" earlier in this
  6681.  chapter) changes its argument by embedding another string in it. The
  6682.  embedded string replaces part or all of the original string.
  6683.  
  6684.  Example
  6685.  
  6686.  The following example replaces parts of a string:
  6687.  
  6688.   Temp$ = "In the sun."
  6689.   PRINT Temp$
  6690.  
  6691.   ' Replace the "I" with an "O":
  6692.   MID$(Temp$,1) = "O"
  6693.  
  6694.   ' Replace "sun." with "road":
  6695.   MID$(Temp$,8) = "road"
  6696.   PRINT Temp$
  6697.  
  6698.  Output
  6699.  
  6700.   In the sun.
  6701.   On the road
  6702.  
  6703.  
  6704.  Sample Application: Converting a String to a Number (STRTONUM.BAS)
  6705.  
  6706.  The following program takes a number that is input as a string, filters any
  6707.  numeric characters (such as commas) out of the string, then converts the
  6708.  string to a number with the  VAL function.
  6709.  
  6710.  
  6711.  Functions Used
  6712.  
  6713.  This program demonstrates the following string-handling functions discussed
  6714.  in this chapter:
  6715.  
  6716.    ■    INSTR ■    LEN
  6717.  
  6718.    ■    MID$ ■    VAL
  6719.  
  6720.  
  6721.  
  6722.  Program Listing
  6723.  
  6724.   ' Input a line:
  6725.   LINE INPUT "Enter a number with commas: "; A$
  6726.  
  6727.  ' Look only for valid numeric characters (0123456789.-)
  6728.  ' in the input string:
  6729.  CleanNum$ = Filter$(A$, "0123456789.-")
  6730.  
  6731.  ' Convert the string to a number:
  6732.  PRINT "The number's value = "; VAL(CleanNum$)
  6733.  END
  6734.  
  6735.  ' ========================== Filter$ =======================
  6736.  '         Takes unwanted characters out of a string by
  6737.  '         comparing them with a filter string containing
  6738.  '         only acceptable numeric characters
  6739.  ' =========================================================
  6740.  
  6741.  FUNCTION Filter$ (Txt$, FilterString$) STATIC
  6742.     Temp$ = ""
  6743.     TxtLength = LEN(Txt$)
  6744.  
  6745.     FOR I = 1 TO TxtLength     ' Isolate each character in
  6746.        C$ = MID$(Txt$, I, 1)   ' the string.
  6747.  
  6748.        ' If the character is in the filter string, save it:
  6749.        IF INSTR(FilterString$, C$) <> 0 THEN
  6750.           Temp$ = Temp$ + C$
  6751.        END IF
  6752.     NEXT I
  6753.  
  6754.     Filter$ = Temp$
  6755.  END FUNCTION
  6756.  
  6757.  
  6758.  
  6759.   ────────────────────────────────────────────────────────────────────────────
  6760.  
  6761.  Chapter 5:  Graphics
  6762.  
  6763.  This chapter shows you how to use the graphics statements and functions
  6764.  of Microsoft BASIC to create a wide variety of shapes, colors, and
  6765.  patterns on your screen. With graphics, you can add a visual dimension to
  6766.  almost any program, whether it's a game, an educational tool, a scientific
  6767.  application, or a financial package.
  6768.  
  6769.  When you have finished studying this chapter, you will know how to perform
  6770.  the following graphics tasks:
  6771.  
  6772.    ■   Use the physical-coordinate system of your personal computer's screen
  6773.        to locate individual pixels, turn those pixels on or off, and change
  6774.        their colors.
  6775.  
  6776.    ■   Draw lines.
  6777.  
  6778.    ■   Draw and fill simple shapes, such as circles, ovals, and boxes.
  6779.  
  6780.    ■   Restrict the area of the screen showing graphics output by using
  6781.        viewports.
  6782.  
  6783.    ■   Adjust the coordinates used to locate a pixel by redefining screen
  6784.        coordinates.
  6785.  
  6786.    ■   Use color in graphics output.
  6787.  
  6788.    ■   Create patterns and use them to fill enclosed figures.
  6789.  
  6790.    ■   Copy images and reproduce them in another location on the screen.
  6791.  
  6792.    ■   Animate graphics output.
  6793.  
  6794.  
  6795.  The next section contains important information on what you'll need to run
  6796.  the graphics examples shown in this chapter. Read it first.
  6797.  
  6798.  
  6799.  Note
  6800.  
  6801.  To learn how to use BASIC's new presentation graphics, see Chapter 6,
  6802.  "Presentation Graphics."
  6803.  
  6804.  
  6805.  What You Need for Graphics Programs
  6806.  
  6807.  To run the graphics examples shown in this chapter, your computer must have
  6808.  graphics capability, either built in or in the form of a graphics card such
  6809.  as the Color Graphics Adapter (CGA), Enhanced Graphics Adapter (EGA), or
  6810.  Video Graphics Array (VGA). You also need a video display (either monochrome
  6811.  or color) that supports pixel-based graphics.
  6812.  
  6813.  Also, these programs all require that your screen be in one of the
  6814.  "screen modes" supporting graphics output. (The screen mode controls
  6815.  the clarity of graphics images, the number of colors available, and the
  6816.  part of the video memory to display.) To select a graphics output mode,
  6817.  use the following statement in your program before using any of the
  6818.  graphics statements or functions described in this chapter:
  6819.  
  6820.  
  6821.   SCREEN  mode%
  6822.  Here,  mode% can be either 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, or 13, depending
  6823.  on the monitor/adapter combination installed on your computer.
  6824.  
  6825.  If you are not sure whether or not the users of your programs have hardware
  6826.  that supports graphics, you can use the following simple test:
  6827.  
  6828.   CONST FALSE = 0, TRUE = NOT FALSE
  6829.  
  6830.   ' Test to make sure user has hardware
  6831.   ' with color/graphics capability:
  6832.   ON ERROR GOTO Message      ' Turn on error trapping.
  6833.   SCREEN 1                   ' Try graphics mode one.
  6834.   ON ERROR GOTO 0            ' Turn off error trapping.
  6835.   IF NoGraphics THEN END     ' End if no graphics hardware.
  6836.   .
  6837.   .
  6838.   .
  6839.   END
  6840.  
  6841.   ' Error-handling routine:
  6842.   Message:
  6843.      PRINT "This program requires graphics capability."
  6844.      NoGraphics = TRUE
  6845.      RESUME NEXT
  6846.  
  6847.  If the user has only a monochrome display with no graphics adapter, the
  6848.  SCREEN statement produces an error that in turn triggers a branch to the
  6849.  error-handling-routine message. (See Chapter 8, "Error Handling," for more
  6850.  information on error handling.)
  6851.  
  6852.  
  6853.  Pixels and Screen Coordinates
  6854.  
  6855.  Shapes and figures on a video display are composed of individual dots of
  6856.  light known as picture elements or "pixels" (or sometimes as "pels") for
  6857.  short. BASIC draws and paints on the screen by turning these pixels on or
  6858.  off and by changing their colors.
  6859.  
  6860.  A typical screen is composed of a grid of pixels. The exact number of
  6861.  pixels in this grid depends on the hardware you have installed and the
  6862.  screen mode you have selected in the  SCREEN statement. The larger the
  6863.  number of pixels, the higher the clarity of graphics output. For example,
  6864.  SCREEN 1 gives a resolution of 320 pixels horizontally by 200 pixels
  6865.  vertically (320 x 200 pixels), while SCREEN 2 gives a resolution of
  6866.  640 x 200 pixels. The higher horizontal density in screen mode 2 -- 640
  6867.  pixels per row versus 320 pixels per row -- gives images a sharper,
  6868.  less ragged appearance than they have in screen mode 1.
  6869.  
  6870.  Depending on the graphics capability of your system, you can use other
  6871.  screen modes that support even higher resolutions (as well as adjust other
  6872.  screen characteristics). Consult online Help for more information.
  6873.  
  6874.  When your screen is in one of the graphics modes, you can locate each pixel
  6875.  by means of pairs of coordinates. The first number in each coordinate pair
  6876.  tells the number of pixels from the left side of the screen, while the
  6877.  second number in each pair tells the number of pixels from the top of the
  6878.  screen. For example, in screen mode 2 the point in the extreme upper-left
  6879.  corner of the screen has coordinates (0, 0), the point in the center of the
  6880.  screen has coordinates (320, 100), and the point in the extreme lower-right
  6881.  corner of the screen has coordinates (639, 199), as shown in Figure 5.1.
  6882.  
  6883.  BASIC uses these screen coordinates to determine where to display
  6884.  graphics (for example, to locate the end points of a line or the center of a
  6885.  circle), as shown in the next section "Drawing Basic Shapes: Points, Lines,
  6886.  Boxes, and Circles."
  6887.  
  6888.  
  6889.  Graphics coordinates differ from text-mode coordinates specified in a
  6890.   LOCATE statement. First,  LOCATE is not as
  6891.  precise: graphics coordinates pinpoint individual pixels on the screen,
  6892.  whereas coordinates used by  LOCATE are character positions.
  6893.  Second, text-mode coordinates are given in the form "row, column," as in the
  6894.  following:
  6895.  
  6896.   ' Move to the 13th row, 15th column,
  6897.   ' then print the message shown:
  6898.   LOCATE 13, 15
  6899.   PRINT "This should appear in the middle of the screen."
  6900.  This is the reverse of graphics coordinates, which are given in the form
  6901.  "column, row." A  LOCATE statement has no effect on the positioning of
  6902.  graphics output on the screen.
  6903.  
  6904.  
  6905.  Drawing Basic Shapes: Points, Lines, Boxes, and Circles
  6906.  
  6907.  You can pass coordinate values to BASIC graphics statements to produce a
  6908.  variety of simple figures, as shown in the following sections.
  6909.  
  6910.  
  6911.  Plotting Points with PSET and PRESET
  6912.  
  6913.  The most fundamental level of control over graphics output is simply turning
  6914.  individual pixels on and off. You do this in BASIC with the  PSET (for pixel
  6915.  set) and  PRESET (for pixel reset) statements. The statement  PSET ( x%,  y%
  6916.  ) gives the pixel at location ( x%,  y%) the current foreground color. The
  6917.  PRESET ( x%,  y% ) statement gives the pixel at location ( x%,  y%) the curre
  6918.  
  6919.  On monochrome monitors, the foreground color is the color that is used for
  6920.  printed text and is typically white, amber, or light green; the background
  6921.  color on monochrome monitors is typically black or dark green. You can
  6922.  choose another color for  PSET and  PRESET to use by adding an optional
  6923.  color argument. The syntax is then:
  6924.  
  6925.  {
  6926.   PSET|PRESET} ( x%, y%), color%
  6927.  
  6928.  See the section "Using Colors" later in this chapter for more information
  6929.  on choosing colors.
  6930.  
  6931.  Because  PSET uses the current foreground color by default and  PRESET uses
  6932.  the current background color by default,  PRESET without a  color argument
  6933.  erases a point drawn with  PSET, as shown in the next example:
  6934.  
  6935.   SCREEN 2' 640 x 200 resolution
  6936.   PRINT "Press any key to end."
  6937.  
  6938.   DO
  6939.  
  6940.      ' Draw a horizontal line from the left to the right:
  6941.      FOR X = 0 TO 640
  6942.         PSET (X, 100)
  6943.      NEXT
  6944.  ' Erase the line from
  6945.  the right to the left:
  6946.  
  6947.      FOR X = 640 TO 0 STEP -1
  6948.         PRESET (X, 100)
  6949.      NEXT
  6950.  
  6951.   LOOP UNTIL INKEY$ <> ""
  6952.   END
  6953.  
  6954.  While it is possible to draw any figure using only  PSET statements to
  6955.  manipulate individual pixels, the output tends to be rather slow, since most
  6956.  pictures consist of many pixels. BASIC has several statements that
  6957.  dramatically increase the speed with which simple figures -- such as lines,
  6958.  boxes, and ellipses -- are drawn, as shown in the following sections
  6959.  "Drawing Lines and Boxes with LINE" and "Drawing Circles and Ellipses with
  6960.  CIRCLE."
  6961.  
  6962.  
  6963.  Drawing Lines and Boxes with LINE
  6964.  
  6965.  When using  PSET or  PRESET, you specify only one coordinate pair since you
  6966.  are dealing with only one point on the screen. With  LINE, you specify two
  6967.  pairs, one for each end of a line segment. The simplest form of the  LINE
  6968.  statement is as follows:
  6969.  
  6970.   LINE( x1%, y1%) - ( x2%, y2%)
  6971.  
  6972.  where ( x1%,  y1%) are the coordinates of one
  6973.  end of a line segment and ( x2%,  y2%) are the coordinates of the other. For
  6974.  example, the following statement draws a straight line from the pixel with
  6975.  coordinates (10, 10) to the pixel with coordinates (150, 200):
  6976.  
  6977.  
  6978.   SCREEN 1
  6979.   LINE (10, 10)-(150, 200)
  6980.  
  6981.  Note that BASIC does not care about the order of the coordinate pairs: a
  6982.  line from ( x1%,  y1%) to ( x2%,  y2%) is the same as a line from ( x2%,
  6983.  y2%) to ( x1%,  y1%). This means you could also write the preceding
  6984.  statement as:
  6985.  
  6986.   SCREEN 1
  6987.   LINE (150, 200)-(10, 10)
  6988.  
  6989.  However, reversing the order of the coordinates could have an effect on
  6990.  graphics statements that follow, as shown in the next section.
  6991.  
  6992.  
  6993.  Using the STEP Keyword
  6994.  
  6995.  Up to this point, screen coordinates have been presented as absolute values
  6996.  measuring the horizontal and vertical distances from the extreme upper-left
  6997.  corner of the screen, which has coordinates (0, 0). However, by using the
  6998.  STEP keyword in any of the following graphics statements, you can make the
  6999.  coordinates that follow  STEP relative to the last point referred to on the
  7000.  screen:
  7001.  
  7002.   CIRCLE                     PRESET
  7003.   GET                        PSET
  7004.   LINE                       PUT
  7005.   PAINT
  7006.  
  7007.  
  7008.  If you picture images as being drawn on the screen by a tiny paintbrush
  7009.  exactly the size of one pixel, then the last point referenced is the
  7010.  location of this paintbrush, or "graphics cursor," when it finishes drawing
  7011.  an image. For example, the following statements leave the graphics cursor at
  7012.  the pixel with coordinates (100, 150):
  7013.  
  7014.   SCREEN 2
  7015.   LINE (10, 10)-(100, 150)
  7016.  
  7017.  If the next graphics statement in the program is as follows, then the point
  7018.  plotted by  PSET is not in the upper-left quadrant of the screen:
  7019.  
  7020.  PSET STEP(20, 20)
  7021.  
  7022.  Instead,  STEP has made the coordinates (20, 20) relative to the last point
  7023.  referenced, which has coordinates (100, 150). This makes the absolute
  7024.  coordinates of the point (100 + 20, 150 + 20) or (120, 170).
  7025.  
  7026.  
  7027.  Example
  7028.  
  7029.  In the preceding example, the last point referenced is determined by a
  7030.  preceding graphics statement. You can also establish a reference point
  7031.  within the same statement, as shown in this example:
  7032.  
  7033.   ' Set 640 x 200 pixel resolution, and make the last
  7034.   ' point referenced the center of the screen:
  7035.   SCREEN 2
  7036.  
  7037.   ' Draw a line from the lower-left corner of the screen
  7038.   ' to the upper-left corner:
  7039.   LINE STEP(-310, 100) -STEP(0, -200)
  7040.  
  7041.   ' Draw the "stair steps" down from the upper-left corner
  7042.   ' to the right side of the screen:
  7043.   FOR I% = 1 TO 20
  7044.      LINE -STEP(30, 0)' Draw the horizontal line.
  7045.      LINE -STEP(0, 5)' Draw the vertical line.
  7046.   NEXT
  7047.  
  7048.   ' Draw the unconnected vertical line segments from the
  7049.   ' right side to the lower-left corner:
  7050.   FOR I% = 1 TO 20
  7051.      LINE STEP(-30, 0) -STEP(0, 5)
  7052.   NEXT
  7053.  
  7054.   SLEEP' Wait for a keystroke.
  7055.  
  7056.  Note the  SLEEP statement at the end of the last program. If you are running
  7057.  a compiled, stand-alone BASIC program that produces graphics output, your
  7058.  program needs a mechanism like this at the end to hold the output on the
  7059.  screen. Otherwise, it vanishes from the screen before the user notices it.
  7060.  
  7061.  
  7062.  Drawing Boxes
  7063.  
  7064.  Using the forms of the  LINE statement already presented, it is quite easy
  7065.  to write a short program that connects four straight lines to form a box, as
  7066.  shown here:
  7067.  
  7068.   SCREEN 1' 320 x 200 pixel resolution
  7069.  
  7070.  ' Draw a box measuring
  7071.  120 pixels on a side:
  7072.  
  7073.   LINE (50, 50)-(170, 50)
  7074.   LINE -STEP(0, 120)
  7075.   LINE -STEP(-120, 0)
  7076.   LINE -STEP(0, -120)
  7077.   However, BASIC provides an even simpler way to draw a box, using a single
  7078.  LINE statement with the  B (for box) option. The  B option is shown in the
  7079.  next example, which produces the same output as the four  LINE statements in
  7080.  the preceding program:
  7081.  
  7082.   SCREEN 1' 320 x 200 pixel resolution
  7083.  
  7084.   ' Draw a box with coordinates (50, 50) for the upper-left
  7085.   ' corner, and (170, 170) for the lower-right corner:
  7086.   LINE (50, 50)-(170, 170), , B
  7087.  
  7088.  When you add the  B option, the  LINE statement no longer connects the two
  7089.  points you specify with a straight line; instead, it draws a rectangle whose
  7090.  opposite corners (upper left and lower right) are at the locations
  7091.  specified.
  7092.  
  7093.  Two commas precede the  B in the last example. The first comma functions
  7094.  here as a placeholder for the unused  color& argument, which allows you to
  7095.  pick the color for a line or the sides of a box. (See the section "Using
  7096.  Colors" later in this chapter for more information on the use of color.)
  7097.  
  7098.  As before, it does not matter what order the coordinate pairs are given in,
  7099.  so the rectangle from the last example could also be drawn with this
  7100.  statement:
  7101.  
  7102.  LINE (170, 170)-(50, 50), , B
  7103.  
  7104.  Adding the F (for fill) option after  B fills the interior of the box with
  7105.  the same color used to draw the sides. With a monochrome display, this is
  7106.  the same as the foreground color used for printed text. If your hardware has
  7107.  color capabilities, you can change this color with the optional  color&
  7108.  argument (see the section "Selecting a Color for Graphics Output" later in
  7109.  this chapter).
  7110.  
  7111.  The syntax introduced here for drawing a box is the general syntax used in
  7112.  BASIC to define a rectangular graphics region, and it also appears in the
  7113.  GET and  VIEW statements:
  7114.  
  7115.   GET |  LINE |  VIEW} ( x1!, y1!) - ( x2!, y2!),...
  7116.  
  7117.  Here, ( x1!,  y1!) and (
  7118.  x2!,  y2!) are the coordinates of diagonally opposite corners of the
  7119.  rectangle (upper left and lower right). (See "Defining a Graphics Viewport"
  7120.  later in this chapter for a discussion of  VIEW, and "Basic Animation
  7121.  Techniques" later in this chapter for information on  GET and  PUT.)
  7122.  
  7123.  
  7124.  
  7125.  Drawing Dotted Lines
  7126.  
  7127.  The previous sections explain how to use  LINE to draw solid lines and use
  7128.  them in rectangles; that is, no pixels are skipped. Using yet another option
  7129.  with  LINE, you can draw dashed or dotted lines instead. This process is
  7130.  known as "line styling." The following is the syntax for drawing a single
  7131.  dashed line from point ( x1,   y1) to point ( x2,   y2) using the current for
  7132.  
  7133.   LINE ( x1!, y1!) - ( x2!, y2!),, B, style%
  7134.  
  7135.  Here  style% is a 16-bit decimal or hexadecimal integer.
  7136.  The  LINE statement uses the binary representation
  7137.  of the line-style argument to create dashes and blank spaces, with a 1 bit
  7138.  meaning "turn on the pixel," and a 0 bit meaning "leave the pixel off." For
  7139.  example, the hexadecimal integer &HCCCC is equal to the binary integer
  7140.  1100110011001100, and when used as a  style% argument it draws a line
  7141.  alternating two pixels on, two pixels off.
  7142.  
  7143.  
  7144.  Example
  7145.  
  7146.  The following example shows different dashed lines produced using different
  7147.  values for  style%:
  7148.  
  7149.   SCREEN 2' 640 x 200 pixel resolution
  7150.  
  7151.   ' Style data:
  7152.   DATA &HCCCC, &HFF00, &HF0F0
  7153.   DATA &HF000, &H7777, &H8888
  7154.  
  7155.   Row% = 4
  7156.   Column% = 4
  7157.   XLeft% = 60
  7158.   XRight% = 600
  7159.   Y% = 28
  7160.  
  7161.   FOR I% = 1 TO 6
  7162.      READ Style%
  7163.      LOCATE Row%, Column%
  7164.      PRINT HEX$(Style%)
  7165.      LINE (XLeft%, Y%)-(XRight%,Y%), , , Style%
  7166.      Row% = Row% + 3
  7167.      Y% = Y% + 24
  7168.   NEXT
  7169.  
  7170.  
  7171.  Drawing Circles and Ellipses with CIRCLE
  7172.  
  7173.  The  CIRCLE statement draws a variety of circular and elliptical, or oval,
  7174.  shapes. In addition,  CIRCLE draws arcs (segments of circles) and pie-shaped
  7175.  wedges. In graphics mode you can produce just about any kind of curved line
  7176.  with some variation of  CIRCLE.
  7177.  
  7178.  
  7179.  Drawing Circles
  7180.  
  7181.  To draw a circle, you need to know only two things: the location of its
  7182.  center and the length of its radius (the distance from the center to any
  7183.  point on the circle). With this information and a reasonably steady hand (or
  7184.  better yet, a compass), you can produce an attractive circle.
  7185.  
  7186.  Similarly, BASIC needs only the location of a circle's center and the length
  7187.  of its radius to draw a circle. The simplest form of the  CIRCLE syntax is:
  7188.  
  7189.   CIRCLE  STEP ( x!, y!),  radius!
  7190.  
  7191.  In this statement,  x!,  y! are the coordinates
  7192.  of the center, and  radius! is the radius of the circle. The
  7193.  next lines of code draw a circle with center (200, 100) and radius 75:
  7194.  
  7195.  
  7196.   SCREEN 2
  7197.   CIRCLE (200, 100), 75
  7198.  
  7199.  You could rewrite the preceding example as follows, making the same circle
  7200.  but using  STEP to make the coordinates relative to the center
  7201.  rather than to the upper-left corner:
  7202.  
  7203.  
  7204.   SCREEN 2       ' Uses center of screen (320,100) as the
  7205.                  ' reference point for the CIRCLE statement:
  7206.   CIRCLE STEP(-120, 0), 75
  7207.  
  7208.  Drawing Ellipses
  7209.  
  7210.  The  CIRCLE statement automatically adjusts the "aspect ratio" to make sure
  7211.  that circles appear round and not flattened on your screen. However, you may
  7212.  need to adjust the aspect ratio to make circles come out right on your
  7213.  monitor, or you may want to change the aspect ratio to draw the oval figure
  7214.  known as an ellipse. In either case, use this syntax:
  7215.  
  7216.   CIRCLE STEP ( x!, y!),  radius!,,,, aspect!
  7217.  
  7218.  Here,  aspect! is a positive real number. (See "Drawing Shapes\
  7219.  to Proportion with the Aspect Ratio" later in this chapter for more
  7220.  information on the aspect ratio and how to calculate it for different
  7221.  screen modes.)
  7222.  
  7223.  The extra commas between  radius! and  aspect! are placeholders for other
  7224.  options that tell  CIRCLE which color to use (if you have a color
  7225.  monitor/adapter and are using one of the screen modes that support color),
  7226.  or whether to draw an arc or wedge. (See the sections "Drawing Arcs" and
  7227.  "Selecting a Color for Graphics Output" later in this chapter for more
  7228.  information on these options.)
  7229.  
  7230.  Since the argument  aspect! specifies the ratio of the vertical to
  7231.  horizontal dimensions, large values for  aspect! produce ellipses stretched
  7232.  out along the vertical axis, while small values for  aspect! produce
  7233.  ellipses stretched out along the horizontal axis. Since an ellipse has two
  7234.  radii -- one horizontal x-radius and one vertical y-radius -- BASIC uses the
  7235.  single  radius! argument in a  CIRCLE statement as follows: if  aspect! is
  7236.  less than one, then  radius! is the x-radius; if  aspect! is greater than or
  7237.  equal to one, then  radius! is the y-radius.
  7238.  
  7239.  
  7240.  Example
  7241.  
  7242.  The following example and its output show how different  aspect! values
  7243.  affect whether the  CIRCLE statement uses the  radius! argument as the
  7244.  x-radius or the y-radius of an ellipse:
  7245.  
  7246.   SCREEN 1
  7247.  
  7248.   ' This draws the ellipse on the left:
  7249.   CIRCLE (60, 100), 80, , , , 3
  7250.  
  7251.   ' This draws the ellipse on the right:
  7252.   CIRCLE (180, 100), 80, , , , 3/10
  7253.  
  7254.  
  7255.  Drawing Arcs
  7256.  
  7257.  An arc is a segment of a ellipse, in other words a short, curved line. To
  7258.  understand how the  CIRCLE statement draws arcs, you need to know how BASIC
  7259.  measures angles.
  7260.  
  7261.  BASIC uses the radian as its unit of angle measure, not only in the  CIRCLE
  7262.  statement, but also in the intrinsic trigonometric functions such as  COS,
  7263.  SIN, or  TAN. (The one exception to this use of radians is the  DRAW
  7264.  statement, which expects angle measurements in degrees. See "DRAW: a
  7265.  Graphics Macro Language" later in this chapter for more information about
  7266.  DRAW.)
  7267.  
  7268.  The radian is closely related to the radius of a circle. In fact, the word
  7269.  "radian" is derived from the word "radius." The circumference of a circle
  7270.  equals 2 *  *  radius, where  is equal to approximately 3.14159265.
  7271.  Similarly, the number of radians in one complete angle of revolution (or
  7272.  360) equals 2 * , or a little more than 6.28.
  7273.  
  7274.  If you are more used to thinking of angles in terms of degrees, here
  7275.  are some common equivalences:
  7276.  
  7277.  
  7278.  360       2(pi)   (approximately 6.283)
  7279.  180       (pi)    (approximately 3.142)
  7280.  90        (pi)/2  (approximately 1.571)
  7281.  60        (pi)/3  (approximately 1.047)
  7282.  
  7283.  
  7284.  If you picture a clock face on the screen,  CIRCLE measures angles by
  7285.  starting at the three o'clock position and rotating counterclockwise, as
  7286.  shown in Figure 5.2:
  7287.  
  7288.  The general formula for converting from degrees to radians is to multiply
  7289.  degrees by (pi)/180.
  7290.  
  7291.  To draw an arc, give angle arguments defining the arc's limits:
  7292.  
  7293.   CIRCLE  STEP( x!, y!), radius!,  color&, start!, end! ,  aspect!
  7294.  
  7295.  The  CIRCLE statements in the next example draw seven arcs, with the
  7296.  innermost arc starting at the three o'clock position (0 radians) and the
  7297.  outermost arc starting at the six o'clock position (3/2 radians), as you can
  7298.  see from the output:
  7299.  
  7300.   SCREEN 2
  7301.   CLS
  7302.  
  7303.   CONST PI = 3.141592653589#      ' Double-precision constant
  7304.  
  7305.   StartAngle = 0
  7306.   FOR Radius% = 100 TO 220 STEP 20
  7307.      EndAngle = StartAngle + (PI / 2.01)
  7308.      CIRCLE (320, 100), Radius%, , StartAngle, EndAngle
  7309.      StartAngle = StartAngle + (PI / 4)
  7310.   NEXT Radius%
  7311.  
  7312.  %
  7313.  Drawing Pie Shapes
  7314.  
  7315.  By making either of  CIRCLE's  start! or  end! arguments negative, you can
  7316.  connect the arc at its beginning or ending point with the center of the
  7317.  circle. By making both arguments negative, you can draw shapes ranging from
  7318.  a wedge that resembles a slice of pie to the pie itself with the piece
  7319.  missing.
  7320.  
  7321.  Example
  7322.  
  7323.  This example code draws a pie shape with a piece missing:
  7324.  
  7325.   SCREEN 2
  7326.  
  7327.   CONST RADIUS = 150, PI = 3.141592653589#
  7328.  
  7329.   StartAngle = 2.5
  7330.   EndAngle = PI
  7331.  
  7332.   ' Draw the wedge:
  7333.   CIRCLE (320, 100), RADIUS, , -StartAngle, -EndAngle
  7334.  
  7335.   ' Swap the values for the start and end angles:
  7336.   SWAP StartAngle, EndAngle
  7337.  
  7338.   ' Move the center 10 pixels down and 70 pixels to the
  7339.   ' right, then draw the "pie" with the wedge missing:
  7340.   CIRCLE STEP(70, 10), RADIUS, , -StartAngle, -EndAngle
  7341.  
  7342.  
  7343.  Drawing Shapes to Proportion with the Aspect Ratio
  7344.  
  7345.  As discussed earlier in "Drawing Ellipses," BASIC's  CIRCLE statement
  7346.  automatically corrects the aspect ratio, which determines how figures are
  7347.  scaled on the screen. However, with other graphics statements you need to
  7348.  scale horizontal and vertical dimensions yourself to make shapes appear with
  7349.  correct proportions. For example, although the following statement draws a
  7350.  rectangle that measures 100 pixels on all sides, it does not look like a
  7351.  square:
  7352.  
  7353.   SCREEN 1
  7354.   LINE (0, 0)-(100, 100), , B
  7355.  
  7356.  In fact, this is not an optical illusion; the rectangle really is taller
  7357.  than it is wide. This is because in screen mode 1 there is more space
  7358.  between pixels vertically than horizontally. To draw a perfect square, you
  7359.  have to change the aspect ratio.
  7360.  
  7361.  The aspect ratio is defined as follows: in a given screen mode consider two
  7362.  lines, one vertical and one horizontal, that appear to have the same length.
  7363.  The aspect ratio is the number of pixels in the vertical line divided by the
  7364.  number of pixels in the horizontal line. This ratio depends on two factors:
  7365.  
  7366.    *  Because of the way pixels are spaced on most screens, a horizontal row
  7367.       has more pixels than a vertical column of the exact same physical
  7368.       length in all screen modes except modes 11 and 12.
  7369.  
  7370.    *  The standard personal computer's video-display screen is wider than it
  7371.       is high. Typically, the ratio of screen width to screen height is 4:3.
  7372.  
  7373.  To see how these two factors interact to produce the aspect ratio, consider
  7374.  a screen after a SCREEN 1 statement, which gives a resolution of 320 x 200
  7375.  pixels. If you draw a rectangle from the top of the screen to the bottom,
  7376.  and from the left side of the screen three-fourths of the way across, you
  7377.  have a square, as shown in Figure 5.3.
  7378.  
  7379.  As you can see from the diagram, this square has a height of 200 pixels
  7380.  and a width of 240 pixels. The ratio of the square's height to its
  7381.  width (200 / 240 or, when simplified, 5 / 6) is the aspect ratio for this
  7382.  screen resolution. In other words, to draw a square in 320 x 200
  7383.  resolution, make its height in pixels equal to 5 / 6 times its width
  7384.  in pixels, as shown in the next example:
  7385.  
  7386.  
  7387.   SCREEN 1' 320 x 200 pixel resolution
  7388.   ' The height of this box is 100 pixels, and the width is
  7389.   ' 120 pixels, which makes the ratio of the height to the
  7390.   ' width equal to 100/120, or 5/6. The result is a square:
  7391.   LINE (50, 50) -STEP(120, 100), , B
  7392.  
  7393.  The formula for calculating the aspect ratio for a given screen mode is:
  7394.  
  7395.  (4 / 3) * (  ypixels /  xpixels)
  7396.  
  7397.  In this formula,  xpixels by  ypixels is the current
  7398.  screen resolution. In screen mode 1, this formula shows the aspect ratio to
  7399.  be (4 / 3) * (200 / 320), or 5 / 6; in screen mode 2, the aspect ratio is (4
  7400.  / 3) * (200 / 640), or 5 / 12.
  7401.  
  7402.  If you have a video display with a ratio of width to height that is not
  7403.  equal to 4:3, use the more general form of the formula for computing the
  7404.  aspect ratio:
  7405.  
  7406.  ( screenwidth /  screenheight) * ( ypixels /  xpixels)
  7407.  
  7408.  The  CIRCLE statement can be made to draw an ellipse by
  7409.  varying the value of the  aspect argument, as shown in
  7410.  "Drawing Ellipses" earlier in this chapter.
  7411.  
  7412.  
  7413.  Defining a Graphics Viewport
  7414.  
  7415.  The graphics examples presented so far have all used the entire
  7416.  video-display screen as their drawing board, with absolute coordinate
  7417.  distances measured from the extreme upper-left corner of the screen.
  7418.  
  7419.  However, using the  VIEW statement you can also define a kind of miniature
  7420.  screen (known as a "graphics viewport") inside the boundaries of the
  7421.  physical screen. Once it is defined, all graphics operations take place
  7422.  within this viewport. Any graphics output outside the viewport boundaries is
  7423.  "clipped"; that is, any attempt to plot a point outside the viewport is
  7424.  ignored. There are two main advantages to using a viewport:
  7425.  
  7426.    *  A viewport makes it easy to change the size and position of the screen
  7427.       area where graphics appear.
  7428.  
  7429.    *  Using  CLS 1, you can clear the screen inside a viewport without
  7430.       disturbing the screen outside the viewport.
  7431.  
  7432.  
  7433.  Note
  7434.  
  7435.  Refer to Chapter 3, "File and Device I/O," to learn how to create a "text
  7436.  viewport" for output printed on the screen.
  7437.  
  7438.  The general syntax for  VIEW is as follows:
  7439.      VIEW [[SCREEN] ( x1!,  y1!)  - ( x2!,  y2!) ,  color& ,  border&
  7440.  
  7441.  The coordinates  (x1 ,  y1 ) and  ( x2 ,  y2 ) define the corners of the view
  7442.  standard BASIC syntax for rectangles (see the section "Drawing Boxes"
  7443.  earlier in this chapter). Note that the  STEP option is not allowed with
  7444.  VIEW. The optional  color& and  border& arguments allow you to choose a
  7445.  color for the interior and edges, respectively, of the viewport rectangle.
  7446.  See the section "Using Colors" later in this chapter for more information on
  7447.  setting and changing colors.
  7448.  
  7449.  The  VIEW statement without arguments makes the entire screen the viewport.
  7450.  Without the  SCREEN option, the  VIEW statement makes all pixel coordinates
  7451.  relative to the viewport, rather than the full screen. In other words, after
  7452.  the following  VIEW statement, the pixel plotted with the  PSET statement is
  7453.  visible, since it is 10 pixels below and 10 pixels to the right of the
  7454.  upper-left corner of the viewport:
  7455.  
  7456.  VIEW (50, 60)-(150, 175)
  7457.  PSET (10, 10)
  7458.  
  7459.  Note that this makes the pixel's absolute screen coordinates (50 + 10, 60 +
  7460.  10) or (60, 70).
  7461.  
  7462.  In contrast, the  VIEW statement with the  SCREEN
  7463.  option keeps all coordinates absolute; that is, coordinates measure
  7464.  distances from the side of the screen, not from the sides of the viewport.
  7465.  
  7466.  Therefore, after the following  VIEW SCREEN statement the pixel plotted with
  7467.  the  PSET is not visible, since it is 10 pixels below and 10 pixels to the
  7468.  right of the upper-left corner of the screen -- outside the viewport:
  7469.  
  7470.  VIEW SCREEN (50, 60)-(150, 175)
  7471.  PSET (10, 10)
  7472.  
  7473.  Examples
  7474.  
  7475.  The output from the next two examples should further clarify the distinction
  7476.  between  VIEW and  VIEW SCREEN:
  7477.  
  7478.   SCREEN 2
  7479.  
  7480.   VIEW (100, 50)-(450, 150), , 1
  7481.  
  7482.   ' This circle's center point has absolute coordinates
  7483.   ' (100 + 100, 50 + 50), or (200, 100):
  7484.   CIRCLE (100, 50), 50
  7485.  
  7486.  
  7487.  SCREEN 2
  7488.  
  7489.   ' This circle's center point has absolute coordinates (100, 50),
  7490.   ' so only part of the circle appears within the viewport:
  7491.   VIEW SCREEN (100, 50)-(450, 150), , 1
  7492.   CIRCLE (100, 50), 50
  7493.  
  7494.  Note that graphics output located outside the current viewport is
  7495.  clipped by the viewport's edges and does not appear on the screen.
  7496.  
  7497.  
  7498.  Redefining Viewport Coordinates with WINDOW
  7499.  
  7500.  This section shows you how to use the  WINDOW statement and your own
  7501.  coordinate system to redefine pixel coordinates.
  7502.  
  7503.  In the preceding sections, the coordinates used to locate pixels on the
  7504.  screen represent actual physical distances from the upper-left corner of the
  7505.  screen (or the upper-left corner of the current viewport, if it has been
  7506.  defined with a  VIEW statement). These are known as "physical coordinates."
  7507.  The "origin," or reference point, for physical coordinates is always the
  7508.  upper-left corner of the screen or viewport, which has coordinates (0, 0).
  7509.  
  7510.  As you move down the screen and to the right,  x values (horizontal
  7511.  coordinates) and  y values (vertical coordinates) get bigger, as shown in
  7512.  the upper diagram of Figure 5.4. While this scheme is standard for video
  7513.  displays, it may seem counterintuitive if you have used other coordinate
  7514.  systems to draw graphs. For example, on the Cartesian grid used in
  7515.  mathematics,  y values get bigger toward the top of a graph and smaller
  7516.  toward the bottom.
  7517.  
  7518.  With BASIC's  WINDOW statement, you can change the way pixels are addressed
  7519.  to use any coordinate system you choose, thus freeing you from the
  7520.  limitations of using physical coordinates.
  7521.  
  7522.  The general syntax for  WINDOW is as follows:
  7523.  
  7524.   WINDOW  SCREEN ( x1!, y1!)  - ( x2!,y2!)
  7525.  
  7526.  The coordinates y1!,  y2!,  x1!, and  x2! are real numbers specifying the top
  7527.  and right sides of the window, respectively. These numbers are known as
  7528.  "window coordinates." For example, the following statement remaps your
  7529.  screen so that it is bounded on the top and bottom by the lines y = 10 and y
  7530.  = -15 and on the left and right by the lines
  7531.  
  7532.  
  7533.   x = -25 and x = 5:
  7534.  
  7535.  WINDOW (-25, -15)-(5, 10)
  7536.  
  7537.  After a  WINDOW statement,  y values get bigger toward the top of the
  7538.  screen. In contrast, after a  WINDOW SCREEN statement,  y values get bigger
  7539.  toward the bottom of the screen. Figure 5.4 shows the effects of a  WINDOW
  7540.  statement and a  WINDOW SCREEN statement on a line drawn in screen mode 2.
  7541.  Note also how both of these statements change the coordinates of the screen
  7542.  corners. A  WINDOW statement with no arguments restores the regular physical
  7543.  coordinate system.
  7544.  
  7545.  The following example uses  VIEW and  WINDOW to simplify writing a program
  7546.  to graph the sine-wave function for angle values from 0 radians to  radians
  7547.  (or 0 to 180). This program is in the file named SINEWAVE.BAS on the
  7548.  Microsoft BASIC distribution disks.
  7549.  
  7550.  SCREEN 2
  7551.  
  7552.   ' Viewport sized to proper scale for graph:
  7553.   VIEW (20, 2)-(620, 172), , 1
  7554.   CONST PI = 3.141592653589#
  7555.  
  7556.  ' Make window large enough to graph sine wave from
  7557.  ' 0 radians to pi radians:
  7558.  WINDOW (0, -1.1)-(2 * PI, 1.1)
  7559.  Style% = &HFF00         ' Use to make dashed line.
  7560.  VIEW PRINT 23 TO 24     ' Scroll printed output in rows 23, 24.
  7561.  DO
  7562.  PRINT TAB(20);
  7563.  INPUT "Number of cycles (0 to end): ", Cycles
  7564.  CLS
  7565.  LINE (2 * PI, 0)-(0, 0), , , Style%  ' Draw the x-axis.
  7566.  IF Cycles > 0 THEN
  7567.  
  7568.  '  Start at (0,0) and plot the graph:
  7569.  FOR X = 0 TO 2 * PI STEP .01
  7570.  Y = SIN(Cycles * X) ' Calculate the y-coordinate.
  7571.  LINE -(X, Y)        ' Draw a line to new point.
  7572.  NEXT X
  7573.  END IF
  7574.  LOOP WHILE Cycles > 0
  7575.  
  7576.  
  7577.  The Order of Coordinate Pairs
  7578.  
  7579.  As with the other BASIC graphics statements that define rectangular areas (
  7580.  GET,  LINE, and  VIEW), the order of coordinate pairs in a  WINDOW statement
  7581.  is unimportant. In the following example, the first pair of statements has
  7582.  the same effect as the second pair of statements:
  7583.  
  7584.   VIEW (100, 20)-(300, 120)
  7585.   WINDOW (-4, -3)-(0, 0)
  7586.  
  7587.   VIEW (300, 120)-(100, 20)
  7588.   WINDOW (0, 0)-(-4, -3)
  7589.  
  7590.  
  7591.  Keeping Track of Window and Physical Coordinates
  7592.  
  7593.  The  PMAP and  POINT functions are useful for keeping track of physical and
  7594.  view coordinates.  POINT( number%) tells you the current location of the
  7595.  graphics cursor by returning either the physical or view coordinates
  7596.  (depending on the value for  number%) of the last point referenced in a
  7597.  graphics statement.  PMAP allows you to translate physical coordinates to
  7598.  view coordinates and vice versa. The physical coordinate values returned by
  7599.  PMAP are always relative to the current viewport.
  7600.  
  7601.  
  7602.  Examples
  7603.  
  7604.  The following example shows the different values that are returned by  POINT
  7605.  ( number%) for  number% values of 0, 1, 2, or 3:
  7606.  
  7607.   SCREEN 2
  7608.   ' Define the window:
  7609.   WINDOW (-10, -30)-(-5, -10)
  7610.   ' Draw a line from the point with window coordinates (-9,-28)
  7611.   ' to the point with window coordinates (-6,-24):
  7612.   LINE (-9, -28)-(-6, -24)
  7613.  
  7614.   PRINT "Physical x-coordinate of the last point = " POINT(0)
  7615.   PRINT "Physical y-coordinate of the last point = " POINT(1)
  7616.   PRINT
  7617.   PRINT "Window x-coordinate of the last point  = " POINT(2)
  7618.   PRINT "Window y-coordinate of the last point  = " POINT(3)
  7619.  
  7620.   END
  7621.  
  7622.  Output
  7623.   Physical x-coordinate of the last point = 511
  7624.   Physical y-coordinate of the last point = 139
  7625.  
  7626.   Window x-coordinate of the last point  = -6
  7627.   Window y-coordinate of the last point  = -24
  7628.  
  7629.  Given the  WINDOW statement in the preceding example, the
  7630.  next four  PMAP statements would print the output that
  7631.  follows:
  7632.  
  7633.  
  7634.   ' Map the window x-coordinate -6 to physical x and print:
  7635.   PhysX% = PMAP(-6, 0)
  7636.   PRINT PhysX%
  7637.  
  7638.   ' Map the window y-coordinate -24 to physical y and print:
  7639.   PhysY% = PMAP(-24, 1)
  7640.   PRINT PhysY%
  7641.  
  7642.   ' Map physical x back to view x and print:
  7643.   WindowX% = PMAP(PhysX%, 2)
  7644.   PRINT WindowX%
  7645.  
  7646.   ' Map physical y back to view y and print:
  7647.   WindowY% = PMAP(PhysY%, 3)
  7648.   PRINT WindowY%
  7649.  
  7650.  Output
  7651.  
  7652.   511
  7653.    139
  7654.   -6
  7655.   -24
  7656.  
  7657.  Using Colors
  7658.  
  7659.  If you have a Color Graphics Adapter (CGA), you can choose between the
  7660.  following two graphics modes only:
  7661.  
  7662.    *  Screen mode 2 has 640 x 200 high resolution with only one foreground
  7663.       and one background color. This is known as "monochrome," since all
  7664.       graphics output has the same color.
  7665.  
  7666.    *  Screen mode 1 has 320 x 200 medium resolution with 4 foreground colors
  7667.       and 16 background colors.
  7668.  
  7669.  There is a tradeoff between color and clarity in the two screen modes
  7670.  supported by most color-graphics display adapter hardware. Depending on the
  7671.  graphics capability of your system, you may not have to sacrifice clarity to
  7672.  get a full range of color. However, this section focuses on screen modes 1
  7673.  and 2.
  7674.  
  7675.  
  7676.  Selecting a Color for Graphics Output
  7677.  
  7678.  The following list shows where to put the  color argument in the graphics
  7679.  statements discussed in previous sections of this chapter. This list also
  7680.  shows other options (such as  BF with the  LINE statement or  border with
  7681.  the  VIEW statement) that can have a different colors. (Please note that
  7682.  these do not give the complete syntax for some of these statements. This
  7683.  summary is intended to show how to use the  color option in those statements
  7684.  that accept it.)
  7685.  
  7686.   PSET ( x%,  y%),  color&
  7687.   PRESET ( x%,  y%),  color&
  7688.   LINE ( x1!,  y1!) \~( x2!,  y2!),  color&,  B F
  7689.   CIRCLE ( x! ,  y!),  radius!,  color&
  7690.   VIEW ( x1!,  y1!) \~( x2!,  y2!),  color&,   border&
  7691.  
  7692.  In screen mode 1, the color argument is a numeric expression with the value 0
  7693.  these values, known as an "attribute," represents a different color, as
  7694.  demonstrated by the following program:
  7695.  
  7696.   ' Draw an "invisible" line (same color as background):
  7697.   LINE (10, 10)-(310, 10), 0
  7698.  
  7699.   ' Draw a light blue (cyan) line:
  7700.   LINE (10, 30)-(310, 30), 1
  7701.  
  7702.   ' Draw a purple (magenta) line:
  7703.   LINE (10, 50)-(310, 50), 2
  7704.  
  7705.   ' Draw a white line:
  7706.   LINE (10, 70)-(310, 70), 3
  7707.   END
  7708.  
  7709.  As noted in the comments for the preceding example, a  color& value of 0
  7710.  produces no visible output, since it is always equal to the current
  7711.  background color. At first glance, this may not seem like such a useful
  7712.  color value, but, in fact, it is useful for erasing a figure without having
  7713.  to clear the entire screen or viewport, as shown in the next example:
  7714.  
  7715.   SCREEN 1
  7716.  
  7717.   CIRCLE (100, 100), 80, 2, , , 3   ' Draw an ellipse.
  7718.   Pause$ = INPUT$(1)                ' Wait for a key press.
  7719.   CIRCLE (100, 100), 80, 0, , , 3   ' Erase the ellipse.
  7720.  
  7721.  
  7722.  Changing the Foreground or Background Color
  7723.  
  7724.  The preceding section describes how to use four different foreground colors
  7725.  for graphics output. You have a wider variety of colors in screen mode 1 for
  7726.  the screen's background: 16 in all.
  7727.  
  7728.  In addition, you can change the foreground color by using a different
  7729.  "palette." In screen mode 1, there are two palettes, or groups of four
  7730.  colors. Each palette assigns a different color to the same attribute; so,
  7731.  for instance, in palette 1 (the default) the color associated with
  7732.  attribute 2 is magenta, while in palette 0 the color associated with
  7733.  attribute 2 is red. If you have a CGA, these colors are predetermined for
  7734.  each palette; that is, the color assigned to number 2 in palette 1 is
  7735.  always magenta, while the color assigned to number 2 in palette 0 is
  7736.  always red.
  7737.  
  7738.  If you have an Enhanced Graphics Adapter (EGA) or Video Graphics Array
  7739.  (VGA), you can use the  PALETTE statement to choose the color displayed by
  7740.  any attribute. For example, by changing arguments in a  PALETTE statement,
  7741.  you could make the color displayed by attribute 1 green one time and brown
  7742.  the next. (See "Changing Colors with PALETTE and PALETTE USING" later in
  7743.  this chapter for more information on reassigning colors.)
  7744.  
  7745.  In screen mode 1, the  COLOR statement allows you to control the background
  7746.  color and the palette for the foreground colors. Here is the syntax for
  7747.  COLOR in screen mode 1:
  7748.  
  7749.   COLOR  background&  ,  palette%The  background& argument is a numeric
  7750.  expression from 0 to 15, and  palette% is a numeric expression equal to
  7751.  either 0 or 1.
  7752.  
  7753.  The following program shows all combinations of the two color palettes with
  7754.  the 16 different background screen colors. This program is in the file named
  7755.  COLORS.BAS on the Microsoft BASIC distribution disks.
  7756.  
  7757.   SCREEN 1
  7758.  
  7759.   Esc$ = CHR$(27)
  7760.   ' Draw three boxes and paint the interior
  7761.   ' of each box with a different color:
  7762.   FOR ColorVal = 1 TO 3
  7763.      LINE (X, Y) -STEP(60, 50), ColorVal, BF
  7764.      X = X + 61
  7765.      Y = Y + 51
  7766.   NEXT ColorVal
  7767.  
  7768.   LOCATE 21, 1
  7769.   PRINT "Press Esc to end."
  7770.   PRINT "Press any other key to continue."
  7771.  
  7772.   ' Restrict additional printed output to the 23rd line:
  7773.   VIEW PRINT 23 TO 23
  7774.   DO
  7775.      PaletteVal = 1
  7776.      DO
  7777.  ' PaletteVal is either 1 or 0:
  7778.         PaletteVal = 1 - PaletteVal
  7779.  
  7780.         ' Set the background color and choose the palette:
  7781.         COLOR BackGroundVal, PaletteVal
  7782.         PRINT "Background ="; BackGroundVal;
  7783.         PRINT "Palette ="; PaletteVal;
  7784.  
  7785.         Pause$ = INPUT$(1)        ' Wait for a keystroke.
  7786.         PRINT
  7787.      ' Exit the loop if both palettes have been shown,
  7788.      ' or if the user pressed the Esc key:
  7789.      LOOP UNTIL PaletteVal = 1 OR Pause$ = Esc$
  7790.  
  7791.      BackGroundVal = BackGroundVal + 1
  7792.  
  7793.   ' Exit this loop if all 16 background colors have
  7794.   ' been shown, or if the user pressed the Esc key:
  7795.   LOOP UNTIL BackGroundVal > 15 OR Pause$ = Esc$
  7796.  
  7797.   SCREEN 0                     ' Restore text mode and
  7798.   WIDTH 80                     ' 80-column screen width.
  7799.  
  7800.  
  7801.  Changing Colors with PALETTE and PALETTE USING
  7802.  
  7803.  The preceding section shows how you can change the color displayed by an
  7804.  attribute simply by specifying a different palette in the  COLOR statement.
  7805.  However, this restricts you to two fixed color palettes, with just four
  7806.  colors in each. Furthermore, each attribute can stand for only one of two
  7807.  possible colors; for example, attribute 1 can signify only green or cyan.
  7808.  
  7809.  With an EGA or VGA, your choices are potentially much broader. (If you don't
  7810.  have an EGA or VGA, you may want to skip this section.) For instance,
  7811.  depending on the amount of video memory available to your computer, with a
  7812.  VGA you can choose from a palette with as many as 256,000 colors and assign
  7813.  those colors to 256 different attributes. Even an EGA allows you to display
  7814.  up to 16 different colors from a palette of 64 colors.
  7815.  
  7816.  In contrast to the  COLOR statement, the  PALETTE and  PALETTE USING
  7817.  statements give you a lot more flexibility in manipulating the available
  7818.  color palette. Using these statements, you can assign any color from the
  7819.  palette to any attribute. For example, after the following statement, the
  7820.  output of all graphics statements using attribute 4 appears in light magenta
  7821.  (color 13):
  7822.  
  7823.  PALETTE 4, 13
  7824.  
  7825.  This color change is instantaneous and affects not only subsequent
  7826.  graphics statements but any output already on the screen. In other words,
  7827.  you can draw and paint your screen, then switch the palette to achieve
  7828.  an immediate change of color, as shown by the following example:
  7829.  
  7830.   SCREEN 8
  7831.   LINE (50, 50)-(150, 150), 4  ' Draws a line in red.
  7832.   SLEEP 1                      ' Pauses program.
  7833.   PALETTE 4, 13                ' Attribute 4 now means color
  7834.                                ' 13, so the line drawn in the
  7835.                                ' last statement is now light
  7836.                                ' magenta.
  7837.  
  7838.  With the  PALETTE statement's  USING option, you can change the colors
  7839.  assigned to every attribute all at once.
  7840.  
  7841.  Example
  7842.  
  7843.  In the following example, the  PALETTE USING statement gives the illusion of
  7844.  movement on the screen by constantly rotating the colors displayed by
  7845.  attributes 1 through 15. This program is in the file named PALETTE.BAS on
  7846.  the Microsoft BASIC distribution disks.
  7847.  
  7848.   DECLARE SUB InitPalette ()
  7849.  
  7850.   DECLARESUB ChangePalette ()
  7851.   DECLARESUB DrawEllipses ()
  7852.  
  7853.   DEFINT A-Z
  7854.   DIM SHARED PaletteArray(15)
  7855.  
  7856.   SCREEN 8' 640 x 200 resolution; 16 colors
  7857.  
  7858.   InitPalette' Initialize PaletteArray.
  7859.   DrawEllipses' Draw and paint concentric ellipses.
  7860.  
  7861.   DO' Shift the palette until a key
  7862.      ChangePalette' is pressed.
  7863.   LOOP WHILE INKEY$ = ""
  7864.  
  7865.   END
  7866.  
  7867.  
  7868.   ' ====================== InitPalette ======================
  7869.   '    This procedure initializes the integer array used to
  7870.   '    change the palette.
  7871.   ' =========================================================
  7872.  
  7873.   SUB InitPaletteSTATIC
  7874.      FOR I = 0 TO15
  7875.         PaletteArray(I) =I
  7876.      NEXTI
  7877.   END SUB
  7878.  ' =====================DrawEllipses ======================
  7879.   '    This procedure draws 15 concentric ellipses and
  7880.   '    paints the interior of each with a different color.
  7881.   ' =========================================================
  7882.  
  7883.   SUB DrawEllipses STATIC
  7884.      CONST ASPECT= 1 / 3
  7885.      FOR ColorVal= 15 TO1 STEP -1
  7886.         Radius = 20 * ColorVal
  7887.         CIRCLE (320, 100), Radius, ColorVal, , , ASPECT
  7888.         PAINT (320, 100),ColorVal
  7889.      NEXT
  7890.   END SUB
  7891.  
  7892.  
  7893.   ' ===================== ChangePalette =====================
  7894.   '  This procedure rotates the palette by one each time it
  7895.   '  is called. For example, after the first call to
  7896.   '  ChangePalette, PaletteArray(1) = 2, PaletteArray(2) = 3,
  7897.   '  . . . , PaletteArray(14) = 15, and PaletteArray(15) = 1
  7898.   ' =========================================================
  7899.  
  7900.   SUB ChangePalette STATIC
  7901.      FOR I = 1 TO15
  7902.         PaletteArray(I) =(PaletteArray(I) MOD 15) + 1
  7903.      NEXTI
  7904.      PALETTE USING PaletteArray(0) ' Shift the color displayed
  7905.   ' by each of the attributes.
  7906.   END SUB
  7907.  
  7908.  
  7909.  Painting Shapes
  7910.  
  7911.  The section "Drawing Boxes" earlier in this chapter shows how to draw a box
  7912.  with the  LINE statement's  B option, then paint the box by appending the F
  7913.  (for fill) option:
  7914.  
  7915.   SCREEN 1
  7916.  
  7917.   ' Draw a square, then paint the interior with color 1
  7918.   ' (cyan in the default palette):
  7919.   LINE (50, 50)-(110, 100), 1, BF
  7920.  
  7921.  With BASIC's  PAINT statement, you can fill any enclosed figure with any
  7922.  color you choose.  PAINT also allows you to fill enclosed figures with your
  7923.  own custom patterns, such as stripes or checks, as shown in "Painting with
  7924.  Patterns: Tiling" later in this chapter.
  7925.  
  7926.  
  7927.  Painting with Colors
  7928.  
  7929.  To paint an enclosed shape with a solid color, use this form of the  PAINT
  7930.  statement:
  7931.  
  7932.   PAINT  STEP( x!, y!) ,  paint,  bordercolor&
  7933.  
  7934.  Here,  x!,  y! are the coordinates of a point in
  7935.  the interior of the figure you want to paint, paint is the number
  7936.  for the color you want to paint with, and  bordercolor& is
  7937.  the color number for the outline of the figure.
  7938.  
  7939.  For example, the following program lines draw a circle in cyan, then paint
  7940.  the inside of the circle magenta:
  7941.  
  7942.   SCREEN 1
  7943.   CIRCLE (160, 100), 50, 1
  7944.   PAINT (160, 100), 2, 1
  7945.  
  7946.  The following three rules apply when painting figures:
  7947.  
  7948.  For example, any one of the following statements would have the same
  7949.  effect as the  PAINT statements shown in the two preceding examples, since
  7950.  all of the coordinates identify points in the interior of the circle:
  7951.  
  7952.  
  7953.  PAINT (150, 90), 2, 1
  7954.  PAINT (170, 110), 2, 1
  7955.  PAINT (180, 80), 2, 1
  7956.  
  7957.  In contrast, since (5, 5) identifies a point outside the circle, the next
  7958.  statement would paint all of the screen except the inside of the circle,
  7959.  leaving it colored with the current background color:
  7960.  
  7961.  PAINT (5, 5), 2, 1
  7962.  
  7963.  If the coordinates in a  PAINT statement specify a point
  7964.  right on the border of the figure, then no painting occurs:
  7965.  
  7966.        The figure must be completely enclosed; otherwise, the paint color
  7967.        will "leak out," filling the entire screen or viewport (or any larger
  7968.        figure completely enclosing the first one).
  7969.  
  7970.        For example, in the following program, the  CIRCLE statement draws an
  7971.        ellipse that is not quite complete (there is a small gap on the right
  7972.        side); the  LINE statement then encloses the partial ellipse inside a
  7973.        box. Even though painting starts in the interior of the ellipse, the
  7974.        paint color flows through the gap and fills the entire box.
  7975.  
  7976.  
  7977.   SCREEN 2
  7978.   CONST PI = 3.141592653589#
  7979.   CIRCLE (300, 100), 80, , 0, 1.9 * PI, 3
  7980.   LINE (200, 10)-(400, 190), , B
  7981.   PAINT (300, 100)
  7982.  
  7983.        If you are painting an object a different color from the one used to
  7984.        outline the object, you must use the  bordercolor& option to tell
  7985.        PAINT where to stop painting.
  7986.  
  7987.        For example, the following program draws a triangle outlined in green
  7988.        (attribute 1 in palette 0) and then tries to paint the interior of the
  7989.        triangle red (attribute 2). However, since the  PAINT statement
  7990.        doesn't indicate where to stop painting, it paints the entire screen
  7991.        red.
  7992.  
  7993.  
  7994.   SCREEN 1
  7995.    COLOR , 0
  7996.    LINE (10, 25)-(310, 25), 1
  7997.    LINE -(160, 175), 1
  7998.    LINE -(10, 25), 1
  7999.    PAINT (160, 100), 2
  8000.  
  8001.    Making the following change to the  PAINT statement (choose red for the
  8002.    interior and stop when a border colored green is reached) produces the
  8003.    desired effect:
  8004.  
  8005.  
  8006.  PAINT (160, 100), 2, 1
  8007.  
  8008.  Note that you don't have to specify a border color in the  PAINT
  8009.  statement if the paint color is the same as the border color.
  8010.  
  8011.  
  8012.  LINE (10, 25)-(310, 25), 1
  8013.   LINE -(160, 175), 1
  8014.  LINE -(10, 25), 1
  8015.  PAINT (160, 100), 1
  8016.  
  8017.  
  8018.  
  8019.  Painting with Patterns: Tiling
  8020.  
  8021.  You can use the  PAINT statement to fill any enclosed figure with a pattern;
  8022.  this process is known as "tiling." A "tile" is the pattern's basic building
  8023.  block. The process is identical to laying down tiles on a floor. When you
  8024.  use tiling, the argument  paint in the syntax for  PAINT is a string
  8025.  expression, rather than a number. While  paint can be any string expression,
  8026.  a convenient way to define tile patterns uses the following form for  paint:
  8027.  
  8028.   CHR$( code1%)+ CHR$( code2%)+ CHR$( code3%)+...+ CHR$( coden%)
  8029.  
  8030.  Here, code1%,  code2%, and so forth are 8-bit integers. See the following sec
  8031.  for an explanation of how these 8-bit integers are derived.
  8032.  
  8033.  
  8034.  
  8035.  Pattern-Tile Size in Different Screen Modes
  8036.  
  8037.  Each tile for a pattern is composed of a rectangular grid of pixels. This
  8038.  tile grid can have up to 64 rows in all screen modes. However, the number of
  8039.  pixels in each row depends on the screen mode.
  8040.  
  8041.  The reason the length of each tile row varies according to the screen mode
  8042.  is because, although the number of bits in each row is fixed at 8 (the
  8043.  length of an integer), the number of pixels these 8 bits can represent
  8044.  decreases as the number of color attributes in a given screen mode
  8045.  increases. For example, in screen mode 2, which has only one color
  8046.  attribute, the number of bits per pixel is 1; in screen mode 1, which has
  8047.  four different attributes, the number of bits per pixel is 2; and in the EGA
  8048.  screen mode 7, which has 16 attributes, the number of bits per pixel is 4.
  8049.  The following formula allows you to compute the bits per pixel in any given
  8050.  screen mode:
  8051.  
  8052.   bits-per-pixel = log2( numattributes)Here,  numattributes is the number of
  8053.  color attributes in that screen mode. (Online Help has this information.)
  8054.  
  8055.  Thus, the length of a tile row is 8 pixels in screen mode 2 (8 bits divided
  8056.  by 1 bit per pixel), but only 4 pixels in screen mode 1 (8 bits divided by 2
  8057.  bits per pixel).
  8058.  
  8059.  The next three sections show the step-by-step process involved in creating a
  8060.  pattern tile. The following section shows how to make a monochrome pattern
  8061.  in screen mode 2. The section after that shows how to make a multicolored
  8062.  pattern in screen mode 1. Finally, if you have an EGA, read the third
  8063.  section to see how to make a multicolored pattern in screen mode 8.
  8064.  
  8065.  
  8066.  Creating a Single-Color Pattern in Screen Mode 2
  8067.  
  8068.  The following steps show how to define and use a pattern tile that resembles
  8069.  the letter "M":
  8070.  
  8071.     1. Draw the pattern for a tile in a grid with eight columns and however
  8072.        many rows you need (up to 64). In this example, the tile has six rows;
  8073.        an asterisk (*) in a box means the pixel is on:
  8074.  
  8075.     2. Next, translate each row of pixels to an 8-bit number, with a one
  8076.       meaning the pixel is on, and a zero meaning the pixel is off:
  8077.  
  8078.     3. Convert the binary numbers given in step 2 to hexadecimal integers:
  8079.  10000100 = &H84
  8080.   11001100 = &HCC
  8081.  10110100 = &HB4
  8082.  10000100 = &H84
  8083.  10000100 = &H84
  8084.  00000000 = &H00
  8085.  
  8086.    These integers do not have to be hexadecimal; they could be decimal or
  8087.    octal. However, binary to hexadecimal conversion is easier. To convert
  8088.    from binary to hexadecimal, read the binary number from right to left.
  8089.    Each group of four digits is then converted to its hexadecimal equivalent,
  8090.    as shown here:
  8091.  
  8092.  Table 5.3 lists 4-bit binary sequences and their hexadecimal equivalents.
  8093.  
  8094.     4. Create a string by concatenating the characters with the ASCII values
  8095.       from step 3 (use the  CHR$ function to get these characters):
  8096.  
  8097.   Tile$ = CHR$(&H84) + CHR$(&HCC) + CHR$(&HB4)
  8098.   Tile$ = Tile$ + CHR$(&H84) + CHR$(&H84) + CHR$(&H00)
  8099.  
  8100.     5. Draw a figure and paint its interior using  PAINT and the string
  8101.       argument from step 4:
  8102.  
  8103.  PAINT (X, Y), Tile$0001 1 0010 2 0011 3 0100 4 0101 5 0110 6
  8104.  1000 8 10019 1010 A
  8105.  1011 B 1100 C 1101 D 1110 E 1111 F Example
  8106.  
  8107.  The following example draws a circle and then paints the circle's interior
  8108.  with the pattern created in the preceding steps:
  8109.  
  8110.   SCREEN 2
  8111.   CLS
  8112.   Tile$ = CHR$(&H84) + CHR$(&HCC) + CHR$(&HB4)
  8113.   Tile$ = Tile$ + CHR$(&H84) + CHR$(&H84) + CHR$(&H00)
  8114.   CIRCLE STEP(0, 0), 150
  8115.   PAINT STEP(0, 0), Tile$
  8116.  
  8117.  
  8118.  Creating a Multicolor Pattern in Screen Mode 1
  8119.  
  8120.  The following steps show how to create a multicolor pattern consisting of
  8121.  alternating diagonal stripes of cyan and magenta (or green and red in
  8122.  palette 0):
  8123.  
  8124.     1. Draw the pattern for a tile in a grid with four columns (four columns
  8125.        because each row of pixels is stored in an 8-bit integer and each
  8126.        pixel in screen mode 1 requires 2 bits) and however many rows you need
  8127.        (up to 64). In this example, the tile has four rows, as shown in the
  8128.        next diagram:
  8129.  
  8130.     2. Convert the colors to their respective color numbers in binary
  8131.        notation, as shown by the following (be sure to use 2-bit values, so
  8132.        that 1 = binary 01 and 2 = binary 10):
  8133.  
  8134.     3. Convert the binary numbers from step 2 to hexadecimal integers:
  8135.  01101010 = &H6A
  8136.   10011010 = &H9A
  8137.  10100110 = &HA6
  8138.  10101001 = &HA9
  8139.  
  8140.     4. Create a string by concatenating the characters with the ASCII values
  8141.       from step 3 (use the  CHR$ function to get these characters):
  8142.  
  8143.  Tile$ = CHR$(&H6A) + CHR$(&H9A) + CHR$(&HA6) + CHR$(&HA9)   5.
  8144.  
  8145.       Draw a figure and paint its interior using  PAINT and the string
  8146.       argument from step 4:
  8147.  
  8148.  PAINT (X, Y), Tile$
  8149.  
  8150.  The following program draws a triangle and then paints its interior
  8151.  with the pattern created in the preceding steps:
  8152.  
  8153.  
  8154.   SCREEN 1
  8155.  
  8156.   ' Define a pattern:
  8157.   Tile$ = CHR$(&H6A) + CHR$(&H9A) + CHR$(&HA6) + CHR$(&HA9)
  8158.  
  8159.  ' Draw a triangle in
  8160.  white (color 3):
  8161.   LINE (10, 25)-(310, 25)
  8162.   LINE -(160, 175)
  8163.   LINE -(10, 25)
  8164.  
  8165.   ' Paint the interior of the triangle with the pattern:
  8166.   PAINT (160, 100), Tile$
  8167.  
  8168.  Note that if the figure you want to paint is outlined in a color that is
  8169.  also contained in the pattern, you must give the  bordercolor& argument with
  8170.   PAINT as shown by the following example; otherwise, the pattern spills over
  8171.  the edges of the figure:
  8172.  
  8173.   SCREEN 1
  8174.  
  8175.   ' Define a pattern:
  8176.   Tile$ = CHR$(&H6A) + CHR$(&H9A) + CHR$(&HA6) + CHR$(&HA9)
  8177.  
  8178.   ' Draw a triangle in magenta (color 2):
  8179.   LINE (10, 25)-(310, 25), 2
  8180.   LINE -(160, 175), 2
  8181.   LINE -(10, 25), 2
  8182.  
  8183.   ' Paint the interior of the triangle with the pattern,
  8184.   ' adding the border argument (, 2) to tell PAINT
  8185.   ' where to stop:
  8186.   PAINT (160, 100), Tile$, 2
  8187.  
  8188.  Sometimes, after painting a figure with a solid color or pattern, you may
  8189.  want to repaint that figure, or some part of it, with a new pattern. If the
  8190.  new pattern contains two or more adjacent rows that are the same as the
  8191.  figure's current background, you will find that tiling does not work.
  8192.  Instead, the pattern starts to spread, finds itself surrounded by pixels
  8193.  that are the same as two or more of its rows, then stops.
  8194.  
  8195.  You can alleviate this problem by using the  background argument with  PAINT
  8196.  if there are at most two adjacent rows in your new pattern that are the same
  8197.  as the old background.  PAINT with  background has the following syntax:
  8198.  
  8199.  
  8200.   PAINT  STEP( x!, y!)   paint ,   bordercolor& ,  background$
  8201.  
  8202.  The background$ argument is a string character of the form  CHR$( code% ) tha
  8203.  specifies the rows in the pattern tile that are the same as the figure's
  8204.  current background. In essence,  background$ tells  PAINT to skip over these
  8205.  rows when repainting the figure. The next example clarifies how this works:
  8206.  
  8207.  
  8208.   SCREEN 1
  8209.  
  8210.   ' Define a pattern (two rows each of cyan, magenta, white):
  8211.   Tile$ = CHR$(&H55) + CHR$(&H55) + CHR$(&HAA)
  8212.   Tile$ = Tile$ + CHR$(&HAA) + CHR$(&HFF) + CHR$(&HFF)
  8213.  
  8214.  ' Draw a triangle in
  8215.  white (color number 3):
  8216.   LINE (10, 25)-(310, 25)
  8217.   LINE -(160, 175)
  8218.   LINE -(10, 25)
  8219.  
  8220.   ' Paint the interior magenta:
  8221.   PAINT (160, 100), 2, 3
  8222.  
  8223.   ' Wait for a keystroke:
  8224.   Pause$ = INPUT$(1)
  8225.  
  8226.   ' Since the background is already magenta, CHR$(&HAA) tells
  8227.   ' PAINT to skip over the magenta rows in the pattern tile:
  8228.   PAINT (160, 100), Tile$, , CHR$(&HAA)
  8229.  
  8230.  Creating a Multicolor Pattern in Screen Mode 8
  8231.  
  8232.  In the EGA and VGA screen modes, it takes more than one 8-bit integer to
  8233.  define one row in a pattern tile. In these screen modes, a row is composed
  8234.  of several layers of 8-bit integers. This is because a pixel is represented
  8235.  three dimensionally in a stack of "bit planes" rather than sequentially in a
  8236.  single plane, as is the case with screen modes 1 and 2. For example, screen
  8237.  mode 8 has four of these bit planes. Each of the 4 bits per pixel in this
  8238.  screen mode is on a different plane.
  8239.  
  8240.  The following steps diagram the process for creating a multicolor pattern
  8241.  consisting of rows of alternating yellow and magenta. Note how each row in
  8242.  the pattern tile is represented by 4 parallel bytes:
  8243.  
  8244.     1. Define one row of pixels in the pattern tile. Each pixel in the row
  8245.        takes 4 bits, and each bit is in a different plane, as shown in the
  8246.        following:
  8247.  
  8248.  Add the  CHR$ values for all four bit planes to get one
  8249.  tile byte. This row is repeated in the pattern tile, so:
  8250.  
  8251.  Row$(1) = Row$(2) = CHR$(&HC3) + CHR$(&H3C) + CHR$(&HFF) + CHR$(&H3C)   2.
  8252.  
  8253.  Row$(3) = Row$(4) = CHR$(&H3C) + CHR$(&HC3) + CHR$(&HFF) + CHR$(&HC3)
  8254.  
  8255.  
  8256.  Example
  8257.  
  8258.  The following example draws a box, then paints its interior with the pattern
  8259.  created in the preceding steps:
  8260.  
  8261.   SCREEN 8
  8262.   DIM Row$(1 TO 4)
  8263.  
  8264.   ' Two rows of alternating magenta and yellow:
  8265.   Row$(1) = CHR$(&HC3) + CHR$(&H3C) + CHR$(&HFF) + CHR$(&H3C)
  8266.   Row$(2) = Row$(1)
  8267.  
  8268.   ' Invert the pattern (two rows of alternating yellow
  8269.   ' and magenta):
  8270.   Row$(3) = CHR$(&H3C) + CHR$(&HC3) + CHR$(&HFF) + CHR$(&HC3)
  8271.   Row$(4) = Row$(3)
  8272.  ' Create a pattern tile from the rows defined above:
  8273.   FOR I% = 1 TO 4
  8274.      Tile$ = Tile$ + Row$(I%)
  8275.   NEXT I%
  8276.  
  8277.   ' Draw box and fill it with the pattern:
  8278.   LINE (50, 50)-(570, 150), , B
  8279.   PAINT (320, 100), Tile$
  8280.  
  8281.  
  8282.  DRAW: A Graphics Macro Language
  8283.  
  8284.  The  DRAW statement is a miniature language by itself. It draws and paints
  8285.  images on the screen using a set of one- or two-letter commands, known as
  8286.  "macros," embedded in a string expression.
  8287.  
  8288.   DRAW offers the following advantages over the other graphics statements
  8289.  discussed so far:
  8290.  
  8291.    ■   The macro string argument to  DRAW is compact: a single, short string
  8292.        can produce the same output as several  LINE statements.
  8293.  
  8294.    ■   Images created with  DRAW can easily be scaled -- that is, enlarged or
  8295.        reduced in size -- by using the  S macro in the macro string.
  8296.  
  8297.    ■   Images created with  DRAW can be rotated any number of degrees by
  8298.        using the  TA macro in the macro string.
  8299.  
  8300.  
  8301.  Consult online Help for more information.
  8302.  
  8303.  Example
  8304.  
  8305.  The following program gives a brief introduction to the movement macros  U,
  8306.  D,  L,  R,  E,  F,  G, and  H; the "plot/don't plot" macro  B; and the color
  8307.  macro  C. This program draws horizontal, vertical, and diagonal lines in
  8308.  different colors, depending on which direction key on the numeric keypad (Up
  8309.  Arrow, Down Arrow, Left Arrow, PgUp, PgDn, and so on) is pressed.
  8310.  
  8311.  This program is in the file named PLOTTER.BAS on the Microsoft BASIC
  8312.  distribution disks.
  8313.  
  8314.  
  8315.  ' Values for keys on the numeric keypad and the Spacebar:
  8316.  
  8317.   CONST UP = 72, DOWN = 80, LFT = 75, RGHT = 77
  8318.   CONST UPLFT = 71, UPRGHT = 73, DOWNLFT = 79, DOWNRGHT = 81
  8319.   CONST SPACEBAR = " "
  8320.  
  8321.   ' Null$ is the first character of the two-character INKEY$
  8322.   ' value returned for direction keys such as Up and Down:
  8323.   Null$ = CHR$(0)
  8324.  ' Plot$ = "" means draw lines; Plot$ = "B" means
  8325.   ' move graphics cursor, but don't draw lines:
  8326.   Plot$ = ""
  8327.  
  8328.   PRINT "Use the cursor movement keys to draw lines."
  8329.   PRINT "Press Spacebar to toggle line drawing on and off."
  8330.   PRINT "Press <ENTER> to begin. Press q to end the program."
  8331.   DO : LOOP WHILE INKEY$ = ""
  8332.  
  8333.   SCREEN 1
  8334.  
  8335.   DO
  8336.      SELECT CASE KeyVal$
  8337.         CASE Null$ + CHR$(UP)
  8338.            DRAW Plot$ + "C1 U2"
  8339.         CASE Null$ + CHR$(DOWN)
  8340.            DRAW Plot$ + "C1 D2"
  8341.         CASE Null$ + CHR$(LFT)
  8342.            DRAW Plot$ + "C2 L2"
  8343.         CASE Null$ + CHR$(RGHT)
  8344.            DRAW Plot$ + "C2 R2"
  8345.         CASE Null$ + CHR$(UPLFT)
  8346.            DRAW Plot$ + "C3 H2"
  8347.         CASE Null$ + CHR$(UPRGHT)
  8348.            DRAW Plot$ + "C3 E2"
  8349.         CASE Null$ + CHR$(DOWNLFT)
  8350.            DRAW Plot$ + "C3 G2"
  8351.         CASE Null$ + CHR$(DOWNRGHT)
  8352.            DRAW Plot$ + "C3 F2"
  8353.         CASE SPACEBAR
  8354.            IF Plot$ = "" THEN Plot$ = "B " ELSE Plot$ = ""
  8355.         CASE ELSE
  8356.    ' The user pressed some key other than one of the
  8357.   ' direction keys, the Spacebar, or "q," so
  8358.   ' don't do anything.
  8359.      END SELECT
  8360.     KeyVal$ = INKEY$
  8361.  
  8362.  LOOP UNTIL KeyVal$ =
  8363.  "q"
  8364.  
  8365.   SCREEN 0, 0' Restore the screen to 80-column
  8366.   WIDTH 80' text mode and end.
  8367.   END
  8368.  
  8369.  
  8370.  Basic Animation Techniques
  8371.  
  8372.  Using only the graphics statements covered in earlier sections, you can do
  8373.  simple animation of objects on the screen. For instance, you can first draw
  8374.  a circle with  CIRCLE, then redraw it with the background color to erase it,
  8375.  and finally recalculate the circle's center point and draw it in a new
  8376.  location.
  8377.  
  8378.  This technique works well enough for simple figures, but its disadvantages
  8379.  become apparent when animating more complex images. Even though the graphics
  8380.  statements are very fast, you can still notice the lag. Moreover, it is not
  8381.  possible to preserve the background with this method: when you erase the
  8382.  object, you also erase whatever was already on the screen.
  8383.  
  8384.  Two statements allow you to do high-speed animation:  GET and  PUT. You can
  8385.  create an image using output from statements such as  PSET,  LINE,  CIRCLE,
  8386.  or  PAINT, then take a "snapshot" of that image with  GET, copying the image
  8387.  to memory. With  PUT, you can then reproduce the image stored with  GET
  8388.  anywhere on the screen or viewport.
  8389.  
  8390.  
  8391.  Saving Images with GET
  8392.  
  8393.  After you have created the original image on the screen, you need to
  8394.  calculate the x- and y-coordinates of a rectangle large enough to hold the
  8395.  entire image. You then use  GET to copy the entire rectangle to memory. The
  8396.  syntax for the graphics  GET statement is as follows:
  8397.  
  8398.   GET  STEP ( x1!,  y1!)  -  STEP(  x2!, y  2!), a  r  rayname#The arguments
  8399.  ( x1! ,  y1! ) and  ( x2! ,  y2! ) give the coordinates of a rectangle's
  8400.  upper-left and lower-right corners. The argument  arrayname# refers to any
  8401.  numeric array. The size specified in a  DIM statement for  arrayname#
  8402.  depends on the following three factors:
  8403.  
  8404.  
  8405.        The height and width of the rectangle enclosing the image
  8406.  
  8407.        The screen mode chosen for graphics output
  8408.  
  8409.        The type of the array (integer, long integer, single precision, or
  8410.        double precision)
  8411.  
  8412.  
  8413.  Note
  8414.  
  8415.  Although the array used to store images can have any numeric type, it is
  8416.  strongly recommended that you use only integer arrays. All possible graphics
  8417.  patterns on the screen can be represented by integers. This is not the case,
  8418.  however, with single-precision or double-precision real numbers. Some
  8419.  graphics patterns are not valid real numbers, and it could lead to
  8420.  unforeseen results if these patterns were stored in a real-number array.
  8421.  
  8422.  The formula for calculating the size in bytes of  arrayname# is as follows:
  8423.  
  8424.   size-in-bytes = 4 +  height *  planes *  INT(( width *  bits-per-pixel/
  8425.  planes + 7)/8)
  8426.  
  8427.  In the preceding syntax,  height and  width are the dimensions, in number of
  8428.  pixels, of the rectangle to get, and the value for  bits-per-pixel depends
  8429.  on the number of colors available in the given screen mode. More colors mean
  8430.  more bits are required to define each pixel. In screen mode 1, two bits
  8431.  define a pixel, while in screen mode 2, one bit is enough. (See
  8432.  "Pattern-Tile Size in Different Screen Modes" earlier in this chapter for
  8433.  the general formula for  bits-per-pixel.) The following list shows the value
  8434.  for  planes for each of the screen modes:
  8435.  
  8436. ╓┌──────────────────────────────────────────┌───────┌────────────────────────╖
  8437.  ────────────────────────────────────────────────────────────────────────────
  8438.  1, 2, 11, and 13                           1
  8439.  9 (64K of video memory) and 10             2
  8440.  7, 8, 9 (more than 64K of video memory),   and 12  4
  8441.  
  8442.  
  8443.  
  8444.  To get the number of elements that should be in the array, divide the  size-i
  8445.  number of bytes for one element in the array. This is where the type of the
  8446.  array comes into play. If it is an integer array, each element takes 2 bytes
  8447.  of memory (the size of an integer), so  size-in-bytes should be divided by
  8448.  two to get the actual size of the array. Similarly, if it is a long integer
  8449.  array,  size-in-bytes should be divided by four (since one long integer
  8450.  requires 4 bytes of memory), and so on. If it is single precision, divide by
  8451.  four; if it is double precision, divide by eight.
  8452.  
  8453.  The following steps show how to calculate the size of an integer array large
  8454.  enough to hold a rectangle in screen mode 1 with coordinates (10, 40) for
  8455.  the upper-left corner and (90, 80) for the lower-right corner:
  8456.  
  8457.    1. Calculate the height and width of the rectangle:
  8458.  
  8459.        RectHeight =  ABS( y2 -  y1) + 1 = 80 - 40 + 1 = 41
  8460.        RectWidth =  ABS( x2 -  x1) + 1 = 90 - 10 + 1 = 81
  8461.        Remember to add one after subtracting  y1 from  y2 or  x1 from  x2.
  8462.        For example, if  x1 = 10 and  x2 = 20, then the width of the rectangle
  8463.        is 20 - 10 + 1, or 11.
  8464.  
  8465.     2. Calculate the size in bytes of the integer array:
  8466.  
  8467.        ByteSize = 4 + RectHeight *  INT((RectWidth * BitsPerPixel + 7) / 8)
  8468.                 = 4 + 41 *  INT((81 * 2 + 7) / 8)
  8469.                 = 4 + 41 *  INT(169 / 8)
  8470.                 = 4 + 41 * 21
  8471.                 = 865
  8472.  
  8473.     3. Divide the size in bytes by the bytes per element (two for integers)
  8474.        and round the result up to the nearest whole number:
  8475.  
  8476.        865 / 2 = 433
  8477.  
  8478.  Therefore, if the name of the array is Image(), the following  DIM statement
  8479.  ensures that  Image is big enough to copy the pixel information in the
  8480.  rectangle:
  8481.  
  8482.  DIM Image (1 TO 433) AS INTEGER
  8483.  
  8484.  
  8485.  Note
  8486.  
  8487.  Although the  GET statement uses view coordinates after a  WINDOW statement,
  8488.  you must use physical coordinates to calculate the size of the array used in
  8489.   GET. (See the section "Redefining Viewport Coordinates with WINDOW" earlier
  8490.  in this chapter for more information on  WINDOW and how to convert view
  8491.  coordinates to physical coordinates.)
  8492.  
  8493.  Note that the steps outlined previously give the minimum size required for
  8494.  the array; however, any larger size will do. For example, the following
  8495.  statement also works:
  8496.  
  8497.  DIM Image (1 TO 500) AS INTEGER
  8498.  
  8499.  The following program draws an ellipse and paints its interior. A  GET
  8500.  statement copies the rectangular area containing the ellipse into memory.
  8501.  (The following section, "Moving Images with PUT" shows how to use the
  8502.   PUT statement to reproduce the ellipse in a different
  8503.  location.)
  8504.  
  8505.  
  8506.   SCREEN 1
  8507.  
  8508.   ' Dimension an integer array large enough
  8509.   ' to hold the rectangle:
  8510.   DIM Image (1 TO 433) AS INTEGER
  8511.  
  8512.   ' Draw an ellipse inside the rectangle, using magenta for
  8513.   ' the outline and painting the interior white:
  8514.   CIRCLE (50, 60), 40, 2, , , .5
  8515.   PAINT (50, 60), 3, 2
  8516.  
  8517.   ' Store the image of the rectangle in the array:
  8518.   GET (10, 40)-(90, 80), Image
  8519.  
  8520.  
  8521.  Moving Images with PUT
  8522.  
  8523.  While the  GET statement allows you to take a snapshot of an image,  PUT
  8524.  allows you to paste that image anywhere you want on the screen. A statement
  8525.  of the following form copies the rectangular image stored in  arrayname#
  8526.  back to the screen and places its upper-left corner at the point with
  8527.  coordinates ( x!,  y!):
  8528.  
  8529.   PUT( x!, y!),  arrayname# ,  actionverbNote that only one coordinate pair ap
  8530.  
  8531.  
  8532.  If a  WINDOW statement appears in the program before  PUT, the coordinates
  8533.  x and  y refer to the lower-left corner of the rectangle.  WINDOW SCREEN,
  8534.  however, does not have this effect; that is, after  WINDOW SCREEN,  x and  y
  8535.  still refer to the upper-left corner of the rectangle.
  8536.  
  8537.  For example, adding the next line to the last example in the section "Saving
  8538.  Images with  GET" causes an exact duplicate of the painted ellipse to appear
  8539.  on the right side of the screen much more quickly than redrawing and
  8540.  repainting the same figure with  CIRCLE and  PAINT:
  8541.  
  8542.  PUT (200, 40), Image
  8543.  Take care not to specify coordinates that would put any part of the image
  8544.  outside the screen or active viewport, as in the following statements:
  8545.  
  8546.   SCREEN 2
  8547.   .
  8548.   .
  8549.   .
  8550.   ' Rectangle measures 141 pixels x 91 pixels:
  8551.   GET (10, 10)-(150, 100), Image
  8552.   PUT (510, 120), Image
  8553.  
  8554.  Unlike other graphics statements such as  LINE or  CIRCLE,  PUT
  8555.  does not clip images lying outside the viewport. Instead, it produces
  8556.  an error message Illegal function call.
  8557.  
  8558.  One of the other advantages of the  PUT statement is that you can control
  8559.  how the stored image interacts with what is already on the screen by using
  8560.  the argument  actionverb. The  actionverb argument can be one of the
  8561.  following options:  PSET,  PRESET,  AND,  OR, or  XOR.
  8562.  
  8563.  If you do not care what happens to the existing screen background, use the
  8564.  PSET option, since it transfers an exact duplicate of the stored image to
  8565.  the screen and overwrites anything that was already there.
  8566.  
  8567.  Table 5.4 shows how other options affect the way the  PUT statement causes
  8568.  pixels in a stored image to interact with pixels on the screen. In this
  8569.  table, 1 means a pixel is on and 0 means a pixel is off.
  8570.  
  8571.  1 XOR 1
  8572.  
  8573.  Example
  8574.  
  8575.  The output from the following program shows the same image superimposed over
  8576.  a filled rectangle using each of the five options discussed previously:
  8577.  
  8578.   SCREEN 2
  8579.  
  8580.   DIM CircImage (1 TO 485) AS INTEGER
  8581.  
  8582.   ' Draw and paint an ellipse then store its image with GET:
  8583.   CIRCLE (22, 100), 80, , , , 4
  8584.   PAINT (22, 100)
  8585.   GET (0, 20)-(44, 180), CircImage
  8586.   CLS
  8587.  
  8588.   ' Draw a box and fill it with a pattern:
  8589.   LINE (40, 40)-(600, 160), , B
  8590.   Pattern$ = CHR$(126) + CHR$(0) + CHR$(126) + CHR$(126)
  8591.   PAINT (50, 50), Pattern$
  8592.  ' Put the images of the ellipse over the box
  8593.   ' using the different action options:
  8594.   PUT (100, 20), CircImage, PSET
  8595.   PUT (200, 20), CircImage, PRESET
  8596.   PUT (300, 20), CircImage, AND
  8597.   PUT (400, 20), CircImage, OR
  8598.   PUT (500, 20), CircImage, XOR
  8599.  
  8600.  PSET PRESET AND OR XOR
  8601.  In screen modes supporting color, the options PRESET,  AND,  OR, and  XOR pro
  8602.  simply turning a pixel on or off. However, the analogy made between these
  8603.  options and logical operators still holds in these modes.
  8604.  
  8605.  For example, if the current pixel on the screen is color 1
  8606.  (cyan in palette 1) and the pixel in the overlaid image is color 2 (magenta
  8607.  in palette 1), then the color of the resulting pixel after a  PUT statement
  8608.  depends on the option, as shown for just 6 of the 16 different combinations
  8609.  of image color and background color in Table 5.5.
  8610.  
  8611.  
  8612.  Pixel color inscreen beforescreen after PUT Action optionstored imagePUT
  8613.  statementstatement In palette 1, cyan is assigned to attribute 1 (01 binary),
  8614.  magenta is assigned to attribute 2 (10 binary), and white is assigned to
  8615.  attribute 3 (11 binary). If you have an EGA, you can use the  PALETTE
  8616.  statement to assign different colors to the attributes 1, 2, and 3.
  8617.  
  8618.  
  8619.  Animation with GET and PUT
  8620.  
  8621.  One of the most useful things that can be done with the  GET and  PUT
  8622.  statements is animation. The two options best suited for animation are  XOR
  8623.  and  PSET. Animation done with  PSET is faster; but as shown by the output
  8624.  from the last program,  PSET erases the screen background. In contrast,  XOR
  8625.  is slower but restores the screen background after the image is moved.
  8626.  Animation with  XOR is done with the following four steps:
  8627.  
  8628.     1. Put the object on the screen with  XOR.  2. Calculate the new position
  8629.  
  8630.     3. Put the object on the screen a second time at the old location, using
  8631.        XOR again, this time to remove the old image.
  8632.  
  8633.     4. Go to step 1, but this time put the object at the new location.
  8634.  Movement done with these four steps leaves the background unchanged after
  8635.  step 3. Flicker can be reduced by minimizing the time between steps 4 and 1
  8636.  and by making sure that there is enough time delay between steps 1 and 3. If
  8637.  more than one object is being animated, every object should be processed at
  8638.  once, one step at a time.
  8639.  
  8640.  If it is not important to preserve the background, use the  PSET option for
  8641.  animation. The idea is to leave a border around the image when you copy it
  8642.  with the  GET statement. If this border is as large as or larger than the
  8643.  maximum distance the object will move, then each time the image is put in a
  8644.  new location, the border erases all traces of the image in the old location.
  8645.  This method can be faster than the method using  XOR described previously,
  8646.  since only one  PUT statement is required to move an object (although you
  8647.  must move a larger image).
  8648.  
  8649.  The following example shows how to use  PUT with the  PSET option to produce
  8650.  the effect of a ball bouncing off the bottom and sides of a box. Note in the
  8651.  output that follows how the rectangle containing the ball, specified in the
  8652.  GET statement, erases the filled box and the printed message.
  8653.  
  8654.  
  8655.  Examples
  8656.  
  8657.  This program is in the file named BALLPSET.BAS on the Microsoft BASIC
  8658.  distribution disks.
  8659.  
  8660.  
  8661.   DECLARE FUNCTION GetArraySize (WLeft, WRight, WTop, WBottom)
  8662.  
  8663.   SCREEN 2
  8664.  
  8665.   ' Define a viewport and draw a border around it:
  8666.   VIEW (20, 10)-(620, 190),,1
  8667.  
  8668.   CONST PI = 3.141592653589#
  8669.  ' Redefine the coordinates of the viewport with window
  8670.   ' coordinates:
  8671.   WINDOW (-3.15, -.14)-(3.56, 1.01)
  8672.  
  8673.   ' Arrays in program are now dynamic:
  8674.   ' $DYNAMIC
  8675.  
  8676.   ' Calculate the window coordinates for the top and bottom of a
  8677.   ' rectangle large enough to hold the image that will be
  8678.   ' drawn with CIRCLE and PAINT:
  8679.   WLeft = -.21
  8680.   WRight = .21
  8681.   WTop = .07
  8682.   WBottom = -.07
  8683.  
  8684.   ' Call the GetArraySize function,
  8685.   ' passing it the rectangle's window coordinates:
  8686.   ArraySize% = GetArraySize(WLeft, WRight, WTop, WBottom)
  8687.  
  8688.   DIM Array (1 TO ArraySize%) AS INTEGER
  8689.  
  8690.   ' Draw and paint the circle:
  8691.   CIRCLE (0, 0), .18
  8692.   PAINT (0, 0)
  8693.  
  8694.   ' Store the rectangle in Array:
  8695.   GET (WLeft, WTop)-(WRight, WBottom), Array
  8696.   CLS
  8697.   ' Draw a box and fill it with a pattern:
  8698.   LINE (-3, .8)-(3.4, .2), , B
  8699.   Pattern$ = CHR$(126) + CHR$(0) + CHR$(126) + CHR$(126)
  8700.   PAINT (0, .5), Pattern$
  8701.  
  8702.   LOCATE 21, 29
  8703.   PRINT "Press any key to end."
  8704.  
  8705.   ' Initialize loop variables:
  8706.   StepSize = .02
  8707.   StartLoop = -PI
  8708.   Decay = 1
  8709.  
  8710.   DO
  8711.      EndLoop = -StartLoop
  8712.      FOR X = StartLoop TO EndLoop STEP StepSize
  8713.  
  8714.         ' Each time the ball "bounces" (hits the bottom of the
  8715.         ' viewport), the Decay variable gets smaller, making
  8716.         ' the height of the next bounce smaller:
  8717.         Y = ABS(COS(X)) * Decay - .14
  8718.         IF Y < -.13 THEN Decay = Decay * .9
  8719.  ' Stop if key pressed or Decay less than .01:
  8720.         Esc$ = INKEY$
  8721.         IF Esc$ <> "" OR Decay < .01 THEN EXIT FOR
  8722.  
  8723.         ' Put the image on the screen. The StepSize offset is
  8724.         ' smaller than the border around the circle. Thus,
  8725.         ' each time the image moves, it erases any traces
  8726.         ' left from the previous PUT (and also erases anything
  8727.         ' else on the screen):
  8728.         PUT (X, Y), Array, PSET
  8729.      NEXT X
  8730.  
  8731.      ' Reverse direction:
  8732.      StepSize = -StepSize
  8733.      StartLoop = -StartLoop
  8734.   LOOP UNTIL Esc$ <> "" OR Decay < .01
  8735.  
  8736.   END
  8737.  
  8738.   FUNCTION GetArraySize (WLeft, WRight, WTop, WBottom) STATIC
  8739.  
  8740.      ' Map the window coordinates passed to this function to
  8741.      ' their physical coordinate equivalents:
  8742.      VLeft = PMAP(WLeft, 0)
  8743.      VRight = PMAP(WRight, 0)
  8744.      VTop = PMAP(WTop, 1)
  8745.      VBottom = PMAP(WBottom, 1)
  8746.     ' Calculate the height and width in pixels
  8747.      ' of the enclosing rectangle:
  8748.      RectHeight = ABS(VBottom - VTop) + 1
  8749.      RectWidth = ABS(VRight - VLeft) + 1
  8750.  
  8751.      ' Calculate size in bytes of array:
  8752.      ByteSize = 4 + RectHeight * INT((RectWidth + 7) / 8)
  8753.  
  8754.      ' Array is integer, so divide bytes by two:
  8755.      GetArraySize = ByteSize \ 2 + 1
  8756.   END FUNCTION
  8757.  
  8758.  Contrast the preceding program with the next program, which uses
  8759.  PUT with  XOR to preserve the screen's background, according to the steps
  8760.  outlined earlier. Note how the rectangle containing the ball is smaller than
  8761.  in the preceding program, since it is not necessary to leave a border. Also
  8762.  note that two  PUT statements are required, one to make the image visible
  8763.  and another to make it disappear. Finally, observe the empty  FOR... NEXT
  8764.  delay loop between the  PUT statements; this loop reduces the flicker that
  8765.  results from the image appearing and disappearing too rapidly.
  8766.  
  8767.  
  8768.  This program is in the file named BALLXOR.BAS on the Microsoft BASIC
  8769.  distribution disks.
  8770.  
  8771.   ' The rectangle is smaller than the one in the previous
  8772.   ' program, which means Array is also smaller:
  8773.   WLeft = -.18
  8774.   WRight = .18
  8775.   WTop = .05
  8776.   WBottom = -.05
  8777.   .
  8778.   .
  8779.   .
  8780.   DO
  8781.      EndLoop = -StartLoop
  8782.      FOR X = StartLoop TO EndLoop STEP StepSize
  8783.         Y = ABS(COS(X)) * Decay - .14
  8784.  ' The first PUT statement places the image
  8785.         ' on the screen:
  8786.         PUT (X,Y), Array, XOR
  8787.  
  8788.         ' Use an empty FOR...NEXT loop to delay
  8789.         ' the program and reduce image flicker:
  8790.         FOR I = 1 TO 5: NEXT I
  8791.  
  8792.         IF Y < -.13 THEN Decay = Decay * .9
  8793.         Esc$ = INKEY$
  8794.         IF Esc$ <> "" OR Decay < .01 THEN EXIT FOR
  8795.  
  8796.         ' The second PUT statement erases the image and
  8797.         ' restores the background:
  8798.         PUT (X, Y), Array, XOR
  8799.      NEXT X
  8800.  
  8801.      StepSize = -StepSize
  8802.      StartLoop = -StartLoop
  8803.   LOOP UNTIL Esc$ <> "" OR Decay < .01
  8804.  
  8805.   END
  8806.   .
  8807.   .
  8808.   .
  8809.  
  8810.  
  8811.  Animating with Screen Pages
  8812.  
  8813.  This section describes an animation technique that utilizes multiple pages
  8814.  of your computer's video memory.
  8815.  
  8816.  Pages in video memory are analogous to pages in a book. Depending on the
  8817.  graphics capability of your computer, what you see displayed on the screen
  8818.  may only be part of the video memory available -- just as what you see when
  8819.  you open a book is only part of the book. However, unlike a book, the unseen
  8820.  pages of your computer's video memory can be active; that is, while you are
  8821.  looking at one page on the screen, graphics output can be taking place on
  8822.  the others. It's as if the author of a book were still writing new pages
  8823.  even as you were reading the book.
  8824.  
  8825.  The area of video memory visible on the screen is called the "visual page,"
  8826.  while the area of video memory where graphics statements put their output is
  8827.  called the "active page." The  SCREEN statement allows you to select visual
  8828.  and active screen pages with the following syntax:
  8829.  
  8830.   SCREEN mode% , activepage%, visiblepage%In this syntax,  activepage% is the
  8831.  number of the visual page. The active page and the visual page can be
  8832.  one and the same (and are by default when the  activepage% or
  8833.   visiblepage% arguments are not used with  SCREEN,
  8834.  in which case the value of both arguments is 0).
  8835.  
  8836.  You can animate objects on the screen by selecting a screen mode with more
  8837.  than one video memory page, then alternating the pages, sending output to
  8838.  one or more active pages while displaying finished output on the visual
  8839.  page. This technique makes an active page visible only when output to that
  8840.  page is complete. Since the viewer sees only a finished image, the display
  8841.  is instantaneous.
  8842.  
  8843.  
  8844.  Example
  8845.  
  8846.  The following program demonstrates the technique discussed previously. It
  8847.  selects screen mode 7, which has two pages, then draws a cube with the  DRAW
  8848.  statement. This cube is then rotated through successive 15 angles by
  8849.  changing the value of the TA macro in the string used by  DRAW. By swapping
  8850.  the active and visual pages back and forth, this program always shows a
  8851.  completed cube while a new one is being drawn.
  8852.  
  8853.  This program is in the file named CUBE.BAS on the Microsoft BASIC
  8854.  distribution disks.
  8855.  
  8856.   ' Define the macro string used to draw the cube
  8857.   ' and paint its sides:
  8858.   One$ ="BR30 BU25 C1 R54 U45 L54 D45 BE20 P1,1    G20 C2 G20"
  8859.   Two$ ="R54 E20 L54 BD5 P2,2 U5 C4 G20 U45 E20 D45 BL5 P4,4"
  8860.   Plot$ = One$ + Two$
  8861.  
  8862.   APage% = 1      ' Initialize values for the active and visual
  8863.   VPage% = 0      ' pages as well as the angle of rotation.
  8864.   Angle% = 0
  8865.  DO
  8866.      SCREEN 7, , APage%, VPage% ' Draw to the active page
  8867.      ' while showing the visual page.
  8868.  
  8869.      CLS 1' Clear the active page.
  8870.  
  8871.      ' Rotate thecube "Angle%" degrees:
  8872.      DRAW"TA" + STR$(Angle%) + Plot$
  8873.  
  8874.      ' Angle% is some multiple of 15 degrees:
  8875.      Angle% = (Angle% + 15) MOD 360
  8876.  
  8877.      ' Drawing is complete, so make the cube visible in its
  8878.      ' new position by switching the active and visual pages:
  8879.      SWAPAPage%,VPage%
  8880.  
  8881.   LOOP WHILE INKEY$ = ""' A keystroke ends the program.
  8882.  
  8883.   END
  8884.  
  8885.  
  8886.  Sample Applications
  8887.  
  8888.  The sample applications in this chapter are a bar-graph generator, a program
  8889.  that plots points in the Mandelbrot Set using different colors, and a
  8890.  pattern editor.
  8891.  
  8892.  
  8893.  Bar-Graph Generator (BAR.BAS)
  8894.  
  8895.  This program uses all the forms of the  LINE statement presented previously
  8896.  to draw a filled bar chart. Each bar is filled with a pattern specified in a
  8897.   PAINT statement. The input for the program consists of titles for the
  8898.  graph, labels for the x- and y-axes, and a set of up to five labels (with
  8899.  associated values) for the bars.
  8900.  
  8901.  
  8902.  Statements Used
  8903.  
  8904.  This program demonstrates the use of the following graphics statements:
  8905.  
  8906.    ■    LINE ■    PAINT (with a pattern)
  8907.  
  8908.    ■    SCREEN
  8909.  
  8910.  Program Listing
  8911.  
  8912.  The bar-graph generator program BAR.BAS follows:
  8913.  
  8914.  ' Define type for the titles:
  8915.  
  8916.   TYPE TitleType
  8917.      MainTitle AS STRING * 40
  8918.      XTitle AS STRING * 40
  8919.      YTitle AS STRING * 18
  8920.   END TYPE
  8921.  
  8922.   DECLARE SUB InputTitles (T AS TitleType)
  8923.   DECLARE FUNCTION DrawGraph$ (T AS TitleType, Label$(), Value!(), N%)
  8924.   DECLARE FUNCTION InputData% (Label$(), Value!())
  8925.  
  8926.   ' Variable declarations for titles and bar data:
  8927.   DIM Titles AS TitleType, Label$(1 TO 5), Value(1 TO 5)
  8928.  
  8929.   CONST FALSE = 0, TRUE = NOT FALSE
  8930.  
  8931.   DO
  8932.      InputTitles Titles
  8933.      N% = InputData%(Label$(), Value())
  8934.      IF N% <> FALSE THEN
  8935.         NewGraph$ = DrawGraph$(Titles, Label$(), Value(), N%)
  8936.      END IF
  8937.   LOOP WHILE NewGraph$ = "Y"
  8938.  
  8939.   END
  8940.  ' ======================== DRAWGRAPH ======================
  8941.   '   Draws a bar graph from the data entered in the
  8942.   '   INPUTTITLES and INPUTDATA procedures.
  8943.   ' =========================================================
  8944.  
  8945.   FUNCTION DrawGraph$ (T AS TitleType, Label$(), Value(), N%) STATIC
  8946.  
  8947.      ' Set size of graph:
  8948.      CONST GRAPHTOP = 24, GRAPHBOTTOM = 171
  8949.      CONST GRAPHLEFT = 48, GRAPHRIGHT = 624
  8950.      CONST YLENGTH = GRAPHBOTTOM - GRAPHTOP
  8951.  
  8952.      ' Calculate maximum and minimum values:
  8953.      YMax = 0
  8954.      YMin = 0
  8955.      FOR I% = 1 TO N%
  8956.         IF Value(I%) < YMin THEN YMin = Value(I%)
  8957.         IF Value(I%) > YMax THEN YMax = Value(I%)
  8958.      NEXT I%
  8959.  ' Calculate width of bars and space between them:
  8960.      BarWidth = (GRAPHRIGHT - GRAPHLEFT) / N%
  8961.      BarSpace = .2 * BarWidth
  8962.      BarWidth = BarWidth - BarSpace
  8963.  
  8964.      SCREEN 2
  8965.      CLS
  8966.  
  8967.      ' Draw y-axis:
  8968.      LINE (GRAPHLEFT, GRAPHTOP)-(GRAPHLEFT, GRAPHBOTTOM), 1
  8969.  
  8970.      ' Draw main graph title:
  8971.      Start% = 44 - (LEN(RTRIM$(T.MainTitle)) / 2)
  8972.      LOCATE 2, Start%
  8973.      PRINT RTRIM$(T.MainTitle);
  8974.  
  8975.      ' Annotate y-axis:
  8976.      Start% = CINT(13 - LEN(RTRIM$(T.YTitle)) / 2)
  8977.      FOR I% = 1 TO LEN(RTRIM$(T.YTitle))
  8978.         LOCATE Start% + I% - 1, 1
  8979.         PRINT MID$(T.YTitle, I%, 1);
  8980.      NEXT I%
  8981.  
  8982.      ' Calculate scale factor so labels aren't bigger than four digits:
  8983.      IF ABS(YMax) > ABS(YMin) THEN
  8984.         Power = YMax
  8985.      ELSE
  8986.         Power = YMin
  8987.      END IF
  8988.      Power = CINT(LOG(ABS(Power) / 100) / LOG(10))
  8989.      IF Power < 0 THEN Power = 0
  8990.  
  8991.      ' Scale minimum and maximum values down:
  8992.      ScaleFactor = 10 ^ Power
  8993.      YMax = CINT(YMax / ScaleFactor)
  8994.      YMin = CINT(YMin / ScaleFactor)
  8995.     ' If power isn't zero then put scale factor on chart:
  8996.      IF Power <> 0 THEN
  8997.         LOCATE 3, 2
  8998.         PRINT "x 10^"; LTRIM$(STR$(Power))
  8999.      END IF
  9000.  
  9001.      ' Put tick mark and number for Max point on y-axis:
  9002.      LINE (GRAPHLEFT - 3, GRAPHTOP) -STEP(3, 0)
  9003.      LOCATE 4, 2
  9004.      PRINT USING "####"; YMax
  9005.  ' Put tick mark and number for Min point on y-axis:
  9006.      LINE (GRAPHLEFT - 3, GRAPHBOTTOM) -STEP(3, 0)
  9007.      LOCATE 22, 2
  9008.      PRINT USING "####"; YMin
  9009.  
  9010.     YMax = YMax * ScaleFactor ' Scale minimum and maximum back
  9011.      YMin = YMin * ScaleFactor ' up for charting calculations.
  9012.  
  9013.      ' Annotate x-axis:
  9014.      Start% = 44 - (LEN(RTRIM$(T.XTitle)) / 2)
  9015.      LOCATE 25, Start%
  9016.      PRINT RTRIM$(T.XTitle);
  9017.  
  9018.     ' Calculate the pixel range for the y-axis:
  9019.      YRange = YMax - YMin
  9020.  
  9021.      ' Define a diagonally striped pattern:
  9022.      Tile$ = CHR$(1)+CHR$(2)+CHR$(4)+CHR$(8)+CHR$(16)+CHR$(32)+_
  9023.   CHR$(64)+CHR$(128)
  9024.  
  9025.      ' Draw a zero line if appropriate:
  9026.      IF YMin < 0 THEN
  9027.         Bottom = GRAPHBOTTOM - ((-YMin) / YRange * YLENGTH)
  9028.         LOCATE INT((Bottom - 1) / 8) + 1, 5
  9029.         PRINT "0";
  9030.      ELSE
  9031.         Bottom = GRAPHBOTTOM
  9032.      END IF
  9033.  
  9034.  ' Draw x-axis:
  9035.      LINE (GRAPHLEFT - 3, Bottom)-(GRAPHRIGHT, Bottom)
  9036.      ' Draw bars and labels:
  9037.      Start% = GRAPHLEFT + (BarSpace / 2)
  9038.      FOR I% = 1 TO N%
  9039.  
  9040.  ' Draw a bar label:
  9041.         BarMid = Start% + (BarWidth / 2)
  9042.         CharMid = INT((BarMid - 1) / 8) + 1
  9043.         LOCATE 23, CharMid - INT(LEN(RTRIM$(Label$(I%))) / 2)
  9044.         PRINT Label$(I%);
  9045.  
  9046.        ' Draw the bar and fill it with the striped pattern:
  9047.         BarHeight = (Value(I%) / YRange) * YLENGTH
  9048.         LINE (Start%, Bottom) -STEP(BarWidth, -BarHeight), , B
  9049.         PAINT (BarMid, Bottom - (BarHeight / 2)), Tile$, 1
  9050.  
  9051.        Start% = Start% + BarWidth + BarSpace
  9052.      NEXT I%
  9053.      LOCATE 1, 1
  9054.      PRINT "New graph? ";
  9055.      DrawGraph$ = UCASE$(INPUT$(1))
  9056.  
  9057.  END FUNCTION
  9058.  '
  9059.  ======================== INPUTDATA ======================
  9060.   '     Gets input for the bar labels and their values
  9061.   ' =========================================================
  9062.  
  9063.   FUNCTION InputData% (Label$(), Value()) STATIC
  9064.  
  9065.      ' Initialize the number of data values:
  9066.      NumData% = 0
  9067.  
  9068.      ' Print data-entry instructions:
  9069.      CLS
  9070.      PRINT "Enter data for up to 5 bars:"
  9071.      PRINT "   * Enter the label and value for each bar."
  9072.      PRINT "   * Values can be negative."
  9073.      PRINT "   * Enter a blank label to stop."
  9074.      PRINT
  9075.      PRINT "After viewing the graph, press any key ";
  9076.      PRINT "to end the program."
  9077.  
  9078.      ' Accept data until blank label or 5 entries:
  9079.      Done% = FALSE
  9080.      DO
  9081.         NumData% = NumData% + 1
  9082.         PRINT
  9083.         PRINT "Bar("; LTRIM$(STR$(NumData%)); "):"
  9084.         INPUT ; "        Label? ", Label$(NumData%)
  9085.  
  9086.        ' Only input value if label isn't blank:
  9087.         IF Label$(NumData%) <> "" THEN
  9088.   LOCATE , 35
  9089.   INPUT "Value? ", Value(NumData%)
  9090.  
  9091.         ' If label is blank, decrement data counter
  9092.         ' and set Done flag equal to TRUE:
  9093.         ELSE
  9094.   NumData% = NumData% - 1
  9095.   Done% = TRUE
  9096.         END IF
  9097.      LOOP UNTIL (NumData% = 5) OR Done%
  9098.  
  9099.      ' Return the number of data values input:
  9100.      InputData% = NumData%
  9101.  
  9102.   END FUNCTION
  9103.  
  9104.  '
  9105.  ====================== INPUTTITLES ======================
  9106.   '     Accepts input for the three different graph titles
  9107.   ' =========================================================
  9108.  
  9109.   SUB InputTitles (T AS TitleType) STATIC
  9110.      SCREEN 0, 0' Set text screen.
  9111.      DO' Input titles.
  9112.         CLS
  9113.         INPUT "Enter main graph title: ", T.MainTitle
  9114.         INPUT "Enter x-axis title    : ", T.XTitle
  9115.         INPUT "Enter y-axis title    : ", T.YTitle
  9116.  
  9117.         ' Check to see if titles are OK:
  9118.         LOCATE 7, 1
  9119.         PRINT "OK (Y to continue, N to change)? ";
  9120.         LOCATE , , 1
  9121.         OK$ = UCASE$(INPUT$(1))
  9122.      LOOP UNTIL OK$ = "Y"
  9123.   END SUB
  9124.  
  9125.  
  9126.  Color in a Figure Generated Mathematically (MANDEL.BAS)
  9127.  
  9128.  This program uses BASIC graphics statements to generate a figure known as a
  9129.  "fractal." A fractal is a graphic representation of what happens to numbers
  9130.  when they are subjected to a repeated sequence of mathematical operations.
  9131.  The fractal generated by this program shows a subset of the class of numbers
  9132.  known as complex numbers; this subset is called the "Mandelbrot Set," named
  9133.  after Benoit B. Mandelbrot of the IBM Thomas J. Watson Research Center.
  9134.  
  9135.  Briefly, complex numbers have two parts, a real part and a so-called
  9136.  imaginary part, which is some multiple of -1. Squaring a complex number,
  9137.  then plugging the real and imaginary parts back into a second complex
  9138.  number, squaring the new complex number, and repeating the process causes
  9139.  some complex numbers to get very large fairly fast. However, others hover
  9140.  around a stable value. The stable values are in the Mandelbrot Set and are
  9141.  represented in this program by the color black. The unstable values -- that
  9142.  is, the ones that are moving away from the Mandelbrot Set -- are represented
  9143.  by the other colors in the palette. The smaller the color attribute, the
  9144.  more unstable the point.
  9145.  
  9146.  See A.K. Dewdney's column, "Computer Recreations," in  Scientific American,
  9147.  August 1985, for more background on the Mandelbrot Set.
  9148.  
  9149.  This program also tests for the presence of an EGA card, and if one is
  9150.  present, it draws the Mandelbrot Set in screen mode 8. After drawing each
  9151.  line, the program rotates the 16 colors in the palette with a  PALETTE USING
  9152.  statement. If there is no EGA card, the program draws a four-color (white,
  9153.  magenta, cyan, and black) Mandelbrot Set in screen mode 1.
  9154.  
  9155.  
  9156.  Statements and Functions Used
  9157.  
  9158.  This program demonstrates the use of the following graphics statements and
  9159.  functions:
  9160.  
  9161.    ■    LINE ■    PALETTE USING
  9162.  
  9163.    ■    PMAP ■    PSET
  9164.  
  9165.    ■    SCREEN ■    VIEW
  9166.  
  9167.    ■    WINDOW
  9168.  
  9169.  Program Listing
  9170.  
  9171.  DEFINT A-Z' Default variable type is integer.
  9172.  
  9173.  
  9174.   DECLARESUB ShiftPalette ()
  9175.   DECLARESUB WindowVals (WL%, WR%, WT%, WB%)
  9176.   DECLARESUB ScreenTest (EM%, CR%, VL%, VR%, VT%, VB%)
  9177.  
  9178.   CONST FALSE = 0, TRUE = NOT FALSE ' Boolean constants
  9179.  
  9180.   ' Set maximum number of iterations per point:
  9181.   CONST MAXLOOP =30, MAXSIZE = 1000000
  9182.  
  9183.   DIM PaletteArray(15)
  9184.   FOR I =0 TO 15: PaletteArray(I) = I: NEXT I
  9185.  
  9186.   ' Call WindowVals to get coordinates of window corners:
  9187.   WindowVals WLeft, WRight, WTop,    WBottom
  9188.  
  9189.   ' Call ScreenTest to find out if this is an EGA machine
  9190.   ' and get coordinates of viewport corners:
  9191.   ScreenTest EgaMode, ColorRange,    VLeft, VRight, VTop, VBottom
  9192.  
  9193.   ' Define viewport and corresponding window:
  9194.   VIEW (VLeft, VTop)-(VRight, VBottom), 0, ColorRange
  9195.   WINDOW (WLeft, WTop)-(WRight, WBottom)
  9196.  
  9197.   LOCATE 24, 10 : PRINT "Press any key to quit.";
  9198.  
  9199.   XLength= VRight - VLeft
  9200.   YLength= VBottom - VTop
  9201.   ColorWidth = MAXLOOP \ ColorRange
  9202.  
  9203.   ' Loop through each pixel in viewport and calculate
  9204.   ' whether or not it is in the Mandelbrot Set:
  9205.   FOR Y =0 TO YLength' Loop through every line
  9206.   ' in the viewport.
  9207.  LogicY = PMAP(Y, 3)' Get the pixel's window
  9208.   ' y-coordinate.
  9209.      PSET(WLeft,LogicY) ' Plot leftmost pixel in the line.
  9210.      OldColor = 0' Start with background color.
  9211.  
  9212.      FOR X = 0 TOXLength ' Loop through every pixel
  9213.   ' in the line.
  9214.         LogicX = PMAP(X, 2)' Get the pixel's window
  9215.   ' x-coordinate.
  9216.         MandelX& = LogicX
  9217.         MandelY& = LogicY
  9218.  ' Do the calculations to see if this point
  9219.         ' is in the Mandelbrot Set:
  9220.         FOR I = 1TO MAXLOOP
  9221.            RealNum& = MandelX& * MandelX&
  9222.            ImagNum& = MandelY& * MandelY&
  9223.            IF (RealNum& + ImagNum&) >= MAXSIZE THEN EXIT FOR
  9224.            MandelY& = (MandelX& * MandelY&) \ 250 + LogicY
  9225.            MandelX& = (RealNum& - ImagNum&) \ 500 + LogicX
  9226.         NEXT I
  9227.  
  9228.         ' Assign a color to the point:
  9229.         PColor = I \ ColorWidth
  9230.  
  9231.         ' If color has changed, draw a line from
  9232.         ' the last point referenced to the new point,
  9233.         ' using the old color:
  9234.         IF PColor<> OldColor THEN
  9235.   LINE -(LogicX, LogicY), (ColorRange - OldColor)
  9236.   OldColor = PColor
  9237.         END IF
  9238.  
  9239.         IF INKEY$<> "" THEN END
  9240.      NEXTX
  9241.  
  9242.      ' Draw the last line segment to the right edge
  9243.      ' of the viewport:
  9244.      LINE-(LogicX, LogicY), (ColorRange - OldColor)
  9245.  
  9246.      ' If this is an EGA machine, shift the palette after
  9247.      ' drawing each line:
  9248.      IF EgaMode THEN ShiftPalette
  9249.   NEXT Y
  9250.  
  9251.   DO
  9252.      ' Continue shifting the palette
  9253.      ' until the user presses a key:
  9254.      IF EgaMode THEN ShiftPalette
  9255.   LOOP WHILE INKEY$ = ""
  9256.  
  9257.   SCREEN 0, 0            ' Restore the screen to text mode,
  9258.   WIDTH 80               ' 80 columns.
  9259.   END
  9260.  
  9261.   BadScreen:             ' Error handler that is invoked if
  9262.      EgaMode = FALSE     ' there is no EGA graphics card.
  9263.      RESUME NEXT
  9264.  
  9265.  '
  9266.  ====================== ShiftPalette =====================
  9267.   '    Rotates the palette by one each time it is called
  9268.   ' =========================================================
  9269.  
  9270.   SUB ShiftPalette STATIC
  9271.      SHARED PaletteArray(), ColorRange
  9272.  
  9273.      FOR I = 1 TOColorRange
  9274.         PaletteArray(I) =(PaletteArray(I) MOD ColorRange) + 1
  9275.      NEXTI
  9276.      PALETTE USING PaletteArray(0)
  9277.  
  9278.   END SUB
  9279.  
  9280.  ' ======================= ScreenTest ======================
  9281.   '    Uses a SCREEN 8 statement as a test to see if user has
  9282.   '    EGA hardware. If this causes an error, the EM flag is
  9283.   '    set to FALSE, and the screen is set with SCREEN 1.
  9284.  
  9285.   '    Also sets values for corners of viewport (VL = left,
  9286.   '    VR = right, VT = top, VB = bottom), scaled with the
  9287.   '    correct aspect ratio so viewport is a perfect square.
  9288.   ' =========================================================
  9289.  
  9290.   SUB ScreenTest (EM, CR,VL, VR,VT, VB) STATIC
  9291.      EM =TRUE
  9292.      ON ERROR GOTO BadScreen
  9293.      SCREEN 8, 1
  9294.      ON ERROR GOTO 0
  9295.  
  9296.      IF EM THEN' No error, SCREEN 8 is OK.
  9297.         VL = 110: VR = 529
  9298.         VT = 5: VB = 179
  9299.         CR = 15' 16 colors (0 - 15)
  9300.  
  9301.      ELSE' Error, so use SCREEN 1.
  9302.         SCREEN 1,1
  9303.         VL = 55: VR = 264
  9304.         VT = 5: VB = 179
  9305.         CR = 3' 4 colors (0 - 3)
  9306.      END IF
  9307.  
  9308.   END SUB
  9309.  
  9310.  '
  9311.  ======================= WindowVals ======================
  9312.   '     Gets window corners as input from the user, or sets
  9313.   '     values for the corners if there is no input.
  9314.   ' =========================================================
  9315.  
  9316.   SUB WindowVals (WL, WR,WT, WB)STATIC
  9317.      CLS
  9318.      PRINT "This program prints the graphic representation of"
  9319.      PRINT "the complete Mandelbrot Set. The default window"
  9320.      PRINT "is from (-1000,625) to (250,-625). To zoom in on"
  9321.      PRINT "part of the figure, input coordinates inside"
  9322.      PRINT "this window."
  9323.      PRINT "Press <ENTER> to see the default window or"
  9324.      PRINT "any other key to input window coordinates: ";
  9325.      LOCATE , , 1
  9326.      Resp$ = INPUT$(1)
  9327.  
  9328.      ' User didn't press ENTER, so input window corners:
  9329.      IF Resp$ <> CHR$(13)THEN
  9330.         PRINT
  9331.         INPUT "x-coordinate of upper-left corner: ", WL
  9332.         DO
  9333.   INPUT "x-coordinate of lower-right corner: ", WR
  9334.   IF WR <= WL THEN
  9335.       PRINT "Right corner must be greater than left corner."
  9336.   END IF
  9337.         LOOP WHILE WR <= WL
  9338.         INPUT "y-coordinate of upper-left corner: ", WT
  9339.         DO
  9340.   INPUT "y-coordinate of lower-right corner: ", WB
  9341.    IF WB >= WT THEN
  9342.      PRINT "Bottom corner must be less than top corner."
  9343.   END IF
  9344.         LOOP WHILE WB >= WT
  9345.  
  9346.      ' User pressed Enter, so set default values:
  9347.      ELSE
  9348.         WL = -1000
  9349.         WR = 250
  9350.         WT = 625
  9351.         WB = -625
  9352.      END IF
  9353.   END SUB
  9354.  
  9355.  Output
  9356.  
  9357.  The following figure shows the Mandelbrot Set in screen mode 1. This is the
  9358.  output you see if you have a CGA and you choose the default window
  9359.  coordinates.
  9360.  
  9361.  The next figure shows the Mandelbrot Set with
  9362.   ( x ,  y ) coordinates of (-500, 250) for the
  9363.  upper-left corner and (-300, 50) for the lower-right corner. This figure is
  9364.  drawn in screen mode 8, the default for an EGA or VGA.
  9365.  
  9366.  
  9367.  Pattern Editor (EDPAT.BAS)
  9368.  
  9369.  This program allows you to edit a pattern tile for use with  PAINT. While
  9370.  you are editing the tile on the left side of the screen, you can check the
  9371.  appearance of the finished pattern on the right side of the screen. When you
  9372.  have finished editing the pattern tile, the program prints the integer
  9373.  arguments used by the  CHR$ function to draw each row of the tile.
  9374.  
  9375.  
  9376.  Statements Used
  9377.  
  9378.  This program demonstrates the use of the following graphics statements:
  9379.  
  9380.    ■    LINE ■    PAINT (with pattern)
  9381.  
  9382.    ■    VIEW
  9383.  
  9384.  Program Listing
  9385.  
  9386.  DECLARESUB DrawPattern ()
  9387.  
  9388.   DECLARESUB EditPattern ()
  9389.   DECLARESUB Initialize ()
  9390.   DECLARESUB ShowPattern (OK$)
  9391.  
  9392.   DIM Bit%(0 TO 7), Pattern$, Esc$, PatternSize%
  9393.  
  9394.   DO
  9395.      Initialize
  9396.      EditPattern
  9397.      ShowPattern OK$
  9398.   LOOP WHILE OK$ = "Y"
  9399.  
  9400.   END
  9401.  ' ======================= DRAWPATTERN ====================
  9402.   '  Draws a patterned rectangle on the right side of screen.
  9403.   ' ========================================================
  9404.  
  9405.   SUB DrawPatternSTATIC
  9406.   SHARED Pattern$
  9407.      VIEW   (320, 24)-(622, 160), 0, 1  ' Set view to rectangle.
  9408.      PAINT (1, 1), Pattern$                  ' Use PAINT to fill it.
  9409.      VIEW                                    ' Set view to full screen.
  9410.  
  9411.   END SUB
  9412.  
  9413.  ' ======================= EDITPATTERN =====================
  9414.   '                  Edits a tile-byte pattern.
  9415.   ' =========================================================
  9416.  
  9417.   SUB EditPattern STATIC
  9418.   SHARED Pattern$, Esc$, Bit%(), PatternSize%
  9419.  
  9420.      ByteNum% = 1' Starting position.
  9421.      BitNum% = 7
  9422.      Null$ = CHR$(0)' CHR$(0) is the first byte of the
  9423.   ' two-byte string returned when a
  9424.   ' direction key such as Up or Down is
  9425.   ' pressed.
  9426.      DO
  9427.  
  9428.         'Calculate starting location on screen of this bit:
  9429.         X% = ((7 - BitNum%) * 16)       + 80
  9430.         Y% = (ByteNum% + 2) * 8
  9431.  'Wait for a key press (flash cursor each 3/10 second):
  9432.         State% = 0
  9433.         RefTime =0
  9434.         DO
  9435.  
  9436.    ' Check timer and switch cursor state if 3/10 second:
  9437.   IF ABS(TIMER - RefTime) > .3 THEN
  9438.   RefTime = TIMER
  9439.   State% = 1 - State%
  9440.  
  9441.   ' Turn the border of bit on and off:
  9442.       LINE (X%-1, Y%-1) -STEP(15, 8), State%, B
  9443.   END IF
  9444.  
  9445.   Check$ = INKEY$' Check for keystroke.
  9446.  
  9447.         LOOP WHILE Check$= ""' Loop until a key is pressed.
  9448.  
  9449.         'Erase cursor:
  9450.         LINE (X%-1, Y%-1) -STEP(15, 8), 0, B
  9451.  
  9452.         SELECT CASE Check$' Respond to keystroke.
  9453.  
  9454.         CASE CHR$(27)' Esc key pressed:
  9455.            EXIT SUB' exit this subprogram.
  9456.  CASE CHR$(32)' Spacebar pressed:
  9457.         ' reset state of bit.
  9458.  
  9459.            ' Invert bit in pattern string:
  9460.            CurrentByte% = ASC(MID$(Pattern$, ByteNum%, 1))
  9461.            CurrentByte% = CurrentByte% XOR Bit%(BitNum%)
  9462.            MID$ (Pattern$, ByteNum%) = CHR$(CurrentByte%)
  9463.  
  9464.            ' Redraw bit on screen:
  9465.            IF (CurrentByte% AND Bit%(BitNum%)) <> 0 THEN
  9466.                CurrentColor% = 1
  9467.            ELSE
  9468.                CurrentColor% = 0
  9469.            END IF
  9470.            LINE (X%+1, Y%+1) -STEP(11, 4), CurrentColor%, BF
  9471.  
  9472.         CASE CHR$(13)' Enter key pressed: draw
  9473.            DrawPattern   ' pattern in box on right.
  9474.  
  9475.         CASE Null$ + CHR$(75)' Left key: move cursor left.
  9476.  
  9477.            BitNum% = BitNum% + 1
  9478.            IFBitNum%> 7 THEN BitNum% = 0
  9479.  
  9480.         CASE Null$ + CHR$(77)' Right key: move cursor right.
  9481.  BitNum% = BitNum% - 1
  9482.            IFBitNum%< 0 THEN BitNum% = 7
  9483.  
  9484.         CASE Null$ + CHR$(72)' Up key: move cursor up.
  9485.  
  9486.            ByteNum% =ByteNum% - 1
  9487.            IFByteNum% < 1 THEN ByteNum% = PatternSize%
  9488.  
  9489.         CASE Null$ + CHR$(80)' Down key: move cursor down.
  9490.  
  9491.            ByteNum% =ByteNum% + 1
  9492.            IFByteNum% > PatternSize%THEN ByteNum% = 1
  9493.  
  9494.         CASE ELSE
  9495.            ' User pressed a key other than Esc, Spacebar,
  9496.            ' Enter, Up, Down, Left, or Right, so don't
  9497.            ' do anything.
  9498.         END SELECT
  9499.      LOOP
  9500.   END SUB
  9501.  
  9502.  ' ======================= INITIALIZE ======================
  9503.   '             Sets up starting pattern and screen
  9504.   ' =========================================================
  9505.  
  9506.   SUB Initialize STATIC
  9507.   SHARED Pattern$, Esc$, Bit%(), PatternSize%
  9508.  
  9509.      Esc$= CHR$(27)' Esc character is ASCII 27.
  9510.  
  9511.      ' Set up an array holding bits in positions 0 to 7:
  9512.      FOR I% = 0 TO 7
  9513.         Bit%(I%) = 2 ^ I%
  9514.      NEXTI%
  9515.  
  9516.      CLS
  9517.  
  9518.      ' Input the pattern size (in number of bytes):
  9519.      LOCATE 5, 5
  9520.      PRINT "Enter pattern size (1-16 rows):";
  9521.      DO
  9522.         LOCATE 5,38
  9523.         PRINT "";
  9524.         LOCATE 5,38
  9525.         INPUT "",PatternSize%
  9526.      LOOPWHILE PatternSize% < 1 OR PatternSize% > 16
  9527.  ' Set initial pattern to all bits set:
  9528.      Pattern$ = STRING$(PatternSize%, 255)
  9529.  
  9530.      SCREEN 2' 640 x 200 monochrome graphics mode
  9531.  
  9532.      ' Draw dividing lines:
  9533.      LINE(0, 10)-(635, 10), 1
  9534.      LINE(300, 0)-(300, 199)
  9535.      LINE(302, 0)-(302, 199)
  9536.  
  9537.      ' Print titles:
  9538.      LOCATE 1, 13: PRINT "Pattern Bytes"
  9539.      LOCATE 1, 53: PRINT "Pattern View"
  9540.  
  9541.  ' Draw editing screen for pattern:
  9542.      FOR I% = 1 TO PatternSize%
  9543.  
  9544.         ' Print label on left of each line:
  9545.         LOCATE I%+ 3, 8
  9546.         PRINT USING "##:"; I%
  9547.  
  9548.         ' Draw "bit" boxes:
  9549.         X% = 80
  9550.         Y% = (I% + 2) * 8
  9551.         FOR J% = 1 TO 8
  9552.   LINE (X%, Y%) -STEP(13, 6), 1,BF
  9553.   X% = X% + 16
  9554.         NEXT J%
  9555.      NEXTI%
  9556.  
  9557.      DrawPattern' Draw "Pattern View" box.
  9558.  
  9559.      LOCATE 21, 1
  9560.      PRINT "DIRECTION keys........Move cursor"
  9561.      PRINT "SPACEBAR............Changes point"
  9562.      PRINT "ENTER............Displays pattern"
  9563.      PRINT "ESC.........................Quits";
  9564.  
  9565.   END SUB
  9566.  
  9567.  '
  9568.  ======================== SHOWPATTERN ====================
  9569.   '   Prints the CHR$ values used by PAINT to make pattern
  9570.   ' =========================================================
  9571.  
  9572.   SUB ShowPattern (OK$) STATIC
  9573.   SHARED Pattern$, PatternSize%
  9574.  
  9575.      ' Return screen to 80-column text mode:
  9576.      SCREEN 0, 0
  9577.      WIDTH 80
  9578.  
  9579.      PRINT "The following characters make up your pattern:"
  9580.      PRINT
  9581.  
  9582.      ' Print out the value for each pattern byte:
  9583.      FOR I% = 1 TO PatternSize%
  9584.         PatternByte% = ASC(MID$(Pattern$,I%, 1))
  9585.         PRINT "CHR$("; LTRIM$(STR$(PatternByte%)); ")"
  9586.      NEXTI%
  9587.      PRINT
  9588.      LOCATE , , 1
  9589.      PRINT "New pattern? ";
  9590.      OK$ = UCASE$(INPUT$(1))
  9591.   END SUB
  9592.  
  9593.  
  9594.   ────────────────────────────────────────────────────────────────────────────
  9595.  
  9596.  Chapter 6:  Presentation Graphics
  9597.  
  9598.  
  9599.  Microsoft BASIC includes a toolbox of BASIC  SUB and function procedures,
  9600.  and assembly language routines you can use to add charts and graphs to your
  9601.  programs quickly and easily. These procedures are collectively known as the
  9602.  Presentation Graphics toolbox and include support for pie charts, bar and
  9603.  column charts, line graphs, and scatter diagrams. Each of these types of
  9604.  charts can convert masses of numbers to a single expressive picture.
  9605.  
  9606.  This chapter shows you how to use the Presentation Graphics toolbox in your
  9607.  BASIC programs. The first section describes which files you need to use the
  9608.  Presentation Graphics toolbox and demonstrates how it simplifies the graphic
  9609.  presentation of data. Subsequent sections explain terminology, present more
  9610.  elaborate examples, and describe some of the toolbox's many capabilities.
  9611.  
  9612.  You'll also learn about Presentation Graphics' default data structures and
  9613.  how to manipulate them. The final section presents a short reference list of
  9614.  all the routines that comprise the Presentation Graphics toolbox and shows
  9615.  you how to include custom graphics fonts in your charts.
  9616.  
  9617.  To use the Presentation Graphics toolbox you need a graphics adapter and a
  9618.  monitor capable of bit-mapped display -- the same equipment mentioned in
  9619.  Chapter 5, "Graphics." Support is provided for CGA, EGA, VGA, MCGA, Hercules
  9620.  monochrome graphics, and the Olivetti Color Board.
  9621.  
  9622.  The BASIC procedures for Presentation Graphics are contained in the
  9623.  source-code module CHRTB.BAS. The assembly language routines are in the
  9624.  object file chrtasm.obj. When you ran the Setup program, you had an
  9625.  opportunity to have a Quick library (.QLB) and a object-module libraries
  9626.  (.LIB) created that contain all necessary BASIC and assembly language
  9627.  routines. To write presentation graphics programs within the QBX
  9628.  environment, load the Quick library CHRTBEFR.qlb when you start QBX, for
  9629.  example:
  9630.  
  9631.  QBX /L CHRTBEFR
  9632.  
  9633.  Table 6.1 lists files and libraries that relate to Presentation Graphics
  9634.  toolbox. The "???" stands for the combination of characteristics you chose
  9635.  for your object files and libraries during Setup. E or A in the first
  9636.  position corresponds to your choice of emulator math or alternate math; F or
  9637.  N in the second position corresponds to your choice of near or far strings;
  9638.  R or P in the third position corresponds to your choice of target
  9639.  environments for executable files, either real or protected mode. For
  9640.  instance, CHRTBEFR.LIB is a library that uses the emulator math package, far
  9641.  strings, and runs only in DOS or the real-mode "compatibility box" of OS/2.
  9642.  
  9643.  
  9644.  Quick libraries can only use the far strings, emulator, and real mode
  9645.  options because these are the only possibilities in the QBX environment. You
  9646.  can construct object-module libraries with the near-strings and alternate
  9647.  math options, but no BASIC graphics programs can run under OS/2. If you want
  9648.  your executable files to use near strings and/or alternate math, you must
  9649.  compile them from the command line because the Alternate Math, Near Strings
  9650.  and OS/2 Protected Mode options are disabled within QBX whenever a Quick
  9651.  library is loaded.
  9652.  
  9653.  The .QLB and .LIB files were created in the \BC7\LIB directory of your root
  9654.  directory, unless you specified a different directory during Setup. Check
  9655.  the file PACKING.LST on your distribution disks for further information on
  9656.  the .BAS and .OBJ files and to find out where they appear on the
  9657.  distribution disks.
  9658.  
  9659.  When you create a stand-alone executable program from within the QBX
  9660.  environment from code that uses Presentation Graphics toolbox, the
  9661.  appropriate Presentation Graphics toolbox object-module library must be in
  9662.  the directory specified in the Option menu's Set Paths dialog box. Using
  9663.  libraries is discussed in Chapter 18, "Using LINK and LIB."
  9664.  
  9665.  The graphic fonts procedures represented by FONTB.BAS, FONTASM.OBJ, and
  9666.  FONTB.BI are built into CHRTBEFR.QLB and other CHRTB object-module libraries
  9667.  during Setup. You can use the graphic fonts in your Presentation Graphics
  9668.  toolbox programs without loading anything but CHRTBEFR.QLB when you start
  9669.  QBX. The graphic fonts are also built as a completely separate library
  9670.  (FONTBEFR.QLB). You can use the graphic fonts separately and combine them
  9671.  with any other libraries. Two font files (TMSRB.FON and helvb.fon) are
  9672.  supplied, but you can use any Microsoft Windows compatible bitmap fonts with
  9673.  these procedures. These files are designed specifically for the EGA aspect
  9674.  ratio.
  9675.  
  9676.  
  9677.  Presentation graphics Program Structure
  9678.  
  9679.  Typically in programming, you have to have a thorough understanding of a
  9680.  tool before you can even begin using it. However, with the Presentation
  9681.  Graphics toolbox you have a choice about the level of understanding you wish
  9682.  to cultivate. Even if you don't understand the Presentation Graphics toolbox
  9683.  any better than you do right now, you can add very adequate charts to your
  9684.  programs by following a few simple steps. To create bar, column, and pie
  9685.  charts, you can simply collect data, label the data's parts, and then call
  9686.  three routines to chart it on the screen. The following example demonstrates
  9687.  how simple it is to make the chart shown in Figure 6.1 with Presentation
  9688.  Graphics toolbox.
  9689.  
  9690.  1  Specify the proper include file:
  9691.  
  9692.  
  9693.  ' $INCLUDE: 'CHRTB.BI'  You must specify the file CHRTB.BI to call
  9694.  Presentation Graphics toolbox routines.
  9695.  
  9696.     2. Declare variables to pass as arguments to the presentation graphics
  9697.        routines:
  9698.  
  9699.  
  9700.  DIM Env AS ChartEnvironment
  9701.   DIM DataValues(1 TO 4) AS SINGLE
  9702.  
  9703.  
  9704.  
  9705.        DIM Labels(1 TO 4) AS STRING
  9706.  
  9707.  
  9708.  
  9709.  
  9710.  
  9711.    You need a variable of the user-defined type ChartEnvironment, plus data
  9712.  and the labels for the charted array variables for your charted data. These
  9713.  arrays are always dimensioned starting at 1 (rather than 0). Numeric data
  9714.  values are always single precision.
  9715.  
  9716.     3. Assemble the plot data (the following  DATA and  READ statements
  9717.        simulate data collection):
  9718.  
  9719.  
  9720.  DATA 28, 20, 30, 32
  9721.   DATA "Admin", "Acctng", "Advert", "Prod"
  9722.  
  9723.  
  9724.  
  9725.        FOR I=1 TO 4
  9726.  
  9727.  
  9728.  
  9729.  
  9730.  
  9731.        READ DataValues(I)
  9732.  
  9733.  
  9734.  
  9735.        NEXT I
  9736.  
  9737.  
  9738.  
  9739.        FOR I=1 TO 4
  9740.  
  9741.  
  9742.  
  9743.  
  9744.  
  9745.        READ Labels(I)
  9746.  
  9747.  
  9748.  
  9749.        NEXT I
  9750.  
  9751.    Data can come from a variety of sources. It can result from processing
  9752.    elsewhere in the program, be read from files, or even entered from the
  9753.    keyboard. Wherever it comes from, you must place it in the arrays
  9754.    dimensioned in the preceding step.
  9755.  
  9756.     4. Use the Presentation Graphics toolbox routine  ChartScreen to set the
  9757.        video mode. You cannot use the  SCREEN statement:
  9758.  
  9759.  
  9760.  ChartScreen 2  You use the  ChartScreen routine as you would normally use
  9761.  the  SCREEN statement. In this case 2 is passed because it gives graphics on
  9762.  a common hardware setup (CGA). If you have a Hercules (or compatible) setup,
  9763.  pass a 3 to  ChartScreen; if you have an Olivetti, pass a 4. Later examples
  9764.  illustrate how to choose the best mode for your  user's hardware.
  9765.  
  9766.     5. Use the Presentation Graphics toolbox routine  DefaultChart to set up
  9767.        the chart environment to display the type of chart you want. You pass
  9768.        the ChartEnvironment type variable declared in step 2, plus constants
  9769.        that describe the kind of chart and its features:
  9770.  
  9771.  
  9772.  DefaultChart Env, cColumn, cPlain   DefaultChart supplies most of the
  9773.  settings you want. After defaults are set with  DefaultChart, you can modify
  9774.  them with simple assignment statements (as illustrated in the examples later
  9775.  in this chapter).
  9776.  
  9777.     6. Use the appropriate Presentation Graphics toolbox routine to display
  9778.        the chart. You pass the variables declared in step 2, plus an integer
  9779.        that specifies the number of values:
  9780.  
  9781.  
  9782.  Chart Env, Labels(), DataValues(), 4  There are separate routines for
  9783.  standard charts (i.e. column, bar, and line charts), for pie charts, scatter
  9784.  charts, and charts that display multiple data series.
  9785.  
  9786.  
  9787.  
  9788.     7. Pause execution while chart is displayed:
  9789.  
  9790.  
  9791.  SLEEP  You can use BASIC's new  SLEEP statement to keep the chart on the
  9792.  screen for viewing.
  9793.  
  9794.     8. Reset the video mode (optional):
  9795.  
  9796.  
  9797.   SCREEN 0
  9798.  
  9799.    When your program detects the signal to continue, you may want to reset
  9800.  the video mode if graphics are not necessary for the rest of the program.
  9801.  
  9802.  
  9803.  
  9804.  Terminology
  9805.  
  9806.  The following explanations of the terms and phrases used when discussing the
  9807.  Presentation Graphics toolbox will help you better understand this chapter
  9808.  and its contents.
  9809.  
  9810.  
  9811.  Data Point
  9812.  
  9813.  A data item having a numeric value. In a chart, a data point is usually one
  9814.  value among a series of values to be illustrated. Data points are shown
  9815.  either as bars, columns, slices of a pie, or as individual plot characters
  9816.  (markers). In the "Presentation Graphics Program Structure" section, each of
  9817.  the elements of the DataValues() array was a data point.
  9818.  
  9819.  
  9820.  Data Series
  9821.  
  9822.  Groups or series of data that can be graphed on the same chart, for example,
  9823.  as a continuous set of data points on a graph. In the preceding section, the
  9824.  collection of elements comprising the DataValues() array represented a data
  9825.  series.
  9826.  
  9827.  Data items related by a common idea or purpose constitute a "series."  For
  9828.  example, the day-to-day prices of a stock over the course of a year form a
  9829.  single data series. The Presentation Graphics toolbox allows you to plot
  9830.  multiple series on the same graph. In theory only your system's memory
  9831.  capacity restricts the number of data series that can appear on a graph.
  9832.  However, there are practical considerations.
  9833.  
  9834.  Characteristics such as color and pattern help distinguish one series from
  9835.  another; you can more readily differentiate series on a color monitor than
  9836.  you can on a monochrome monitor. The number of series that can comfortably
  9837.  appear on the same chart depends on the chart type and the number of
  9838.  available colors. Only experimentation can tell you what is best for the
  9839.  system on which your program will run.
  9840.  
  9841.  
  9842.  Categories
  9843.  
  9844.  "Categories" are non-numeric data. A set of categories forms a frame of
  9845.  reference for comparisons of numeric data. For example, the months of the
  9846.  year are categories against which numeric data such as rainfall can be
  9847.  plotted. In the example in the section "Presentation Graphics Program
  9848.  Structure," each element of the Labels() array was a category.
  9849.  
  9850.  Regional sales provide another example. A chart can show comparisons of a
  9851.  company's sales in different parts of the country. Each region forms a
  9852.  category. The sales within each region are numeric data that have meaning
  9853.  only within the context of a particular category.
  9854.  
  9855.  
  9856.  Values
  9857.  
  9858.  "Values" are numeric data. Each value could be represented on a chart by a
  9859.  data point. Each element (or data point) in a data series has a value.
  9860.  Sales, stock prices, air temperatures, populations--all are series of values
  9861.  that can be plotted against categories or against other values.
  9862.  
  9863.  The Presentation Graphics toolbox allows you to represent different series
  9864.  of value data on a single graph. For example, average monthly temperatures
  9865.  or monthly sales of heating oil during different years -- or a combination
  9866.  of temperatures and sales -- can be plotted together on the same graph.
  9867.  
  9868.  
  9869.  Pie Charts
  9870.  
  9871.  467fbfffPresentation graphics can display either a standard or an "exploded"
  9872.  pie chart. The exploded view shows the pie with one or more pieces separated
  9873.  out for emphasis. Presentation graphics optionally labels each slice of a
  9874.  pie chart with a percentage figure. You use a legend to associate each slice
  9875.  of the pie with a category name.
  9876.  Bar and Column Charts
  9877.  
  9878.  48b2bfff4821bfff-----------------------------------------------------
  9879.  Line Charts
  9880.  
  9881.  4f11bfffTraditionally, line charts show a collection of data points
  9882.  connected by lines; hence the name. However, Presentation Graphics toolbox
  9883.  can also plot points that are not connected by lines.
  9884.  Scatter Diagrams
  9885.  
  9886.  512dbfffScatter diagrams illustrate the relationship between numeric values
  9887.  in different groups of data to show trends and correlations. This is why
  9888.  scatter diagrams are a favorite tool of statisticians and forecasters.They
  9889.  are most useful with relatively large populations of data. Consider, for
  9890.  example, the relationship between personal income and family size. If you
  9891.  poll 1,000 wage earners for their income and family size, you have a scatter
  9892.  diagram with 1,000 points. If you combine your results so you're left with
  9893.  one average income for each family size, you have a line graph.Sometimes the
  9894.  related points on scatter charts are connected by lines. For example, if you
  9895.  plotted the mathematical relationship f(x)=x2, you would plot the value x
  9896.  against the value x2, and connecting the points with lines would illustrate
  9897.  the relationship. However, for statistical graphs involving large groups,
  9898.  connecting values with lines would make the chart incomprehensible.
  9899.  Axes
  9900.  
  9901.  All charts created with the Presentation Graphics toolbox (except pie
  9902.  charts) are displayed with two perpendicular reference lines called "axes."
  9903.  These axes are "yardsticks" against which data is charted. Generally, the
  9904.  vertical or y-axis runs from top to bottom of the chart and is placed
  9905.  against the left side of the screen. The horizontal or x-axis runs from left
  9906.  to right across the bottom of the screen.
  9907.  
  9908.  The chart type determines which axis is used for category data and which
  9909.  axis is used for value data. The x-axis is the category axis for column and
  9910.  line charts and the value axis for bar charts. The y-axis is the value axis
  9911.  for column and line charts and the category axis for bar charts. The x- and
  9912.  y-axes are used for value data in scatter charts.
  9913.  
  9914.  
  9915.  Chart Windows
  9916.  
  9917.  The "chart window" defines that part of the screen on which the chart is
  9918.  drawn. Normally the window fills the entire screen, but Presentation
  9919.  Graphics toolbox allows you to resize the window for smaller graphs. By
  9920.  moving the chart window to different screen locations, you can view separate
  9921.  graphs together on the same screen.
  9922.  
  9923.  
  9924.  Data Windows
  9925.  
  9926.  While the chart window defines the entire graph including axes and labels,
  9927.  the "data window" defines only the actual plotting area. This is the portion
  9928.  of the graph to the right of the y-axis and above the x-axis. You cannot
  9929.  directly specify the size of the data window. Presentation Graphics
  9930.  automatically determines its size based on the dimensions of the chart
  9931.  window.
  9932.  
  9933.  
  9934.  Chart Styles
  9935.  
  9936.  Each of the five types of Presentation Graphics toolbox charts can appear in
  9937.  two different "chart styles," as described in Table 6.2.
  9938.  
  9939.  BarSide-by-sideStackedColumnSide-by-sideStackedLinePoints connected by
  9940.  linesPoints onlyScatterPoints connected by linesPoints only
  9941.  Bar and column charts have only one style when displaying a single series of
  9942.  data. The styles "side-by-side" and "stacked" are applicable when more than
  9943.  one series appears on the same chart. The first style arranges the bars or
  9944.  columns for the different series side by side, showing relative heights or
  9945.  lengths. The stacked style, illustrated in Figure 6.2 for a column chart,
  9946.  emphasizes relative sizes between bars or columns and shows the totals of
  9947.  the series.
  9948.  
  9949.  59a9bfff
  9950.  Legends
  9951.  
  9952.  Presentation graphics can display a "legend" to label the different series
  9953.  of a chart, in addition to differentiating between series by using colors,
  9954.  lines, or patterns. Pie charts, which only represent a single series, can
  9955.  use a legend to identify each "slice" of the pie.
  9956.  
  9957.  The format of a legend is similar to the legends found on printed graphs and
  9958.  maps. A sample of the color and pattern used to graph each distinct data
  9959.  series appears next to the series label. The section "Palettes" later in
  9960.  this chapter, explains how different data series are identified by color and
  9961.  pattern.
  9962.  
  9963.  
  9964.  Five Example Chart Programs
  9965.  
  9966.  The sample programs that follow use only five of the 19 procedures in the
  9967.  Presentation Graphics toolbox:   DefaultChart,  ChartScreen,  ChartPie,
  9968.  Chart, and  ChartScatter. The  BASIC Language Reference describes these and
  9969.  the remaining Presentation Graphics toolbox. For information on including
  9970.  online help for presentation graphics routines in the Microsoft Advisor
  9971.  online Help system, see Chapter 22, "Customizing Online Help."The code in
  9972.  the example programs is straightforward, and you should be able to follow
  9973.  the programs easily without completely understanding all the details. Each
  9974.  program is described with comments so that you can recognize the steps
  9975.  mentioned earlier in the section "Presentation Graphics Program Structure."
  9976.  The examples make use of the same secondary procedure, BestMode. BestMode
  9977.  checks the display adapter and returns the best available mode value for
  9978.  displaying charts. As in the preceding example, data and read statements are
  9979.  used to simulate data
  9980.  generation.-----------------------------------------------------
  9981.  
  9982.  
  9983.  A Sample Data Set
  9984.  
  9985.  Suppose a grocer wants to graph the sales of orange juice over the course of
  9986.  a single year. Sales figures are on a monthly basis, so the grocer selects
  9987.  as category data the months of the year from January through December. The
  9988.  sales figures are shown in Table 6.3.
  9989.  
  9990.  February27March42April64May106June157July182August217September128October62Nov
  9991.  Example: Pie Chart
  9992.  
  9993.  The example in this section uses Presentation Graphics toolbox to display a
  9994.  pie chart for the grocer's data. Interesting elements of the pie-charting
  9995.  example include:
  9996.  
  9997.    ■    Exploded slices   A feature unique to pie charts is that any or all
  9998.        of the slices can be separated from the rest of the chart for
  9999.        emphasis. When pieces are separated, they are sometimes referred to as
  10000.        "exploded." You designate which slices should be separated from the
  10001.        rest by defining an integer array of "flags," then setting the flags
  10002.        by assigning non-zero values to those array elements corresponding to
  10003.        the pie slices you want to have separated from the rest. In the
  10004.        example in this section, the Exploded() array causes any slice of the
  10005.        pie representing a peak sales month (i.e., OJvalues value greater than
  10006.        or equal to 100) to be to be separated from the rest.
  10007.  
  10008.    ■    Setting chart characteristics   The wording, alignment, and color of
  10009.        the chart's main title and subtitle are set by assigning values to
  10010.        elements of the structured variable Env (a variable of user-defined
  10011.        type ChartEnvironment). A constant assigned to another element of the
  10012.        Env variable specifies that the chart itself is to have no border
  10013.        (Env.Chartwindow.Border = cNo). Note that in all these cases, the
  10014.        elements themselves are of user-defined type.
  10015.  
  10016.  
  10017.  
  10018.    ■   The section "Customizing Presentation Graphics" later in this chapter
  10019.        describes the ChartEnvironment type, as well as its constituent types,
  10020.        including TitleType (for the elements MainTitle.Title,
  10021.        MainTitle.TitleColor, MainTitle.Justify, SubTitle.Title,
  10022.        SubTitle.TitleColor, and SubTitle.Justify) and RegionType (for the
  10023.        ChartWindow.Border element). Because of the nesting of user-defined
  10024.        types, the names of these variables can be lengthy, but the principle
  10025.        is a simple one: specifying the appearance of any chart by simple
  10026.        assignment of values to variables that are the same for every chart.
  10027.  
  10028.    ■    Presentation graphics error codes   When an error occurs during
  10029.        execution of a Presentation Graphics toolbox routine, an error
  10030.        condition code is placed in the ChartErr variable (defined in the
  10031.        common block /ChartLib/). A ChartErr value of 0 indicates the routine
  10032.        has completed its work without error. In the following examples,
  10033.        ChartErr is checked after the call to the  ChartScreen routine to make
  10034.        sure the chart can be displayed. See the include file CHRTB.BI for a
  10035.        listing of the error codes.
  10036.  
  10037.  
  10038.  Example
  10039.  
  10040.  The following example displays a pie chart (PGPIE.BAS) based on the values
  10041.  in Table 6.3.
  10042.  
  10043.  ' PGPIE.BAS: Create sample pie chart
  10044.  
  10045.  DEFINT A-Z
  10046.  ' $INCLUDE: 'FONTB.BI'
  10047.  ' $INCLUDE: 'CHRTB.BI'
  10048.  DECLARE FUNCTION BestMode ()
  10049.  CONST FALSE = 0, TRUE = NOT FALSE, MONTHS = 12
  10050.  CONST HIGHESTMODE = 13, TEXTONLY = 0
  10051.  
  10052.  DIM Env AS ChartEnvironment' See CHRTB.BI for declaration of
  10053.  ' the ChartEnvironment type.
  10054.  DIM MonthCategories(1 TO MONTHS) AS STRING' Array for categories
  10055.  DIM OJvalues(1 TO MONTHS) AS SINGLE' Array for 1st data series
  10056.  DIM Exploded(1 TO MONTHS) AS INTEGER' "Explode" flags array
  10057.   ' (specifies which pie slices
  10058.  ' are separated).
  10059.  ' Initialize the data arrays.
  10060.  FOR index = 1 TO MONTHS: READ OJvalues(index): NEXT index
  10061.  FOR index = 1 TO MONTHS: READ MonthCategories$(index): NEXT index
  10062.  
  10063.  ' Set elements of the array that determine separation of pie slices
  10064.  FOR Flags = 1 TO MONTHS    ' If value of OJvalues(Flags)
  10065.  Exploded(Flags) = (OJvalues(Flags) >= 100) ' >= 100 the corre-
  10066.  ' sponding flag is set
  10067.  ' true, separating slices.
  10068.  NEXT Flags
  10069.  
  10070.  ' Pass the value returned by the BestMode function to the Presentation
  10071.  ' Graphics routine ChartScreen to set the graphics mode for charting.
  10072.  
  10073.  ChartScreen (BestMode) ' Even if SCREEN is already set to an acceptable
  10074.  ' mode, you still must set it with ChartScreen.
  10075.  
  10076.  ' Check to make sure ChartScreen succeeded:
  10077.  IF ChartErr = cBadScreen THEN
  10078.  PRINT "Sorry, there is a screen-mode problem in the chart library"
  10079.  END
  10080.  END IF
  10081.  
  10082.  ' Initialize a default pie chart. Pass Env (the environment variable),
  10083.  DefaultChart Env, cPie, cPercent' the constant cPie (for Pie Chart)
  10084.  ' and cPercent (label slices with
  10085.  ' percentage).
  10086.  
  10087.  ' Add Titles and some chart options. These assignments modify some
  10088.  ' default values set in the variable Env (of type ChartEnvironment)
  10089.  ' by DefaultChart.
  10090.  
  10091.  Env.MainTitle.Title = "Good Neighbor Grocery"  ' Specifies the title,
  10092.  Env.MainTitle.TitleColor = 15' color of title text,
  10093.  Env.MainTitle.Justify = cCenter' alignment of title text,
  10094.  Env.SubTitle.Title = "Orange Juice Sales"' text of chart subtitle,
  10095.  Env.SubTitle.TitleColor = 11' color of subtitle text,
  10096.  Env.SubTitle.Justify = cCenter' alignment of subtitle text,
  10097.  Env.ChartWindow.Border = cYes' and presence of a border.
  10098.  
  10099.  ' Call the pie-charting routine --- Arguments for call to ChartPie are:
  10100.  ' EnvEnvironment variable
  10101.  ' MonthCategories()Array containing Category labels
  10102.  ' OJvalues()Array containing Data values to chart
  10103.  ' Exploded()Integer array tells which pieces of the pie should
  10104.  ' be separated (non-zero=exploded, 0=not exploded)
  10105.  ' MONTHSTells number of data values to chart
  10106.  
  10107.  ChartPie Env, MonthCategories(), OJvalues(), Exploded(), MONTHS
  10108.  SLEEP
  10109.  ' If the rest of your program isn't graphic, you could reset original
  10110.  ' video mode here.
  10111.  END
  10112.  
  10113.  ' Simulate data generation for chart values and category labels.
  10114.  DATA 33,27,42,64,106,157,182,217,128,62,43,36
  10115.  DATA "Jan","Feb","Mar","Apr","May","Jun","Jly","Aug","Sep","Oct","Nov"
  10116.  DATA "Dec"
  10117.  
  10118.  '=========== Function to determine and set highest resolution ========
  10119.  ' The BestMode function uses a local error trap to check available
  10120.  ' modes, then assigns the integer representing the best mode for
  10121.  ' charting to its name so it is returned to the caller. The function
  10122.  ' terminates execution if the hardware doesn't support a mode
  10123.  ' appropriate for Presentation Graphics.
  10124.  '======================================================================
  10125.  FUNCTION BestMode
  10126.  
  10127.  ' Set a trap for expected local errors -- handled within the function.
  10128.  ON LOCAL ERROR GOTO ScreenError
  10129.  
  10130.  FOR TestValue = HIGHESTMODE TO 0 STEP -1
  10131.  DisplayError = FALSE
  10132.  SCREEN TestValue
  10133.  IF DisplayError = FALSE THEN
  10134.  SELECT CASE TestValue
  10135.  CASE 0
  10136.  PRINT "Sorry, you need graphics to display charts"
  10137.  END
  10138.  CASE 12, 13
  10139.  BestMode = 12
  10140.  CASE 2, 7
  10141.  BestMode = 2
  10142.  CASE ELSE
  10143.  BestMode = TestValue
  10144.  END SELECT
  10145.  EXIT FUNCTION
  10146.  END IF
  10147.  NEXT TestValue
  10148.  ' Note there is no need to turn off the local error handler.
  10149.  ' It is turned off automatically when control passes out of
  10150.  ' the function.
  10151.  
  10152.  EXIT FUNCTION
  10153.  
  10154.  '====================  Local error handler code =====================
  10155.  ' The ScreenError label identifies a local error handler referred to
  10156.  ' above. Invalid SCREEN values generate Error # 5 (Illegal
  10157.  ' function call) --- so if that is not the error reset ERROR to the
  10158.  ' ERR value that was generated so the error can be passed to other,
  10159.  ' possibly more appropriate, error-handling routine.
  10160.  ' =====================================================================
  10161.  ScreenError:
  10162.  IF ERR = 5 THEN
  10163.  DisplayError = TRUE
  10164.  RESUME NEXT
  10165.  ELSE
  10166.  ERROR ERR
  10167.  END IF
  10168.  END FUNCTION
  10169.  
  10170.  Output
  10171.  
  10172.  The pie chart in Figure 6.3 remains on the screen until a key is pressed.
  10173.  
  10174.  36b3bfff-----------------------------------------------------
  10175.  Bar Chart
  10176.  
  10177.  The code for PGPIE.BAS needs only the following alterations to produce bar,
  10178.  column, and line charts for the same data:
  10179.  
  10180.    ■   Give new arguments to  DefaultChart that specify chart type and style.
  10181.  
  10182.    ■   Assign Titles for the x-axis and y-axis in the Env structure (this is
  10183.        optional).
  10184.  
  10185.    ■   Replace the call to  ChartPie with  Chart. This function produces bar,
  10186.        column, and line charts depending on the value of the second argument
  10187.        to  DefaultChart.
  10188.  
  10189.    ■   Remove references to the Exploded flag array (applicable only to pie
  10190.        charts).
  10191.  
  10192.  
  10193.  Example
  10194.  
  10195.  PGBAR.BAS is the module-level code for a program to display a bar chart. It
  10196.  calls the same procedure (BestMode) listed with the preceding PGPIE.BAS
  10197.  example. The following example produces the bar chart shown in Figure 6.4.
  10198.  
  10199.  ' PGBAR.BAS: Creates sample bar chart
  10200.  
  10201.  DEFINT A-Z
  10202.  ' $INCLUDE: 'CHRTB.BI'
  10203.  DECLARE FUNCTION BestMode ()
  10204.  CONST FALSE = 0, TRUE = NOT FALSE, MONTHS = 12
  10205.  CONST HIGHESTMODE = 13, TEXTONLY = 0
  10206.  
  10207.  DIM Env AS ChartEnvironment' See CHRTB.BI for declaration of
  10208.   ' the ChartEnvironment type
  10209.  DIM MonthCategories(1 TO MONTHS) AS STRING  ' Array for categories
  10210.   ' (used for pie, column,
  10211.  ' and bar charts).
  10212.  
  10213.  DIM OJvalues(1 TO MONTHS) AS SINGLE    ' Array for data series.
  10214.  
  10215.  ' Initialize the data arrays
  10216.  FOR index = 1 TO MONTHS: READ OJvalues(index): NEXT index
  10217.  FOR index = 1 TO MONTHS: READ MonthCategories$(index): NEXT index
  10218.  
  10219.  ' Pass the value returned by the BestMode function to the Presentation
  10220.  ' Graphics routine ChartScreen to set the graphics mode for charting.
  10221.  
  10222.  
  10223.  
  10224.  ChartScreen (BestMode)' Even if SCREEN is already set to an acceptable
  10225.  ' mode, you still must set it with ChartScreen.
  10226.  ' Check to make sure ChartScreen succeeded.IF ChartErr = cBadScreen THEN
  10227.  PRINT "Sorry, there is a screen-mode problem in the chart library."
  10228.  END
  10229.  END IF
  10230.  ' Initialize a default pie chart
  10231.  DefaultChart Env, cBar, cPlain' Pass Env (the environment variable),
  10232.  ' the constant cBar (for Bar Chart)
  10233.  ' and cPlain.
  10234.  
  10235.  ' Add Titles and some chart options. These assignments modify some
  10236.  ' default values set in the variable Env (of type ChartEnvironment)
  10237.  ' by DefaultChart.
  10238.  
  10239.  Env.MainTitle.Title = "Good Neighbor Grocery" ' Specifies text of
  10240.  ' the chart title,
  10241.  Env.MainTitle.TitleColor = 15' color of title text,
  10242.  Env.MainTitle.Justify = cRight' alignment of title text,
  10243.  Env.SubTitle.Title = "Orange Juice Sales"' text of chart subtitle,
  10244.  Env.SubTitle.TitleColor = 15' color of subtitle text,
  10245.  Env.SubTitle.Justify = cRight' alignment of subtitle text,
  10246.  Env.ChartWindow.Border = cNo' and absence of a border.
  10247.  
  10248.  ' The next 2 assignments label the x-axis and y-axis
  10249.  Env.XAxis.AxisTitle.Title = "Quantity (cases)"
  10250.  Env.YAxis.AxisTitle.Title = "Months"
  10251.  
  10252.  ' Call the bar-charting routine --- Arguments for call to Chart are:
  10253.  ' EnvEnvironment variable
  10254.  ' MonthCategories()Array containing Category labels
  10255.  ' OJvalues()Array containing Data values to chart
  10256.  ' MONTHSTells number of data values to chart
  10257.  
  10258.  Chart Env, MonthCategories(), OJvalues(), MONTHS
  10259.  SLEEP
  10260.  ' If the rest of your program isn't graphic,
  10261.  ' reset original screen mode here.
  10262.  END
  10263.  
  10264.  ' Simulate data generation for chart values and category labels
  10265.  DATA 33,27,42,64,106,157,182,217,128,62,43,36
  10266.  DATA "Jan","Feb","Mar","Apr","May","Jun","Jly","Aug","Sep","Oct"
  10267.  DATA "Nov","Dec"
  10268.  
  10269.  Output
  10270.  
  10271.  The PGBAR.BAS example produces the chart shown in Figure 6.4.
  10272.  
  10273.  37a6bfff
  10274.  Line and Column Charts
  10275.  
  10276.  You could turn the grocer's bar chart into a line chart in two easy steps.
  10277.  Simply specify the new chart type when calling  DefaultChart and switch the
  10278.  axis Titles. To produce a line chart for the data, replace the call to
  10279.  DefaultChart with:
  10280.  
  10281.  DefaultChart(Env, cLine, cLines)
  10282.  
  10283.  
  10284.  The constant cLine specifies a line chart, and the constant cLines specifies
  10285.  that the points are to be joined by lines. If you pass cNoLines as the third
  10286.  argument, the points would appear, but would not be connected. To switch the
  10287.  labels on the axes, just replace the assignments to the axis labels as
  10288.  follows:
  10289.  
  10290.  Env.XAxis.AxisTitle.Title="Months"
  10291.  Env.YAxis.AxisTitle.Title="Quantity (cases)"
  10292.  
  10293.  Output
  10294.  
  10295.  Notice that now the x-axis is labelled "Months" and the y -axis is labelled
  10296.  "Quantity (cases)."  Figure 6.5 shows the resulting line chart.
  10297.  
  10298.  332bbfffCreating an equivalent column chart requires only one change. Use
  10299.  the same code as for the line chart and replace the call to  DefaultChart
  10300.  with:
  10301.  
  10302.  DefaultChart( Env, cColumn, cPlain )
  10303.  
  10304.  Output
  10305.  
  10306.  Figure 6.6 shows the column chart for the grocer's data.
  10307.  
  10308.  38bdbfff
  10309.  Scatter Diagram
  10310.  
  10311.  Now suppose that the store owner wants to compare the sales of orange juice
  10312.  to the sales of another product, say hot chocolate. Table 6.4 shows a
  10313.  tabular comparison.
  10314.  
  10315.  Example
  10316.  
  10317.  PGSCAT.BAS is the module-level code for a program to display a scatter
  10318.  diagram that illustrates the relationship between the sales of orange juice
  10319.  and hot chocolate throughout a 12-month period. It calls the same function
  10320.  (BestMode) listed with the PIE.BAS example preceding. Note that the data
  10321.  array HCvalues replaces the MonthCategories array in this example.
  10322.  
  10323.  ' PGSCAT.BAS: Create sample scatter diagram.
  10324.  
  10325.  DEFINT A-Z
  10326.  ' $INCLUDE: 'CHRTB.BI'
  10327.  DECLARE FUNCTION BestMode ()
  10328.  CONST FALSE = 0, TRUE = NOT FALSE, MONTHS = 12
  10329.  CONST HIGHESTMODE = 13, TEXTONLY = 0
  10330.  DIM Env AS ChartEnvironment' See CHRTB.BI for declaration of the
  10331.  ' ChartEnvironment type
  10332.  DIM OJvalues(1 TO MONTHS) AS SINGLE' Array for 1st data series
  10333.  DIM HCvalues(1 TO MONTHS) AS SINGLE' Array for 2nd data series
  10334.  
  10335.  ' Initialize the data arrays
  10336.  FOR index = 1 TO MONTHS: READ OJvalues(index): NEXT index
  10337.  FOR index = 1 TO MONTHS: READ HCvalues(index): NEXT index
  10338.  
  10339.  ' Pass the value returned by the BestMode function to the Presentation
  10340.  ' Graphics routine ChartScreen to set the graphics mode for charting.
  10341.  
  10342.  ChartScreen (BestMode)' Even if SCREEN is already set to an
  10343.  ' acceptable mode, you still have to
  10344.  ' set it with ChartScreen.
  10345.  IF ChartErr = cBadScreen THEN' Make sure ChartScreen succeeded.
  10346.  PRINT "Sorry, there is a screen-mode problem in the chart library"
  10347.  END
  10348.  END IF
  10349.  
  10350.  ' Initialize a default pie chart.
  10351.  ' Pass Env (the environment
  10352.  DefaultChart Env, cScatter, cNoLines' variable), constant cScatter
  10353.  ' (for scatter chart),
  10354.  ' cNoLines (unjoined points).
  10355.  
  10356.  ' Add Titles and some chart options. These assignments modify some
  10357.  ' default values set in the variable Env (of type ChartEnvironment)
  10358.  ' by DefaultChart.
  10359.  
  10360.  Env.MainTitle.Title = "Good Neighbor Grocery" ' Specify chart title,
  10361.  Env.MainTitle.TitleColor = 11' color of title text,
  10362.  Env.MainTitle.Justify = cRight' alignment of title text,
  10363.  Env.SubTitle.Title = "OJ vs. Hot Chocolate"   ' text of chart subtitle,
  10364.  Env.SubTitle.TitleColor = 15' color of subtitle text,
  10365.  Env.SubTitle.Justify = cRight' alignment of subtitle text,
  10366.  Env.ChartWindow.Border = cNo' and absence of a border.
  10367.  
  10368.  ' The next two assignments label the x and y axes of the chart
  10369.  Env.XAxis.AxisTitle.Title = "Orange Juice Sales"
  10370.  Env.YAxis.AxisTitle.Title = "Hot Chocolate Sales"
  10371.  
  10372.  ' Call the pie-charting routine --- Arguments for call to ChartPie are:
  10373.  ' EnvEnvironment variable
  10374.  'OJvaluesArray containing orange-juice sales values to chart
  10375.  ' HCvaluesArray containing hot-chocolate sales values to chart
  10376.  'MONTHSNumber of data values to chart
  10377.  
  10378.  ChartScatter Env, OJvalues(), HCvalues(), MONTHS
  10379.  SLEEP
  10380.  ' If the rest of your program isn't graphic, you could
  10381.   ' reset original screen mode here.
  10382.  END
  10383.  
  10384.  ' Simulate data generation for chart values and category labels.
  10385.  DATA 33,27,42,64,106,157,182,217,128,62,43,36
  10386.  DATA 37,37,30,19,10,5,2,1,7,15,28,39
  10387.  
  10388.  
  10389.  Output
  10390.  
  10391.  Figure 6.7 shows the results of PGSCAT.BAS. Notice that the scatter points
  10392.  form a slightly curved line, indicating a correlation exists between the
  10393.  sales of the two products. The store owner can conclude from the scatter
  10394.  diagram that the demand for orange juice is roughly the inverse of the
  10395.  demand for hot chocolate.
  10396.  
  10397.  3497bfff
  10398.  Customizing Presentation Graphics
  10399.  
  10400.  The Presentation Graphics toolbox is built for flexibility. In the preceding
  10401.  examples, you saw how easy it was to change subtitles and axis labels with
  10402.  simple assignment statements. Other elements of the environment can be
  10403.  modified and customized just as easily. You can use its system of default
  10404.  values to produce professional-looking charts with a minimum of programming
  10405.  effort. Or, you can fine-tune the appearance of your charts by overriding
  10406.  default values and initializing variables explicitly in your program. The
  10407.  following section describes all the user-defined data types in the
  10408.  Presentation Graphics toolbox so you can decide which characteristics to
  10409.  accept as supplied and which ones you want to modify. Modification involves
  10410.  declaring a variable of the specified type, then assigning values to its
  10411.  constituent elements. These modifications are always done between the call
  10412.  to  DefaultChart and the call to the routine that actually displays the
  10413.  chart.
  10414.  
  10415.  
  10416.  Chart Environment
  10417.  
  10418.  The include file CHRTB.BI declares a user-defined type, ChartEnvironment,
  10419.  that declares the constituent elements of a structured variable called the
  10420.  "chart environment" variable (Env in the preceding examples). The chart
  10421.  environment describes everything about a chart except the actual data to be
  10422.  plotted. The environment determines the appearance of text, axes, grid
  10423.  lines, and legends.
  10424.  
  10425.  Calling  DefaultChart fills the chart environment with default values.
  10426.  Presentation Graphics allows you to modify any variable in the environment
  10427.  before displaying a chart. Most initialization of internal Chartlib
  10428.  variables is done through the structured variable you define as having the
  10429.  ChartEnvironment data type, when it is passed to  DefaultChart.
  10430.  
  10431.  The sample chart programs provided earlier illustrate how to adjust
  10432.  variables in the chart environment. These programs define a structured
  10433.  variable, Env, having the ChartEnvironment data type . The elements of the
  10434.  Env structure are the chart environment variables, initialized by the call
  10435.  to  DefaultChart. Environment variables such as the chart title are then
  10436.  given specific values, as in:
  10437.  
  10438.  Env.MainTitle.Title = "Good Neighbor Grocery"
  10439.  
  10440.  Environment variables that determine colors and line styles deserve special
  10441.  mention. The chart environment holds several such variables which can be
  10442.  recognized by their names. For example, the variable TitleColor specifies
  10443.  the color of title text. Similarly, the variable GridStyle specifies the
  10444.  line style used to draw the chart grid.
  10445.  
  10446.  These variables are index numbers, but do not refer directly to the colors
  10447.  or line styles. They correspond instead to palette-entry numbers (Palettes
  10448.  are described later in the chapter). If you set TitleColor to 2,
  10449.  Presentation Graphics toolbox uses the color code in the second palette
  10450.  entry to determine the title's color. Thus the title in this case would be
  10451.  the same color as the chart's second data series. If you change the color
  10452.  code in the palette, you'll also change the title's color. You don't have to
  10453.  understand palettes to use the Presentation Graphics toolbox, but
  10454.  understanding how they work gives you greater flexibility in specifying the
  10455.  appearance of charts.
  10456.  
  10457.  The user-defined type ChartEnvironment has 10 elements, 7 of which are
  10458.  themselves user-defined types. ChartEnvironment is described in detail in
  10459.  the section "ChartEnvironment" later in this chapter.
  10460.  
  10461.  
  10462.  The next several sections lead up to that description by describing the
  10463.  user-defined types nested within the ChartEnvironment data type. The
  10464.  declaration of ChartEnvironment appearing in CHRTB.BI is as follows:
  10465.  
  10466.  TYPE ChartEnvironment
  10467.  ChartType  AS INTEGER
  10468.  ChartStyle AS INTEGER
  10469.  DataFontAS INTEGER
  10470.  ChartWindow AS RegionType
  10471.  DataWindow AS RegionType
  10472.  MainTitle  AS TitleType
  10473.  SubTitle AS TitleType
  10474.  XAxis AS AxisType
  10475.  YAxis AS AxisType
  10476.  Legend  AS LegendType
  10477.  END TYPE
  10478.  
  10479.  The remainder of this section describes the chart environment data structure
  10480.  of the Presentation Graphics toolbox. It first examines structures of the
  10481.  four secondary types which make up the chart environment structure. The
  10482.  section concludes with a description of the ChartEnvironment structure type.
  10483.  Each discussion begins with a brief explanation of the structure's purpose,
  10484.  followed by a listing of the structure type definition as it appears in the
  10485.  CHRTB.BI file. All symbolic constants are defined in the file CHRTB.BI .
  10486.  
  10487.  
  10488.  RegionType
  10489.  
  10490.  Structures of the type RegionType contain sizes, locations, and color codes
  10491.  for the three windows produced by the Presentation Graphics toolbox: the
  10492.  chart window, the data window, and the legend. Refer to the "Terminology"
  10493.  section earlier in this chapter for definitions of these terms. Placement of
  10494.  the chart window is relative to the screen's logical origin. Placement of
  10495.  the data and legend windows is relative to the chart window.
  10496.  
  10497.  The CHRTB.BI file defines RegionType as:
  10498.  
  10499.  TYPE RegionType
  10500.  X1AS INTEGER
  10501.  Y1AS INTEGER
  10502.  X2AS INTEGER
  10503.  Y2AS INTEGER
  10504.  Background AS INTEGER
  10505.  BorderAS INTEGER
  10506.  BorderStyleAS INTEGER
  10507.  BorderColorAS INTEGER
  10508.  END TYPE
  10509.  
  10510.  
  10511.  The following table describes the RegionType elements:
  10512.  
  10513. ╓┌───────────────┌─────────────────────────────┌─────────────────────────────╖
  10514.  ────────────────────────────────────────────────────────────────────────────
  10515.  X1, Y1, X2, Y2  Window (region) coordinates   The reference point for the
  10516.                  in pixels. The ordered pair   coordinates depends on the
  10517.                  (X1, Y1) specifies the        type of window. The chart
  10518.                  coordinate of the upper left  window is located relative
  10519.                  corner of the window. The     to the upper left corner of
  10520.                  ordered pair (x2, y2)         the screen. The data and
  10521.                  specifies the coordinate of   legend windows are located
  10522.                  the lower right corner.       relative to the upper left
  10523.  ────────────────────────────────────────────────────────────────────────────
  10524.                 the lower right corner.       relative to the upper left
  10525.                                                corner of the chart window.
  10526.                                                This allows you to change
  10527.                                                the position of the chart
  10528.                                                window without having to
  10529.                                                redefine coordinates for the
  10530.                                                other two windows.
  10531.  
  10532.  Background      An integer between 0 and
  10533.                  cPalLen representing a
  10534.                  palette index (described in
  10535.                  the section "Palettes") that
  10536.                  specifies the window's
  10537.                  background color. The
  10538.                  default value for Background
  10539.                  is 0.
  10540.  
  10541.  Border          A cYes, cNo (true/false)
  10542.                  variable that determines
  10543.                  whether a border frame is
  10544.  ────────────────────────────────────────────────────────────────────────────
  10545.                 whether a border frame is
  10546.                  drawn around a window.)
  10547.  
  10548.  BorderStyle     An integer between 0 and
  10549.                  cPalLen representing a
  10550.                  palette index (described in
  10551.                  the section "Palettes") that
  10552.                  specifies the line style of
  10553.                  the window's border frame.
  10554.                  The default value is 1..
  10555.  
  10556.  BorderColor     An integer between 0 and
  10557.                  cPalLen representing a
  10558.                  palette index (described in
  10559.                  the section "Palettes") that
  10560.                  specifies the color of the
  10561.                  window's border frame. The
  10562.                  default value is 1.
  10563.  
  10564.  
  10565.  
  10566.  
  10567.  
  10568.  
  10569.  TitleType
  10570.  
  10571.  Structures of the type TitleType determine text, color, font, and alignment
  10572.  of titles appearing in the graph. The CHRTB.BI file defines the structure
  10573.  type as:
  10574.  
  10575.  TYPE TitleType
  10576.  TitleAs STRING * 70
  10577.  TitleFontAS INTEGER
  10578.  TitleColor AS INTEGER
  10579.  Justify AS INTEGER
  10580.  END TYPE
  10581.  
  10582.  The following list describes TitleType elements:
  10583.  
  10584. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  10585.  ────────────────────────────────────────────────────────────────────────────
  10586.  ────────────────────────────────────────────────────────────────────────────
  10587.  Title                                   A string containing title text.
  10588.                                          For example, if Env is a
  10589.                                          structured variable of type
  10590.                                          ChartEnvironment, then the
  10591.                                          variable Env.MainTitle.Title holds
  10592.                                          the character string used for the
  10593.                                          main title of the chart. Similarly,
  10594.                                          Env.XAxis.AxisTitle.Title contains
  10595.                                          the x-axis title.
  10596.  
  10597.  TitleFont                               An integer between 1 and the
  10598.                                          number of fonts loaded that
  10599.                                          specifies a title's font. The
  10600.                                          default value for TitleFont is 1.
  10601.  
  10602.  TitleColor                              An integer between 0 and cPalLen
  10603.                                          that specifies a title's color.
  10604.                                          The default value for TitleColor
  10605.                                          is 1.
  10606.  
  10607.  ────────────────────────────────────────────────────────────────────────────
  10608. 
  10609.  Justify                                 An integer specifying how the
  10610.                                          title is placed on its line within
  10611.                                          the chart window. The symbolic
  10612.                                          constants defined in the CHRTB.BI
  10613.                                          file for this variable are cLeft,
  10614.                                          cCenter, and cRight, meaning flush
  10615.                                          left, centered, and flush right.
  10616.  
  10617.  
  10618.  
  10619.  
  10620.  
  10621.  
  10622.  AxisType
  10623.  
  10624.  Structures of type AxisType contain variables for the axes such as color,
  10625.  scale, grid style, and tick marks. The CHRTB.BI file defines the AxisType
  10626.  structure as:
  10627.  
  10628.  TYPE AxisType
  10629.  GridAS INTEGER
  10630.  GridStyleAS INTEGER
  10631.  AxisTitleAS TitleType
  10632.  AxisColorAS INTEGER
  10633.  Labeled AS INTEGER
  10634.  RangeTypeAS INTEGER
  10635.  LogBase AS SINGLE
  10636.  AutoScaleAS INTEGER
  10637.  ScaleMinAS SINGLE
  10638.  ScaleMaxAS SINGLE
  10639.  ScaleFactorAS SINGLE
  10640.  ScaleTitle AS TitleType
  10641.  TicFont  AS INTEGER
  10642.  TicIntervalAS SINGLE
  10643.  TicFormatAS INTEGER
  10644.  TicDecimalsAS INTEGER
  10645.  END TYPE
  10646.  
  10647.  The following list describes the elements of the AxisType structure:
  10648.  
  10649. ╓┌──────────┌───────────────────────────────┌────────────────────────────────╖
  10650.  Element    Description
  10651.  ────────────────────────────────────────────────────────────────────────────
  10652.  Grid       A cYes, cNo (true/false) value
  10653.             that determines whether grid
  10654.             lines are drawn for the
  10655.             associated axis. Grid lines
  10656.             span the data window
  10657.             perpendicular to the axis. One
  10658.             grid line is drawn for each
  10659.             tick mark on the axis.
  10660.  
  10661.  GridStyle  An integer between 0 and        Note that the color of the
  10662.             cPalLen that specifies the      parallel axis determines the
  10663.             grid's line style. Lines can    color of the grid lines. Thus
  10664.             be solid, dashed, dotted, or    the x-axis grid is the same
  10665.             some combination. Grid styles   color as the y-axis, and the
  10666.             are drawn from  PaletteB%,      y-axis grid is the same color
  10667.             described in the section        as the x-axis.
  10668.             "Palettes," later in this
  10669.             chapter. The default value for
  10670.  Element    Description
  10671.  ────────────────────────────────────────────────────────────────────────────
  10672.            chapter. The default value for
  10673.             GridStyle is 1.
  10674.  
  10675.  AxisTitle  A  TitleType structure that
  10676.             defines the title of the
  10677.             associated axis. The title of
  10678.             the y-axis displays vertically
  10679.             to the left of the y-axis, and
  10680.             the title of the x-axis
  10681.             displays horizontally below
  10682.             the x-axis.
  10683.  
  10684.  
  10685.  
  10686.  
  10687.  
  10688.  
  10689. ╓┌──────────┌────────────────────┌────────────────────┌──────────────────────╖
  10690.  ────────────────────────────────────────────────────────────────────────────
  10691.  ────────────────────────────────────────────────────────────────────────────
  10692.  AxisColor  An integer between
  10693.             0 and cPalLen that
  10694.             specifies the color
  10695.             used for the axis
  10696.             and parallel grid
  10697.             lines. (See the
  10698.             preceding
  10699.             description for
  10700.             GridStyle.) Note
  10701.             that this member
  10702.             does not determine
  10703.             the color of the
  10704.             axis title. That
  10705.             selection is made
  10706.             through the
  10707.             structure AxisTitle.
  10708.             The default value
  10709.             is 1.
  10710.  
  10711.  Labelled   A cYes, cNo
  10712.  ────────────────────────────────────────────────────────────────────────────
  10713. Labelled   A cYes, cNo
  10714.             (true/false) value
  10715.             that determines
  10716.             whether tick marks
  10717.             and labels are
  10718.             drawn on the axis.
  10719.             Axis labels should
  10720.             not be confused
  10721.             with axis titles.
  10722.             Axis labels are
  10723.             numbers or
  10724.             descriptions such
  10725.             as "23.2" or
  10726.             "January" attached
  10727.             to each tick mark.
  10728.  
  10729.  RangeType  An integer that      Specify a linear     Use cLogAxis to
  10730.             determines whether   scale with the       specify a logarithmic
  10731.             the scale of the     cLinearAxis          RangeType.
  10732.             axis is linear or    constant. A linear   Logarithmic scales
  10733.  ────────────────────────────────────────────────────────────────────────────
  10734.            axis is linear or    constant. A linear   Logarithmic scales
  10735.             logarithmic. The     scale is best when   are useful  when the
  10736.             RangeType variable   the difference       data varies
  10737.             applies only to      between axis         exponentially. Line
  10738.             value data (not to   minimum and maximum  graphs of
  10739.             category labels).    is relatively small.  exponentially varying
  10740.                                  For example, a       data can be made
  10741.                                  linear axis range 0  straight with a
  10742.                                  - 10 results in 10   logarithmic RangeType.
  10743.                                  tick marks evenly
  10744.                                  spaced along the
  10745.                                  axis.
  10746.  
  10747.  LogBase    If RangeType is
  10748.             logarithmic, the
  10749.             LogBase variable
  10750.             determines the log
  10751.             base used to scale
  10752.             the axis. Default
  10753.             value is 10.
  10754.  ────────────────────────────────────────────────────────────────────────────
  10755.            value is 10.
  10756.  
  10757.  AutoScale  A cYes, cNo
  10758.             (true/false)
  10759.             variable. If
  10760.             AutoScale is cYes,
  10761.             the Presentation
  10762.             Graphics toolbox
  10763.             automatically
  10764.             determines values
  10765.             for ScaleMin,
  10766.             ScaleMax,
  10767.             ScaleFactor,
  10768.             ScaleTitle,
  10769.             TicInterval,
  10770.             TicFormat, and
  10771.             TicDecimals (see
  10772.             the following). If
  10773.             AutoScale equals
  10774.             cNo, these seven
  10775.  ────────────────────────────────────────────────────────────────────────────
  10776.            cNo, these seven
  10777.             variables must be
  10778.             specified in your
  10779.             program.
  10780.  
  10781.  ScaleMin   Lowest value
  10782.             represented by the
  10783.             axis.
  10784.  
  10785.  ScaleMax   Highest value
  10786.             represented by the
  10787.             axis.
  10788.  
  10789.  
  10790.  
  10791.  
  10792.  
  10793.  
  10794. ╓┌──────────────┌─────────────────────────────┌──────────────────────────────╖
  10795.  ────────────────────────────────────────────────────────────────────────────
  10796.  ────────────────────────────────────────────────────────────────────────────
  10797.  ScaleFactor    All numeric data is scaled    If AutoScale is set to cYes,
  10798.                 by dividing each value by     the Presentation Graphics
  10799.                 ScaleFactor. For relatively   toolbox automatically
  10800.                 small values, the variable    determines a suitable value
  10801.                 ScaleFactor should be 1,      for ScaleFactor based on the
  10802.                 which is the default. But     range of data to be plotted.
  10803.                 data with large values        The Presentation Graphics
  10804.                 should be scaled by an        toolbox selects only values
  10805.                 appropriate factor. For       that are a factor of 1000 --
  10806.                 example, data in the range 2  that is, values such as one
  10807.                 million - 20 million should   thousand, one million, or one
  10808.                 be plotted with ScaleMin set  billion. It then labels the
  10809.                 to 2, ScaleMax set to 20,     ScaleTitle appropriately (see
  10810.                 and ScaleFactor set to 1      the following). If you desire
  10811.                 million.                      some other value for scaling,
  10812.                                               you must set AutoScale to cNo
  10813.                                               and set ScaleFactor to the
  10814.                                               desired scaling value.
  10815.  
  10816.  ScaleTitle     A TitleType structure          Note: The following four
  10817.  ────────────────────────────────────────────────────────────────────────────
  10818. ScaleTitle     A TitleType structure          Note: The following four
  10819.                 defining the attributes of a  variables apply to axes with
  10820.                 title. If AutoScale is cYes,  value data. TicFont also
  10821.                 Presentation Graphics         applies to category labels;
  10822.                 toolbox automatically writes  the remainder are ignored for
  10823.                 a scale description to        the category axis.
  10824.                 ScaleTitle. If AutoScale
  10825.                 equals cNo and ScaleFactor
  10826.                 is 1, ScaleTitle.Title
  10827.                 should be blank. Otherwise
  10828.                 your program should copy an
  10829.                 appropriate scale
  10830.                 description to
  10831.                 ScaleTitle.Title, such as
  10832.                 "(x 1000)," "(in millions of
  10833.                 units)," "times 10 thousand
  10834.                 dollars," etc. For the
  10835.                 y-axis, the Scaletitle text
  10836.                 displays vertically between
  10837.                 the axis title and the
  10838.  ────────────────────────────────────────────────────────────────────────────
  10839.                the axis title and the
  10840.                 y-axis. For the x-axis the
  10841.                 scale title appears below
  10842.                 the x-axis title.
  10843.  
  10844.  TicFont        An  integer between 1 and
  10845.                 the total number of fonts
  10846.                 loaded specifying which of a
  10847.                 group of currently loaded
  10848.                 fonts to use for this axis's
  10849.                 tick marks. The default
  10850.                 value is 1.
  10851.  
  10852.  TicInterval    Sets interval between tick
  10853.                 marks on the axis. The tick
  10854.                 interval is measured in the
  10855.                 same units as the numeric
  10856.                 data associated with the
  10857.                 axis. For example, if two
  10858.                 sequential tick marks
  10859.  ────────────────────────────────────────────────────────────────────────────
  10860.                sequential tick marks
  10861.                 correspond to the values 20
  10862.                 and 25, the tick interval
  10863.                 between them is 5.
  10864.  
  10865.  TicFormat      An integer that determines
  10866.                 the format of the labels
  10867.                 assigned to each tick mark.
  10868.                 Set TicFormat to cExpFormat
  10869.                 for exponential format or to
  10870.                 cDecFormat for decimal (the
  10871.                 default).
  10872.  
  10873.  TicDecimals    Number of digits to display
  10874.                 after the decimal point in
  10875.                 tick labels. Maximum value
  10876.                 is 9.
  10877.  
  10878.  
  10879.  
  10880.  
  10881.  
  10882.  
  10883.  LegendType
  10884.  
  10885.  Structured variables of the LegendType user-defined type contain size,
  10886.  location, and colors of the chart legend. The CHRTB.BI file defines the
  10887.  elements of LegendType as:
  10888.  
  10889.  TYPE LegendType
  10890.  Legend  AS INTEGER
  10891.  PlaceAS INTEGER
  10892.  TextColorAS INTEGER
  10893.  TextFontAS INTEGER
  10894.  AutoSizeAS INTEGER
  10895.  LegendWindowAS RegionType
  10896.  
  10897.  The following table describes LegendType elements:
  10898.  
  10899. ╓┌──────────┌───────────────────────────────┌────────────────────────────────╖
  10900.  ────────────────────────────────────────────────────────────────────────────
  10901.  ────────────────────────────────────────────────────────────────────────────
  10902.  Legend     A cYes, cNo (true/false)
  10903.             variable that determines
  10904.             whether a legend is to appear
  10905.             on a multi-series chart. Pie
  10906.             charts always have a legend.
  10907.             The Legend variable is ignored
  10908.             by routines that graph other
  10909.             single-series charts.
  10910.  
  10911.  Place      An integer that specifies the   These settings influence the
  10912.             location of the legend          size of the data window. If
  10913.             relative to the data window.    Place equals cBottom or cRight,
  10914.             Setting the variable Place      Presentation Graphics toolbox
  10915.             equal to the constant cRight    automatically sizes the data
  10916.             positions the legend to the     window to accommodate the
  10917.             right of the data window.       legend. If Place equals
  10918.             Setting Place to cBottom        cOverlay the data window is
  10919.             positions the legend below the  sized without regard to the
  10920.             data window. Setting Place to   legend.
  10921.             cOverLay positions the legend
  10922.  ────────────────────────────────────────────────────────────────────────────
  10923.            cOverLay positions the legend
  10924.             within the data window.
  10925.  
  10926.  TextColor  An integer between 0 and
  10927.             cPalLen that specifies the
  10928.             color of text within the
  10929.             legend window.
  10930.  
  10931.  TextFont   An integer specifying which of
  10932.             a group of currently loaded
  10933.             fonts to use for the legend
  10934.             text.
  10935.  
  10936.  
  10937.  
  10938.  
  10939.  
  10940.  
  10941. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  10942.  ────────────────────────────────────────────────────────────────────────────
  10943.  ────────────────────────────────────────────────────────────────────────────
  10944.  AutoSize                                A cYes, cNo (true/false) variable
  10945.                                          that determines whether the
  10946.                                          Presentation Graphics toolbox will
  10947.                                          automatically calculate the size
  10948.                                          of the legend. If AutoSize equals
  10949.                                          cNo, the legend window must be
  10950.                                          specified in the LegendWindow
  10951.                                          structure (see the following).
  10952.  
  10953.  LegendWindow                            A RegionType structure that
  10954.                                          defines coordinates, background
  10955.                                          color, and border frame for the
  10956.                                          legend. Coordinates given in
  10957.                                          LegendWindow are ignored (and
  10958.                                          overwritten) if AutoSize is cYes.
  10959.  
  10960.  
  10961.  
  10962.  
  10963.  
  10964.  
  10965.  ChartEnvironment
  10966.  
  10967.  A structured variable of the ChartEnvironment type defines the chart
  10968.  environment. The following listing shows that a ChartEnvironment type
  10969.  structure consists almost entirely of structures of the four types discussed
  10970.  in the preceding sections.
  10971.  
  10972.  The CHRTB.BI file defines the ChartEnvironment structure type as:
  10973.  
  10974.  TYPE ChartEnvironment
  10975.  ChartType  AS INTEGER
  10976.  ChartStyle AS INTEGER
  10977.  DataFontAS INTEGER
  10978.  ChartWindow AS RegionType
  10979.  DataWindow AS RegionType
  10980.  MainTitle  As TitleType
  10981.  SubTitle As TitleType
  10982.  XAxis As AxisType
  10983.  YAxis As AxisType
  10984.  Legend As LegendType
  10985.  END TYPE
  10986.  
  10987.  The following list describes ChartEnvironment elements:
  10988.  
  10989. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  10990.  ────────────────────────────────────────────────────────────────────────────
  10991.  ChartType                               An integer that determines the
  10992.                                          type of chart displayed. The value
  10993.                                          of the variable ChartType is
  10994.                                          either cBarChart, cColumnChart,
  10995.                                          cLineChart, cScatterChart, or
  10996.                                          cPieChart. This variable is set
  10997.                                          from the second argument for the
  10998.                                          DefaultChart routine.
  10999.  
  11000.  
  11001.  
  11002.  
  11003.  
  11004.  
  11005. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  11006.  ────────────────────────────────────────────────────────────────────────────
  11007.  ChartStyle                              An integer that determines the
  11008.                                          style of the chart. Legal values
  11009.                                          for ChartStyle are cPercent and
  11010.                                          cNoPercent for pie charts;
  11011.                                          cStacked and cPlain for bar and
  11012.                                          column charts; and cLines and
  11013.                                          cPoints for line graphs and
  11014.                                          scatter diagrams. This variable
  11015.                                          corresponds to the third argument
  11016.                                          for the  DefaultChart routine.
  11017.  
  11018.  DataFont                                An integer that identifies the
  11019.                                          font to use in drawing the
  11020.                                          plotting characters in line charts
  11021.                                          and scatter charts. The range of
  11022.                                          possible values depends on how
  11023.                                          many fonts are loaded. See the
  11024.                                          section "Loading Graphics Fonts"
  11025.                                          later in this chapter, for
  11026.                                          information on loading fonts.
  11027.  ────────────────────────────────────────────────────────────────────────────
  11028.                                         information on loading fonts.
  11029.  
  11030.  ChartWindow                             A RegionType structure that
  11031.                                          defines the appearance of the
  11032.                                          chart window.
  11033.  
  11034.  DataWindow                              A RegionType structure that
  11035.                                          defines the appearance of the data
  11036.                                          window..
  11037.  
  11038.  MainTitle                               A TitleType structure that defines
  11039.                                          the appearance of the main title
  11040.                                          of the chart.
  11041.  
  11042.  SubTitle                                A TitleType structure that defines
  11043.                                          the appearance of the chart's
  11044.                                          subtitle.
  11045.  
  11046.  XAxis                                   An AxisType structure that defines
  11047.                                          the appearance of the x-axis.
  11048.  ────────────────────────────────────────────────────────────────────────────
  11049.                                         the appearance of the x-axis.
  11050.                                          (This variable is not applicable
  11051.                                          for pie charts.)
  11052.  
  11053.  YAxis                                   An AxisType structure that defines
  11054.                                          the appearance of the y-axis.
  11055.                                          (This variable is not applicable
  11056.                                          for pie charts.)
  11057.  
  11058.  Legend                                  A LegendType structure that
  11059.                                          defines the appearance of the
  11060.                                          legend window. Applies to
  11061.                                          multi-series and pie charts. Not
  11062.                                          applicable to single-series charts.
  11063.  
  11064.  
  11065.  
  11066.  
  11067.  
  11068.  
  11069.  Note that all the data in a ChartEnvironment type structure is initialized
  11070.  by calling the  DefaultChart routine. If your program does not call
  11071.  DefaultChart, it must explicitly define every variable in the chart
  11072.  environment --  a tedious and unnecessary procedure. The recommended method
  11073.  for adjusting the appearance of your chart is to initialize variables for
  11074.  the proper chart type by calling the  DefaultChart routine, and then
  11075.  reassign selected environment variables such as Titles.
  11076.  
  11077.  
  11078.  Palettes
  11079.  
  11080.  The Presentation Graphics toolbox displays each data series in a way that
  11081.  makes it discernible from other series. It does this by defining separate
  11082.  "palettes" to determine the color, line style, fill pattern, and plot
  11083.  characters for each different data series in a chart. There is also a
  11084.  palette of line styles used to determine the appearance of window borders
  11085.  and grid lines.
  11086.  
  11087.  The Presentation Graphics toolbox maintains a set of default palettes. What
  11088.  appears in the default palettes depends on the mode specified in the
  11089.  ChartScreen routine, which in turn depends on the BASIC screen modes
  11090.  supported by the host system. Figure 6.8 illustrates the default palettes
  11091.  for a screen mode that permits four colors (indexed as 0,1,2,3).
  11092.  
  11093.  2097bfff-----------------------------------------------------
  11094.  Each column in Figure 6.8 represents one of the Presentation Graphics
  11095.  toolbox palettes. When a data series is displayed on a chart, one value from
  11096.  each column in the chart is used to determine the corresponding
  11097.  characteristic. Therefore, each of rows 1-15 in Figure 6.8 represents the
  11098.  characteristics that would be displayed for one of up to 15 data series in a
  11099.  chart displayed on the specified hardware.
  11100.  
  11101.  Note
  11102.  
  11103.  Don't confuse the Presentation Graphics toolbox palettes with BASIC's
  11104.  PALETTE statement (used to map colors other than the defaults to the color
  11105.  attributes for EGA, VGA, and MCGA adapters). The Presentation Graphics
  11106.  toolbox data series palettes are specific to the Presentation Graphics
  11107.  toolbox.
  11108.  
  11109.  The Presentation Graphics toolbox provides three routines that you can use
  11110.  to customize the palettes if you wish. Because they are declared in the
  11111.  include file chrtb.bi (as follows), you can invoke them without a call
  11112.  statement and without parentheses around the argument list:
  11113.  
  11114.   GetPaletteDef  PaletteC% ( ),  PaletteS% ( ),  PaletteP$ ( ),  PaletteCH% (
  11115.  ),  PaletteB% ( )
  11116.   SetPaletteDef  PaletteC% ( ),  PaletteS% ( ),  PaletteP$ ( ),  PaletteCH% (
  11117.  ),  PaletteB% ( )
  11118.   ResetPaletteDef
  11119.  
  11120.  All the parameters are one-dimensional arrays of length cPalLen (starting
  11121.  with the subscript 0 and extending to subscript 15).  GetPaletteDef lets you
  11122.  access the current palette values.  SetPaletteDef can be used to substitute
  11123.  custom values within the palette arrays.  ResetPaletteDef reinstates the
  11124.  default values.  GetPaletteDef is the sub procedure that gets the array
  11125.  values.
  11126.  
  11127.  When you invoke  ChartScreen to reset the video mode, the arrays are
  11128.  initialized to their default values. Once  ChartScreen has filled the arrays
  11129.  with the default values for the specified screen mode, you can change values
  11130.  in any of the arrays. You use  GetPaletteDef to transfer the default values
  11131.  to your array variables, then invoke  SetPaletteDef after you assign your
  11132.  custom values to the arrays you want to change.  ResetPaletteDef restores
  11133.  the internal chart palette to the original default values, so you need not
  11134.  save the values of your first  GetPaletteDef call for resetting defaults. If
  11135.  you try to call any of these routines before an initial call to
  11136.  ChartScreen, an error is generated. The parameters of  GetPaletteDef and
  11137.  SetPaletteDef are defined in the following table:
  11138.  
  11139.  
  11140. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  11141.  Parameter                               Definition
  11142.  ────────────────────────────────────────────────────────────────────────────
  11143.   PaletteC%( )                           Integer array corresponding to
  11144.                                          color number palette entries.
  11145.                                          Changes here change colors of
  11146.                                          items like data lines and text.
  11147.  
  11148.   PaletteS%( )                           Integer array determining
  11149.                                          appearance of lines in
  11150.                                          multi-series line graphs (for
  11151.                                          example, solid, dotted, dashed,
  11152.                                          etc.).
  11153.  Parameter                               Definition
  11154.  ────────────────────────────────────────────────────────────────────────────
  11155.                                         etc.).
  11156.  
  11157.   PaletteP$( )                           String array determining the
  11158.                                          bit-map pattern of the filled-in
  11159.                                          areas in pie, bar, and column
  11160.                                          charts.
  11161.  
  11162.   PaletteCH%( )                          Integer array specifying which
  11163.                                          ASCII character is used on a graph
  11164.                                          for the plot points of each data
  11165.                                          series in a multi-series line
  11166.                                          graphs.
  11167.  
  11168.   PaletteB%( )                           Integer array used to set lines in
  11169.                                          the display that don't appear as
  11170.                                          data lines within a graph, for
  11171.                                          example window borders and grid
  11172.                                          lines.
  11173.  
  11174.  Parameter                               Definition
  11175.  ────────────────────────────────────────────────────────────────────────────
  11176. 
  11177.  
  11178.  
  11179.  
  11180.  
  11181.  The following five sections further describe each of the parameters in the
  11182.  preceding list, and effects of calls to the  SetPaletteDef routine using
  11183.  non-default values in the arrays.
  11184.  
  11185.  Note
  11186.  
  11187.  If a pie chart has more than 15 slices, slice 16 will have the same color or
  11188.  fill pattern as slice 2; slice 17 will have the same color or fill pattern
  11189.  as slice 3, and so on. Similarly, if the background is not the default, one
  11190.  slice of the pie may be the same color as the background. See the section
  11191.  "Fill Patterns" later in this chapter for an explanation of how to change
  11192.  these elements.
  11193.  
  11194.  
  11195.  Colors
  11196.  
  11197.  One of the elements used to distinguish one data series from another is
  11198.  color. The possible colors correspond to pixel values valid for the current
  11199.  graphics mode. (See column 1 in Figure 6.8. Refer to Chapter 5, "Graphics,"
  11200.  for a description of pixel values.) Each row in Figure 6.8 contains pixel
  11201.  values that refer to these available colors. These color codes are the
  11202.  values placed in the  PaletteC%() array when  ChartScreen is called. The
  11203.  color code in each entry (i.e. the individual elements of the  PaletteC%()
  11204.  array) then determines the color used to graph the data series associated
  11205.  with the entry.
  11206.  
  11207.  Say for example, you have a chart with several lines representing different
  11208.  series of data. The default background color for the chart is represented by
  11209.  the code in the PaletteC%(0) element, the color for the first line
  11210.  corresponds to the code in the PaletteC%(1) element, the color for the
  11211.  second line corresponds to the code in the PaletteC%(2) element, and so
  11212.  forth. These colors are also used for labels, titles, and legends.
  11213.  
  11214.  The first color is always black, which is the pixel value for the screen
  11215.  background color. The second is always white. The remaining elements are
  11216.  repeating sequences of available pixel values, beginning with 1.
  11217.  
  11218.  
  11219.  The values in the  PaletteC%() array are passed to a BASIC color statement
  11220.  by the internal charting routines. Each value represents three things: a
  11221.  display attribute, a pixel value, and a color attribute.
  11222.  
  11223.  For example, calling ChartScreen 1 with a CGA system (320 X 200 graphics)
  11224.  provides four colors for display. Pixel values from 0 to 3 determine the
  11225.  possible pixel colors--say, black, cyan, magenta, and white respectively. In
  11226.  this case the first eight available color values would be as follows:
  11227.  
  11228. ╓┌────────────┌────────────┌─────────────────────────────────────────────────╖
  11229.  Color index  Pixel value  Color
  11230.  ────────────────────────────────────────────────────────────────────────────
  11231.  0            0            Black
  11232.  1            3            White
  11233.  2            1            Cyan
  11234.  3            2            Magenta
  11235.  4            3            White
  11236.  5            1            Cyan
  11237.  Color index  Pixel value  Color
  11238.  ────────────────────────────────────────────────────────────────────────────
  11239. 5            1            Cyan
  11240.  6            2            Magenta
  11241.  7            3            White
  11242.  
  11243.  
  11244.  
  11245.  
  11246.  Notice that the sequence of available colors repeats from the third element.
  11247.  The first data series in this case would be plotted in white, the second
  11248.  series in cyan, the third series in magenta, the fourth series again in
  11249.  white, and so forth.
  11250.  
  11251.  Video adapters such as the EGA allow 16 on-screen colors. This allows the
  11252.  Presentation Graphics toolbox to graph more series without duplicating
  11253.  colors.
  11254.  
  11255.  You can use  SetPaletteDef to change the color code assignments. For
  11256.  example, in the preceding CGA example, if you didn't want the color red to
  11257.  appear in your chart, you could refill the elements of the  PaletteC % ()
  11258.  array as follows:
  11259.  
  11260.  PaletteC %(3) = 3 : PaletteC %(4) = 1 : PaletteC %(5) = 3 :
  11261.   PaletteC %(6) = 1 PaletteC %(7) = 3
  11262.  
  11263.  However, if more than two data series appeared in the graph, the lines
  11264.  representing series after the first two would repeat the colors of the first
  11265.  two. To differentiate these lines clearly, you would have to adjust the
  11266.  available line styles (described in the following section "Line Styles") for
  11267.  the palettes. In types of charts other than line charts (e.g. pie charts),
  11268.  changing color assignments would also necessitate modifying the available
  11269.  fill patterns (described in the section "Fill Patterns") as well as the line
  11270.  styles available. Note that the colors in pie, bar, and column charts are
  11271.  not determined by the available colors but by color attributes of the fill
  11272.  patterns. Reassigning the values in the PaletteC%() array changes only the
  11273.  outline of a bar, column, or pie slice.
  11274.  
  11275.  
  11276.  Line Styles
  11277.  
  11278.  The Presentation Graphics toolbox matches the available colors with a
  11279.  collection of different line styles (column two in Figure 6.8). These are
  11280.  the values in  PaletteS%() array (the second parameter to the  GetPaletteDef
  11281.  routine ). Entries for the line styles define the appearance of lines such
  11282.  as axes and grids. Lines can be solid, dotted, dashed, or of some
  11283.  combination.
  11284.  
  11285.  Each palette entry (each entry in the PaletteS% column) is a code that
  11286.  refers to one of the line styles in the same way that each entry in the
  11287.  PaletteC% column is a color code that refers to a color index. The style
  11288.  code value in a palette is applicable only to line graphs and lined scatter
  11289.  diagrams. The style code determines the appearance of the lines drawn
  11290.  between points.
  11291.  
  11292.  The palette entry's style code adds further variety to the lines of a
  11293.  multi-series graph. It is most useful when the number of lines in a chart
  11294.  exceeds the number of available colors. For example, a graph of nine
  11295.  different data series must repeat colors if only three foreground colors are
  11296.  available for display. However, the style code for each color repetition
  11297.  will be different, ensuring that none of the lines look the same. As
  11298.  mentioned previously, if you modified the repetition pattern for the colors,
  11299.  you would have to adjust the line styles repetition pattern as well.
  11300.  Extending the example of the previous section, if you limited your chart
  11301.  lines to two of the available three foreground colors, you would need to
  11302.  adjust the style pool so that differentiation by type of line would begin
  11303.  earlier. You would do this by changing the  PaletteS % () array as follows:
  11304.  
  11305.  PaletteS%(0) = &HFFFF : PaletteS%(1) = &HFFFF : PaletteS%(2) = &HFFFF
  11306.   PaletteS%(3) = &HF0F0 : PaletteS%(4) = &HF0F0 : PaletteS%(5) = &HF060
  11307.   PaletteS%(6) = &HF060 : PaletteS%(7) = &HCCCC
  11308.  
  11309.  Fill Patterns
  11310.  
  11311.  The Presentation Graphics toolbox environment also maintains a default
  11312.  collection of fill patterns (column three,  PaletteP $ ( ), in Figure 6.8).
  11313.  Fill patterns determine the fill design for column, bar, and pie charts.
  11314.  These are the values in the  PaletteP $ ( ) array  (the third parameter to
  11315.  the  GetPaletteDef routine). The  PaletteP$( ) array values are used within
  11316.  the Presentation Graphics toolbox as the  paint parameters to the  PAINT
  11317.  statement. Manipulating the fill patterns is trickier than the colors and
  11318.  line styles, because the fill patterns determine both the pattern and the
  11319.  color of a fill. The discussion in this section depends on an understanding
  11320.  of information in the section "Painting with Patterns: Tiling" in Chapter 5,
  11321.  "Graphics," which describes how to manipulate "bit maps" in BASIC. The
  11322.  example in the section "Pattern Editor" in that chapter is also instructive.
  11323.  
  11324.  Each string in the  PaletteP $ () array  contains information that is passed
  11325.  as the  paint parameter  to the  PAINT statement which then defines the fill
  11326.  pattern (and the color of the fill, if color is available), for the data
  11327.  series associated with the palette.
  11328.  
  11329.  You can change the fill color and pattern for pie, column, and bar charts
  11330.  using the  MakeChartPattern$ and  GetPattern$ routines in combination with
  11331.  the  GetPaletteDef and  SetPaletteDef routines.
  11332.  
  11333.  
  11334.  To change fill pattern and color, first create a one-dimensional string
  11335.  array with 0 to cPalLen elements just as with the PaletteC%() and
  11336.  Palettes%() integer arrays. For example:
  11337.  
  11338.  DIM Fills$(0 to cPalLen)
  11339.  
  11340.  After calling  GetPaletteDef with Fills$() as the third argument, use the
  11341.  MakeChartPattern$ routine to construct the values to pass as elements of the
  11342.  Fills$() array.  MakeChartPattern$ has the following syntax:
  11343.  
  11344.   MakeChartPattern$( RefPattern$,  Foreground%,  Background%)
  11345.  
  11346. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  11347.  Parameter                               Description
  11348.  ────────────────────────────────────────────────────────────────────────────
  11349.  Env                                     Environment variable of
  11350.                                          ChartEnvironment type.
  11351.  
  11352.  Cat$()                                  String array of category
  11353.                                          specifications for x-axis.
  11354.  
  11355.  Val!()                                  Two-dimensional array of
  11356.                                          single-precision numbers
  11357.                                          representing numeric data to be
  11358.                                          plotted.
  11359.  
  11360.  N%                                      Number of rows in the
  11361.                                          two-dimensional array (that is,
  11362.                                          the number of elements in the
  11363.  Parameter                               Description
  11364.  ────────────────────────────────────────────────────────────────────────────
  11365.                                         the number of elements in the
  11366.                                          first dimension).
  11367.  
  11368.  First%                                  Integer representing the first
  11369.                                          column of data (that is, which
  11370.                                          element of the second array
  11371.                                          dimension represents the first
  11372.                                          data series).
  11373.  
  11374.  Last%                                   Integer representing the final
  11375.                                          column of data (that is, which
  11376.                                          element of the second array
  11377.                                          dimension represents the last data
  11378.                                          series)..
  11379.  
  11380.  SerLabels$()  and  Labs$()              String arrays that describe the
  11381.                                          labels for each data series in the
  11382.                                          legend of the chart. There is one
  11383.                                          string for each of the columns in
  11384.  Parameter                               Description
  11385.  ────────────────────────────────────────────────────────────────────────────
  11386.                                         string for each of the columns in
  11387.                                          the Val!() array.
  11388.  
  11389.  ValX!()                                 Two-dimensional array of
  11390.                                          single-precision numbers
  11391.                                          representing the x-axis part of
  11392.                                          each scatter-chart point.
  11393.  
  11394.  ValY!()                                 Two-dimensional array of
  11395.                                          single-precision numbers
  11396.                                          representing the y-axis part of
  11397.                                          each scatter-chart point.
  11398.  
  11399.  
  11400.  
  11401.   ────────────────────────────────────────────────────────────────────────────
  11402.  
  11403.  Chapter 7:  Programming with Modules
  11404.  
  11405.  This chapter shows you gain more control over your programming projects
  11406.  by dividing them into modules. Modules provide a powerful organizing
  11407.  function by letting you divide a program into logically related parts
  11408.  (rather than keeping all the code in one file).
  11409.  
  11410.  This chapter will show you how to use modules to:
  11411.  
  11412.    ■   Write and test new procedures separately from the rest of the program.
  11413.  
  11414.    ■   Create libraries of your own  SUB and  FUNCTION procedures that can be
  11415.        added to any new program.
  11416.  
  11417.    ■   Combine routines from other languages (such as C or MASM) with your
  11418.        BASIC programs.
  11419.  
  11420.    ■   Preserve memory when working within QBX.
  11421.  
  11422.  
  11423.  
  11424.  Why Use Modules?
  11425.  
  11426.  A module is a file that contains an executable part of your program. A
  11427.  complete program can be contained in a single module, or it can be divided
  11428.  among two or more modules.
  11429.  
  11430.  In dividing a program into modules, logically related sections are placed in
  11431.  separate files. This organization can speed and simplify the process of
  11432.  writing, testing, and debugging.
  11433.  
  11434.  Dividing your program into modules has these advantages:
  11435.  
  11436.    ■   Procedures can be written separately from the rest of the program,
  11437.        then combined with it. This arrangement is especially useful for
  11438.        testing the procedures, since they can then be checked outside the
  11439.        environment of the program.
  11440.  
  11441.    ■   Two or more programmers can work on different parts of the same
  11442.        program without interference. This is especially helpful in managing
  11443.        complex programming projects.
  11444.  
  11445.    ■   As you create procedures that meet your own specific programming
  11446.        needs, you can add these procedures to their own module. They can then
  11447.        be reused in new programs simply by loading that module.
  11448.  
  11449.    ■   Multiple modules simplify software maintenance. A procedure used by
  11450.        many programs can be in one library module; if changes are needed, the
  11451.        procedure only has to be modified once.
  11452.  
  11453.    ■   Modules can contain general-purpose procedures that are useful in a
  11454.        variety of programs (such as procedures that evaluate matrixes, send
  11455.        binary data to a communications port, alter strings, or handle
  11456.        errors). These procedures can be stored in modules, then used in new
  11457.        programs simply by loading the appropriate module into QBX.
  11458.  
  11459.  
  11460.  
  11461.  Main Modules
  11462.  
  11463.  The module containing the first executable statement of a program is called
  11464.  the "main module." This statement is never part of a procedure because
  11465.  execution cannot begin within a procedure.
  11466.  
  11467.  Everything in a module except  SUB and  FUNCTION procedures is "module-level
  11468.  code." This includes declarations and definitions, executable program
  11469.  statements and module-level error-handling routines. Figure 7.1 illustrates
  11470.  the relationship between these elements.
  11471.  
  11472.  
  11473.  Non-Main Modules and Procedure-Level Modules
  11474.  
  11475.  A non-main module can have module-level and procedure-level code. The
  11476.  module-level code consists of metacommands (such as  $INCLUDE), declarations
  11477.  and definitions (including  COMMON statements), and any event-trapping or
  11478.  module-level error-handling routine.
  11479.  
  11480.  A non-main module does not have to contain module-level code. It can consist
  11481.  of nothing but  SUB and  FUNCTION procedures. Indeed, such a procedures-only
  11482.  module is the most important use of modules. Here's how to create one:
  11483.  
  11484.     1. Invoke QBX without opening or loading any files.
  11485.  
  11486.     2. Write all the  SUB and  FUNCTION procedures you wish, but don't enter
  11487.        any module-level code. (Any error- or event-trapping routines and
  11488.        BASIC declarations needed are exceptions.)
  11489.  
  11490.     3. Choose Save As from the File menu to name and save this module.
  11491.  To move procedures from one module to another:
  11492.  
  11493.     4. From QBX, load the files containing the procedures you want to move.  2
  11494.        menu to load it, too. If it doesn't exist, choose Create File from the
  11495.        File menu to make the new file.
  11496.  
  11497.     5. Choose SUBs from the View menu and the Move option to transfer the
  11498.        procedures from the old to the new file. This transfer is made final
  11499.        when you quit QBX and respond Yes to the dialog box that asks whether
  11500.        you want to save the modified files; otherwise, the procedures remain
  11501.        where they were when you started.
  11502.  
  11503.  
  11504.  
  11505.  Loading Modules
  11506.  
  11507.  In QBX you can load as many modules as you wish (limited only by the
  11508.  available memory) by choosing Load File from the File menu. All the
  11509.  procedures in all the loaded modules can be called from any other procedure
  11510.  or from module-level code. No error occurs if a module happens to contain a
  11511.  procedure that is never called.
  11512.  
  11513.  QBX begins execution with the module-level code of the first module loaded.
  11514.  If you want execution to begin in a different module, choose Set Main Module
  11515.  from the Run menu. Execution halts at the  END statement in the designated
  11516.  main module.
  11517.  
  11518.  The ability to choose which module-level code gets executed is useful when
  11519.  comparing two versions of the same program. For example, you might want to
  11520.  test different user interfaces by putting each in a separate module. You can
  11521.  also place test code in a module containing only procedures, then use the
  11522.  Set Main Module command to switch between the program and the tests.
  11523.  
  11524.  You do not have to keep track of which modules your program uses.
  11525.  Whenever you choose Save All from the File menu, QBX creates (or updates)
  11526.  a .MAK file, which lists all the modules currently loaded. The next time
  11527.  the main module is loaded by choosing Open Program from the File menu,
  11528.  QBX consults this .MAK file and automatically loads the modules listed
  11529.  in it.
  11530.  
  11531.  
  11532.  Using DECLARE with Multiple Modules
  11533.  
  11534.  The  DECLARE statement has several important functions in BASIC. Using a
  11535.  DECLARE statement will do the following:
  11536.  
  11537.    ■   Specify the sequence and data types of a procedure's parameters. ■   En
  11538.        that the arguments agree with the parameters in both number and data
  11539.        type.
  11540.  
  11541.    ■   Identify a  FUNCTION procedure's name as a procedure name, not a
  11542.        variable name.
  11543.  
  11544.  
  11545.  QBX has its own system for automatically inserting the required  DECLARE
  11546.  statements into your modules. The section "Checking Arguments with the
  11547.  DECLARE Statement" in Chapter 2, "SUB and FUNCTION Procedures," explains the
  11548.  features and limitations of this system.
  11549.  
  11550.  Despite QBX's automatic insertion of the  DECLARE statement, you may wish to
  11551.  create a separate include file that contains all the  DECLARE statements
  11552.  required for a program. You can update this file manually as you add and
  11553.  delete procedures or modify your argument lists.
  11554.  
  11555.  
  11556.  Note
  11557.  
  11558.  If you write your programs with a text editor (rather than in QBX) and
  11559.  compile from the command line, you must insert  DECLARE statements manually.
  11560.  
  11561.  
  11562.  Accessing Variables from Two or More Modules
  11563.  
  11564.  You can use the  SHARED attribute to make variables accessible at module
  11565.  level and within that module's procedures. If these procedures are moved to
  11566.  another module, however, these variables are no longer shared.
  11567.  
  11568.  You could pass these variables to each procedure through its argument list.
  11569.  This may be inconvenient, though, if you need to pass a large number of
  11570.  variables.
  11571.  
  11572.  One solution is to use  COMMON statements, which enable two or more modules
  11573.  to access the same group of variables. The section "Sharing Variables with
  11574.  SHARED" in Chapter 2, "SUB and FUNCTION Procedures," explains how to do
  11575.  this.
  11576.  
  11577.  Another solution is to use a  TYPE... END TYPE statement to combine all the
  11578.  variables you wish to pass into a single structure. The argument and
  11579.  parameter lists then have to include only one variable name, no matter how
  11580.  many variables are passed.
  11581.  
  11582.  
  11583.  If you are simply splitting up a program and its procedures into separate mod
  11584.  these approaches works well. If, on the other hand, you are adding a
  11585.  procedure to a module (for use in other programs), you should avoid using a
  11586.  COMMON statement. Modules are supposed to make it easy to add existing
  11587.  procedures to new programs;  COMMON statements complicate the process. If a
  11588.  procedure needs a large group of variables, it may not belong in a separate
  11589.  module.
  11590.  
  11591.  
  11592.  Using Modules During Program Development
  11593.  
  11594.  When you start a new programming project, you should first look through
  11595.  existing modules to see if there are procedures that can be reused in your
  11596.  new software. If any of these procedures aren't already in a separate
  11597.  module, you should consider moving them to one.
  11598.  
  11599.  As your program takes shape, newly written procedures automatically become
  11600.  part of the program file (that is, the main module). You can move them to a
  11601.  separate module for testing or perhaps add them to one of your own modules
  11602.  along with other general-purpose procedures that are used in other programs.
  11603.  
  11604.  Your program may need procedures written in other languages. (For example,
  11605.  MASM is ideal for direct interface with the hardware, FORTRAN has almost any
  11606.  math function you might want, Pascal allows the creation of sophisticated
  11607.  data structures, and C provides structured code and direct memory access.)
  11608.  These procedures are compiled and linked into a Quick library for use in
  11609.  your program. You can also write a separate module to test Quick library
  11610.  procedures in the same way you would test other procedures.
  11611.  
  11612.  
  11613.  Compiling and Linking Modules
  11614.  
  11615.  The end product of your programming efforts is usually a stand-alone
  11616.  executable file. You can create one in QBX by loading all of a program's
  11617.  modules, then choosing Make EXE File from the Run menu.
  11618.  
  11619.  You can also compile modules from the command line using the BASIC Compiler
  11620.  (BC), then use LINK to combine the object code. Object files from code
  11621.  written in other languages can be linked at the same time.
  11622.  
  11623.  
  11624.  Note
  11625.  
  11626.  When you choose Make EXE File from QBX, all the module-level code and every
  11627.  procedure currently loaded is included in the executable file, whether or
  11628.  not the program uses this code. If you want your program to be as compact as
  11629.  possible, you must unload all unneeded module-level code and all unneeded
  11630.  procedures before compiling. The same rule applies when using BC to compile
  11631.  from the command line; all unused code should be removed from the files.
  11632.  
  11633.  
  11634.  
  11635.  Quick Libraries
  11636.  
  11637.  Although Microsoft Quick libraries are not modules, it is important that you
  11638.  understand their relationship to modules.
  11639.  
  11640.  A Quick library contains nothing but procedures. These procedures can be
  11641.  written in BASIC or any other Microsoft language.
  11642.  
  11643.  A Quick library contains only compiled code. (Modules contain BASIC source
  11644.  code.) The code in a Quick library can come from any combination of
  11645.  Microsoft languages. The chapter "Creating and Using Quick Libraries"
  11646.  explains how to create Quick libraries from object code and how to add new
  11647.  object code to existing Quick libraries.
  11648.  
  11649.  Quick libraries have several uses:
  11650.  
  11651.    ■   They provide an interface between BASIC and other languages.
  11652.  
  11653.    ■   They allow designers to hide proprietary software. Updates and
  11654.        utilities can be distributed as Quick libraries without revealing the
  11655.        source code.
  11656.  
  11657.    ■   They load faster and are usually smaller than modules. If a large
  11658.        program with many modules loads slowly, converting the modules other
  11659.        than the main module into a Quick library will improve loading
  11660.        performance.
  11661.  
  11662.  
  11663.  Note, however, that modules are the easiest way to work on procedures during
  11664.  development because modules are immediately ready to run after each edit;
  11665.  you don't have to recreate the Quick library. If you want to put your BASIC
  11666.  procedures in a Quick library, wait until the procedures are complete and
  11667.  thoroughly debugged.
  11668.  
  11669.  When a Quick library is created, any module-level code in the file it was
  11670.  created from is automatically included. However, other modules cannot access
  11671.  this code, so it just wastes space. Before converting a module to a Quick
  11672.  library, be sure that all module-level statements (except any error or event
  11673.  handlers and declarations that are used by the procedures) have been
  11674.  removed.
  11675.  
  11676.  
  11677.  Note
  11678.  
  11679.  Quick libraries are not included in .MAK files and must be loaded with the
  11680.  /L option when you run QBX. A Quick library has the file extension .QLB.
  11681.  During the process of creating the Quick library, an object-module library
  11682.  file with the extension .LIB is created. This file contains the same code as
  11683.  the Quick library but in a form that allows it to be linked with the rest of
  11684.  the program to create a stand-alone application.
  11685.  
  11686.  If you use Quick libraries to distribute proprietary code (data-manipulation
  11687.  procedures, for example), be sure to include the object-module library files
  11688.  (.LIB) so that your customers can create stand-alone applications that use
  11689.  these procedures. Otherwise, they will be limited to running applications
  11690.  within the QBX environment.
  11691.  
  11692.  
  11693.  Creating Quick Libraries
  11694.  
  11695.  You can create a Quick library of BASIC procedures with the Make Library
  11696.  command from the Run menu. The Quick library created contains every
  11697.  procedure currently loaded, whether or not your program calls it. (It also
  11698.  contains all the module-level code.) If you want the Quick library to be
  11699.  compact, be sure to remove all unused procedures and all unnecessary
  11700.  module-level code first.
  11701.  
  11702.  You can create as many Quick libraries as you like, containing whatever
  11703.  combination of procedures you wish. However, only one Quick library can be
  11704.  loaded into QBX at a time. (You would generally create application-specific
  11705.  Quick libraries, containing only the procedures a particular program needs.)
  11706.  Large Quick libraries can be created by loading many modules, then choosing
  11707.  Make Library from the Run menu.
  11708.  
  11709.  You can also compile one or more modules with the BC command and then link
  11710.  the object code files to create a Quick library. Quick libraries of
  11711.  procedures written in other languages are created the same way. In linking,
  11712.  you are not limited to one language; the object-code files from any number
  11713.  of Microsoft languages can be combined in one Quick library. Chapter 19,
  11714.  "Creating and Using Quick Libraries," explains how to convert object-code
  11715.  files (.OBJ) into Quick libraries.
  11716.  
  11717.  
  11718.  Tips for Good Programming with Modules
  11719.  
  11720.  You can use modules in any way you think will improve your program or help
  11721.  organize your work. The following suggestions are offered as a guide:
  11722.  
  11723.    ■   Think and organize first.
  11724.  
  11725.    ■    When you start on a new project, make a list of the operations you
  11726.        want to be performed by procedures. Then look through your own
  11727.        procedure library to see if there are any you can use, either as is or
  11728.        with slight modifications. Don't waste time "reinventing the wheel."
  11729.  
  11730.    ■   Write generalized procedures with broad application.
  11731.  
  11732.    ■    Try to write procedures that are useful in a wide variety of programs.
  11733.        Don't, however, make the procedure needlessly complex. A good
  11734.        procedure is a simple, finely honed tool.
  11735.  
  11736.    ■    It is sometimes useful to alter an existing procedure to work in a new
  11737.        program. This might require modifying programs you've already written,
  11738.        but it's worth the trouble if the revised procedure is more powerful
  11739.        or has broader application.
  11740.  
  11741.    ■   When creating your own procedure modules, keep logically separate
  11742.        procedures in separate modules.
  11743.  
  11744.    ■    It makes sense to put string-manipulation procedures in one module,
  11745.        matrix-handling procedures in another, and data-communication
  11746.        procedures in a third. This arrangement avoids confusion and makes it
  11747.        easy to find the procedure you need.
  11748.  
  11749.  
  11750.  
  11751.   ────────────────────────────────────────────────────────────────────────────
  11752.  Chapter 8:  Error Handling
  11753.  
  11754.  ────────────────────────────────────────────────────────────────────────────
  11755.  
  11756.  This chapter explains how to intercept and deal with errors that occur while
  11757.  a program is running. Using these methods, you can protect your program from
  11758.  such errors as opening a nonexistent file or trying to use the printer when
  11759.  it is out of paper.
  11760.  
  11761.    ■   After reading this chapter, you will know how to:
  11762.  
  11763.    ■   Enable error trapping.
  11764.  
  11765.    ■   Write an error-handling routine to process the trapped errors.
  11766.  
  11767.    ■   Return control from an error-handling routine.
  11768.  
  11769.    ■   Trap errors at the procedure and module level.
  11770.  
  11771.    ■   Trap errors in programs composed of more than one module.
  11772.  
  11773.  
  11774.  
  11775.  Why Use Error Handling?
  11776.  
  11777.  Error handling is the process of intercepting and dealing with errors that
  11778.  occur at run time and cause your program to stop executing. A typical error
  11779.  would be if a stand-alone program attempted to save data on a disk that was
  11780.  out of space. For example:
  11781.  
  11782.  OPEN "A:RESUME" FOR OUTPUT AS #1
  11783.  PRINT #1, MyBusinessResume$
  11784.  CLOSE #1
  11785.  
  11786.  In this case, since there is no provision for error handling, BASIC issues
  11787.  the message
  11788.  
  11789.  Disk Full and terminates. The operator's data, stored in the string
  11790.  variable MyBusinessResume$, and all other data in memory, is lost.
  11791.  
  11792.  To avoid this situation, you can use BASIC's error handling features to
  11793.  intercept errors and take action once they occur. For example, the full disk
  11794.  problem could be handled by making the following changes to the code (the
  11795.  details of this example are described in the next section):
  11796.  
  11797.  ON ERROR GOTO Handler' Write document to disk.
  11798.  OPEN "A:RESUME" FOR OUTPUT AS #1
  11799.  PRINT #1, MyBusinessResume$CLOSE #1
  11800.  ON ERROR GOTO Handler' Turn on error trapping.
  11801.  OPEN "A:RESUME" FOR OUTPUT AS #1' Write document to disk.
  11802.  PRINT #1, MyBusinessResume$
  11803.  CLOSE #1
  11804.  END
  11805.  ' Branch here if an error occurs
  11806.  Handler:
  11807.  ' Have operator get another disk if this one's full.
  11808.  IF ERR = 61 THEN
  11809.  CLOSE #1
  11810.  KILL "A:RESUME"
  11811.  PRINT "Disk in drive A too full for operation."
  11812.  PRINT "Insert new formatted disk."
  11813.  PRINT "Press any key when ready"
  11814.  Pause$ = INPUT$(1)
  11815.  OPEN "A:RESUME" FOR OUTPUT AS #1
  11816.  RESUME
  11817.  ELSE
  11818.  PRINT "Unanticipated error number";ERR ;"occurred."
  11819.  PRINT "Program Terminated."
  11820.  END
  11821.  END IF
  11822.  
  11823.  Your program can correct many kinds of operator errors using methods such as
  11824.  those shown in this example. You can also use error handling for program
  11825.  control and for gaining information about devices (monitors, printers,
  11826.  modems) that are connected to the computer.
  11827.  
  11828.  
  11829.  How to Handle Errors
  11830.  
  11831.    There are three steps necessary for handling errors:
  11832.  
  11833.     1. Set the error trap by telling the program where to branch to when an
  11834.        error occurs.
  11835.  
  11836.  
  11837.  
  11838.        In the example in the preceding section, this is accomplished by the
  11839.        ON ERROR GOTO statement which directs the program to the label
  11840.        Handler.
  11841.  
  11842.     2. Write a routine that takes action based on the specific error that has
  11843.        occurred -- the error-handling routine.
  11844.  
  11845.  
  11846.  
  11847.        In the example in the preceding section, the Handler routine did this
  11848.        using a  IF THEN statement. When it encountered a disk full error, it
  11849.        prompted the operator for a new disk. If that wasn't the solution to
  11850.        the error, it terminated.
  11851.  
  11852.     3. Exit the error-handling routine.
  11853.  
  11854.  
  11855.  
  11856.        In the example in the preceding section, the  RESUME statement was
  11857.        used to branch back to the line where the disk full error occurred.
  11858.        Data was then written to a new disk.
  11859.  
  11860.  
  11861.  Details on how to perform these steps are given in the following sections.
  11862.  
  11863.  
  11864.  Setting the Error Trap
  11865.  
  11866.  There are two types of error traps -- module level and procedure level. Both
  11867.  traps become enabled when BASIC executes the  ON  LOCAL  ERROR GOTO
  11868.  statement. Once enabled, the module-level trap remains enabled for the
  11869.  duration of the program (or until you turn it off). The procedure-level trap
  11870.  is enabled only while the procedure containing it is active -- that is until
  11871.  an  EXIT SUB or  END SUB statement is executed for that procedure. See the
  11872.  section "Procedure- vs. Module-Level Error Handling" later in this chapter
  11873.  for a discussion of when to use procedure- and module-level trapping.
  11874.  
  11875.  To enable a module-level trap, use the  ON ERROR GOTO  line statement, where
  11876.   line is a line number or label identifying the first line of an
  11877.  error-handling routine. Place the statement in the code where you want error
  11878.  trapping enabled, usually at the beginning of the main module. As soon as
  11879.  BASIC executes this line, the trap is enabled.
  11880.  
  11881.  To enable a procedure-level trap, add the  LOCAL keyword to the statement so
  11882.  the syntax becomes  ON LOCAL ERROR GOTO  line. Place the statement in the
  11883.  procedure where you want error trapping enabled. As soon as BASIC executes
  11884.  this statement, the trap is enabled until an  EXIT SUB or  END SUB statement
  11885.  for this procedure is encountered.
  11886.  
  11887.  Important
  11888.  
  11889.  The line number 0 when used in any  ON  LOCAL  ERROR GOTO statement does not
  11890.  enable error handling. It has two other purposes: if it does not appear in
  11891.  an error-handling routine, it turns off error handling (see the section
  11892.  "Turning Off Error Handling" later in this chapter for details); if it
  11893.  appears in an error-handling routine, it causes BASIC to issue the error
  11894.  again.
  11895.  
  11896.  
  11897.  Writing an Error-Handling Routine
  11898.  
  11899.  This first statement in an error-handling routine contains the label or line
  11900.  number contained in the  ON  LOCAL  ERROR GOTO  line statement. For the
  11901.  example in this section, the label PrintErrorHandler is used.
  11902.  
  11903.  Locate the error-handling routine in a place where it cannot be executed
  11904.  during normal program flow. A module-level error-handling routine is
  11905.  normally placed after the  END statement. A procedure-level error-handling
  11906.  routine, is normally placed between the  EXIT SUB and  END SUB statements,
  11907.  as shown in the example in this section.
  11908.  
  11909.  To find out which error occurred use the  ERR function. It returns a numeric
  11910.  code for the program's most recent run-time error. (See Appendix D, "Error
  11911.  Messages," in the  BASIC  Language Reference for a complete list of the
  11912.  error codes associated with run-time errors.) By using  ERR in combination
  11913.  with  SELECT CASE or  IF THEN statements, you can take specific action for
  11914.  any error that occurs.
  11915.  
  11916.  Example
  11917.  
  11918.  In the following code, the PrintList procedure sends the array List$() to
  11919.  the printer. The  ERR function is used to determine which message should be
  11920.  sent to the operator so the problem can be corrected.
  11921.  
  11922.  
  11923.  SUB PrintList(List$())
  11924.  ON LOCAL ERROR GOTO PrinterErrorHandler' Enable error trapping.
  11925.  FOR I% = LBOUND(List$) to UBOUND(List$)
  11926.  LPRINT List$(I%)
  11927.   NEXT I%
  11928.   EXIT SUB
  11929.  PrinterErrorHandler: ' Error-handling routine
  11930.   ' Use ERR to determine which error
  11931.   ' caused the branch. Send out the appropriate message.
  11932.  SELECT CASE ERR
  11933.   CASE 25
  11934.   ' 25 is the code for a Device fault error, which occurs
  11935.   ' if the printer is offline or turned off.
  11936.  PRINT "Printer offline or turned off"
  11937.  CASE 27
  11938.   ' 27 is the code for an Out of paper error:
  11939.   PRINT "Printer is out of paper."
  11940.  CASE 24
  11941.  ' 24 is the code for a Device timeout error, which
  11942.  ' occurs if the printer cable is disconnected.
  11943.  PRINT "Printer cable is disconnected."
  11944.  CASE ELSE
  11945.  PRINT "Unknown error."
  11946.  EXIT SUB
  11947.  END SELECT
  11948.  
  11949.  PRINT "Please correct the problem and"
  11950.  PRINT "Press any key when ready."
  11951.   Pause$ = INPUT$(1)
  11952.  ' Go try again after operator fixes the problem.
  11953.  RESUME
  11954.  
  11955.  END SUB
  11956.  
  11957.  Exiting an Error-Handling Routine
  11958.  
  11959.  To exit from an error-handling routine, use one of the statements shown in
  11960.  Table 8.1.
  11961.  
  11962.  RESUME NEXTReturns to the statement immediately following the one that
  11963.  caused the error or immediately following the last call out of the
  11964.  error-handling procedure or module.  ERROR ERRInitiates a search of the
  11965.  invocation path for another error-handling routine. ON ERROR GOTO 0Initiates
  11966.  a search of the invocation path for another error-handling routine.
  11967.   RESUME returns to the statement that caused the error or the last call out
  11968.  of the error-handling procedure or module. Use it to repeat an operation
  11969.  after taking corrective action.
  11970.  
  11971.  The  RESUME NEXT statement returns to the statement immediately following
  11972.  the one that caused the error or immediately following the last call out of
  11973.  the error-handling procedure or module. The difference between  RESUME and
  11974.  RESUME NEXT is shown for a module-level error-handling routine in a
  11975.  single-module program in Figure 8.1.
  11976.  
  11977.  Use  RESUME NEXT to ignore the error. For example, this code performs modulo
  11978.  64K arithmetic by ignoring the overflow error.
  11979.  
  11980.  ON ERROR GOTO Handler
  11981.  ' Add two hexadecimal numbers.
  11982.  Result% = &H1234 + &HFFFF
  11983.  PRINT HEX$( Result%)
  11984.  END
  11985.  Handler:
  11986.  ' If an overflow occurs, keep going.
  11987.  RESUME NEXT
  11988.  
  11989.  Sometimes, if an error occurs in a loop, you need to restart an operation
  11990.  rather than continue from where you left off with the  RESUME statement.
  11991.  This would be true if a Disk full error occurs during execution of the
  11992.  following procedure which saves an array called ClientNames$ on a floppy
  11993.  disk.
  11994.  
  11995.  SUB SaveOnDisk (ClientNames$())
  11996.  ON LOCAL ERROR GOTO Handler
  11997.  Restart:
  11998.  OPEN "A:CLIENTS.TXT" FOR OUTPUT AS #1
  11999.  ' Save array of client names to disk.
  12000.  FOR I% = LBOUND(ClientNames$) TO UBOUND(ClientNames$)
  12001.  PRINT #1, ClientNames$(I%)
  12002.  NEXT I%
  12003.  CLOSE #1
  12004.  EXIT SUB
  12005.  Handler:
  12006.  ' Have operator get another disk if this one's full.
  12007.  IF ERR = 61 THEN
  12008.  CLOSE #1
  12009.  KILL "A:CLIENTS.TXT"
  12010.  PRINT "Disk Full.  Insert formatted disk"
  12011.  PRINT "Press any key when ready"
  12012.  Pause$ = INPUT$(1)
  12013.  RESUME Restart
  12014.  ELSE
  12015.  ' Unanticipated error. Simulate the error & look for
  12016.  ' another handler.
  12017.  ERROR ERR
  12018.  END IF
  12019.  
  12020.  END SUB
  12021.  
  12022.  In this case, the  RESUME  line statement was used, where  line is any valid
  12023.  line number (except 0) or label. This allowed the error-handling routine to
  12024.  erase the incomplete file on the full disk, prompt the operator for a new
  12025.  disk and then return to the line labelled Restart to print the file from the
  12026.  beginning.
  12027.  
  12028.  
  12029.  This last example also illustrates how to use  ERROR ERR, the recommended
  12030.  way of handling an unanticipated error. When BASIC executes this statement,
  12031.  it simulates the occurrence of the error again, but this time the error
  12032.  occurs in the error-handling routine itself. In this case, BASIC searches
  12033.  back through the invocation path (if there is one) for another
  12034.  error-handling routine. If one is found, the program continues from that
  12035.  point. Otherwise, the appropriate error message is issued and the program
  12036.  terminates. The "invocation path" is a list of procedures that have been
  12037.  invoked in order to arrive at the program's current location. The invocation
  12038.  path is found on the stack and is also called the execution stack. (See the
  12039.  section "Unanticipated Errors" later in this chapter for details on how
  12040.  BASIC searches back through the invocation path.)
  12041.  
  12042.  Note
  12043.  
  12044.  To maintain compatibility with previous releases, this version of Microsoft
  12045.  BASIC allows you to use  ON  LOCAL  ERROR  GOTO 0 in an error-handling
  12046.  routine to initiate a search of the invocation path. But because this same
  12047.  statement is used outside of error-handling routines to turn off error
  12048.  trapping, your coding will be clearer if you always use  ERROR ERR within an
  12049.  error-handling routine. See "Turning Off Error Trapping" later in this
  12050.  chapter for examples of this other usage.
  12051.  
  12052.  
  12053.  Procedure- Vs. Module-Level Error Handling
  12054.  
  12055.  For many applications, procedure-level error handling is preferred. This is
  12056.  because procedures tend to be organized by task (video display driver, line
  12057.  printer, disk I/O, etc.), and errors are also task-related. Therefore
  12058.  program organization can be simpler and more straightforward when the two
  12059.  are grouped together.
  12060.  
  12061.  Examples
  12062.  
  12063.  This first example outlines how error trapping can be organized for several
  12064.  independent procedures; one for disk I/O, one for computations, and one for
  12065.  operator input. The module-level error-handling routine LastResort is used
  12066.  to take emergency action for an unanticipated error that the procedure-level
  12067.  error-handling routines don't deal with.
  12068.  
  12069.  ' Module-level code.
  12070.  ON ERROR GOTO LastResort
  12071.  ' Main program executes here.
  12072.  .
  12073.  .
  12074.  .
  12075.  END
  12076.  
  12077.  LastResort:
  12078.  ' Module-level error-handling routine
  12079.  ' It makes a last attempt to deal with unanticipated errors trapped in
  12080.  ' procedure-level error-handling routines.
  12081.   .
  12082.  .
  12083.  .
  12084.  END
  12085.  SUB MathProcessor
  12086.  ON LOCAL ERROR GOTO MathHandler
  12087.  ' Calculations are performed in this SUB.
  12088.  .
  12089.  .
  12090.  .
  12091.  EXIT SUB
  12092.  MathHandler:
  12093.  SELECT CASE ERR
  12094.  CASE 11
  12095.  ' Routine to handle divide by zero goes here.
  12096.  .
  12097.  .
  12098.  .
  12099.  RESUME NEXT
  12100.  CASE 6
  12101.  Routine to handle overflows goes here.
  12102.  .
  12103.  .
  12104.  .
  12105.  RESUME NEXT
  12106.  CASE ELSE
  12107.  ' Unanticipated error.
  12108.  ERROR ERR
  12109.  END SELECT
  12110.  END SUB
  12111.  
  12112.  SUB DiskDriver
  12113.  ON LOCAL ERROR GOTO DiskHandler
  12114.  ' Disk read and write performed in this SUB.
  12115.  .
  12116.  .
  12117.  .
  12118.  EXIT SUB
  12119.  DiskHandler:
  12120.  SELECT CASE ERR
  12121.  CASE 72
  12122.   ' Routine to handle damaged disk goes here.
  12123.  .
  12124.  .
  12125.  .
  12126.  RESUME
  12127.  CASE 71
  12128.  ' Routine to handle open drive door goes here.
  12129.  .
  12130.  .
  12131.  .
  12132.  RESUME
  12133.  CASE 57
  12134.  ' Routine to handle internal disk drive problem goes here.
  12135.  .
  12136.  .
  12137.  .
  12138.  RESUME
  12139.  CASE 61
  12140.  ' Routine to handle full disk goes here.
  12141.  .
  12142.  .
  12143.  .
  12144.  RESUME
  12145.  CASE ELSE
  12146.  ' Unanticipated error.
  12147.  ERROR ERR
  12148.  END SELECT
  12149.  END SUB
  12150.  
  12151.  FUNCTION GetFileName
  12152.  ON LOCAL ERROR GOTO FileNameErrorHandler
  12153.  ' Code to get a filename from the operator goes here.
  12154.  .
  12155.  .
  12156.  .
  12157.  EXIT FUNCTION
  12158.  FileNameErrorHandler:
  12159.  SELECT CASE ERR
  12160.  CASE 64
  12161.  ' Routine to handle an illegal filename goes here.
  12162.  .
  12163.  .
  12164.  .
  12165.  RESUME
  12166.  CASE 76
  12167.  ' Routine to handle a non-existent path goes here.
  12168.  .
  12169.  .
  12170.  .
  12171.  RESUME
  12172.  CASE ELSE
  12173.  ' Unanticipated error--try searching invocation path.
  12174.  ERROR ERR
  12175.  END SELECT
  12176.  END FUNCTION
  12177.  
  12178.  If you have several related procedures where the same kinds of errors may
  12179.  occur, it makes more sense to write one error-handling routine and put it at
  12180.  the module level. This is outlined in the following example which sends
  12181.  different kinds of data to the line printer.
  12182.  
  12183.  ON ERROR GOTO HandleItAll
  12184.  DECLARE SUB PrintNumericArray (StudentArray& ())
  12185.  DECLARE SUB PrintStringArray (ClientArray$ ())
  12186.  DIM Students& ( 1 to 100, 1 to 2), Clients$ (1 to 100, 1 to 2)
  12187.  ' Main-module-level code goes here. It acquires data from an operator
  12188.  ' and puts it in the previously dimensioned arrays.
  12189.  .
  12190.  .
  12191.  .
  12192.  CALL PrintNumericArray (Students&() )
  12193.  CALL PrintStringArray (Clients&() )
  12194.  END
  12195.  HandleItAll:
  12196.  ' Branch here to handle an error in either SUB
  12197.  SELECT CASE
  12198.  CASE 25
  12199.  ' Routine for handling printer off or disconnected goes here.
  12200.  .
  12201.  .
  12202.  .
  12203.  RESUME
  12204.  CASE 68
  12205.  ' Routine for handling printer being offline goes here.
  12206.  .
  12207.  .
  12208.  .
  12209.   RESUME
  12210.  CASE 27
  12211.  ' Routine for handling printer out of paper goes here.
  12212.  .
  12213.  .
  12214.  .
  12215.  RESUME
  12216.  CASE ELSE
  12217.  ERROR ERR
  12218.  END SELECT
  12219.  
  12220.  SUB PrintNumericArray (StudentArray& ())
  12221.  ' This prints a 2D numeric array of student numbers and test scores.
  12222.  FOR I% = 1 to 100
  12223.  LPRINT StudentArray&( I%, 1), StudentArray& (I%, 2)
  12224.  NEXT I%
  12225.  END SUB
  12226.  
  12227.  SUB PrintStringArray (ClientArray$ ())
  12228.  ' This prints a 2D string array of client names and zip codes.
  12229.  FOR I% = 1 to 100
  12230.  LPRINT ClientArray$ ( I%, 1), ClienttArray$ (I%, 2)
  12231.  NEXT I%
  12232.  END SUB
  12233.  
  12234.  Error Handling in Multiple Modules
  12235.  
  12236.  Microsoft BASIC allows you to detect and handle errors that occur in
  12237.  multiple-module programs. To see how this works, try tracing through the
  12238.  following code. It begins in the main module where it handles a device I/O
  12239.  error. It then calls the procedure Module2Sub in the second module which
  12240.  calls the Module3Sub procedure in the third module. An Out of memory error
  12241.  occurs in Module3Sub which is handled at the module level. The program
  12242.  returns to Module3Sub which exits back to Module2Sub in the second module.
  12243.  Here a Type mismatch error occurs that is handled at the procedure level.
  12244.  The program then returns to Module2Sub which exits back to the main module
  12245.  and ends.
  12246.  
  12247.  '=========================================================
  12248.  'MODULE #1 (MAIN)
  12249.  '=========================================================
  12250.  ' Identify external procedure
  12251.  DECLARE SUB Module2Sub ()
  12252.  ON ERROR GOTO MainHandler
  12253.  ERROR 57' Simulate occurrence of error 57
  12254.  ' (Device I/O error).
  12255.  CALL Module2Sub
  12256.  PRINT "Back in main-module after handling all errors."
  12257.  END
  12258.  MainHandler:
  12259.  PRINT "Handling error"; ERR; "in main-module-level handler."
  12260.  RESUME NEXT
  12261.  ' =========================================================
  12262.  'MODULE #2
  12263.  ' =========================================================
  12264.  ' Identify external procedure
  12265.  DECLARE SUB Module3Sub ()
  12266.  
  12267.  SUB Module2Sub
  12268.  ON LOCAL ERROR GOTO Module2Handler
  12269.  CALL Module3Sub
  12270.  ERROR 13' Simulate a Type mismatch error.
  12271.  EXIT SUB
  12272.  
  12273.  Module2Handler:
  12274.  PRINT "Handling error"; ERR; "in module 2 procedure-level handler."
  12275.  RESUME NEXT
  12276.  
  12277.  END SUB
  12278.  
  12279.  ' =========================================================
  12280.  'MODULE #3
  12281.  ' =========================================================
  12282.  Module3Handler:
  12283.  PRINT "Handling error"; ERR; "in module 3 module-level handler."
  12284.  RESUME NEXT
  12285.  
  12286.  SUB Module3Sub
  12287.  ON ERROR GOTO Module3Handler
  12288.  ERROR 7' Simulate an Out of memory error.
  12289.  END SUB
  12290.  
  12291.  
  12292.  Output
  12293.  
  12294.  Handling error 57 in main-module-level handler
  12295.  Handling error 7 in module 3 module-level handler
  12296.  Handling error 13 in module 2 procedure-level handler
  12297.  Back in main module after handling all errors.
  12298.  
  12299.  In the preceding example, note the error-handling technique employed in the
  12300.  third module. The  ON ERROR GOTO statement is used in a procedure without
  12301.  the  LOCAL keyword. This is the only way to enable a module-level
  12302.  error-handling routine in a module other than the main module.
  12303.  
  12304.  
  12305.  Unanticipated Errors
  12306.  
  12307.  When an error occurs and there is no error-handling routine within the
  12308.  current scope of the program, BASIC searches the invocation path for an
  12309.  error-handling routine.
  12310.  
  12311.  BASIC follows these steps when an unanticipated error is encountered:
  12312.  
  12313.     1. BASIC searches each frame starting with the most recently invoked and
  12314.        continuing until an error-handling routine is found or the path is
  12315.        exhausted.
  12316.  
  12317.     2. BASIC searches across module boundaries. Before crossing into another
  12318.        module, it searches the module-level code even if that code is not in
  12319.        the invocation path.
  12320.  
  12321.     3. If no error-handling routine is found, the program terminates.
  12322.  
  12323.     4. If an enabled error-handling routine is found, program execution
  12324.        continues in that error-handling routine. If the error-handling
  12325.        routine contains a  RESUME or  RESUME NEXT statement, the program
  12326.        continues as shown in Table 8.2.
  12327.  
  12328.  
  12329.  error-handling
  12330.  routineSingle module programMultiple module program
  12331.  Examples
  12332.  
  12333.  To understand how program flow is affected when unanticipated errors occur,
  12334.  consider the following example which performs this calculation: (3 * 3) + 2.
  12335.  It does this by passing the numbers 2 and 3 to a procedure called Total.
  12336.  This procedure calls Square. This second procedure squares the number 3 and
  12337.  returns to Total with the answer. Total then adds the number 2 to the answer
  12338.  and returns to the module level where the answer is printed.
  12339.  
  12340.  DECLARE FUNCTION Total% (A%, B%)
  12341.  DECLARE FUNCTION Square% (B%)
  12342.  DEFINT A-Z
  12343.  ' Go do some calculations with the numbers 2 and 3.
  12344.  Answer = Total(2, 3)
  12345.  ' Show us the results.
  12346.  PRINT "(3 * 3) + 2 = "; Answer
  12347.  END
  12348.  
  12349.  FUNCTION Square (B)
  12350.  ' Find the square of B.
  12351.  Square = B * B
  12352.  END FUNCTION
  12353.  
  12354.  
  12355.  FUNCTION Total (A, B)
  12356.  ON LOCAL ERROR GOTO Handler
  12357.  ' Go square B, then add A to the result.
  12358.  Result = Square(B)
  12359.  Total = Result + A
  12360.  EXIT FUNCTION
  12361.  Handler:
  12362.  ' Ignore overflow errors.
  12363.  IF ERR = 6 THEN
  12364.  RESUME NEXT
  12365.  ELSE
  12366.  ERROR ERR
  12367.  END IF
  12368.  END FUNCTION
  12369.  
  12370.  If no error occurs in our example we get the expected result:
  12371.  
  12372.  (3 * 3) + 2 = 11
  12373.  
  12374.  But suppose an error occurs in the Square function, as simulated by the
  12375.  following code:
  12376.  
  12377.  FUNCTION Square (B)
  12378.  ' Simulate the occurrence of an overflow error.
  12379.  ERROR 6
  12380.  ' Find the square of 3.
  12381.  Square = B ^ 2
  12382.  END FUNCTION
  12383.  
  12384.  This time when Square is called, an error occurs before the calculation is
  12385.  made. BASIC finds no error-handling routine in this procedure, so it
  12386.  searches back through the invocation path for the closest available enabled
  12387.  error-handling routine. It finds one in Total, so it starts that
  12388.  error-handling routine. This error-handling routine has code for dealing
  12389.  with ERROR 6
  12390.  
  12391.  -- an overflow error. In this case, it does a  RESUME NEXT. Execution then
  12392.  continues in Total at the line following the call to Square, and the program
  12393.  proceeds as before. But when the result is printed, we get:
  12394.  
  12395.  (3 * 3) + 2 = 2
  12396.  
  12397.  This is because BASIC never returned to the Square procedure to make the
  12398.  calculation. This illustrates an important point: when BASIC encounters a
  12399.  RESUME NEXT statement in a procedure-level error-handling routine, it always
  12400.  returns to a statement in that procedure. This can be troublesome if you
  12401.  have a missing error-handling routine. In other words, your program may
  12402.  continue running, but it may not run as intended.
  12403.  
  12404.  To see another example of how BASIC searches for error-handling routines,
  12405.  make the procedure-level error-handling routine in Total into a module-level
  12406.  error-handling routine so that the example looks like this:
  12407.  
  12408.  
  12409.  DECLARE FUNCTION Total% (A%, B%)
  12410.  DECLARE FUNCTION Square% (B%)
  12411.  DEFINT A-Z
  12412.  ON ERROR GOTO MainHandler
  12413.  ' Go do some calculations with the numbers 2 and 3.
  12414.  Answer = Total(2, 3)
  12415.  ' Show us the results.
  12416.  PRINT "(3 * 3) + 2 = "; Answer
  12417.  END
  12418.  MainHandler:
  12419.  ' Ignore overflow errors.
  12420.  IF ERR = 6 THEN
  12421.  RESUME NEXT
  12422.  ELSE
  12423.  ERROR ERR
  12424.  END IF
  12425.  
  12426.  FUNCTION Square (B)
  12427.  ' Simulate the occurrence of an error.
  12428.  ERROR 6
  12429.  ' Find the square of B.
  12430.  Square = B * B
  12431.  END FUNCTION
  12432.  
  12433.  FUNCTION Total (A, B)
  12434.  ' Go square B, then add A to the result.
  12435.  Result = Square(B)
  12436.  Total = Result + A
  12437.  END FUNCTION
  12438.  
  12439.  This time BASIC doesn't find an error-handling routine in the Total
  12440.  procedure so it looks further back and finds one at the module level. When
  12441.  the  RESUME NEXT statement is executed in the MainHandler procedure, the
  12442.  program returns to the Square procedure and begins executing at the Square =
  12443.  B * B statement. Program flow then continues as in the original example that
  12444.  executed without an error. Thus the result this time is correct:
  12445.  
  12446.  (3 * 3) + 2 = 11
  12447.  
  12448.  The reason this last example works is because the error-handling routine was
  12449.  at the module level. This illustrates another important point: in a single
  12450.  module program, whenever BASIC encounters a  RESUME NEXT statement in a
  12451.  module-level error-handling routine, it always returns to the statement
  12452.  directly following the one where the error occurred.
  12453.  
  12454.  To see how unanticipated errors are handled in multiple-module programs,
  12455.  change the preceding example into two modules as follows:
  12456.  
  12457.  
  12458.  '================================================================
  12459.  'MODULE #1
  12460.  '================================================================
  12461.  DEFINT A-Z
  12462.  DECLARE FUNCTION Total (A, B)
  12463.  DECLARE FUNCTION Square (B )
  12464.  ON ERROR GOTO Handler
  12465.  ' Go do some calculations with the numbers 2 and 3.
  12466.  Answer = Total ( 2, 3)
  12467.  ' Show us the results.
  12468.  PRINT "(3 * 3) + 2 = "; Answer
  12469.  END
  12470.  
  12471.  Handler:
  12472.  ' Ignore overflow errors.
  12473.  IF ERR = 6 THEN
  12474.   RESUME NEXT
  12475.  ELSE
  12476.  ERROR ERR
  12477.  END IF
  12478.  
  12479.  '=================================================================
  12480.  'MODULE #2
  12481.  '=================================================================
  12482.  FUNCTION Total (A, B)
  12483.  ' Go square B, then add A to the result.
  12484.  Result = Square( B)
  12485.  Total = Result + A
  12486.  END FUNCTION
  12487.  FUNCTION Square ( B)
  12488.  ' Find the square of B.
  12489.  ERROR 6
  12490.  Square = B * B
  12491.  END FUNCTION
  12492.  
  12493.  Now when BASIC searches for an error-handling routine, it finds one in the
  12494.  first module. The  RESUME NEXT causes the program to return to the line
  12495.  following the last executed line in that module. Therefore program execution
  12496.  continues with the  PRINT statement and the result is:
  12497.  
  12498.  (3 * 3) + 2 = 0
  12499.  
  12500.  To see a final demonstration of program flow after dealing with an
  12501.  unanticipated error, move the module-level error-handling routine to the
  12502.  second module as shown here:
  12503.  
  12504.  
  12505.  '================================================================
  12506.  'MODULE #1
  12507.  '================================================================
  12508.  DEFINT A-Z
  12509.  DECLARE FUNCTION Total (A, B)
  12510.  DECLARE FUNCTION Square (B )
  12511.  ON ERROR GOTO Handler
  12512.  ' Go do some calculations with the numbers 2 and 3.
  12513.  Answer = Total ( 2, 3)
  12514.  ' Show us the results.
  12515.  PRINT "(3 * 3) + 2 = "; Answer
  12516.  END
  12517.  '=================================================================
  12518.  'MODULE #2
  12519.  '=================================================================
  12520.  Handler:
  12521.  ' Ignore overflow errors.
  12522.  IF ERR = 6 THEN
  12523.   RESUME NEXT
  12524.  ELSE
  12525.  ERROR ERR
  12526.  END IF
  12527.  
  12528.  FUNCTION Total (A, B)
  12529.  ' Enable the error handler at the module level
  12530.  ON ERROR GOTO Handler
  12531.  ' Go square B, then add A to the result.
  12532.  Result = Square( B)
  12533.  Total = Result + A
  12534.  END FUNCTION
  12535.  FUNCTION Square ( B)
  12536.  ' Find the square of B.
  12537.  ERROR 6
  12538.  Square = B * B
  12539.  END FUNCTION
  12540.  
  12541.  This time BASIC finds an error-handling routine in the same module as where
  12542.  the error occurred. It therefore returns to the Square procedure and
  12543.  produces the correct answer:
  12544.  
  12545.  (3 * 3) + 2 = 11
  12546.  
  12547.  Guidelines for Complex Programs
  12548.  
  12549.  Because BASIC makes such a thorough attempt to find missing error-handling
  12550.  routines, the following guidelines should be observed when writing complex
  12551.  programs with extensive operator interfaces:
  12552.  
  12553.    ■   Write a "fail safe" error-handling routine for the main module of your
  12554.        program application. This will be executed if a search for an
  12555.        error-handling routine is unsuccessful and winds up back at the main
  12556.        module level. The error-handling routine should make an attempt to
  12557.        save the operator's data before the program terminates.
  12558.  
  12559.    ■   Use procedure-level error-handling routines wherever possible to deal
  12560.        with anticipated errors. Errors caught in a procedure can usually be
  12561.        corrected more easily there.
  12562.  
  12563.    ■   Put an  ERROR ERR statement in all procedure-level error-handling
  12564.        routines and in all module-level error-handling routines outside of
  12565.        the main module in case there is no code in the error-handling routine
  12566.        to deal with a specific error. This lets your program try to correct
  12567.        the error in other error-handling routines along the invocation path.
  12568.  
  12569.    ■   Use the  STOP statement to force termination if you don't want a
  12570.        previous procedure or module to trap the error.
  12571.  
  12572.  
  12573.  
  12574.  Errors Within Error- and Event-Handling Routines
  12575.  
  12576.  An error occurring within an error-handling routine is treated differently
  12577.  depending on whether the error-handling routine is for an error or an event:
  12578.  
  12579.    ■   If an error occurs in an error-handling routine, BASIC begins
  12580.        searching the invocation path using the principles demonstrated in the
  12581.        section "Unanticipated Errors" earlier in this chapter.
  12582.  
  12583.    ■   If an error occurs in an event-handling routine, including any
  12584.        procedure called by the event-handling routine, BASIC also searches
  12585.        the invocation path, but only as far back as the event frame. If it
  12586.        can't find an error-handling routine in that part of the invocation
  12587.        path, or in the module-level code, it terminates.
  12588.  
  12589.  
  12590.  
  12591.  Delayed Error Handling
  12592.  
  12593.  At times you may need to detect errors but not handle them until a certain
  12594.  section of code is finished executing. Do this with the  ON ERROR RESUME
  12595.  NEXT statement which tells BASIC to record the error but not interrupt the
  12596.  program.
  12597.  
  12598.  You then can write a routine for dealing with the errors that can be
  12599.  executed whenever it is convenient. The routine can tell if an error
  12600.  occurred by the value of  ERR. If  ERR is not zero, an error has occurred
  12601.  and the error-handling routine can take action based on the value of  ERR as
  12602.  shown by the following example:
  12603.  
  12604.  CONST False = 0, True = NOT False
  12605.  ' Don't let an error disrupt the program.
  12606.  ON ERROR RESUME NEXT
  12607.  Condition% = False
  12608.  DO UNTIL Condition% = True
  12609.  ' A long calculation loop that exits when the
  12610.  ' variable Condition becomes true.
  12611.  .
  12612.  .
  12613.  .
  12614.  LOOP
  12615.  ' Now see if an error occurred and if so take action.
  12616.  
  12617.  SELECT CASE ERR
  12618.  CASE 0
  12619.  ' No error--Don't send a message. Continue with program.
  12620.  Goto MoreProgram
  12621.  ' Find out which error it is and deal with it.
  12622.  CASE 6
  12623.  PRINT "An overflow has occurred"
  12624.  CASE 11
  12625.  PRINT "You have attempted to divide by zero"
  12626.  END SELECT
  12627.  PRINT "Enter 'I' to ignore, anything else to quit"; S$
  12628.  S$ = input(1)
  12629.  IF UCASE$(S$) <> "I" THEN END
  12630.  
  12631.  MoreProgram:
  12632.  ' Program continues here after checking for errors.
  12633.  .
  12634.  .
  12635.  .
  12636.  END
  12637.  
  12638.  There are two points to remember when doing this kind of error handling:
  12639.  
  12640.    ■   The routine that detects and deals with the error (ERR) is different
  12641.        from the error-handling routines we have been discussing -- it does
  12642.        not use any of the  RESUME statements.
  12643.  
  12644.    ■   The error number contained in  ERR is the number of the most recent
  12645.        error. Any other errors that occurred earlier in the preceding loop
  12646.        are lost.
  12647.  
  12648.  
  12649.  Another reason for using  ON ERROR RESUME NEXT is to tailor the error
  12650.  handling to each statement, or a group of related statements, in your code
  12651.  rather than having a single error-handling routine per procedure. This is
  12652.  outlined by the following example which opens a file, converts it to ASCII
  12653.  text, and sends it to the line printer:
  12654.  
  12655.  SUB ConvertToASCII (Filename$)
  12656.  ON LOCAL ERROR RESUME NEXT
  12657.  ' Try to open the specified file. Correct errors if they occur.
  12658.  OPEN Filename$ FOR INPUT AS #1
  12659.  IF ERR < > 0 THEN
  12660.  OpenErrorHandler:
  12661.  ' Routine to identify and deal with file-open errors.
  12662.  .
  12663.  .
  12664.  .
  12665.  END IF
  12666.  FOR Counter% = 1 to LOF(1)
  12667.  ' Read in a character and correct errors if they occur.
  12668.  S$ = INPUT$(1, #1)
  12669.  IF ERR < > THEN
  12670.  InputError-handling routine:
  12671.  ' Routine to identify and deal with file input errors.
  12672.  .
  12673.  .
  12674.  .
  12675.  END IF
  12676.  ASCII% = ASC( S$) AND &H7F
  12677.  ' Print a character and correct errors if they occur.
  12678.  LPRINT CHR$(Ascii%);
  12679.  IF ERR < > THEN
  12680.   ' Routine to identify and deal with printer errors.
  12681.  .
  12682.  .
  12683.  .
  12684.  NEXT Counter%
  12685.  PrinterErrorHandler:
  12686.  END SUB
  12687.  
  12688.  Turning Off Error Handling
  12689.  
  12690.  To turn off error handling, use the  ON  LOCAL  ERROR GOTO  0 statement.
  12691.  Once BASIC executes this statement, errors are neither trapped nor detected.
  12692.  If the  LOCAL keyword is used, then error handling is turned off only within
  12693.  the procedure where the statement appears. Otherwise error handling is
  12694.  turned off for the current module error-handling routine.
  12695.  
  12696.  Important
  12697.  
  12698.  The only place you cannot turn off error handling is in the error-handling
  12699.  routine itself. If BASIC encounters an  ON ERROR GOTO  0 statement there, it
  12700.  will treat it the same as an  ERROR ERR statement and begin searching back
  12701.  through the invocation path for another error-handling routine.
  12702.  
  12703.  Example
  12704.  
  12705.  The following example turns off error handling in the procedure DemoSub.
  12706.  
  12707.  SUB DemoSub
  12708.  ON ERROR GOTO SubHandler
  12709.  ' Error trapping is enabled.
  12710.  ' Errors need to be caught and corrected here.
  12711.  .
  12712.  .
  12713.  .
  12714.  ON ERROR GOTO 0
  12715.  ' Error trapping is turned off here because it's not needed.
  12716.  .
  12717.  .
  12718.  .
  12719.  ON ERROR GOTO SubHandler
  12720.  ' Error trapping is enabled again.
  12721.  .
  12722.  .
  12723.  .
  12724.  EXIT SUB
  12725.  SubHandler:
  12726.  ' Error-handling routine goes here.
  12727.  .
  12728.  .
  12729.  .
  12730.  RESUME
  12731.  END SUB
  12732.  
  12733.  Additional Applications
  12734.  
  12735.  Besides handling operator errors, you can also use error handling for
  12736.  program control. Although not recommended as a general rule, occasionally it
  12737.  is a convenient method. As an example of this, consider the following code
  12738.  which gets a number from the operator and returns the arc tangent. Because
  12739.  the arc tangent of zero or any multiple of  produces a Division by zero
  12740.  error, an error-handling routine is employed to ignore the result of the
  12741.  computation when the operator enters one of those values.
  12742.  
  12743.  CONST False = 0, True = NOT False
  12744.  INPUT "Enter a number";Number
  12745.  ON ERROR GOTO Handler
  12746.  NoError = True
  12747.   ArcTangent = 1 / (TAN (Number))
  12748.  PRINT "The arctangent of "; Number; "is ";
  12749.  ' If the answer is a real number then print it.
  12750.  IF NoError THEN
  12751.  PRINT ArcTangent
  12752.  ' Otherwise tell the operator the answer is undefined (infinite).
  12753.  ELSE PRINT "undefined"
  12754.  END IF
  12755.  END
  12756.  
  12757.  Handler:
  12758.  NoError = False
  12759.  RESUME NEXT
  12760.  
  12761.  Another use for error handling is to gain information, unavailable with
  12762.  other techniques, about the computer on which your application is running.
  12763.  For example, the following procedure Adapter, tells the operator which
  12764.  display adapter is installed, based on the errors produced by various
  12765.  SCREEN statements.
  12766.  
  12767.  DEFINT A-Z
  12768.  
  12769.  SUB Adapter
  12770.  ON LOCAL ERROR GOTO Handler
  12771.  CONST False = 0, True = NOT FALSE
  12772.  ' Use an array to keep track of our test results.
  12773.  DIM Mode( 1 to 13)
  12774.  
  12775.  ' Try screen modes and see which work.
  12776.  FOR ModeNumber = 1 to 13
  12777.  ' Assume the test works unless you get an error.
  12778.  Mode (ModeNumber) = True
  12779.  SCREEN ModeNumber
  12780.  NEXT ModeNumber
  12781.  
  12782.  ' Reset the screen after testing it.
  12783.  SCREEN 0, 0
  12784.  WIDTH 80
  12785.  
  12786.  ' Using test results figure out which adapter is out there.
  12787.  ' Tell operator which one he has.
  12788.  ' (See tables in SCREEN statement section of BASIC Language Reference
  12789.  ' to understand why this logic works.)
  12790.  PRINT "You have a";
  12791.  IF Mode(13) THEN
  12792.  IF Mode(7) THEN
  12793.  PRINT "VGA";
  12794.  UsableModes$ = "0-2, 7-13."
  12795.  ELSE
  12796.  PRINT "MCGA";
  12797.  UsableModes$ = "0-2, 11 & 13."
  12798.  END IF
  12799.  ELSE
  12800.  IF Mode(7) THEN
  12801.  PRINT "EGA";
  12802.  UsableModes$ = "0-2, 7-10."
  12803.  ELSE
  12804.  IF Mode(3) THEN
  12805.  PRINT "Hercules";
  12806.  UsableModes$ = "3."
  12807.  END IF
  12808.  IF Mode(4) THEN
  12809.  PRINT "Olivetti";
  12810.  UsableModes$ = "4."
  12811.  END IF
  12812.  IF Mode(1) THEN
  12813.  PRINT "CGA";
  12814.  UsableModes$ = "0-2."
  12815.  ELSE
  12816.  PRINT "MDPA";
  12817.  UsableMode$ = "0."
  12818.  END IF
  12819.  END IF
  12820.  END IF
  12821.  PRINT "Graphics card that supports screen mode(s) "; UsableModes$
  12822.  EXIT SUB
  12823.  
  12824.  ' Branch here when test fails and change text result.
  12825.  Handler:
  12826.  Mode (ModeNumber) = False
  12827.  RESUME NEXT
  12828.  END SUB
  12829.  
  12830.  Output with VGA Adapter
  12831.  
  12832.  You have a VGA Graphics card that supports screen modes(s) 0-2, 7-13.
  12833.  
  12834.  Note
  12835.  
  12836.  The list of screen modes produced by the preceding example may be incomplete
  12837.  for some non-IBM VGA and EGA adapters.
  12838.  
  12839.  
  12840.  Trapping User-Defined Errors
  12841.  
  12842.  There are many error codes that are not used by Microsoft BASIC. By using an
  12843.  unused error code in an  ERROR statement, you can let BASIC's error handling
  12844.  logic control program flow for special error conditions you anticipate.
  12845.  
  12846.  Example
  12847.  
  12848.  The following simplified example uses an error-handling routine in a
  12849.  procedure called CertifiedOperator to control the operator's access to a
  12850.  network. The procedure checks to see that the operator's password is
  12851.  "Swordfish" and that the first four numbers of the account number are 1234.
  12852.  If the operator doesn't enter the correct information after three attempts,
  12853.  the procedure returns false and the network connection is not made.
  12854.  
  12855.  'PASSWRD.BAS
  12856.  CONST False = 0, True = NOT False
  12857.  DECLARE FUNCTION CertifiedOperator%
  12858.  
  12859.  IF CertifiedOperator = False THEN
  12860.  PRINT "Connection Refused."
  12861.  END
  12862.  END IF
  12863.  
  12864.  PRINT "Connected to Network."
  12865.  ' Main program continues here.
  12866.  .
  12867.  .
  12868.  .
  12869.  END
  12870.  
  12871.  FUNCTION CertifiedOperator%
  12872.  ON LOCAL ERROR GOTO Handler
  12873.  ' Count the number of times the operator tries to sign on.
  12874.  Attempts% = 0
  12875.  
  12876.  TryAgain:
  12877.  ' Assume the operator has valid credentials
  12878.  CertifiedOperator = True
  12879.  ' Keep track of bad entries
  12880.  Attempts% = Attempts% + 1
  12881.  IF Attempts% > 3 then ERROR 255
  12882.  ' Check out the operator's credentials
  12883.  INPUT "Enter Account Number"; Account$
  12884.  IF LEFT$ (Account$, 4) <> "1234" THEN ERROR 200
  12885.  
  12886.  INPUT "Enter Password"; Password$
  12887.  IF Password$ <> "Swordfish" THEN ERROR 201
  12888.  EXIT SUB
  12889.  
  12890.  Handler:
  12891.  SELECT CASE
  12892.  ' Start over if account number doesn't have "1234" in it.
  12893.  CASE 200
  12894.  PRINT "Illegal account number. Please re-enter both items."
  12895.  RESUME TryAgain
  12896.  ' Start over if the password is wrong.
  12897.  CASE 201
  12898.  PRINT "Wrong password. Please re-enter both items."
  12899.  RESUME TryAgain
  12900.  ' Return false if operator makes too many mistakes.
  12901.  CASE 255
  12902.  CertifiedOperator% = FALSE
  12903.  EXIT SUB
  12904.  END SELECT
  12905.  
  12906.  END SUB
  12907.  
  12908.  Compiling from the Command Line
  12909.  
  12910.  When compiling from the command line, you must use one or both of these
  12911.  error-handling options:
  12912.  
  12913.    ■   Use /E if your code contains:
  12914.  
  12915.    ■    ON  LOCAL  ERROR GOTO
  12916.  
  12917.    ■   RESUME  line
  12918.  
  12919.    ■   Use /X if your code contains:
  12920.  
  12921.    ■    RESUME  0
  12922.  
  12923.    ■    RESUME NEXT
  12924.  
  12925.    ■    ON  LOCAL  ERROR RESUME NEXT
  12926.  
  12927.  
  12928.  Note
  12929.  
  12930.  You will get compilation errors if you do not use /E and /X in the preceding
  12931.  situations.
  12932.  
  12933.  
  12934.  
  12935.   ────────────────────────────────────────────────────────────────────────────
  12936.  
  12937.  Chapter 9:  Event Handling
  12938.  
  12939.  This chapter explains how to detect and respond to events that
  12940.  occur while your program is running. This process is called "event
  12941.  handling." After reading this chapter, you will know how to:
  12942.  
  12943.    ■   Specify an event to trap and enable event handling.
  12944.  
  12945.    ■   Write a routine to process the trapped event.
  12946.  
  12947.    ■   Return control from an event-handling routine.
  12948.  
  12949.    ■   Write a program that traps any keystroke or combination of keystrokes.
  12950.  
  12951.    ■   Trap music events and user-defined events.
  12952.  
  12953.    ■   Trap events in programs composed of more than one module.
  12954.  
  12955.  
  12956.  
  12957.  @AB@%Event Trapping Vs. Polling
  12958.  
  12959.  Many times during program execution, an event occurs which requires the
  12960.  program to suspend normal operation and take some action. The event could be
  12961.  the operator pressing the Ctrl+Break key combination, the arrival of data at
  12962.  the communications port, or the ending of a phrase of music playing in the
  12963.  background of a computer game.
  12964.  
  12965.  There are two ways to detect these events: polling and event trapping. To
  12966.  understand the difference, imagine we are in a loop which will continue
  12967.  forever unless the operator presses Ctrl+C. To detect this with polling, you
  12968.  write code that must be executed repeatedly. In this example, the  INKEY$
  12969.  function is performed every time the loop is executed:
  12970.  
  12971.  DO
  12972.  ' Normal flow of program occurs here.
  12973.  .
  12974.  .
  12975.  .
  12976.  ' Loop until operator presses Ctrl+C (ASCII 03).
  12977.  LOOP UNTIL INKEY$ = CHR$(3)
  12978.  ' Program interrupted by the operator stops here.
  12979.  END
  12980.  
  12981.  Although polling works for the preceding example, it can degrade performance
  12982.  to check for events this way. And if the loop is too big, you might miss
  12983.  events that occur too quickly.
  12984.  
  12985.  A better alternative
  12986.  for many cases is to use event trapping. This scheme allows BASIC to do the
  12987.  detection on an interrupt basis and redirect the program as soon as the
  12988.  interrupt is detected. For the preceding scenario, the event trap would look
  12989.  like the following (the details of this example are described in the next
  12990.  section):
  12991.  
  12992.  
  12993.  ' Define the key depression to look for (Ctrl+C),
  12994.  ' where to go when it's pressed,
  12995.  ' and turn on event trapping.
  12996.  KEY 15, CHR$(4) + CHR$(46)
  12997.  ON KEY (15) GOSUB Handler
  12998.  KEY (15) ON
  12999.  DO
  13000.  ' Normal program flow occurs here.
  13001.  .
  13002.  .
  13003.  .
  13004.  LOOP
  13005.  ' Branch here when Ctrl+C is pressed.
  13006.  Handler:
  13007.  END
  13008.  
  13009.  How  to Trap Events
  13010.  
  13011.    There are three steps necessary for event trapping:
  13012.  
  13013.     1. Set the trap by telling the program where to branch to when a specific
  13014.        event occurs.
  13015.  
  13016.        In the preceding example, this is accomplished by the ON KEY (15)
  13017.        GOSUB statement which directs the program to the label Handler.
  13018.  
  13019.     2. Turn on event trapping for the particular event you want.
  13020.  
  13021.        In the preceding example, the KEY (15) ON does this.
  13022.  
  13023.     3. Write a routine that takes action based on the specific event that has
  13024.        occurred--the event-handling routine.
  13025.  
  13026.        In the preceding example, the action was very simple: the program is
  13027.        terminated with the  END statement. At other times, you may need to go
  13028.        back to the main program. In that case, you put a  RETURN statement at
  13029.        the end of the event-handling routine.
  13030.  
  13031.  If you are trapping a predefined event (such as the pressing of one of the
  13032.  function keys), only the three preceding steps are required. Otherwise, you
  13033.  need another line of code to define the event that is to be trapped. This
  13034.  was done in the preceding example with the  KEY statement which informed
  13035.  BASIC that the key we were looking for was Ctrl+C.Examples of trapping
  13036.  predefined and user-defined events can be found throughout this
  13037.  chapter.
  13038.  
  13039.  
  13040.  Where to Put the Event-Handling Routine
  13041.  
  13042.  The event-handling routine must be in the module-level code. This is the
  13043.  only place where BASIC will look for it. If you accidentally put it in a
  13044.  BASIC procedure, you will get a Label not found error at run time.
  13045.  
  13046.  Usually the event-handling routine goes after the  END statement as in this
  13047.  sample fragment:
  13048.  
  13049.  END
  13050.  SampleHandler:
  13051.  PRINT "You have pressed the F1 Key."
  13052.  PRINT "It caused a branch to the event-handling routine."
  13053.  RETURN
  13054.  
  13055.  The event-handling routine is located here so that it does not get executed
  13056.  during normal program flow. You could also put it above the  END statement
  13057.  and skip around it with the  GOTO statement, but putting the event-handling
  13058.  routine after  END is the better programming practice.
  13059.  
  13060.  
  13061.  Trapping Preassigned Keystrokes
  13062.  
  13063.  To detect any of the following preassigned keystrokes and route program
  13064.  control to a key-press routine, you need both of the following statements in
  13065.  your program:
  13066.  
  13067.   ON KEY( n%)  GOSUB  line
  13068.   KEY( n%)  ON
  13069.  
  13070.  
  13071.  The following two lines cause the program to branch to the KeySub routine
  13072.  each time the F2 function key is pressed:
  13073.  
  13074.  ON KEY(2) GOSUB KeySub
  13075.   KEY(2) ON
  13076.  
  13077.  The following four lines cause the program to branch to the DownKey routine
  13078.  when the Down direction key is pressed and to the UpKey routine when the Up
  13079.  Arrow key is pressed:
  13080.  
  13081.   ON KEY(11) GOSUB UpKey
  13082.   ON KEY(14) GOSUB DownKey
  13083.   KEY(11) ON
  13084.   KEY(14) ON
  13085.   .
  13086.   .
  13087.   .
  13088.  Important
  13089.  
  13090.  For compatibility with previous versions of BASIC, the  ON KEY (n)  GOSUB
  13091.  statement traps all function key depressions whether or not they are
  13092.  shifted. This means that you cannot use event trapping to distinguish
  13093.  between, for example, F1, Ctrl+F1, Alt+F1 and Shift+F1.
  13094.  
  13095.  
  13096.  
  13097.  Trapping User-Defined Keystrokes
  13098.  
  13099.  In addition to providing the preassigned key numbers 1 - 14 (plus 30 and 31
  13100.  with the 101-key keyboard), BASIC allows you to assign the numbers 15 - 25
  13101.  to any of the remaining keys on the keyboard. The key can be any single key
  13102.  such as the lowercase "s," or it can be a key combination, such as Ctrl+Z,
  13103.  as explained in the next two sections.
  13104.  
  13105.  
  13106.  Defining and Trapping a Non-Shifted Key
  13107.  
  13108.  To define and trap a single key, use these three statements:
  13109.  
  13110.   KEY  n% , CHR$(0) + CHR$( code%)
  13111.   ON KEY( n%)  GOSUB  line
  13112.   KEY( n%)  ON
  13113.  
  13114.  Here,  n% is a value from 15 to 25, and  code% is
  13115.  the scan code for that key. (See Appendix A in the  BASIC Language
  13116.  Reference for a listing of keyboard scan codes.) The CHR(0) function,
  13117.  used in the first line, tells BASIC that the trapped key is a single key.
  13118.  
  13119.  The following example causes the program to branch to the TKey routine each
  13120.  time the user presses the lowercase "t":
  13121.  
  13122.  ' Define key 15 (the scan code for "t" is decimal 20):
  13123.   KEY 15, CHR$(0) + CHR$(20)
  13124.  
  13125.   ' Define the trap (where to go when "t" is pressed):
  13126.   ON KEY(15) GOSUB TKey
  13127.   KEY(15) ON' Turn on detection of key 15.
  13128.  
  13129.  PRINT "Press q to end."
  13130.  DO ' Idle loop: wait for user to
  13131.   LOOP UNTIL INKEY$ = "q"' press "q", then exit.
  13132.   END
  13133.  
  13134.   TKey:' Key-handling routine
  13135.   PRINT "Pressed t."
  13136.   RETURN
  13137.  
  13138.  
  13139.  Defining and Trapping a Shifted Key
  13140.  
  13141.  This is how to trap the following key combinations:
  13142.  
  13143.   KEY  n% , CHR$( keyboardflag%) +  CHR$( code%)
  13144.   ON KEY( n%)  GOSUB  line
  13145.   KEY( n%)  ON
  13146.  
  13147.  Here,  n% is a value from 15 to 25,  code% is the
  13148.  scan code for the primary key, and  keyboardflag% is the
  13149.  sum of the individual codes for the special keys pressed.
  13150.  
  13151.  For example, the following statements turn on trapping of Ctrl+S. Note these
  13152.  statements are designed to trap the Ctrl+S (lowercase) and Ctrl+Shift+S
  13153.  (uppercase) key combinations. To trap the uppercase S, your program must
  13154.  recognize capital letters produced by holding down the Shift key, as well as
  13155.  those produced when the Caps Lock key is active, as shown here:
  13156.  
  13157.  ' 31 = scan code for S key
  13158.   ' 4 = code for Ctrl key
  13159.   KEY 15, CHR$(4) + CHR$(31)' Trap Ctrl+S.
  13160.  
  13161.   ' 5 = code for Ctrl key + code for Shift key
  13162.   KEY 16, CHR$(5) + CHR$(31)' Trap Ctrl+Shift+S.
  13163.  
  13164.   ' 68 = code for Ctrl key + code for CAPSLOCK
  13165.   KEY 17, CHR$(68) + CHR$(31)' Trap Ctrl+CAPSLOCK+S.
  13166.  
  13167.   ON KEY (15) GOSUB CtrlSTrap' Tell program where to
  13168.   ON KEY (16) GOSUB CtrlSTrap' branch (note: same
  13169.   ON KEY (17) GOSUB CtrlSTrap' routine for each key).
  13170.  
  13171.   KEY (15) ON' Activate key detection for
  13172.   KEY (16) ON' all three combinations.
  13173.   KEY (17) ON
  13174.   .
  13175.   .
  13176.   .
  13177.  
  13178.  The following statements turn on trapping of Ctrl+Alt+Del:
  13179.  
  13180.   ' 12 = 4 + 8 = (code for Ctrl key) + (code for Alt key)
  13181.   ' 83 = scan code for Del key
  13182.   KEY 20, CHR$(12) + CHR$(83)
  13183.   ON KEY(20) GOSUB KeyHandler
  13184.   KEY(20) ON
  13185.   .
  13186.   .
  13187.   .
  13188.  
  13189.  Note in the preceding example that the BASIC event trap overrides the normal
  13190.  effect of Ctrl+Alt+Del (system reset). Using this trap in your program is a
  13191.  handy way to prevent the user from accidentally rebooting while a program is
  13192.  running.
  13193.  
  13194.  If you use a 101-key keyboard, you can trap any of the keys on the dedicated
  13195.  keypad by assigning the string as to any of the  n% values from 15 to 25:
  13196.  
  13197.   CHR$( 128)  + CHR$( code%)Example
  13198.  
  13199.  The following example shows how to trap the Left Arrow keys on the dedicated
  13200.  cursor keypad and the numeric keypad.
  13201.  
  13202.   ' 128 = keyboard flag for keys on the
  13203.   ' dedicated cursor keypad
  13204.   ' 75 = scan code for Left Arrow key
  13205.  
  13206.   KEY 15, CHR$(128) + CHR$(75)' Trap Left key on
  13207.   ON KEY(15) GOSUB CursorPad' the dedicated
  13208.   KEY(15) ON ' cursor keypad.
  13209.  
  13210.   ON KEY(12) GOSUB NumericPad' Trap Left key on
  13211.   KEY(12) ON ' the numeric keypad.
  13212.  
  13213.   DO: LOOP UNTIL INKEY$ = "q"' Start idle loop.
  13214.   END
  13215.  
  13216.   CursorPad:
  13217.   PRINT "Pressed Left key on cursor keypad."
  13218.   RETURN
  13219.  
  13220.   NumericPad:
  13221.   PRINT "Pressed Left key on numeric keypad."
  13222.   RETURN
  13223.  
  13224.  
  13225.  Trapping Music Events
  13226.  
  13227.  When you use the  PLAY statement to play music, you can choose whether the
  13228.  music plays in the foreground or in the background. If you choose foreground
  13229.  music (which is the default) nothing else can happen until the music
  13230.  finishes playing. However, if you use the  MB (Music Background) option in a
  13231.   PLAY music string, the tune plays in the background while subsequent
  13232.  statements in your program continue executing.
  13233.  
  13234.  The  PLAY statement plays music in the background by feeding up to 32 notes
  13235.  at a time into a buffer, then playing the notes in the buffer while the
  13236.  program does other things. A "music trap" works by checking the number of
  13237.  notes currently left to be played in the buffer. As soon as this number
  13238.  drops below the limit you set in the trap, the program branches to the first
  13239.  line of the specified routine.
  13240.  
  13241.  To set a music trap in your program, you need the following statements:
  13242.  
  13243.    ON PLAY( queuelimit% ) GOSUB  line
  13244.    PLAY ON
  13245.    PLAY "MB"
  13246.    PLAY  commandstring$
  13247.  .
  13248.  .
  13249.  .
  13250.  
  13251.  Here,  queuelimit% is a number between 1 and 32. For example, this fragment
  13252.  causes the program to branch to the MusicTrap routine whenever the number of
  13253.  notes remaining to be played in the music buffer goes from eight to seven:
  13254.  
  13255.   ON PLAY(8) GOSUB MusicTrap
  13256.   PLAY ON
  13257.   .
  13258.   .
  13259.   .
  13260.   PLAY "MB"' Play subsequent notes in the background.
  13261.   PLAY "o1 A# B# C-"
  13262.   .
  13263.   .
  13264.   .
  13265.   MusicTrap:
  13266.   .' Routine to play addition notes in the background.
  13267.  .
  13268.   .
  13269.   RETURN
  13270.  
  13271.  
  13272.  Important
  13273.  
  13274.  A music trap is triggered only when the number of notes goes from
  13275.  queuelimit% to  queuelimit -\~1 . For example, if the music buffer in the
  13276.  preceding example never contained more than seven notes, the trap would
  13277.  never occur. In the example, the trap happens only when the number of notes
  13278.  drops from eight to seven.
  13279.  
  13280.  
  13281.  Example
  13282.  
  13283.  You can use a music-trap routine to play the same piece of music repeatedly
  13284.  while your program executes, as shown in the following example:
  13285.  
  13286.   ' Turn on trapping of background music events:
  13287.   PLAY ON
  13288.  
  13289.   ' Branch to the Refresh subroutine when there are fewer than
  13290.   ' two notes in the background music buffer:
  13291.   ON PLAY(2) GOSUB Refresh
  13292.  
  13293.   PRINT "Press any key to start, q to end."
  13294.   Pause$ = INPUT$(1)
  13295.  
  13296.   ' Select the background music option for PLAY:
  13297.   PLAY "MB"
  13298.  
  13299.   ' Start playing the music, so notes will be put in the
  13300.   ' background music buffer:
  13301.   GOSUB Refresh
  13302.  
  13303.   I = 0
  13304.  
  13305.   DO
  13306.  
  13307.   ' Print the numbers from 0 to 10,000 over and over until
  13308.   ' the user presses the "q" key. While this is happening,
  13309.   ' the music will repeat in the background:
  13310.   PRINT I
  13311.   I = (I + 1) MOD 10001
  13312.   LOOP UNTIL INKEY$ = "q"
  13313.  
  13314.   END
  13315.  
  13316.   Refresh:
  13317.  
  13318.   ' Plays the opening motive of
  13319.   ' Beethoven's Fifth Symphony:
  13320.   Listen$ = "t180 o2 p2 p8 L8 GGG L2 E-"
  13321.   Fate$ = "p24 p8 L8 FFF L2 D"
  13322.   PLAY Listen$ + Fate$
  13323.   RETURN
  13324.  
  13325.  
  13326.  Trapping a User-Defined Event
  13327.  
  13328.  This section uses assembly language examples and calls to the DOS operating
  13329.  system. You may want to skip it if you are unfamiliar with these items.
  13330.  
  13331.  Trapping a user-defined event involves writing a non-BASIC routine, such as
  13332.  in Microsoft Macro Assembler (MASM) or C, to define the event and inform
  13333.  BASIC when the event occurs. Once this is done, and the routine is
  13334.  installed, program flow continues much as in the preceding examples but uses
  13335.  these statements instead:
  13336.  
  13337.   ON UEVENT GOSUB  line
  13338.   UEVENT ONExample
  13339.  
  13340.  As an example of trapping a user-defined event, suppose you have a special
  13341.  task that needs to be done every 4.5 seconds. The following code
  13342.  accomplishes this, using the system timer chip which provides an interrupt
  13343.  to the CPU 18.2 times per second. The interrupt is DOS number 1CH. The
  13344.  address where DOS expects to find the far pointer to the interrupt service
  13345.  routine is 0:70H.
  13346.  
  13347.  The code requires three MASM routines. The first one, SetInt, informs DOS
  13348.  that a new interrupt service routine ErrorHandler is to be executed whenever
  13349.  interrupt 1CH occurs.
  13350.  
  13351.  The second routine, EventHandler, is called 18.2 times per second. It
  13352.  increments a counter. After 82 interrupts (4.5 seconds) it calls the
  13353.  SetUevent routine which is loaded from the BASIC main library during
  13354.  compilation. The routine sets a flag indicating that a user event has
  13355.  occurred. When BASIC encounters the flag, it causes the BASIC program to
  13356.  branch to the SpecialTask routine indicated in the  ON UEVENT GOSUB
  13357.  statement.
  13358.  
  13359.  The third routine, RestInt, restores the original service routine for the
  13360.  interrupt when the BASIC program terminates.
  13361.  
  13362.  .model  medium, basic ; Stay compatible
  13363.  .data; with BASIC.
  13364.  .code
  13365.  SetIntprocuses ds ; Get old interrupt vector
  13366.  mov  ax, 351CH      ; and save it.
  13367.  int  21h
  13368.  mov  word ptr cs:OldVector, bx
  13369.  mov  word ptr cs:OldVector + 2, es
  13370.  
  13371.  push  cs; Set the new
  13372.  pop  ds; interrupt vector
  13373.  lea  dx, Eventhandler  ; to the address
  13374.  mov  ax, 251CH ; of our service
  13375.  int  21H; routine.
  13376.  ret
  13377.  SetIntendp
  13378.  
  13379.  public  EventHandler  ;
  13380.  Make the following routine public
  13381.  
  13382.  EventHandler  proc; for debugging.
  13383.  extrn  SetUevent: proc; Define BASIC library routine.
  13384.  push  bx
  13385.  lea  bx, cs:TimerTicks; See if 4.5 secs have passed.
  13386.  inc  byte ptr cs:[bx]
  13387.  cmp  byte ptr cs:[bx], 82
  13388.  jnz  Continue
  13389.  mov  byte ptr cs:[bx], 0; if true, reset counter,
  13390.  pushax  ; save registers, and
  13391.  pushcx  ; have BASIC
  13392.  pushdx  ; set the user
  13393.  pushes  ; event flag.
  13394.  callSetUevent
  13395.  pop  es
  13396.  pop  dx; Restore registers.
  13397.  pop  cx
  13398.  pop  ax
  13399.  Continue:
  13400.  pop  bx
  13401.  jmp  cs:OldVector; Continue on with the
  13402.  ; old service routine.
  13403.  
  13404.  TimerTicks  db  0 ; Keep data in code segment
  13405.  OldVector  dd  0; where it can be found no
  13406.  ; matter where in memory the
  13407.  EventHandler endp; interrupt occurs.
  13408.  
  13409.  RestIntprocuses ds; Restore the old
  13410.  lds  dx, cs:OldVector; interrupt vector
  13411.  movx, 251CH ; so things will
  13412.  int  21h; keep working when
  13413.  ret  ; this BASIC program is
  13414.  RestIntendp; finished.
  13415.  end
  13416.  
  13417.  The BASIC program shown here provides an outline of how our special task is
  13418.  performed using the  UEVENT statement. The program first installs the
  13419.  interrupt, sets the path to the BASIC event-handling routine and then
  13420.  enables event trapping.
  13421.  
  13422.  ' Declare external MASM procedures.
  13423.  DECLARE SUB SetInt
  13424.  DECLARE SUB RestInt
  13425.  ' Install new interrupt
  13426.  service routine.
  13427.  CALL SetInt
  13428.  
  13429.  ' Set up the BASIC event handler.
  13430.  ON UEVENT GOSUB SpecialTask
  13431.  UEVENT ON
  13432.  
  13433.  DO
  13434.  ' Normal program operation occurs here.
  13435.  ' Program ends when any key is pressed.
  13436.  LOOP UNTIL INKEY$ <> ""
  13437.  
  13438.  ' Restore old interrupt service routine before quitting.
  13439.  CALL RestInt
  13440.  
  13441.  END
  13442.  
  13443.  ' Program branches here every 4.5 seconds.
  13444.  SpecialTask:
  13445.  ' Code for performing the special task goes here, for example:
  13446.  PRINT "Arrived here after 4.5 seconds."
  13447.  RETURN
  13448.  
  13449.  
  13450.  Generating Smaller, Faster Code
  13451.  
  13452.  Event trapping adds execution time and code length to a BASIC program. To
  13453.  make your programs smaller and faster, you can turn off event trapping in
  13454.  sections where it is unnecessary.  Do this with the  EVENT OFF statement, as
  13455.  shown in the following example. When  EVENT OFF is encountered by the
  13456.  compiler, it stops generating code to trap events, but the events will still
  13457.  be detected. When the compiler encounters the  EVENT ON statement, code is
  13458.  inserted to re-enable the traps. Any previously detected event, and any
  13459.  newly occurring event, will cause the program to branch to the appropriate
  13460.  event-handling routine.
  13461.  
  13462.  ON KEY (1) GOTO Handler1
  13463.  ON KEY (2) GOTO Handler2
  13464.  KEY (1) ON
  13465.  KEY (2) ON
  13466.  ' All events are trapped here.
  13467.  .
  13468.  .
  13469.  .
  13470.  EVENT OFF
  13471.  ' Events are still detected but no longer trapped.
  13472.  ' Code generated for these statements is smaller and faster.
  13473.  .
  13474.  .
  13475.  .
  13476.  EVENT ON
  13477.  ' Events are trapped again including previously detected ones.
  13478.  .
  13479.  .
  13480.  .
  13481.  END
  13482.  Handler1:
  13483.  ' F1 key event-handling routine goes here.
  13484.  RETURN
  13485.  Handler2:
  13486.  ' F2 key event-handling routine goes here.
  13487.  RETURN
  13488.  
  13489.  
  13490.  Turning Off and Suspending Specific Event Traps
  13491.  
  13492.  If you want to selectively turn off certain event traps and leave others on,
  13493.  use the  event  OFF statement. The  event occurring after an  event  OFF
  13494.  statement has been executed is then ignored. This is shown by the following
  13495.  example where the F1 key trap is turned off, but the trap for F2 is left on:
  13496.  
  13497.  ON KEY (1) GOTO Handler1
  13498.  ON KEY (2) GOTO Handler2
  13499.  KEY (1) ON
  13500.  KEY (2) ON
  13501.  ' Both key traps are turned on here.
  13502.  .
  13503.  .
  13504.  .
  13505.  KEY (1) OFF
  13506.  ' The F1 trap is turned off and ignored here, but we still trap F2.
  13507.  .
  13508.  .
  13509.  .
  13510.  
  13511.  KEY (1) ON
  13512.  ' Now we can trap them both again.
  13513.  .
  13514.  .
  13515.  .
  13516.  END
  13517.  Handler1:
  13518.  ' F1 key event-handling routine goes here.
  13519.  RETURN
  13520.  Handler2:
  13521.  ' F2 key event-handling routine goes here.
  13522.  RETURN
  13523.  
  13524.  Sometimes you need to suspend event trapping, without turning it off. This
  13525.  allows you to record events that occur and take action on them after a
  13526.  specific time period has elapsed.
  13527.  
  13528.  To suspend event trapping, use the  event  STOP statement as demonstrated in
  13529.  the next example. In the following example, after the  event  STOP statement
  13530.  is encountered, if the timer event occurs, there is no branch to the
  13531.  event-handling routine. However, the program remembers that the event
  13532.  occurred, and as soon as trapping is turned back on with  event  ON, it
  13533.  immediately branches to the ShowTime routine.
  13534.  
  13535.   ' Once every minute (60 seconds),
  13536.   ' branch to the ShowTime routine:
  13537.   ON TIMER(60) GOSUB ShowTime
  13538.  
  13539.   ' Activate trapping of the 60-second event:
  13540.   TIMER ON
  13541.   .
  13542.   .
  13543.   .
  13544.   TIMER STOP' Suspend trapping.
  13545.   ' A sequence of lines you don't want interrupted,
  13546.  ' even if 60 or more seconds elapse.
  13547.   .
  13548.  .
  13549.  .
  13550.  TIMER ON' Reactivate trapping.
  13551.   ' If a timer event occurs above, it will now
  13552.  ' be handled by the ShowTime routine.
  13553.   .
  13554.  .
  13555.  .
  13556.   END
  13557.  
  13558.  
  13559.  ShowTime:
  13560.  
  13561.   ' Get the current row and column position of the cursor,
  13562.   ' and store them in the variables Row and Column:
  13563.   Row    = CSRLIN
  13564.   Column = POS(0)
  13565.  
  13566.   ' Go to the 24th row, 20th column, and print the time:
  13567.   LOCATE 24, 20
  13568.   PRINT TIME$
  13569.  
  13570.   ' Restore the cursor to its former position
  13571.   ' and return to the main program:
  13572.   LOCATE Row, Column
  13573.   RETURN
  13574.  
  13575.  Events Occurring Within Event-Handling Routines
  13576.  
  13577.  If an event occurs during an event-handling routine, and the event trap is
  13578.  on for the new event, then the program branches to the event-handling
  13579.  routine for this new event. For instance, in the first example in the
  13580.  preceding section, if the F2 key is pressed while Handler1 is executing, the
  13581.  program branches to Handler2. When Handler2 is finished, the program returns
  13582.  to Handler1 and then goes back to the main program.
  13583.  
  13584.  The only time that branching doesn't occur in a event-handling routine is if
  13585.  both events are the same. In this case branching cannot occur because
  13586.  event-handling routines execute an implicit  event  STOP statement for a
  13587.  given event whenever program control is in the routine. This is followed by
  13588.  an implicit  event  ON for that event when program control returns from the
  13589.  routine.
  13590.  
  13591.  For example, if a key-handling routine is processing a keystroke, trapping
  13592.  the same key is suspended until the previous keystroke is completely
  13593.  processed by the routine. If the user presses the same key during this time,
  13594.  this new keystroke is remembered and trapped after control returns from the
  13595.  key-handling routine.
  13596.  
  13597.  
  13598.  Event Trapping Across Modules
  13599.  
  13600.  Events whose traps are turned on in one module are detected and trapped in
  13601.  any module that is running. This is demonstrated in the following program
  13602.  where a trap set for the F1 function key in the main module is triggered
  13603.  even when program control is in the other module.
  13604.  
  13605.  ' =========================================================
  13606.   'MODULE
  13607.   ' =========================================================
  13608.   ON KEY (1) GOSUB GotF1Key
  13609.   KEY (1) ON
  13610.   PRINT "In main module. Press c to continue."
  13611.  
  13612.  DO: LOOP UNTIL INKEY$ = "c"
  13613.  CALL SubKey
  13614.  
  13615.   PRINT "Back in main module. Press q to end."
  13616.   DO : LOOP UNTIL INKEY$ = "q"
  13617.   END
  13618.  
  13619.   GotF1Key:
  13620.   PRINT "Handled F1 keystroke in main module."
  13621.   RETURN
  13622.  
  13623.   ' =========================================================
  13624.   'SUBKEY MODULE
  13625.   ' =========================================================
  13626.   SUB SubKey STATIC
  13627.   PRINT "In module with SUBKEY. Press r to return."
  13628.  
  13629.   ' Pressing F1 here still invokes the GotF1Key
  13630.   ' subroutine in the MAIN module:
  13631.   DO : LOOP UNTIL INKEY$ = "r"
  13632.   END SUB
  13633.  
  13634.  Output
  13635.  
  13636.   In main module. Press c to continue.
  13637.   Handled F1 keystroke in main module.
  13638.   In module with SUBKEY. Press r to return.
  13639.   Handled F1 keystroke in main module.
  13640.   Back in main module. Press q to end.
  13641.   Handled F1 keystroke in main module.
  13642.  
  13643.  
  13644.  Compiling Programs From the Command Line
  13645.  
  13646.  When compiling code containing any of these statements from the command line
  13647.  you must use one of the compiler options described in Table 8.1.
  13648.  
  13649.   ON  event  GOSUB
  13650.   event  ON
  13651.   EVENT ON
  13652.  
  13653.  
  13654.  The /V option detects events sooner, however the program will run slower and
  13655.  take up more memory. As an alternative, you can add labels to your program
  13656.  at the places where you need detection and compile with the /W option as
  13657.  shown in this example:
  13658.  
  13659.  ON KEY (1) GOSUB F1Handler
  13660.  KEY (1) ON
  13661.  .
  13662.  .
  13663.  .
  13664.  ' Check for an event here.
  13665.  Checkpoint1:
  13666.  ' Continue processing without checking.
  13667.  .
  13668.  .
  13669.  .
  13670.  DO UNTIL Condition%
  13671.  ' Check for event every time we loop.
  13672.  Checkpoint2:
  13673.  .
  13674.  .
  13675.  .
  13676.  LOOP
  13677.  ' No more event checking.
  13678.  .
  13679.  .
  13680.  .
  13681.  END
  13682.  F1Handler:
  13683.  ' Code to take action when F1 is pressed goes here.
  13684.  RETURN
  13685.  
  13686.   ────────────────────────────────────────────────────────────────────────────
  13687.  Chapter 10:  Database Programming with ISAM
  13688.  ────────────────────────────────────────────────────────────────────────────
  13689.  
  13690.  Microsoft BASIC gives you the power and flexibility of Indexed Sequential
  13691.  Access Method (ISAM) through a group of straightforward statements and
  13692.  functions that are part of the BASIC language. ISAM statements and functions
  13693.  provide an efficient and simple method for quickly accessing specific
  13694.  records in large and complex data files. This chapter describes ISAM, its
  13695.  statements and functions, and how to use them in programs that access and
  13696.  manipulate the records in ISAM database files. These statements and
  13697.  functions make it easy for your programs to manage database files as large
  13698.  as 128 megabytes.
  13699.  
  13700.  When you finish this chapter, you'll understand:
  13701.  
  13702.    ■   What ISAM is, and when and why it is useful.
  13703.  
  13704.    ■   The new and modified BASIC statements used for ISAM file access and
  13705.        manipulation.
  13706.  
  13707.    ■   A general approach to creating, accessing, and manipulating records in
  13708.        ISAM databases.
  13709.  
  13710.    ■   The structure of an ISAM file.
  13711.  
  13712.    ■   Using indexes to work with  data records as though they were sorted in
  13713.        many ways.
  13714.  
  13715.    ■   Using EMS (expanded memory) with ISAM programs.
  13716.  
  13717.    ■   Using transaction statements in applications with complex block
  13718.        processing requirements.
  13719.  
  13720.    ■   Converting existing database code to ISAM code.
  13721.  
  13722.    ■   Using ISAM utilities to convert your sequential and database files to
  13723.        ISAM format, to compact ISAM databases, repair damaged databases, and
  13724.        exchange tables between database files and sequential files.
  13725.  
  13726.  
  13727.  Note
  13728.  
  13729.  ISAM is supported only in MS-DOS. You cannot use it in OS/2 programs.
  13730.  
  13731.  
  13732.  What Is ISAM?
  13733.  
  13734.  When a program uses or modifies records stored in a file, it often has to
  13735.  sort and re-sort the records in various ways. When a file contains many
  13736.  complex records, sorting can require substantial program code and a great
  13737.  deal of processing time. ISAM is an approach to creating and maintaining a
  13738.  special data file, in which the way records typically need to be sorted can
  13739.  be easily defined and efficiently stored along with the records themselves.
  13740.  This means your program doesn't have to re-sort the records each time the
  13741.  file is used or each time you want a different perspective on the records.
  13742.  
  13743.  In addition to your data records, an ISAM file contains information that
  13744.  describes and facilitates access to each data record. Much of this
  13745.  information is maintained in "tables" and "indexes." Tables serve many
  13746.  purposes, including allowing quick access to any of the values of a specific
  13747.  data record. Indexes represent various ways of ordering the presentation of
  13748.  records in a table, and they permit you to easily access a whole record by
  13749.  the value of a field in the record.
  13750.  
  13751.  ISAM statements and functions manipulate, present, and manage the records in
  13752.  ISAM data files. ISAM's record-searching and ordering algorithms are faster
  13753.  and more efficient than routines that you might create in BASIC to perform
  13754.  these tasks, so it not only saves you significant programming effort, but
  13755.  improves the speed and capacity of many database programs as well.
  13756.  
  13757.  For database applications, ISAM files are more convenient and efficient than
  13758.  random-access files because they allow you to access the file as though the
  13759.  records were ordered in a variety of different ways. The next several
  13760.  sections compare ISAM to other types of files and introduce some concepts
  13761.  and terms that are helpful in understanding ISAM. (Traditional sequential
  13762.  and random-access files are discussed in Chapter 3, "File and Device I/O.")
  13763.  
  13764.  
  13765.  ISAM Statements and Procedures
  13766.  
  13767.  The statements for performing ISAM file tasks are integrated into the BASIC
  13768.  language. In most cases, new statements have been added for ISAM. In a few
  13769.  cases, existing statements have simply been expanded. The following list
  13770.  categorizes the ISAM statements by the type of task for which you use them:
  13771.  
  13772. ╓┌─────────────────────────────────────┌─────────────────────────────────────╖
  13773.  Task                                  Statements
  13774.  ────────────────────────────────────────────────────────────────────────────
  13775.  File and table creation/access         OPEN,  CLOSE,  DELETETABLE,  TYPE...
  13776.                                        END TYPE
  13777.  
  13778.  Task                                  Statements
  13779.  ────────────────────────────────────────────────────────────────────────────
  13780. 
  13781.  Controlling presentation order of      CREATEINDEX,  GETINDEX$,  SETINDEX,
  13782.  data (indexing)                       DELETEINDEX
  13783.  
  13784.  Position change relative to the        MOVEFIRST,  MOVELAST,  MOVENEXT,
  13785.  current record                        MOVEPREVIOUS, TEXTCOMP
  13786.  
  13787.  Position change  by field value        SEEKGT,  SEEKGE,  SEEKEQ
  13788.  
  13789.  Table information                      BOF,  EOF, LOF, FILEATTR
  13790.  
  13791.  Data exchange                          INSERT,  RETRIEVE,  UPDATE,  DELETE
  13792.  
  13793.  Transaction processing                BeginTrans, Committrans, CheckPoint,
  13794.                                        RollBack, SavePoint
  13795.  
  13796.  
  13797.  
  13798.  
  13799.  
  13800.  
  13801.  
  13802.  Some ISAM statement usage rules parallel BASIC rules, while others are more
  13803.  specific due to the characteristics of the ISAM file. For example, the BASIC
  13804.   LOF function, which returns the length of a sequential file or the number
  13805.  of records in a random-access file, returns the number of records in the
  13806.  specified table when used on an ISAM file.
  13807.  
  13808.  
  13809.  The  TYPE... END TYPE statement is used to define the structure of the
  13810.  record variables that will be used to exchange data between your program and
  13811.  the ISAM file. The elements in a  TYPE... END TYPE statement can have any
  13812.  user-defined type or BASIC data type except variable-length strings and
  13813.  dynamic arrays. However, the  TYPE... END TYPE statement used for ISAM
  13814.  access cannot contain BASIC's  SINGLE type. Floating-point numeric elements
  13815.  in a  TYPE... END TYPE statement used for ISAM access must have  DOUBLE
  13816.  type. Fixed-point decimal numeric elements can have BASIC's new currency
  13817.  type.
  13818.  
  13819.  Similarly, when you name the elements of the user-defined type, you must
  13820.  name them according to the ISAM naming convention (which is a subset of the
  13821.  BASIC identifier-naming convention). The name of the user-defined type
  13822.  itself, however, is a BASIC identifier and follows the BASIC naming
  13823.  convention. Similarly, some arguments to ISAM statements follow BASIC naming
  13824.  conventions, while others follow the ISAM subset (described in the section
  13825.  "ISAM Naming Convention" later in this chapter).
  13826.  
  13827.  Note
  13828.  
  13829.  The ISAM statements and functions are integrated into the BASIC language.
  13830.  However, within  the QBX environment the ISAM statements are recognized, but
  13831.  cannot be executed unless you invoke a terminate-and-stay-resident (TSR)
  13832.  program before starting QBX. Using a TSR allows QBX to provide full ISAM
  13833.  support, but only when your programs need it. For programs that don't use
  13834.  ISAM, not loading (or unloading) the TSR saves substantial memory.
  13835.  
  13836.  
  13837.  ISAM Vs. Other Types of File Access
  13838.  
  13839.  ISAM files are often used in place of random-access files because ISAM
  13840.  provides more flexible access to any arbitrary record within the database.
  13841.  Although it is not an ASCII text file, an ISAM file is a sequential-access
  13842.  file. When an ISAM file is opened, BASIC uses ISAM routines that handle all
  13843.  interaction between the operating system and the actual file. ISAM organizes
  13844.  your data records into a structure called a table. You can think of this
  13845.  table as a series of horizontal "rows," each row corresponding to a full
  13846.  data record. The table is also divided into vertical "columns," each column
  13847.  corresponding to one of the fields in your data records.
  13848.  
  13849.  With random-access files you use the  GET statement to access a record by
  13850.  its record number (which represents the order in which the record was
  13851.  inserted into the file). Random access does not provide access to records
  13852.  based on the values in specific fields within the record. When you access an
  13853.  ISAM file, ISAM's indexing and  SEEK operand statements allow you to test
  13854.  the values in a specified group of "vertical" fields (columns) against a
  13855.  stated condition. Put another way, a random-access file is accessible by row
  13856.  number only, like a list. With an ISAM file you can access records either by
  13857.  relative position (row) or by the contents of any field in a specified
  13858.  column. The ISAM statements give you the ability to access specific records
  13859.  in the file with the speed of a random-access file, but with a great deal
  13860.  more flexibility.
  13861.  
  13862.  
  13863.  The following list contrasts the unit of access used by BASIC file types:
  13864.  
  13865. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  13866.  File type                               Access unit
  13867.  ────────────────────────────────────────────────────────────────────────────
  13868.  Binary                                  By byte
  13869.  
  13870.  Sequential                              By line or by byte
  13871.  
  13872.  Random                                  By record number (i.e., row only)
  13873.  
  13874.  ISAM                                    By position, or by the value of
  13875.                                          any field (or group of fields)
  13876.                                          within a table
  13877.  
  13878.  
  13879.  
  13880.  
  13881.  
  13882.  ISAM also differs from other record-indexing approaches because all the
  13883.  information relating to the records is contained in a single file that also
  13884.  contains the data records themselves. This can greatly facilitate user
  13885.  management of complicated databases without sacrificing speed and
  13886.  convenience.
  13887.  
  13888.  
  13889.  The ISAM Programming Model
  13890.  
  13891.  The following chart describes the general sequence of steps used in ISAM
  13892.  database programming. It compares the ISAM model and statements to the
  13893.  corresponding tasks and statements used with random-file access.
  13894.  
  13895. ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
  13896.  Task to be performed     ISAM approach            Random-file approach
  13897.  ────────────────────────────────────────────────────────────────────────────
  13898.  Associate program        Use  TYPE... END  TYPE,  Use  TYPE... END  TYPE,
  13899.  variables with database  and  DIM.                and  DIM.
  13900.  records.
  13901.  
  13902.  Access records.          Use  OPEN to access a    Use  OPEN to access a
  13903.                           table.                   file..
  13904.  Task to be performed     ISAM approach            Random-file approach
  13905.  ────────────────────────────────────────────────────────────────────────────
  13906.                          table.                   file..
  13907.  
  13908.  Change presentation      Use  CREATEINDEX and/or  No support provided.
  13909.  order of records          SETINDEX.               Requires sorting code,
  13910.  according to value in a                           unless record-insertion
  13911.  specified field.                                  order is adequate.
  13912.  
  13913.  Specify record to work   Use  MOVE dest to move   Use  GET to retrieve a
  13914.  with.                    by row or  SEEK operand  record by record number,
  13915.                           to move to a record      or if that is not
  13916.                           containing a specified   adequate, requires
  13917.                           field value.             searching code to
  13918.                                                    determine which record
  13919.                                                    to  GET.
  13920.  
  13921.  
  13922.  
  13923.  
  13924.  
  13925.  
  13926. ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
  13927.  ────────────────────────────────────────────────────────────────────────────
  13928.  Data exchange.           Use  RETRIEVE to assign  Use  GET and  PUT for
  13929.                           a record from a table    simple fetching and
  13930.                           to program variables.    overwriting of existing
  13931.                           Use  UPDATE to assign    records. To delete a
  13932.                           program variables to a   record, you typically
  13933.                           record in a table. Use   code to mark it for
  13934.                           INSERT to insert a       deletion; write a
  13935.                           record in a table. Use   temporary file that
  13936.                           DELETE to delete a       omits it; then delete
  13937.                           record from a table.     the original file; and
  13938.                           When you overwrite,      finally, rename the
  13939.                           insert, or delete        temporary file with the
  13940.                           records, ISAM handles    original filename. To do
  13941.                           all table and index      a simple insert of a
  13942.                           maintenance              record, you must code to
  13943.                           transparently.           keep track of the number
  13944.                                                    of records, then insert
  13945.                                                    each new record as
  13946.  ────────────────────────────────────────────────────────────────────────────
  13947.                                                   each new record as
  13948.                                                    number n+1. Inserting at
  13949.                                                    a specific position
  13950.                                                    requires code to swap
  13951.                                                    records.
  13952.  
  13953.  Change presentation      Use  CREATEINDEX or      Requires sorting code.
  13954.  order of records to get  SETINDEX.
  13955.  a different perspective
  13956.  on data.
  13957.  
  13958.  Close the file(s).       Use  CLOSE for tables.   Use  CLOSE.
  13959.  
  13960.  
  13961.  
  13962.  
  13963.  
  13964.  
  13965.  ISAM Concepts and Terms
  13966.  
  13967.  Many terms used in describing ISAM are familiar, however when used with ISAM
  13968.  many terms have specialized connotations. For example, when using random
  13969.  file access, one typically thinks of a file as a collection of logically
  13970.  related records. In ISAM such a collection of records is called a "table,"
  13971.  since an ISAM disk file (called a "database") can contain multiple and
  13972.  distinct collections of records. This section explains some fundamental ISAM
  13973.  concepts and terms.
  13974.  
  13975.   Field  A single data item constituent of a record.
  13976.  
  13977.   Record  A collection of logically related data-item fields. The association
  13978.  of the fields is defined by a  TYPE... END TYPE statement in your program.
  13979.  
  13980.   Row  Synonym for record. When placed in an ISAM table, the collection of
  13981.  fields in a specific record is referred to as a row. Thus, a row in a table
  13982.  corresponds to a single data record. See Figure 10.1.
  13983.  
  13984.   Table  An ordered collection of records (rows), each of which contains a
  13985.  single data record. The records in a table have some logical relationship to
  13986.  one another. The default order of records in the table corresponds to the
  13987.  order in which records were added.
  13988.  
  13989.   Column  Each column in a table has a name. A column in a table is the
  13990.  collection of all fields having the same column name. Thus, a column is a
  13991.  vertical collection of fields in the same way a row is a horizontal
  13992.  collection of fields.
  13993.  
  13994.   Database  A collection of tables and indexes contained in a disk
  13995.  file.
  13996.  
  13997.   Index  An independent structure within the ISAM file created when a
  13998.  CREATEINDEX statement is executed. Each index represents an alternative
  13999.  order for presentation of the records in the table. The order is based on
  14000.  the relative values of each data item in the column (or columns) specified
  14001.  in the  CREATEINDEX statement. You might want to think of an index as a
  14002.  "virtual table," that is, a virtual ordering of the table's data records. An
  14003.  index must have a name, and may have other attributes. Any index you create
  14004.  is saved and maintained as part of the database until it is explicitly
  14005.  deleted. Figure 10.2 illustrates a table. Beside the table, a list of
  14006.  positions indicates where each record actually resides in the table. Beneath
  14007.  the table, a diagram illustrates how an index on one of the columns (the
  14008.  Invoice column) changes the presentation order of the data when the index is
  14009.  specified.
  14010.  
  14011.  Specifying an index is a separate step in which the order of the indexed
  14012.  column (or columns) is imposed on any presentation of the table's records.
  14013.  To present a table's records in the index's order, you first specify that
  14014.  index in a setindex statement. If you were to create, and then specify the
  14015.  index on the Invoice column in the preceding figure, the records would be
  14016.  presented in the order shown in the final part of Figure 10.2, with the
  14017.  record at table position 3 first, followed by the record at table position
  14018.  6, followed by the record at table position 2, and so on.
  14019.  
  14020.   NULL  Index  The default index for a table; that is, the presentation order
  14021.  of the records when no user-created index is specified. When the NULL index
  14022.  is in effect (for instance, when the table is first opened), the order of
  14023.  the records is the order in which they were inserted into the table. In
  14024.  Figure 10.2, this order is illustrated by the table itself.
  14025.  
  14026.   Record Order  The actual physical order of records on disk is arbitrary
  14027.  because whenever records are deleted from a table, their physical disk
  14028.  positions are filled by the next records inserted in the table. This
  14029.  optimizes access speed and disk-space usage. For example, if you delete the
  14030.  third record added to the table, then add the sixth record, the sixth record
  14031.  would be placed in the physical disk position previously occupied by the
  14032.  third record as shown in Figure 10.3.
  14033.  
  14034.   Insertion Order  The order in which the records are inserted into
  14035.  the table. This order corresponds to the order imposed by the NULL index
  14036.  (the default index).
  14037.  
  14038.   Presentation Order  The apparent order imposed on the table by the current
  14039.  index. Note that file-space optimization has the side effect that
  14040.  subordering of records when an index is applied corresponds to the actual
  14041.  physical order of records on the disk. If you want presentation order to
  14042.  include specific subordering, use a combined index.
  14043.  
  14044.   Combined Index  An index that is based on more than a single column.
  14045.  Specifying a combined index enforces a specific subordering on the
  14046.  presentation order of the records.
  14047.  
  14048.   Indexed Value  The value (or combination of values) that determines a
  14049.  record's position on a particular index. While an index is a collection of
  14050.  indexed values, there is one indexed value for each record (row). Therefore,
  14051.  with a combined index, the indexed value is the combination of the
  14052.  constituent fields of the index.
  14053.  
  14054.   Key Value  A value against which indexed values are tested when using a
  14055.  SEEK operand  statement to seek a record that meets the specified condition.
  14056.  
  14057.   Unique  Index  An index requiring that each indexed value (i.e., each field
  14058.  in the column on which the index is defined) be different from all others.
  14059.  When you use the  CREATEINDEX statement to create an index, you can specify
  14060.  it as "unique." For example, if you specified a unique index on ClientCode
  14061.  column in the table in Figure 10.2, ISAM would generate a trappable error if
  14062.  the value of any ClientCode field ever duplicated the value of any other
  14063.  ClientCode field in the table. You could use a unique index to prevent a
  14064.  user from assigning the same ClientCode to two different clients.
  14065.  
  14066.   Current Position  The focus of activity in a table. Understanding
  14067.  "position" in the table is important because, in all cases, position is
  14068.  relative to some structure within the table that doesn't exist in other
  14069.  BASIC files. In an ISAM table, "currency" (meaning the current position, not
  14070.  the new  CURRENCY data type) is determined by several factors. Since
  14071.  multiple indexes may be created on any table, the current position depends
  14072.  on which index has been specified. There is no concept of a "current table"
  14073.  in ISAM, but there is always a "current index" for each open ISAM table. If
  14074.  a table contains any records, one record is the "current record," except in
  14075.  two cases:
  14076.  
  14077.    ■   There is no current record at the beginning or end of the table (when
  14078.        BOF or  EOF return true).
  14079.  
  14080.    ■   There is no current record after the unsuccessful execution of a  SEEK
  14081.        operand  statement (because eof then returns true).
  14082.  
  14083.  
  14084.  Otherwise, every open table has a current index and a current record (if it
  14085.  contains any records).
  14086.  
  14087.   Current Index  The index whose sorting order determines the order of
  14088.  appearance of a table's records. When a table is first opened, the NULL
  14089.  index is the current index. Once an index is created on a column or
  14090.  combination of columns, specifying it as the current index imposes the
  14091.  sorting order of that column or combination of columns on the presentation
  14092.  order of all records in the table. That index remains the current index on
  14093.  that table until it is deleted, a different index is specified for that
  14094.  table, or the table is closed. Every open table has a current index.
  14095.  
  14096.   Current Record  The focus of activity between your program and an ISAM
  14097.  table. Directly following a setindex statement, the current record is the
  14098.  record with the smallest indexed value, according to the index specified.
  14099.  ISAM provides many statements for making different records current, and for
  14100.  altering the current record. At any time, one and only one record can be the
  14101.  current record in each open table. When the NULL index is specified with
  14102.  setindex, the current record is the record that was inserted into the table
  14103.  first.
  14104.  
  14105.   Focus  The focus of data exchange (that is, the  record affected by an ISAM
  14106.  statement that fetches, overwrites, inserts, or deletes data in a table).
  14107.  The focus is always the current record, as determined by the current index.
  14108.  
  14109.   ISAM Engine  Routines used by ISAM to access and maintain the database.
  14110.  
  14111.   ISAM File  A database created and maintained using ISAM.
  14112.  
  14113.   Data Dictionary  Tables and indexes used by the ISAM engine in maintaining
  14114.  the database.
  14115.  
  14116.  
  14117.  ISAM Components
  14118.  
  14119.  ISAM works by applying routines contained in the ISAM engine to a physical
  14120.  disk structure called an ISAM file. The following sections describe these
  14121.  components and the table/index model for record access.
  14122.  
  14123.  
  14124.  The ISAM Engine
  14125.  
  14126.  The ISAM engine creates a table representing your data records to enhance
  14127.  the storage of and rapid access to each record. The relationship between the
  14128.  individual records in the table and what is actually in memory as your
  14129.  program runs is determined by the ISAM engine. This lets you easily
  14130.  manipulate the records of very large files as though they all fit in memory
  14131.  at once. The actual order of physical storage of records in the file is
  14132.  unimportant because ISAM allows you to deal with the records as though they
  14133.  were stored in a variety of convenient ways.
  14134.  
  14135.  Your program can present the records in the file according to the sorting
  14136.  order of any column  in the table simply by creating, and then specifying an
  14137.  index. Using the  SETINDEX statement is functionally equivalent to sorting
  14138.  the records according to the values of the constituent fields of the indexed
  14139.  column (or combination of columns). When indexes are created, they become
  14140.  part of the database. They can then be saved as part of the database, or
  14141.  deleted. When saved, the ISAM file contains the indexes you've created, in
  14142.  addition to the tables containing the data records themselves. Indexes are
  14143.  described in detail in the section "Creating and Specifying Indexes on Table
  14144.  Columns" later in this chapter.
  14145.  
  14146.  
  14147.  The Parts of the ISAM File
  14148.  
  14149.  ISAM places your data records in the table (or tables) you specify. Each
  14150.  table represents a group of logically related records, but the logic of the
  14151.  groupings is completely up to you. For example, it might be useful to keep
  14152.  one table in the database for your inventory and another for clients.
  14153.  
  14154.  Information describing an ISAM file is maintained within the file in a set
  14155.  of system tables called the data dictionary. The data dictionary itself is
  14156.  invisible to your program, and you never have to deal with it if you don't
  14157.  want to. In fact, it is safest to simply let the ISAM engine handle all
  14158.  interaction with the data dictionary, since corrupting it could destroy your
  14159.  database. Information in the data dictionary includes table names, indexes
  14160.  and index names, column names, and all the other information used by ISAM to
  14161.  access and manipulate the records in response to the ISAM statements.
  14162.  
  14163.  You can create any number of tables within a database file, although the
  14164.  number of tables you can open simultaneously has an upper limit of 13 and
  14165.  decreases each time an additional database is opened. A practical maximum of
  14166.  four databases can be opened at once. The section "Using Multiple Files"
  14167.  describes how many tables can be opened, relative to the number of open
  14168.  databases.
  14169.  
  14170.  
  14171.  ISAM File Allocation and Growth
  14172.  
  14173.  Because an ISAM file contains descriptive information, it has some file-size
  14174.  overhead. Additionally, to optimize access speed and flexibility, ISAM files
  14175.  grow periodically in large chunks (32K per chunk), rather than in
  14176.  record-sized increments as single records are added. A database contains a
  14177.  header of about 3K. Each table has 4K of overhead beyond its actual data
  14178.  records; each index requires at least 2K. The data dictionary consists of
  14179.  five system tables plus eight system indexes, resulting in a total initial
  14180.  overhead of about 39K. Therefore, the smallest ISAM file is 64K. Though an
  14181.  ISAM file with a single record is 64K, there is considerable room for adding
  14182.  data records within that 64K file before the next 32K chunk is added. The
  14183.  initial combination of system tables and system indexes is about 39K; the
  14184.  remaining 25K are used for your data records and the new indexes and tables
  14185.  you create.
  14186.  
  14187.  
  14188.  When to Use ISAM
  14189.  
  14190.  For data files too large to completely load into memory, ISAM vastly
  14191.  simplifies file manipulation because ISAM support replaces the kind of
  14192.  sorting that can only otherwise be accomplished efficiently by loading all
  14193.  data records in memory simultaneously. This makes ISAM an excellent method
  14194.  for dealing with large amounts of data which require sorted access. An ISAM
  14195.  file can be as large as 128 megabytes. ISAM handles all the work of moving
  14196.  portions of such a huge file in and out of memory during record
  14197.  manipulation.
  14198.  
  14199.  Whenever data records contain many fields that need to be examined in a
  14200.  variety of ways, using ISAM simplifies the programming. Although you can
  14201.  write code to sort or index random-access files, ISAM integrates these tasks
  14202.  for you with high-level statements that manipulate sophisticated file
  14203.  structures. This lets you easily manipulate records by the values in
  14204.  specific fields, and is far more flexible than a random file's
  14205.  one-dimensional ordering by record number. (For an example of how much BASIC
  14206.  code just one index for a random file requires, see the program INDEX.BAS
  14207.  listed in Chapter 3, "File and Device I/O.") However, if disk space is at a
  14208.  premium, don't automatically choose ISAM for short, easily-sorted files of
  14209.  relatively constant size. These may be better handled using other methods
  14210.  (for example, by creating and using hash tables). However, if you need to
  14211.  sort on different fields at different times, or if you need very fast access
  14212.  to records according to complex subsorting orders, the benefits of the ISAM
  14213.  file quickly make up for its overhead. Also, consider that for very large
  14214.  files, the amount of descriptive information relative to actual records
  14215.  remains relatively constant, so the percentage of the file devoted to
  14216.  overhead decreases progressively.
  14217.  
  14218.  
  14219.  The Table/Index Model
  14220.  
  14221.  In ISAM, tables and indexes represent various fundamental arrangements of
  14222.  the data records. When you insert a record in an ISAM table, its place in
  14223.  the table is the result of a process that optimizes file size and speed of
  14224.  access. References to the record are immediately placed in all of that
  14225.  table's existing indexes, including the NULL index, so the presentation
  14226.  order of all records is always internally consistent. The default order for
  14227.  a table is the chronological order of insertion.
  14228.  
  14229.  Specifying an index other than the NULL index orders the records of the
  14230.  table by the sorting order of each field in the indexed column (or
  14231.  combination of columns). For example, if each row in a table contains five
  14232.  columns, you can create indexes on any, or all, or any combination of the
  14233.  five columns. When you specify one of these indexes (with  SETINDEX), you
  14234.  impose the sort order of the index on the presentation order of the records.
  14235.  (The sort order is either numeric or alphabetic, depending on the data type
  14236.  of the column or columns). Specifying a different index changes the
  14237.  presentation order of the records. As you add and remove records from the
  14238.  table, ISAM maintains all relevant information about the table. Figure 10.4
  14239.  illustrates a simple table in which each record is a collection of six
  14240.  user-defined fields. The table can be represented as four rows, each having
  14241.  six columns.
  14242.  
  14243.  The values in each field in the Number column represent the order in which
  14244.  each record was added to the table. In practice, you would probably never
  14245.  define such a column, since insertion order, the NULL index, is the default
  14246.  ordering of the records in an ISAM table, but it is included here for
  14247.  illustrative purposes. In a random-access file, the Number column would
  14248.  correspond to the record number, and would be the only way you could
  14249.  reference a record without writing special code to sort the file. However,
  14250.  because this is an ISAM table, you can use an index to specify a
  14251.  presentation order that corresponds to the sort order of any of the columns.
  14252.  
  14253.  Suppose you wanted to organize a celebration, and you wanted to compile an
  14254.  invitation list that included only women. With just the  OPEN,  CREATEINDEX,
  14255.  and  SETINDEX statements you could create and specify an index on the Sex
  14256.  column. Since F sorts before M, all the women in the table would be
  14257.  presented before any of the men, as shown in Figure 10.5.
  14258.  
  14259.  With this order the program could easily start from the first record,
  14260.  display its data, then use the  MOVENEXT statement to make each successive
  14261.  record the current record. Conversely, if you wanted to invite only men, the
  14262.  program could start from the last record (using the  MOVELAST statement),
  14263.  and then use  MOVEPREVIOUS to  traverse the records in reverse order. As the
  14264.  program displayed each previous record, you could choose among the men.
  14265.  
  14266.  An index can be specified on each column of a table. Figure 10.6 presents
  14267.  the table information indexed on the Sport column.
  14268.  
  14269.  You can also create combined indexes by "combining" the values in several
  14270.  fields so records appear sorted, first by one field, then sorted by another,
  14271.  and so on. For example, you could create and specify a combined index that
  14272.  presented the records sorted first by the Phone column, then by the Birthday
  14273.  column. This would present the records sorted first by household, then
  14274.  within each household, by the order of the birthdays of each person with the
  14275.  same phone number, as shown in Figure 10.7.
  14276.  
  14277.  The preceding examples are for illustrative purposes. Normally, when
  14278.  designing a program you would provide the user with several useful indexes
  14279.  on the records, rather than designing the program to let the user create
  14280.  indexes as needed. However, if you want to let users create their own
  14281.  indexes, you can do so using the ISAM statements and functions.
  14282.  
  14283.  
  14284.  A Sample Database
  14285.  
  14286.  The following sections describe a table within an ISAM database file that
  14287.  could be used by a library to keep track of its inventory of books. Examples
  14288.  demonstrate how to create or open the database and view the records from
  14289.  several different perspectives. (This program, BOOKLOOK.BAS, as well as its
  14290.  associated .MAK file (BOOKLOOK.MAK), secondary modules (BOOKMOD1.BAS,
  14291.  BOOKMOD2.BAS, BOOKMOD3.BAS), database file (BOOKS.MDB), and include file
  14292.  (BOOKLOOK.BI) are included on the disks supplied with Microsoft BASIC. When
  14293.  you ran the Setup program, they were placed in the directory you specified
  14294.  for BASIC source and include files.
  14295.  
  14296.  
  14297.  Designing the BookStock Table
  14298.  
  14299.  Inventory maintenance of a book-lending library can be used to illustrate
  14300.  the ISAM approach. Assume that the patrons of the library are concerned only
  14301.  with books dealing with the BASIC programming language. For example, you can
  14302.  create a database containing a single table that includes pertinent
  14303.  information about all the library's books about BASIC. Figure 10.8
  14304.  illustrates the form such a table might take.
  14305.  
  14306.  
  14307.  Creating, Opening, and Closing a Table
  14308.  
  14309.  The statements used for creating, opening, and closing databases and tables
  14310.  are the familiar BASIC  TYPE... END TYPE,  OPEN, and  CLOSE statements.
  14311.  However, they are used differently with ISAM files than with other types of
  14312.  files.
  14313.  
  14314.  
  14315.  Naming the Columns of the Table
  14316.  
  14317.  The first step in creating a table is to include an appropriate  TYPE... END
  14318.  TYPE statement in the declarations part of your program. The name you use
  14319.  for this user-defined type is an argument to the  OPEN statement  that
  14320.  creates the database file and the table, and may be used subsequently
  14321.  whenever the table is opened. When the table is first created, the names
  14322.  used for the elements in the  TYPE... END TYPE statement become the names of
  14323.  the corresponding columns in the table (See Figure 10.8).
  14324.  
  14325.  
  14326.  Specifying the  Data Types of the Columns
  14327.  
  14328.  What can appear in a column of a table is determined by the column's data
  14329.  type. For instance, a column having  INTEGER type can accept whole numbers
  14330.  in the normal integer range. Similarly, a column having  STRING type can
  14331.  contain a string as large as 32K. Assigning data types to the columns makes
  14332.  it possible for ISAM to create the indexes that can be used to change the
  14333.  presentation order of the table's records.
  14334.  
  14335.  Note
  14336.  
  14337.  Although you can fetch and write data that has the following characteristics
  14338.  to an ISAM table, you cannot create indexes on them:
  14339.  
  14340.    ■    STRING columns longer than 255 bytes.
  14341.  
  14342.    ■   Columns with aggregate (i.e., array) type.
  14343.  
  14344.    ■   Columns with structure (i.e ., user-defined) type.
  14345.  
  14346.  
  14347.  Each column in a table has the data type specified in the  TYPE... END TYPE
  14348.  statement used as the  tabletype in the  OPEN statement that created the
  14349.  table. Data types you specify in an ISAM  TYPE... END TYPE statement must be
  14350.  one of those shown in Table 10.1.
  14351.  
  14352.  Note that BASIC's  SINGLE data type is not legal in ISAM; use  DOUBLE or
  14353.  CURRENCY instead. The following declaration can be used in a program that
  14354.  creates or accesses the sample BookStock table.
  14355.  
  14356.  TYPE Books
  14357.    IDnumAS DOUBLE    ' ID number for this copy
  14358.    PriceAS CURRENCY  ' Original cost of book
  14359.    Edition  AS INTEGER  ' Edition number of book
  14360.    Title AS STRING * 50' The title of the book
  14361.    PublisherAS STRING * 50' The Publisher's name
  14362.    AuthorAS STRING * 36' The author's name
  14363.  END TYPE
  14364.  
  14365.  Although BASIC would accept element identifiers up to 40 characters long,
  14366.  the elements in this statement must follow the ISAM naming convention (see
  14367.  the section "ISAM Naming Convention" later in this chapter), since they will
  14368.  become the names of columns within the ISAM database file. The actual name
  14369.  of the user-defined type can be any valid BASIC identifier however, because
  14370.  it is never actually used within the ISAM file.
  14371.  
  14372.  
  14373.  Data Type Coercion
  14374.  
  14375.  Although BASIC performs considerable data type coercion in other situations,
  14376.  the only coercion performed between your BASIC program and ISAM is in
  14377.  relation to seek operand statements, and even then only between integer and
  14378.  long values. Therefore, if a long value is expected by ISAM, and you pass an
  14379.  integer, the integer will be coerced to a long, and no type-mismatch error
  14380.  is generated. However, if you try to pass a long when ISAM expects an
  14381.  integer, coercion may result in an Overflow error. In other situations, such
  14382.  as passing a currency value when a double is expected, a Type mismatch error
  14383.  is generated. Since BASIC's default data type is single precision numeric,
  14384.  passing a literal (even 0) to ISAM can cause a type mismatch (since single
  14385.  is not a valid ISAM data type). In such a case, you should append the
  14386.  type-declaration character for the type expected by ISAM to the number. Even
  14387.  if you reset the default data type with a def type statement, it is a good
  14388.  idea to screen the types of all numbers passed to ISAM to make sure they are
  14389.  properly typed and that they will fit within the range of the expected type.
  14390.  
  14391.  
  14392.  Opening the BookStock Table
  14393.  
  14394.  The declaration of the user-defined type is all the preparation a simple
  14395.  program needs to prepare for opening an ISAM database and table. The
  14396.  following code can now be used to create or open the table within the ISAM
  14397.  file:
  14398.  
  14399.  ' You could write code here to check to see if the file exists,
  14400.   ' then open the file if it does or display a message if it doesn't.
  14401.  
  14402.   OPEN "BOOKS.MDB" FOR ISAM Books "BookStock" AS # 1
  14403.  
  14404.  Using OPEN and CLOSE with ISAM
  14405.  
  14406.  To open a table, you use the traditional BASIC  OPEN statement with
  14407.  arguments and clauses specific to ISAM. Whenever you open a table, you must
  14408.  specify the database file that contains the table. The syntax for an ISAM
  14409.  OPEN is as follows:
  14410.  
  14411.   OPEN  database$  FOR ISAM  tabletype  tablename$  AS # filenumber%
  14412.  
  14413. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  14414.  Argument                                Description
  14415.  ────────────────────────────────────────────────────────────────────────────
  14416.   database$                              a string expression representing a
  14417.                                          DOS filename, so it follows
  14418.                                          operating-system file-naming
  14419.                                          restrictions. This argument can
  14420.                                          include a drive letter and a path.
  14421.  
  14422.   tabletype                              A BASIC identifier that specifies
  14423.                                          a user-defined type already
  14424.                                          declared in the program. Note that,
  14425.                                          unlike the other arguments, it
  14426.                                          cannot be a string expression.
  14427.  
  14428.   tablename$                             A string expression that follows
  14429.  Argument                                Description
  14430.  ────────────────────────────────────────────────────────────────────────────
  14431.  tablename$                             A string expression that follows
  14432.                                          the ISAM naming convention.
  14433.  
  14434.   filenumber%                            An integer within the range 1 -
  14435.                                          255, the same as in the
  14436.                                          traditional BASIC  OPEN statement.
  14437.                                          Note that  filenumber% is
  14438.                                          associated with both the
  14439.                                          tablename of the table being
  14440.                                          opened and the database file (
  14441.                                          database$) itself containing the
  14442.                                          table. Therefore, the same
  14443.                                          database$ can appear in any number
  14444.                                          of  OPEN statements, each of which
  14445.                                          opens a different table (with a
  14446.                                          unique  filenumber%) in the same
  14447.                                          database file. You can use
  14448.                                          FREEFILE to get available values
  14449.                                          for  filenumber%.
  14450.  Argument                                Description
  14451.  ────────────────────────────────────────────────────────────────────────────
  14452.                                         for  filenumber%.
  14453.  
  14454.  
  14455.  
  14456.  
  14457.  
  14458.  String arguments are  not case sensitive, so you can use inconsistent
  14459.  capitalization in any of these references.
  14460.  
  14461.  The close statement is the same for an ISAM database as for any other file:
  14462.  
  14463.   CLOSE  #  filenumber%  , # filenumber% ...
  14464.  
  14465.  Opening a Table
  14466.  
  14467.  The  FOR ISAM clause simply replaces the  FOR OUTPUT (or  APPEND, or  INPUT)
  14468.  clause used for other sequential-file access. The ISAM engine then handles
  14469.  all file interaction.
  14470.  
  14471.  The behavior of an  OPEN... FOR ISAM statement is similar to  OPEN... FOR
  14472.  OUTPUT or  OPEN... FOR APPEND with other types of sequential files. For
  14473.  example, if  database$ does not yet exist as a disk file, it is created by
  14474.  the  OPEN statement. Similarly, if  tablename does not exist within the
  14475.  database, the  OPEN statement creates a table of that name within the
  14476.  database, and opens it. The  tabletype argument must identify a user-defined
  14477.  type previously  declared in the program with  a  TYPE... END TYPE
  14478.  statement. This precludes writing programs that permit the end user to
  14479.  design custom tables at run time. If an ISAM  OPEN statement fails, all ISAM
  14480.  buffers are written to disk and any pending transactions are committed (See
  14481.  the section "Block Processing with Transactions" later in this chapter for
  14482.  information on transactions.)
  14483.  
  14484.  Note
  14485.  
  14486.  You cannot lock an ISAM database using open...for isam. However, you can
  14487.  open a database that has been designated read-only by some other process. If
  14488.  your program opens such a file, certain ISAM statements will generate
  14489.  errors, including: delete, deleteindex, deletetable, createindex, insert,
  14490.  and update. These statements cause Permission denied error messages.
  14491.  
  14492.  
  14493.  Closing a Table
  14494.  
  14495.  A  CLOSE statement with  filenumber% as an argument closes the  tablename$
  14496.  associated with  filenumber%.  CLOSE with no arguments closes all open
  14497.  tables (and any other files, ISAM or otherwise). Any ISAM  CLOSE statement
  14498.  causes all pending transactions to be committed. (See the section "Block
  14499.  Processing with Transactions" later in this chapter for information on
  14500.  transactions.)
  14501.  
  14502.  
  14503.  The Attributes of filenumber%
  14504.  
  14505.  A program that opens an ISAM table can open other files for other types of
  14506.  access. In such cases, you may need to determine at some point which files
  14507.  (or tables), associated with which file numbers, are open for which types of
  14508.  access.  FILEATTR has the following syntax:
  14509.  
  14510.   FILEATTR( filenumber%,  attribute%)
  14511.  
  14512.  When you use this function, you pass the number of the file or table you
  14513.  want to know about as the first argument, and either 1 or 2 as  attribute%.
  14514.  If you pass a 1, the value returned in  FILEATTR is 64 if  filenumber% was
  14515.  opened as an ISAM table.
  14516.  
  14517.  
  14518.  Other return values indicate the file was opened for another mode, as
  14519.  follows:
  14520.  
  14521. ╓┌───────────────────────┌───────────────────────────────────────────────────╖
  14522.  ────────────────────────────────────────────────────────────────────────────
  14523.  1                        INPUT
  14524.  2                        OUTPUT
  14525.  4                        RANDOM
  14526.  16                       APPEND
  14527.  32                       BINARY
  14528.  64                       ISAM
  14529.  
  14530.  
  14531.  
  14532.  
  14533.  With an ISAM file, if you pass a 2 as  attribute%,  FILEATTR returns zero.
  14534.  
  14535.  
  14536.  Defining a Record Variable
  14537.  
  14538.  Although it isn't necessary to do it at the same time you open the database,
  14539.  you eventually need to define a record variable having the proper
  14540.  user-defined type for the table. This variable is used in transferring data
  14541.  between your program and the ISAM file. In the case of the Books type, it
  14542.  could look like this:
  14543.  
  14544.  DIM Inventory AS Books
  14545.  
  14546.  Creating and Specifying Indexes on Table Columns
  14547.  
  14548.  Much of the power of ISAM derives from the ease with which the apparent
  14549.  order of data records can be changed. This is accomplished by specifying a
  14550.  previously created "index" on a column (or columns) in a  SETINDEX
  14551.  statement.
  14552.  
  14553.  If you don't specify an index on a table, the default index (the NULL index)
  14554.  is used, and the apparent order of the records is the order in which the
  14555.  records were added to the file. Therefore, when you initially open a table,
  14556.  the current index is the NULL index until (and unless) you specify a
  14557.  different index. You create your own indexes with the  CREATEINDEX
  14558.  statement, using the following syntax:
  14559.  
  14560.   CREATEINDEX  #  filenumber%,  indexname$,  unique%,  columnname$ ,
  14561.  columnname$...
  14562.  
  14563. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  14564.  Argument                                Description
  14565.  ────────────────────────────────────────────────────────────────────────────
  14566.   filenumber%                            The integer used to open the table
  14567.                                          on which the index is to be
  14568.                                          created .
  14569.  
  14570.   indexname$                             A string expression that follows
  14571.                                          the ISAM naming conventions. The
  14572.                                          index is known by  indexname$
  14573.                                          until explicitly deleted.
  14574.  
  14575.   unique%                                A numeric expression. A non-zero
  14576.  Argument                                Description
  14577.  ────────────────────────────────────────────────────────────────────────────
  14578.  unique%                                A numeric expression. A non-zero
  14579.                                          value specifies a unique index on
  14580.                                          the column, meaning that no values
  14581.                                          in any of that column's fields can
  14582.                                          duplicate any of the others. A
  14583.                                          value of zero for this argument
  14584.                                          means the indexed values need not
  14585.                                          be unique.
  14586.  
  14587.  
  14588.  
  14589.  
  14590.  
  14591.  
  14592. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  14593.  ────────────────────────────────────────────────────────────────────────────
  14594.   columnname$                            A string expression following the
  14595.                                          ISAM naming convention that
  14596.                                          specifies the column to be indexed.
  14597.  ────────────────────────────────────────────────────────────────────────────
  14598.                                         specifies the column to be indexed.
  14599.                                          Note that multiple  columnname$
  14600.                                          entries do not create multiple
  14601.                                          independent indexes, but rather
  14602.                                          create a single combined index.
  14603.                                          Each succeeding  columnname$
  14604.                                          identifies a subordinate sorting
  14605.                                          order for the records (when that
  14606.                                          index is specified).
  14607.  
  14608.  
  14609.  
  14610.  
  14611.  
  14612.  The  CREATEINDEX statement is  used only once for each index. If you try to
  14613.  create an index that already exists for the table, a trappable error is
  14614.  generated.
  14615.  
  14616.  Once an index exists, you use the  SETINDEX statement to make it the current
  14617.  index (thereby imposing its order on the presentation order of the records).
  14618.   SETINDEX has the following syntax:
  14619.  
  14620.   SETINDEX  #  filenumber% , indexname$
  14621.  
  14622.  An  indexname$  argument is mandatory in the  CREATEINDEX statement, but
  14623.  optional with  SETINDEX. If you do not specify an index name with  SETINDEX,
  14624.  the NULL index becomes the current index. Immediately after execution of
  14625.  SETINDEX, the specified index is the current index, and the current record
  14626.  becomes the record having the lowest sorting value in that column.
  14627.  
  14628.  Note
  14629.  
  14630.  Comparisons made by ISAM when sorting strings differ somewhat from those
  14631.  performed by BASIC. When collating, the case of characters is not
  14632.  significant, and trailing blank spaces are stripped from a string before
  14633.  comparison is made. In strings that are otherwise identical, accents are
  14634.  significant, and collating is performed based on the choice you made when
  14635.  running the Setup program. English, French, German, Portuguese, and Italian
  14636.  comprise the default group. Dutch and Spanish each have their own collating
  14637.  orders, and the Scandinavian languages (Danish, Norwegian, Finnish,
  14638.  Icelandic, and Swedish) comprise the fourth group. See Appendix E,
  14639.  "International Character Sort Order Tables," in the  BASIC Language
  14640.  Reference for specifics of each group.
  14641.  
  14642.  
  14643.  Indexes on BookStock's Columns
  14644.  
  14645.  For example, assuming the BookStock table illustrated in Figure 10.8 was
  14646.  opened as # 1, you could use the  CREATEINDEX statement to create the index
  14647.  TitleIndexBS (on the table's Title column) as follows:
  14648.  
  14649.  CREATEINDEX 1, "TitleIndexBS", 0, "Title"
  14650.  
  14651.  After this definition, you can use setindex to make this index the current
  14652.  index as follows:
  14653.  
  14654.  SETINDEX 1, "TitleIndexBS"
  14655.  
  14656.  Once specified as current, the index represents a virtual table in which the
  14657.  data records are ordered according to the values in the Title column. This
  14658.  index imposes an alphabetic presentation order on the table. Therefore, all
  14659.  copies of books entitled  QuickBASIC Made Easy would appear in sequence, and
  14660.  precede copies of  QuickBASIC ToolBox, and so forth.
  14661.  
  14662.  
  14663.  Creating a Unique Index
  14664.  
  14665.  In the BookStock table, the IDnum column contains numbers for each copy of
  14666.  each book in the library. When new copies of a book are acquired, each is
  14667.  given a unique number. In this example all copies of  QuickBASIC ToolBox
  14668.  have a whole number part of 15561, but each has a different fractional part.
  14669.  The following statement creates an index on this column and passes a
  14670.  non-zero value as the  unique% argument, ensuring that no duplicate IDnum
  14671.  values can be entered for any copies of any books:
  14672.  
  14673.  CREATEINDEX 1, "IDIndex", 1, "IDnum"
  14674.  
  14675.  If there are already duplicates in the column, a trappable error is
  14676.  generated when your program attempts to execute the createindex statement.
  14677.  If your program ever attempts to update the table with a duplicate value in
  14678.  a field in a unique index, a trappable error is generated.
  14679.  
  14680.  In determining whether string values are duplicates, comparisons are
  14681.  case-insensitive and trailing blanks are ignored. Accented letters are not
  14682.  duplicates of their unaccented counterparts.
  14683.  
  14684.  
  14685.  Subordering of Records Within an Indexed Column
  14686.  
  14687.  There are many cases in which a user might need or expect a specific
  14688.  subordering. For example, when browsing a group of customer orders, if you
  14689.  are traversing an index based on account numbers, you might expect the
  14690.  actual orders associated with a specific customer name to be in the order in
  14691.  which the records were added. To ensure that records are presented in the
  14692.  way your user expects, you can create a combined index.
  14693.  
  14694.  In the BookStock table example, the idea of multiple records representing
  14695.  multiple copies of a specific book in the library means that the
  14696.  TitleIndexBS index created previously would be suitable for a librarian who
  14697.  wanted to see quickly how many copies of a specific book the library owned.
  14698.  The librarian also might want to have the books presented in the order in
  14699.  which they were purchased. Creating, then specifying a combined index on the
  14700.  Title and Author columns would present all of the books with a specific
  14701.  title/author combination grouped together. When you index on a column that
  14702.  contains the same value in more than one record, the subordering of records
  14703.  with the same value for the column (in this case, the combination of Title
  14704.  and Author columns) is unpredictable because many table entries may have
  14705.  been inserted and deleted at different times. The books would appear in the
  14706.  order in which their records actually appear on the disk. ISAM optimizes for
  14707.  space by allowing new records to be placed on the disk in space previously
  14708.  occupied by deleted records. Therefore, although the title/author combined
  14709.  index would group the presentation of all copies of books titled  QuickBASIC
  14710.  ToolBox and written by D. Hergert, it would present them in the order in
  14711.  which they appear on the disk.
  14712.  
  14713.  However, the IDnum for each copy would correspond to the dates when each was
  14714.  added to the collection (assuming the library did not reuse an old IDnum
  14715.  once an old copy of a book was replaced). A combined index that included the
  14716.  Title, Author, and IDnum columns would present the records with the oldest
  14717.  copy appearing first among that group of titles by that author, and so on.
  14718.  
  14719.  
  14720.  Similarly, if the library had purchased three hard-bound and five paperback
  14721.  copies, the difference would show up as a significant differential in price.
  14722.  In presenting these books in the database, a librarian might want to have
  14723.  all the hard-bound copies appear in sequence, separated from the paperback
  14724.  copies. A combined index on the Title, Author, and Price columns would
  14725.  create that presentation order (assuming paperbacks of a specific title are
  14726.  always cheaper than their hardbound counterparts). If the IDnum column were
  14727.  added to create a Title, Author, Price, IDnum index, then all the paperbacks
  14728.  would appear in the order in which they were added to the collection before
  14729.  any of the hard-bound copies.
  14730.  
  14731.  
  14732.  Creating a Combined  Index
  14733.  
  14734.  A combined index can be created using as many as 10 columns in a table by
  14735.  listing multiple  columnname$ arguments in the  CREATEINDEX statement. When
  14736.  such a multi-column index is the current index, the records appear as though
  14737.  first sorted by the first  columnname$. Then those records whose first
  14738.  indexed values are identical appear as though sorted by the next
  14739.  columnname$, and so on. The following example creates a combined index:
  14740.  
  14741.  CREATEINDEX 1 , "BigIndex", 0, "Title", "Author", "IDnum"
  14742.  
  14743.  When  SETINDEX is used to specify BigIndex as the current index, the records
  14744.  appear sorted first by Title. The same title might appear in the database
  14745.  for several books by different authors, but making the Author the second
  14746.  part of the combined index would keep all those by a particular author
  14747.  grouped. Finally, giving the IDnum as the last part of the index would cause
  14748.  the oldest copy of the desired book (by the given author) to be presented
  14749.  first in its group.
  14750.  
  14751.  You can designate a combined index as unique. If you do so, only the
  14752.  combination must be unique. For example, a unique index on the Author and
  14753.  Title columns would permit any number of occurrences of the same author  or
  14754.  the same title, but only for one instance of the same author and the same
  14755.  title.
  14756.  
  14757.  
  14758.  Null Characters Within Indexed Strings in a Combined Index
  14759.  
  14760.  If you place null characters within strings in columns that are components
  14761.  of a combined index, there are situations in which the order of the index
  14762.  may deviate from what you expect. This is rare, but results from the fact
  14763.  that ISAM uses the null character as a separator in combined indexes. For
  14764.  instance, if the last character in a string field is a null, and its index
  14765.  is combined with one whose first character is a null, and in all other ways
  14766.  they are the same, the two fields will compare equal. This applies only to
  14767.  combined indexes. There is no restriction on null characters in an ISAM
  14768.  string, but you should be aware of this situation if you plan to use null
  14769.  characters in strings of indexed columns.
  14770.  
  14771.  
  14772.  Practical Considerations with Indexes
  14773.  
  14774.  Remember that each time an insert, delete, or update statement is executed,
  14775.  every index in the affected table is adjusted to reflect the changed state
  14776.  of the records. In the normal course of moving through a database and making
  14777.  changes to records, the time needed to adjust indexes would not be
  14778.  noticeable to a user. However, the time required by an automated process
  14779.  that makes these types of changes may be significantly affected by the
  14780.  number of indexes in the table. In some cases it may make sense to delete
  14781.  unnecessary indexes before such a process begins, then recreate them when it
  14782.  is finished.
  14783.  
  14784.  
  14785.  Restrictions on Indexing
  14786.  
  14787.  Part of the information ISAM maintains is the data type of each column in
  14788.  each table. These data types are stored in the data dictionary so the ISAM
  14789.  engine can make valid comparisons when sorting records in an index. ISAM can
  14790.  index columns having up to 255 bytes in combined length. Therefore, you can
  14791.  create an index on a string column having a length of up to 255 bytes, or a
  14792.  combined index whose constituent columns total a little less than 255 bytes
  14793.  or less (there is a little overhead associated with each constituent index).
  14794.  Columns having array or structure (that is, user-defined) type cannot be
  14795.  indexed.
  14796.  
  14797.  Attempting to create an index on a column with array or structure type, or
  14798.  on a  STRING column longer than 255 characters, or defining a combined index
  14799.  whose total length is greater than 255 bytes, causes a trappable error.
  14800.  
  14801.  
  14802.  Determining the Current Index
  14803.  
  14804.  The  GETINDEX$ function lets you find out what the current index is.
  14805.  GETINDEX$ has the following syntax:
  14806.  
  14807.   GETINDEX$ ( filenumber% )
  14808.  
  14809.  The  filenumber% argument is an integer identifying any open table.
  14810.  GETINDEX$ returns a string representing the name of the current index. If
  14811.  the value returned in  GETINDEX$ is a null string (represented by ""), then
  14812.  the current index is the NULL index.
  14813.  
  14814.  In a complex program, it may become difficult to predict which index is the
  14815.  current index on a specific table. Although  SETINDEX is a single call, it
  14816.  is usually more efficient to test the current index with  GETINDEX$ first,
  14817.  then only use  SETINDEX if you actually want a different index. The
  14818.  following fragment illustrates this:
  14819.  
  14820.  IF GETINDEX$(TableNum%) <> "MyIndex" THEN
  14821.  SETINDEX TableNum%, "MyIndex"
  14822.  END IF
  14823.  
  14824.  Even though the preceding is more code than simply using  SETINDEX, it is
  14825.  usually more efficient. Note also that the effect on the current record may
  14826.  be different depending on whether the setindex statement is executed. If the
  14827.  current index is already MyIndex, the current record will be the same (on
  14828.  that index) as it was previously. If the setindex  statement is executed,
  14829.  the current record will be the one that sorts lowest in the index.
  14830.  
  14831.  
  14832.  Transferring and Deleting Record Data
  14833.  
  14834.  The syntax for the data-manipulation statements is similar to that for
  14835.  setindex:
  14836.  
  14837.   DELETE  #  filenumber%
  14838.   RETRIEVE  #  filenumber%,  recordvariable UPDATE  #   filenumber%,
  14839.  recordvariable INSERT  #  filenumber%,  recordvariable
  14840.  
  14841. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  14842.  Argument                                Description
  14843.  ────────────────────────────────────────────────────────────────────────────
  14844.   filenumber%                            The integer used to open the table
  14845.                                          whose current record you want to
  14846.                                          remove, fetch, or overwrite. In
  14847.                                          the case of an insertion, it is
  14848.                                          the table in which you want the
  14849.  Argument                                Description
  14850.  ────────────────────────────────────────────────────────────────────────────
  14851.                                         the table in which you want the
  14852.                                          record inserted.
  14853.  
  14854.   recordvariable                         A variable of the user-defined
  14855.                                          type corresponding to the table
  14856.                                          into which the current record
  14857.                                          values are placed, or with which
  14858.                                          the current record is to be
  14859.                                          overwritten. In the case of an
  14860.                                          insertion,  recordvariable is  the
  14861.                                          record you wish to insert. Its
  14862.                                          elements (in its  TYPE... END TYPE
  14863.                                          declaration) may be exactly the
  14864.                                          same as those of the table, or a
  14865.                                          subset of them. Subsets of
  14866.                                          recordvariable are discussed in
  14867.                                          the section "Record Variables as
  14868.                                          Subsets of a Table's Columns"
  14869.                                          later in this chapter.
  14870.  Argument                                Description
  14871.  ────────────────────────────────────────────────────────────────────────────
  14872.                                         later in this chapter.
  14873.  
  14874.  
  14875.  
  14876.  
  14877.  
  14878.   RETRIEVE,  UPDATE, and  DELETE all refer to the current record. The data
  14879.  transfer statements all take the data in  recordvariable and either place it
  14880.  in the table ( UPDATE and  INSERT) or fetch the current record ( RETRIEVE)
  14881.  and place its data into  recordvariable.
  14882.  
  14883.   DELETE removes the current record from the specified table, and all
  14884.  affected indexes are adjusted appropriately. Following a deletion, if the
  14885.  current record was not the last record in the current index, the new current
  14886.  record is the record that immediately succeeded  the deleted record. If the
  14887.  deleted record was the last record, no record is current, and  EOF returns
  14888.  true.
  14889.  
  14890.  When you use  RETRIEVE, the contents of the current record are assigned to
  14891.  recordvariable.
  14892.  
  14893.  When you use  UPDATE, the contents of  recordvariable overwrite the current
  14894.  record, and all affected indexes are adjusted appropriately.
  14895.  
  14896.  A trappable error occurs if no record is current when a  DELETE,  RETRIEVE,
  14897.  or  UPDATE statement is executed.
  14898.  
  14899.   INSERT places the contents of  recordvariable in the table, then adjusts
  14900.  all affected indexes appropriately. A newly inserted record assumes its
  14901.  appropriate position in the current index. Therefore, if you display the
  14902.  current record immediately after an insertion, the record displayed is the
  14903.  same record that was displayed prior to the insertion, not the newly
  14904.  inserted record. To see the new record, execute a setindex statement to make
  14905.  the null index current, then execute a movelast statement, then display the
  14906.  current record. The insert statement itself does not affect positioning. A
  14907.  trappable error occurs if you try to insert a record containing a duplicate
  14908.  value in a column on which a unique index exists.
  14909.  
  14910.  
  14911.  The Current Position
  14912.  
  14913.  The current position within a table depends on that table's current index.
  14914.  When you specify an index with setindex, you specify the table (with the
  14915.  filenumber%  argument) and the index name. After setindex is executed, the
  14916.  current record is the first record on the specified index. The current
  14917.  record is the focus of data exchange.
  14918.  
  14919.  
  14920.  Changing the Current Index
  14921.  
  14922.  After opening a table, and until you specify an index, the current index is
  14923.  the NULL index. To change to another index, use the setindex statement. It
  14924.  has the following syntax:
  14925.  
  14926.  setindex  #  filenumber%, ,   indexname$
  14927.  
  14928. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  14929.  Argument                                Description
  14930.  ────────────────────────────────────────────────────────────────────────────
  14931.   #                                      The optional number character.
  14932.  
  14933.  Argument                                Description
  14934.  ────────────────────────────────────────────────────────────────────────────
  14935. 
  14936.   filenumber%                            The integer used to open the table
  14937.                                          for which you want to set a new
  14938.                                          current index.
  14939.  
  14940.   indexname$                             A string expression naming a
  14941.                                          previously created index. If
  14942.                                          indexname is omitted, the NULL
  14943.                                          index becomes the current index,
  14944.                                          otherwise  indexname becomes the
  14945.                                          current index.
  14946.  
  14947.  
  14948.  
  14949.  
  14950.  
  14951.  
  14952.  Making a Different Record Current
  14953.  
  14954.  ISAM permits you to make records current either by their position within the
  14955.  current index (using a move dest statement), or by testing field value(s) in
  14956.  the current index against key value(s) you supply (in a seek operand
  14957.  statement).
  14958.  
  14959.  
  14960.  Setting the Current Record By Position
  14961.  
  14962.  The  MOVE dest statements let you make a record in the specified table
  14963.  current based on its position in the current index. Use the  BOF and  EOF
  14964.  functions to test the current position in the table. When a move is made, it
  14965.  is relative to the current position on the table specified by the
  14966.  filenumber%  argument.
  14967.  
  14968.  
  14969.  The syntax for the  MOVE dest statements and the position-testing functions
  14970.  is as follows:
  14971.  
  14972.   MOVEFIRST  #  filenumber%
  14973.   MOVELAST  #  filenumber%
  14974.   MOVENEXT  #  filenumber%
  14975.   MOVEPREVIOUS  #  filenumber% EOF   ( filenumber% )  BOF ( filenumber% )
  14976.  
  14977.  Each record in a table has a previous record and a next record, except the
  14978.  records that are first and last according to the current index. Given the
  14979.  current index, the beginning of the table is the position  preceding the
  14980.  first record; the end of the table is the position  following the last
  14981.  record.
  14982.  
  14983.  The effect of any of the  MOVE dest statements, or position-testing
  14984.  functions, is relative to the current position in the table specified by
  14985.  filenumber%. If there is a record following the current record,  MOVENEXT
  14986.  makes it the current record. If there is a record preceding the current
  14987.  record,  MOVEPREVIOUS makes it the current record. An attempt to use
  14988.  MOVENEXT from the last record in the table, or to use  MOVEPREVIOUS from the
  14989.  first record in the table moves the position to the end of file, or the
  14990.  beginning of file, respectively.
  14991.  
  14992.  You can test for the end-of-file and beginning-of-file conditions with the
  14993.  eof and bof functions. eof returns true (-1) when the current position is
  14994.  beyond the last record on the current index; bof returns true (-1) when the
  14995.  current position precedes the first record on the current index.
  14996.  
  14997.  If the current record is not already the first or last in the table, then
  14998.  MOVEFIRST and  MOVELAST make those records current. When a table contains no
  14999.  records, both Bof and eof return true (-1). If the table has no records, an
  15000.  attempt to execute any of the move dest statements will fail and eof will
  15001.  return true.
  15002.  
  15003.  
  15004.  Displaying the BookStock Table
  15005.  
  15006.  When your program specifies an index for the first time after opening a
  15007.  table, the current record is the one that sorts first in the index. Some
  15008.  preliminary BASIC code, plus the ISAM  RETRIEVE,  BOF,  EOF,  MOVENEXT, and
  15009.  MOVEPREVIOUS statements, are all you need to allow the user to move through
  15010.  an open table and view its records.
  15011.  
  15012.  
  15013.  A Typical ISAM Program
  15014.  
  15015.  Although you can write your program to allow users to create their own
  15016.  database files, it is more typical to supply the program with a database
  15017.  file having no records, with the filename hard-coded into the program. This
  15018.  database would contain all the predefined indexes you anticipate your user
  15019.  would need. Once the file contains these indexes, your program doesn't need
  15020.  the code used to create them. Supplying an empty database file that contains
  15021.  all the necessary indexes simplifies user interaction with the program, and
  15022.  also means ISAM can use less memory. For more information on the different
  15023.  ways you can include ISAM support in your programs, and how to use it during
  15024.  program development, see the sections "Starting ISAM for Use in QBX" and
  15025.  "Using ISAM with Compiled Programs" later in this chapter.
  15026.  
  15027.  The following example shows the module-level code of a program that opens
  15028.  several tables, including the BookStock table (discussed earlier) in the
  15029.  BOOKS.MDB database. It allows the user to view the records for all the books
  15030.  in a variety of orders, depending on which index is chosen. Only the
  15031.  module-level code and the Retriever procedure appear here. Other procedures,
  15032.  most of which control the user interface (to let the user add, edit, and
  15033.  search for specific records), are called as necessary. You can see those
  15034.  procedures in the disk files listed in the .MAK file BOOKLOOK.MAK (including
  15035.  BOOKLOOK.BAS, the main module; BOOKMOD1.BAS; BOOKMOD2.BAS; and
  15036.  BOOKMOD3.BAS). As noted previously, these example files are included on the
  15037.  Microsoft BASIC distribution disks and may be copied to your hard disk
  15038.  during Setup.
  15039.  
  15040.  Note that the error-handling routine at the bottom of the code handles error
  15041.  86, Illegal operation on a unique index. When an attempt is made to update
  15042.  or insert a record containing a duplicate value for a unique index, the
  15043.  error-handling routine prompts the user to enter a new value.
  15044.  
  15045.  To view the BOOKLOOK.BAS sample application, move to the directory where it
  15046.  was installed during Setup and invoke the ISAM TSR and QBX by typing the
  15047.  following two lines:
  15048.  
  15049.  PROISAM /I b:24qbx  BOOKLOOK
  15050.  
  15051.  The /Ib  argument to PROISAM specifies the number of buffers ISAM will need
  15052.  to manipulate data. Options for the ISAM TSR are fully explained in the
  15053.  section "Starting ISAM for Use in QBX" later in this chapter. You can see
  15054.  the effect of a combined index by using the Title+Author+ID index on the
  15055.  BookStock table in the example. The library contains five copies of the
  15056.  title  Structured BASIC Applied to Technology by Thomas A. Adamson. Using
  15057.  the combined index, the various copies of the book are presented in ID
  15058.  number order, whether you are moving forward in the table or backwards. If
  15059.  you just use the Title index (or any of the other indexes for which the
  15060.  fields are duplicates), the order moving forward is probably what you would
  15061.  expect, but the order moving backward may surprise you. This illustrates the
  15062.  fact that specific subordering is only guaranteed when a specific combined
  15063.  index is current.
  15064.  
  15065.  The Retriever procedure illustrates fetching a record from the database and
  15066.  placing it in a defined  recordvariable. CheckPosition updates the
  15067.  Viewing/Editing keys box when the first or last record in the table is
  15068.  reached.
  15069.  
  15070.  
  15071.  The MakeOver procedure referred to in the error-handling routine is not
  15072.  included in the listing, but illustrates how indexes can be created. You can
  15073.  use MakeOver (and the Reader procedure that it calls) to create an ISAM
  15074.  database containing the same tables as BOOKS.MDB, but containing records
  15075.  read from text files. The text files must be in the appropriate directory,
  15076.  but they don't all have to contain records. For example, you wouldn't
  15077.  necessarily want to start a database with entries in the BooksOut table. If
  15078.  the database already exists, the new records are appended to the appropriate
  15079.  table. In that case, the Duplicate definition error is generated when an
  15080.  attempt is made to create the indexes. The error is trapped, and the
  15081.  procedure ends. The fields of the text files should be comma delimited, and
  15082.  strings should be enclosed in double quotation marks.
  15083.  
  15084.  
  15085.  Setting the Current Record by Condition
  15086.  
  15087.  You can specify conditions to be met when making a record current with the
  15088.  SEEKGT,  SEEKGE, and  SEEKEQ statements. Their syntax is summarized as
  15089.  follows:
  15090.  
  15091.   SEEK operand   filenumber% ,  keyvalue ,  keyvalue...
  15092.  
  15093.  Depending on the  operand and the current index, these statements make the
  15094.  first matching record in the table specified by  filenumber the current
  15095.  record. A match occurs when an indexed value fulfills the  operand condition
  15096.  with respect to the specified  keyvalue.
  15097.  
  15098.  The following table indicates the operation associated with each of the
  15099.  operand specifiers:
  15100.  
  15101. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  15102.  Statement                               Makes this record current
  15103.  ────────────────────────────────────────────────────────────────────────────
  15104.   SEEKGT                                 The first record whose indexed
  15105.                                          value is greater than  keyvalue.
  15106.  
  15107.   SEEKGE                                 The first record whose indexed
  15108.                                          value is greater than, or equal to,
  15109.                                           keyvalue.
  15110.  
  15111.   SEEKEQ                                 The first record whose indexed
  15112.                                          value equals  keyvalue.
  15113.  
  15114.  
  15115.  
  15116.  
  15117.  
  15118.  The  keyvalue expression should have the same data type as the column
  15119.  represented by the current index. Although type coercion is performed
  15120.  between integer and Long values, you can experience overflow errors if you
  15121.  rely on automatic type coercion. With all other types, a  keyvalue error is
  15122.  detected when a value is of the wrong data type, and a Type mismatch error
  15123.  results. If the current index is a combined index, and the number of
  15124.  keyvalue values exceeds the number of constituent columns in the combined
  15125.  index, a Syntax error error message is generated when the program runs. A
  15126.  trappable error is generated if you attempt to execute a  SEEK  operand
  15127.  statement while the NULL index is the current index.
  15128.  
  15129.  If the number of  keyvalue values is fewer than the number of constituent
  15130.  columns in a combined index, the missing values are replaced by a value less
  15131.  than the smallest possible value, and the outcome depends on the  operand.
  15132.  For example, a  SEEKEQ will fail (and  EOF will return true). A   SEEKGE or
  15133.  SEEKGT will perform the seek based on whatever  keyvalue values are
  15134.  supplied, and will find the first record that matches the supplied  keyvalue
  15135.  values.  For example, assume the following type, variable, and index are
  15136.  created:
  15137.  
  15138.  
  15139.  TYPE ForExample
  15140.    FirstName   AS STRING * 20
  15141.    LastNameAS STRING * 25
  15142.  END TYPE
  15143.  DIM  ExampleVariable AS ForExample
  15144.  CREATEINDEX TableNum%, "FullNameIndex", 0, FirstName, LastName
  15145.  SETINDEX TableNum%, "FullNameIndex"
  15146.  
  15147.  If you execute the following statement, the seek will fail because no last
  15148.  name was provided:
  15149.  
  15150.  SEEKEQ TableNum%, "Tom"
  15151.  
  15152.  If you execute the following statement, the seek will find the first record
  15153.  for which FirstName is "Tom":
  15154.  
  15155.  SEEKGT TableNum%, "Tom"
  15156.  
  15157.  When a  SEEK operand statement fails (that is, when no match is made), there
  15158.  is no current record, and eof is set to true. Therefore, any immediately
  15159.  succeeding operation that depends on the current record (such as a
  15160.  RETRIEVE,  DELETE,  INSERT,  MOVENEXT, or  MOVEPREVIOUS) will cause a
  15161.  trappable error. You can prevent generation of this error by only executing
  15162.  statements that depend on the existence of a current record if  EOF returns
  15163.  false.
  15164.  
  15165.  
  15166.  Seeking on Strings and ISAM String Comparison
  15167.  
  15168.  When seeking a string, ISAM performs comparisons on a case-insensitive
  15169.  basis. Trailing blanks are ignored. This is a less strict comparison than
  15170.  that made by the BASIC equality operator. Additionally, international
  15171.  conventions are observed (see Appendix E, "International Character Sort
  15172.  Order Tables," in the  BASIC Language Reference for more information). The
  15173.  TEXTCOMP function allows you to perform string comparisons within your
  15174.  program in the same way they are compared by ISAM. Its syntax is as follows:
  15175.  
  15176.   TEXTCOMP  ( string1$,   string2$ )
  15177.  
  15178.  The  string1$ and  string2$ arguments are string expressions.  TEXTCOMP
  15179.  returns -1 if  string1$ compares less than  string2$, 1 if  string1$
  15180.  compares greater than  string2$, and 0 if the two strings compare equal.
  15181.  Only the first 255 characters of the respective strings are compared. For
  15182.  instance, if BigRec is the name of the  recordvariable into which  RETRIEVE
  15183.  places records from the BookStock table, and you want to print a quick list
  15184.  of the titles in the table that begin with the word QuickBASIC, you could
  15185.  use code like the following to find the first qualified title, then print
  15186.  the ensuing qualified titles:
  15187.  
  15188.  (Code appears in printed book, and in \SAMPCODE\BASIC directory)
  15189.  
  15190.  
  15191.  Because the comparison performed by  TEXTCOMP is case-insensitive, all
  15192.  variations of titles whose first word is QuickBASIC will be printed.
  15193.  
  15194.  Example
  15195.  
  15196.  The following listing begins with the fragment of the module-level code of
  15197.  BOOKLOOK.BAS that handles the SEEKFIELD case, which is selected when the
  15198.  user chooses Find Record. First the user is prompted to choose an index by
  15199.  ChooseOrder, the same procedure called in the REORDER case. Then, the
  15200.  SeekRecord procedure is called. It prompts the user to enter a value to
  15201.  search for on the chosen index. After the user enters the value, he or she
  15202.  is prompted to choose the condition (=, >, >=, <, or <=) that controls the
  15203.  search. The default search is set in this example to use  SEEKEQ, although
  15204.  the  SEEKGE statement would be a better default in many cases.
  15205.  
  15206.  SeekRecord calls procedures including ValuesAccepted (to make sure the input
  15207.  values have the same format as values in the tables), ClearEm and ShowIt (to
  15208.  show users what will be sought), GetKeyVals (in case the user is supplying
  15209.  values for a combined index), and GetOperand (to let the user choose whether
  15210.  the seek will be based on equality, greater or less than, greater than or
  15211.  equal to, or less than or equal to). Note that ShowIt shows what the user
  15212.  has entered in its appropriate field, not data from the database file
  15213.  itself. Other procedures include MakeString, DrawScreen, ShowRecord (which
  15214.  shows a database record), ShowMessage, EraseMessage, and IndexBox, all of
  15215.  which keep the user interface updated with each operation. Since names are
  15216.  in a single field, with last name first, TransposeName checks the format of
  15217.  a name entered and puts it in the proper format for searching or displaying.
  15218.  
  15219.  
  15220.  A Multi-Table Database
  15221.  
  15222.  So far, only the BookStock table has been shown in the BOOKS.MDB database.
  15223.  Even in the database of a hypothetical library, it would make sense to have
  15224.  another table in BOOKS.MDB to hold information about each library-card
  15225.  holder and a third table to hold information that relates specific copies of
  15226.  books to card holders who may have borrowed them. In fact, CardHolders is a
  15227.  reasonable name for one table, and BooksOut will do for the other. Figure
  15228.  10.9 illustrates a possible design for the CardHolders table. Figure 10.10
  15229.  illustrates the BooksOut table.
  15230.  
  15231.  The user-defined type by which a program gains access to the CardHolders
  15232.  table looks as follows:
  15233.  
  15234.  TYPE CardHolders
  15235.  CardNum AS INTEGER
  15236.  Zip AS LONG
  15237.  TheName AS STRING * 36
  15238.  CityAS STRING  *  26
  15239.  StreetAS STRING  *   50
  15240.  StateAS STRING * 2
  15241.  END TYPE
  15242.  
  15243.  The user-defined type by which a program gains access to the BooksOut table
  15244.  can have element names that duplicate those in other tables, since they are
  15245.  accessed using dot notation. The OutBooks type looks as follows:
  15246.  
  15247.  type BooksOut
  15248.  IDnum AS DOUBLE
  15249.  CardNum AS LONG
  15250.  DueDateAS DOUBLE
  15251.  end type
  15252.  Figure  10.10  The BooksOut Table in BOOKS.MDB
  15253.  
  15254.  When these tables are added, BOOKS.MDB contains three tables. The BookStock
  15255.  table is related to the CardHolders table through the IDnum column in the
  15256.  BooksOut table. For inventory purposes, you might manipulate just the
  15257.  BookStock table, and for doing a mailing of new-titles notices you could
  15258.  manipulate only the CardHolders table. To find out which card holder has
  15259.  withdrawn which copy of a particular book, you can get the IDnum from the
  15260.  BookStock table, then look up that IDnum in the BooksOut table. If the book
  15261.  was overdue, you could get the CardNum value from the BooksOut table, then
  15262.  look up that card number in the CardNum column in the CardHolders table to
  15263.  get information about the borrower.
  15264.  
  15265.  
  15266.  Example
  15267.  
  15268.  As your librarian traverses the BookStock table, he or she might want to
  15269.  check the due date on some of the books. Pressing the W key calls the
  15270.  GetStatus procedure to look up the book ID number in the BooksOut table and
  15271.  retrieve the corresponding BooksOut record. The ShowStatus procedure is
  15272.  called to display the due date of the book. Note that ShowStatus currently
  15273.  displays the date in raw serial form, as it appears in the table. To convert
  15274.  a serial date for normal display, you can replace the expression
  15275.  STR$(ValueToShow) with appropriate calls to the date and time function
  15276.  library (DTFMTER.QLB), supplied with Microsoft BASIC.
  15277.  
  15278.  BOOKLOOK.BAS contains other routines that use information from all the
  15279.  tables to automate library procedures. For example, the BooksBorrowed
  15280.  procedure is accessible when the CardHolders table is being displayed. If
  15281.  the user presses B (for Books Outstanding), BooksBorrowed compiles a list of
  15282.  the books checked out to that card holder. LendeeProfile gives information
  15283.  on the borrower of the title currently displayed from the BookStock table.
  15284.  The BorrowBook procedure (not shown in the following listing) allows a book
  15285.  to be checked out simply by typing in the user's name. If the user is not a
  15286.  valid card holder, a warning is displayed. If the user has a library card,
  15287.  BorrowBook displays the card holder's information so it can be checked to
  15288.  see what the due date will be, and also to see if the personal information
  15289.  needs to be updated. The ReturnBook procedure (not shown in the following
  15290.  listing) displays the name of the borrower, and calculates and displays any
  15291.  fines that may be due.
  15292.  
  15293.  
  15294.  Deleting Indexes and Tables
  15295.  
  15296.  The  DELETEINDEX and  DELETETABLE statements let you delete an index or a
  15297.  table from the database. These statements have the following syntax:
  15298.  
  15299.   DELETEINDEX  filenumber%,  indexname$
  15300.  
  15301.  DELETETABLE  database$,  tablename$
  15302.  
  15303. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  15304.  Argument                                Description
  15305.  ────────────────────────────────────────────────────────────────────────────
  15306.   filenumber%                            A numeric expression representing
  15307.                                          the table on which the index to be
  15308.                                          deleted was created.
  15309.  
  15310.   indexname$                             A string expression representing
  15311.  Argument                                Description
  15312.  ────────────────────────────────────────────────────────────────────────────
  15313.  indexname$                             A string expression representing
  15314.                                          the name of the index to be
  15315.                                          deleted.
  15316.  
  15317.   database$                              A string expression representing
  15318.                                          the name of the database
  15319.                                          containing the table to be deleted.
  15320.  
  15321.   tablename$                             A string expression representing
  15322.                                          the name of the table to be
  15323.                                          deleted.
  15324.  
  15325.  
  15326.  
  15327.  
  15328.  
  15329.  It isn't always easy to anticipate which columns a user may want to index
  15330.  when using a database. As an advanced feature of a program you might want to
  15331.  let a user create indexes during run time. If so,  DELETEINDEX can be used
  15332.  to remove the specified index from the database when it is no longer needed.
  15333.  
  15334.  
  15335.  You can use the  DELETETABLE statement to delete an old table when you no
  15336.  longer need any of its data records. Once you delete a table and close the
  15337.  database, there is no way to recover the records. You should assume that the
  15338.  table is deleted from the file immediately upon execution of this statement,
  15339.  rather than at some future time (for example, when the file is closed). Note
  15340.  that when you delete a table, all information (including indexes, etc.) in
  15341.  the data dictionary relating to the table is also deleted.
  15342.  
  15343.  You cannot practically write routines to permit a user to create custom
  15344.  tables during run time, since the user-defined type that describes a table
  15345.  must already exist when the program begins. Although it isn't covered here,
  15346.  you can write routines that permit a user to create a table that duplicates
  15347.  the columns of a table already in the database. For example, you might want
  15348.  to permit copying of a subset of a table's records to a table of the same
  15349.  type.
  15350.  
  15351.  Executing deletetable or deleteindex commits any pending transactions.
  15352.  
  15353.  
  15354.  ISAM Naming Convention
  15355.  
  15356.  Some parts of an ISAM database require names (for example, tables, columns,
  15357.  and indexes), and these names must conform to the ISAM naming convention.
  15358.  The ISAM convention is essentially a subset of the BASIC convention, as
  15359.  shown in the following table:
  15360.  
  15361. ╓┌─────────────────────────────────────┌─────────────────────────────────────╖
  15362.  ────────────────────────────────────────────────────────────────────────────
  15363.  30 characters or fewer.               40 characters or fewer.
  15364.  
  15365.  Alphanumeric characters only,         Alphanumeric characters, plus the
  15366.  including  A-Z, a-z, and 0-9.         BASIC  type-declaration characters,
  15367.                                        where appropriate (variables and
  15368.                                        functions).
  15369.  
  15370.  Must begin with alphabetic character.  Must begin with alphabetic character,
  15371.                                        but only  DEF FN functions can begin
  15372.                                        with "fn."
  15373.  
  15374.  ────────────────────────────────────────────────────────────────────────────
  15375. 
  15376.  No special characters allowed.        The period is not allowed in the
  15377.                                        names of elements within a
  15378.                                        user-defined type. Since these are
  15379.                                        the names BASIC and ISAM have in
  15380.                                        common, there is no conflict.
  15381.  
  15382.  Not case sensitive.                   Not case sensitive.
  15383.  
  15384.  
  15385.  
  15386.  
  15387.  
  15388.  
  15389.  Starting ISAM for Use in QBX
  15390.  
  15391.  Microsoft BASIC includes two terminate-and-stay-resident (TSR) programs,
  15392.  PROISAM.EXE and PROISAMD.EXE. You can use either of these programs to place
  15393.  the ISAM routines in memory when you develop or run database programs within
  15394.  the QBX environment. The benefit of this approach is that, when you are
  15395.  working on programs or modules that don't need ISAM, the amount of memory
  15396.  required by QBX is substantially reduced.
  15397.  
  15398.  PROISAM.EXE contains all ISAM routines needed to run most database programs.
  15399.  It does not contain the "data dictionary" statements --  CREATEINDEX,
  15400.  DELETEINDEX, and deletetable. It contains a restricted version of the
  15401.  open... FOR ISAM statement that will open a database or table, but will not
  15402.  create it if it does not already exist. Since you often will not need to
  15403.  program the creation and deletion of indexes and tables within an end-user
  15404.  program, PROISAM.EXE is usually sufficient.
  15405.  
  15406.  PROISAMD.EXE contains all the ISAM routines.
  15407.  
  15408.  You use the following syntax to start either PROISAM.EXE or PROISAMD.EXE:
  15409.  
  15410.  {PROISAM | PROISAMD} /Ib:  pagebuffers /Ie:  emsreserve /Ii:  indexes /D
  15411.  
  15412. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  15413.  Argument                                Description
  15414.  ────────────────────────────────────────────────────────────────────────────
  15415.  /Ib:  pagebuffers                       Increases the amount of
  15416.  Argument                                Description
  15417.  ────────────────────────────────────────────────────────────────────────────
  15418.  /Ib:  pagebuffers                       Increases the amount of
  15419.                                          conventional memory reserved for
  15420.                                          ISAM's buffers. The defaults are 6
  15421.                                           pagebuffers (12K of 2K pages) for
  15422.                                          PROISAM, and 9  pagebuffers (18K
  15423.                                          of 2K pages) for PROISAMD. There
  15424.                                          is also 3 - 5K used for data by
  15425.                                          PROISAM and 14 - 16K for PROISAMD.
  15426.                                          Maximum value for  pagebuffers is
  15427.                                          512. However, since DOS only
  15428.                                          provides 640K, this maximum is not
  15429.                                          possible in conventional memory.
  15430.                                          Determine the optimal value for a
  15431.                                          specific program by
  15432.                                          experimentation. Note that the
  15433.                                          default values for  pagebuffers
  15434.                                          are the minimums necessary for an
  15435.                                          ISAM program, not the optimal or
  15436.                                          average values. If you (or your
  15437.  Argument                                Description
  15438.  ────────────────────────────────────────────────────────────────────────────
  15439.                                         average values. If you (or your
  15440.                                          anticipated users) don't have EMS,
  15441.                                          more than the default number of
  15442.                                          pagebuffers may be necessary for
  15443.                                          running the program. Even if a
  15444.                                          program runs with the default
  15445.                                          number of  pagebuffers, specifying
  15446.                                          the most buffers possible improves
  15447.                                          ISAM performance. Note, however,
  15448.                                          that if no EMS is available, too
  15449.                                          high a value for /Ib could
  15450.                                          allocate so much memory to ISAM
  15451.                                          buffers that the TSR would not be
  15452.                                          able to remove itself from memory
  15453.                                          when invoked with the /D option.
  15454.                                          When EMS is available, the amount
  15455.                                          specified with /Ib  is taken first
  15456.                                          from EMS, and if that is not
  15457.                                          sufficient, the rest is taken from
  15458.  Argument                                Description
  15459.  ────────────────────────────────────────────────────────────────────────────
  15460.                                         sufficient, the rest is taken from
  15461.                                          conventional memory.
  15462.  
  15463.  /Ie:  emsreserve                        If you have expanded memory, ISAM
  15464.                                          will automatically use up to 1.2
  15465.                                          megabytes of it for buffers (that
  15466.                                          is, it will place up to 512 2K
  15467.                                          pagebuffers, plus about 10 percent
  15468.                                          overhead space,  into EMS). ISAM
  15469.                                          takes as much EMS memory as
  15470.                                          possible by default, which frees
  15471.                                          conventional memory for other uses
  15472.                                          while improving performance. The
  15473.                                          number given in the  /Ie option
  15474.                                          lets you specify how much expanded
  15475.                                          memory should be reserved for
  15476.                                          purposes other than ISAM. This
  15477.                                          limits the amount of EMS that ISAM
  15478.                                          uses for  pagebuffers, since ISAM
  15479.  Argument                                Description
  15480.  ────────────────────────────────────────────────────────────────────────────
  15481.                                         uses for  pagebuffers, since ISAM
  15482.                                          will take only EMS between the
  15483.                                          emsreserve specified and the total
  15484.                                          EMS available. The default value
  15485.                                          for  emsreserve is 0. State values
  15486.                                          in kilobytes; for example, /Ie:500
  15487.                                          specifies 500K should be left
  15488.                                          available for other purposes. In
  15489.                                          practice, you only need to specify
  15490.                                          /Ie if your program code (or a
  15491.                                          loaded Quick library) actually
  15492.                                          manages EMS memory. In such a case,
  15493.                                          you should also specify the /Es
  15494.                                          option when starting QBX or when
  15495.                                          compiling with BC. Specifying a
  15496.                                          value of -1 for /Ie reserves all
  15497.                                          EMS for other uses, and none is
  15498.                                          used by ISAM.
  15499.  
  15500.  Argument                                Description
  15501.  ────────────────────────────────────────────────────────────────────────────
  15502. 
  15503.  /Ii:  indexes                           Specifies the number of non-NULL
  15504.                                          indexes used in the program. Use
  15505.                                          this option if your program has
  15506.                                          more than 30 indexes. If you don't
  15507.                                          specify a value for this option,
  15508.                                          ISAM assumes your database
  15509.                                          contains no more than 30 defined
  15510.                                          indexes. If this value is too low,
  15511.                                          your program may fail. Maximum
  15512.                                          permissible value is 500.
  15513.  
  15514.  /D                                      Removes the ISAM TSR from memory.
  15515.  
  15516.  
  15517.  
  15518.  
  15519.  
  15520.  
  15521.  
  15522.  Note
  15523.  
  15524.  When you use transactions, ISAM keeps a transaction log. The name of the log
  15525.  file is guaranteed to be unique, so multiple ISAM programs can be run in
  15526.  simultaneous windows in an operating environment like Windows 386 without
  15527.  the danger of conflicts within the log files. In versions of DOS earlier
  15528.  than 3.0 however, the name of the file is ~proisam.LOG, and it is created in
  15529.  the /TMP directory by default; otherwise it goes in the current directory.
  15530.  If you set your /TMP environment variable to a RAM drive, transaction
  15531.  logging will be faster. Don't confuse this log file with the PROISAM.EXE
  15532.  TSR. If a log file appears in your current directory, you can delete it;
  15533.  ISAM overwrites the old log file each time a transaction is initiated.
  15534.  
  15535.  
  15536.  Estimating Minimum ISAM Buffer Values
  15537.  
  15538.  When you load the ISAM TSR, it requires memory beyond its disk-file size
  15539.  because it reserves a certain amount of memory for buffers in which it does
  15540.  most of its work. The actual amount of buffer space reserved depends on
  15541.  which version of the TSR you are using, and the number you specify for the
  15542.  /Ib and /Ii options. The defaults represent the absolute minimum required
  15543.  for a minimal ISAM program. Having more buffers available always improves
  15544.  performance, and in some cases may be necessary for a program to run at all.
  15545.  Additional buffers improve performance because, when the buffers are full,
  15546.  some of their contents is written back to the disk. This causes a disk
  15547.  access to update the disk file, and another access when the material that
  15548.  was swapped out has to be swapped back in. The swapping system is based on a
  15549.  least-recently-used (LRU) algorithm. The more buffers that are available,
  15550.  the less likely it is that any particular piece of material will need to be
  15551.  swapped in or out. To get a basic idea of the minimum number of buffers your
  15552.  program needs, use the maximum of 9 or 6 (the default buffer settings,
  15553.  depending on whether you use PROISAM or PROISAMD), or the following formula:
  15554.  
  15555.   pagebuffers = 1 +  w +  x + 4 y + 8 z
  15556.  
  15557.  In the preceding formula:
  15558.  
  15559.   w = the maximum number of open tables containing data
  15560.  
  15561.   x = total of non-NULL indexes used in the program
  15562.  
  15563.   y = 1, if  INSERT or  UPDATE statements are executed, otherwise 0
  15564.  
  15565.   z = 1, if a  CREATEINDEX statement is executed, otherwise 0
  15566.  
  15567.  Depending on the density of ISAM statements in any section of code, it is
  15568.  possible that the default number of buffers will not be adequate to handle
  15569.  the necessary processing.
  15570.  
  15571.  Any EMS (up to 1.2 megabytes) that is available is used for ISAM buffers.
  15572.  This leaves an equivalent amount of conventional memory for other purposes.
  15573.  Note however, that only the ISAM buffers are placed in EMS, the ISAM code
  15574.  (represented approximately by the disk-file size) itself resides in
  15575.  conventional memory. Use the /Ie option to reserve any EMS that may be
  15576.  needed when your program actually manages EMS internally, or works
  15577.  concurrently with any other programs that use EMS.
  15578.  
  15579.  
  15580.  ISAM and Expanded Memory (EMS)
  15581.  
  15582.  As noted above, ISAM uses conventional and expanded memory as long as the
  15583.  expanded memory conforms to the Lotus-Intel-Microsoft Expanded Memory
  15584.  Specification (LIM 4.0). Using expanded memory correctly can enhance both
  15585.  the performance and capacity of programs. The actual management of expanded
  15586.  memory is done for you by BASIC within the limits you set with the /Ie ISAM
  15587.  option and the /Es QBX  option. If expanded memory is available, ISAM uses
  15588.  the difference between the total and the amount you specify with the /Ie
  15589.  option, up to a maximum of about 1.2 megabytes.
  15590.  
  15591.  There are several factors to consider in using the /Ib and /Ie ISAM options
  15592.  (and the /Es and /Ea QBX options), including the following:
  15593.  
  15594.    ■   The system on which you develop a program may have different memory
  15595.        resources than the system on which your user runs the program.
  15596.  
  15597.    ■   If your program performs explicit EMS management, or uses a library
  15598.        that does, you need to reserve the necessary EMS when starting the TSR
  15599.        or compiling the program. QBX and BC have options dealing with EMS
  15600.        (/Es and /Ea) that interact with ISAM's use of EMS in certain
  15601.        situations. You may need to use the /Es option when invoking QBX or
  15602.        BC.
  15603.  
  15604.  
  15605.  In order to provide for users who may not have EMS, you should always
  15606.  specify an optimal setting for the /Ib ISAM option. Beyond this, in most
  15607.  cases, it should suffice to allow the defaults to determine the apportioning
  15608.  of EMS between ISAM and other EMS usage. The EMS defaults are designed to
  15609.  make the best use of whatever combinations of conventional memory and EMS
  15610.  may be available. In general, trying to optimize values between /Ib and /Ie
  15611.  only makes sense if your program itself actually performs expanded memory
  15612.  management, in which case you should be sure there is enough EMS available
  15613.  for it at run time.
  15614.  
  15615.  For example, if you want ISAM to use 22 buffers, the buffers require 44K
  15616.  (each buffer requires 2K) plus up to 5K for data in PROISAM.EXE, and up to
  15617.  16k for data with PROISAMD.EXE. The 22 buffers plus overhead for PROISAM.EXE
  15618.  will require about 49K. However, if EMS is available and you don't specify a
  15619.  value for /Ie, ISAM will use one megabyte of  expanded memory. If you have
  15620.  only one megabyte of expanded memory available, and you have written your
  15621.  program to explicitly manage 500K of that, you need to specify 500 as a
  15622.  value for the /Ie  option. This reserves the amount of expanded memory your
  15623.  program manages. If an end user of the program has no EMS available beyond
  15624.  the 500K you have reserved, all the memory needed for the ISAM  pagebuffers
  15625.  is taken from conventional memory.
  15626.  
  15627.  The actual algorithm used by ISAM for apportioning buffers and EMS is as
  15628.  follows:
  15629.  
  15630.     1. Reserve EMS as specified by /Ie. If less EMS is available than is
  15631.        specified, reserve all EMS.
  15632.  
  15633.     2. Allocate non-buffer memory needed by ISAM. Take this first from
  15634.        available EMS, then, if that is insufficient, take the remainder from
  15635.        conventional memory.
  15636.  
  15637.     3. Allocate the number of buffers specified by the /Ib option, first from
  15638.        EMS, then, if that is insufficient, from conventional memory.
  15639.  
  15640.     4. If more EMS is available than was needed to satisfy /Ib, keep
  15641.        allocating buffers from EMS until all EMS is consumed, or until the
  15642.        ISAM limit is reached (512 buffers, or about 1.2 megabytes including
  15643.        overhead).
  15644.  
  15645.     5. Release the EMS reserved in step 1 (by the /Ib option) for use by
  15646.        other programs.
  15647.  
  15648.  
  15649.  When you create an executable file from within QBX, whether or not the
  15650.  program will need to have the TSR invoked depends on options you chose for
  15651.  ISAM during Setup, as explained in the next section.
  15652.  
  15653.  
  15654.  Using ISAM with Compiled Programs
  15655.  
  15656.  There are several types of executable files you can produce for ISAM
  15657.  programs, depending on your needs and the needs of your users. Programs that
  15658.  require the presence of a TSR make sense if you are distributing several
  15659.  distinct ISAM-dependent programs on the same disk. Each individual program
  15660.  could be significantly smaller if all made use of the ISAM routines from the
  15661.  TSR. If you compile programs so they need the run-time module, you can have
  15662.  the ISAM routines linked in as part of the run-time module, or have the user
  15663.  start one of the TSR programs before starting the application.
  15664.  
  15665.  In any of these cases, you have a choice of TSRs. PROISAMD.EXE contains all
  15666.  the ISAM routines, including the data dictionary routines (for creating and
  15667.  deleting indexes, tables, databases, etc.). At various times, not all the
  15668.  routines are necessary. For example, if you create a program and supply an
  15669.  empty database file (one with tables and indexes, but no data records), your
  15670.  program would have no real need to create tables or indexes, since they
  15671.  would already exist in the file. In such cases, you could supply the smaller
  15672.  TSR program (called PROISAM.EXE) to conserve memory. Table 10.2 describes
  15673.  the requirements for various ISAM configurations.
  15674.  
  15675.  
  15676.  When you ran the Setup program, you had the opportunity to choose libraries
  15677.  that would create executable files containing all the ISAM routines. You
  15678.  could also choose an option that created run-time modules that contained all
  15679.  the ISAM routines. If you didn't choose these options, your executable files
  15680.  (and run-time modules) will require your users to run either PROISAM.EXE or
  15681.  PROISAMD.EXE before using the database programs. To qualify for Case 1 in
  15682.  Table 10.2, you should have specified the full ISAM or reduced ISAM option
  15683.  for BCL70 mso.LIB. Then, when the executable file is created, you need to
  15684.  have either PROISAMD.LIB or PROISAM.LIB in your current directory or library
  15685.  search path. For Case 2 you should have chosen "ISAM Routines in TSR" during
  15686.  Setup. In this case you don't need to have PROISAM.LIB or PROISAMD.LIB
  15687.  accessible when the executable file is created. You made the same selections
  15688.  regarding run-time modules during Setup (cases 3 and 4).
  15689.  
  15690.  When you compile a stand-alone program from the command line (that is, one
  15691.  that does not require the presence of the TSR at run time), you can use /Ib,
  15692.  /Ie, and /Ii as options to the BC command. Their syntax and general effects
  15693.  are the same with the stand-alone program as described in the previous
  15694.  section. If your program uses run-time overlays, the EMS is automatically
  15695.  allocated for the overlays first, before ISAM. If you don't want overlays to
  15696.  use EMS you can link the program with NOEMS.OBJ and overlays will be swapped
  15697.  to disk instead. If overlays use EMS, ISAM will take whatever remains after
  15698.  EMS allocation for the overlays -- up to 1.2 megabyte. If your program does
  15699.  internal EMS memory management, it can only be done from within a
  15700.  non-overlayed module. However, in such a case, you should probably link with
  15701.  NOEMS.OBJ. Also, remember to compile with the /Es option to BC. You should
  15702.  always specify a correct value for /Ii if  your program uses more than 30
  15703.  indexes.
  15704.  
  15705.  However, note that sharing expanded memory between ISAM and other uses
  15706.  inhibits ISAM performance, since the ISAM  buffers and other EMS usage must
  15707.  use the same EMS window to access the expanded memory. This means that with
  15708.  each call to the ISAM library, the EMS state must be saved and restored. If
  15709.  you must share EMS memory between ISAM and other things, use the relative
  15710.  amounts that optimize ISAM performance. In such cases, use the /Es option to
  15711.  guarantee EMS save and restore with mixed-language code that manages EMS.
  15712.  
  15713.  Important
  15714.  
  15715.  When you compile a program from within QBX, only the QBX options are passed
  15716.  to the compiler. This is fine if an ISAM stand-alone program will use the
  15717.  TSR, since the buffer and index options are specified when the TSR is
  15718.  invoked. However, if the program is to have the ISAM routines included in
  15719.  the executable file, you must compile the program's main module from the
  15720.  command line, and specify the appropriate /I and /E options as arguments to
  15721.  BC.
  15722.  
  15723.  
  15724.  Practical Considerations when Using EMS
  15725.  
  15726.  Note that the ISAM /Ie option  and the QBX options /E, /Ea, and /Es have the
  15727.  effect of reserving EMS for programs that use internal EMS management (or
  15728.  other applications), rather than specifically limiting the amount of EMS
  15729.  used by the program for which the option is supplied. ISAM uses EMS to
  15730.  improve performance by radically reducing the frequency of disk access. In
  15731.  general, the automatic apportioning of conventional and EMS memory should
  15732.  cover the widest range of situations best, because with each allocation of
  15733.  EMS, whatever is available is used whenever it can be.
  15734.  
  15735.  During development of a very large program, it may be more beneficial to
  15736.  reserve most available EMS for QBX (except the minimum ISAM needs for
  15737.  buffers and indexes), since the speed of ISAM is probably not as important
  15738.  as the ability to have QBX place units of code in EMS, thus increasing the
  15739.  potential size of the source files you can fit in QBX. However, since QBX
  15740.  only places units of code in the 0.5-16K range in expanded memory, this is
  15741.  only optimal if your coding style is to use small to moderate code units (
  15742.  SUB and  FUNCTION procedures, and module-level code). In a compiled program,
  15743.  the ISAM performance in the executable file is the most important feature,
  15744.  so compiling with high values for /Ib (just to provide for users with no
  15745.  EMS) and no specification for /Ie should offer the best results. ISAM never
  15746.  uses more than 1.2 megabytes, so all remaining EMS is automatically
  15747.  available for other uses. Such other uses include your program's code units,
  15748.  arrays up to 16K (if /Ea is specified), or explicit EMS management within
  15749.  the program.
  15750.  
  15751.  Note however, that dividing EMS between ISAM and other uses slows ISAM and
  15752.  QBX performance to some degree. It may make sense during program
  15753.  development, but might not be satisfactory in a compiled program. If you
  15754.  want this type of sharing, use the /Ie option to reserve EMS for the
  15755.  overlays, plus the /Es option to ensure EMS saving and restoration. EMS in a
  15756.  compiled program is automatically used for run-time overlays (if you use
  15757.  them). To prevent EMS sharing, compile with BC without using the /Ie option
  15758.  or /Es, and specify /E:0 to prevent use of EMS for anything but ISAM. For
  15759.  more information on using the /Es option for QBX, see Chapter 3, "Memory
  15760.  Management for QBX," in  Getting Started. Run-time overlays are discussed in
  15761.  Chapters 15, "Optimizing Program Size and Speed," and 18, "Using LINK and
  15762.  LIB," of this book.
  15763.  
  15764.  Note
  15765.  
  15766.  BASIC releases EMS when the program terminates due to a run-time error, as
  15767.  well as an  END, stop, or system statement. If a program terminates for some
  15768.  other reason while EMS is being used, that portion of EMS will not be
  15769.  available again until the EMS manager is restarted. If the EMS manager is
  15770.  the one used in Microsoft Windows 386, you can simply exit from Windows,
  15771.  then start Windows again to recover the EMS. If your EMS manager is started
  15772.  by an entry in a CONFIG.SYS file, you may need to reboot to recover use of
  15773.  EMS.
  15774.  
  15775.  
  15776.  TSRs and Installation/Deinstallation Order
  15777.  
  15778.  If you (or your users) will be using other TSR programs besides the ISAM
  15779.  TSR, they should be installed before the ISAM TSR. The reason for this is
  15780.  that the ISAM TSR is only needed when the ISAM program is run. If you finish
  15781.  with your ISAM program and have installed another TSR after ISAM, you will
  15782.  have to remove any more-recently installed TSR programs before you can
  15783.  successfully remove the ISAM TSR. Otherwise, the /D option to the ISAM TSR
  15784.  will remove ISAM from memory, but the memory cannot be used by the other
  15785.  programs, and the operating system may be destabilized. If you attempt to
  15786.  remove TSRs in an improper order, a warning message is displayed.
  15787.  
  15788.  
  15789.  Block Processing Using Transactions
  15790.  
  15791.  To accommodate data entry errors, ISAM includes three transaction statements
  15792.  and one transaction function that allow you to restore a database to a
  15793.  previous state. By using these in conjunction with the  CHECKPOINT statement
  15794.  (which lets you explicitly write all open databases to disk) you can enhance
  15795.  the integrity of your user's databases.
  15796.  
  15797.  When you use  UPDATE to change  a record in a table, the change is made
  15798.  immediately. However, the actual writing of data to disk is done at periodic
  15799.  intervals determined by the ISAM engine. The checkpoint statement requires
  15800.  no argument. It simply forces the current state of all open databases to be
  15801.  written to disk.
  15802.  
  15803.  Conversely, you can code your program to allow a user (or a routine in the
  15804.  program) to retract a sequence of operations either selectively or as a
  15805.  block. Using transactions (block processing) can help ensure consistency to
  15806.  operations performed on multiple tables and multiple databases.
  15807.  
  15808.  The following table briefly describes these block-processing statements:
  15809.  
  15810. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  15811.  Statement                               Description
  15812.  ────────────────────────────────────────────────────────────────────────────
  15813.  BeginTrans                              Starts a transaction log of all
  15814.                                          operations.
  15815.  Statement                               Description
  15816.  ────────────────────────────────────────────────────────────────────────────
  15817.                                         operations.
  15818.  
  15819.  CommitTrans                             Ends maintenance of the
  15820.                                          transaction log.
  15821.  
  15822.  SavePoint                               Marks points within the
  15823.                                          transaction log to which the
  15824.                                          transaction can be rolled back.
  15825.  
  15826.  RollBack All                            Restores the state of the database
  15827.                                          to what it was at a specified save
  15828.                                          point or at the beginning of the
  15829.                                          transaction.
  15830.  
  15831.  
  15832.  
  15833.  
  15834.  
  15835.  
  15836.  Specifying a Transaction Block
  15837.  
  15838.  Bracketing certain portions of your code with begintrans and committrans
  15839.  statements provides a mechanism to retract all changes made to a database
  15840.  within the transaction block. No results of processing within the block will
  15841.  become part of the database unless everything resulting from processing in
  15842.  the block becomes part of the database. By following the block with a
  15843.  CHECKPOINT statement, you can guarantee that all results of the block are
  15844.  written immediately to disk. Save points allow you to define points within
  15845.  transactions to which the state of the database can be rolled back. Don't
  15846.  confuse a save point with the checkpoint  command. The savepoint function
  15847.  doesn't write records to disk. It simply returns integer identifiers for
  15848.  each of the markers it sets in the transaction log.
  15849.  
  15850.  
  15851.  The Transaction Log
  15852.  
  15853.  The  BEGINTRANS statement causes ISAM to start logging every change made to
  15854.  the database. Note that ISAM only logs changes made to the database -- it
  15855.  does not keep track of execution flow of your program. After  BEGINTRANS is
  15856.  executed, changes are still made to the database, but ISAM can backtrack
  15857.  through those changes by referring to the transaction log. Included in the
  15858.  log entries are each of the save points you set with the  SAVEPOINT
  15859.  function. If a  ROLLBACK statement is executed at some point within the
  15860.  transaction block, ISAM checks the log and restores the database to the
  15861.  state it was in when the specified save point was executed. This includes
  15862.  removing any changes that were made to data records since the save point,
  15863.  and restoring all indexes to the state they were in at the save point.
  15864.  BEGINTRANS and  COMMITTRANS take no arguments.
  15865.  
  15866.  
  15867.  Using Save Points
  15868.  
  15869.  Since ISAM maintains only one transaction log, you cannot nest one
  15870.  transaction within another. However, the ability to set multiple save points
  15871.  within a transaction supplies similar functionality with greater
  15872.  flexibility. While  BEGINTRANS and  COMMITTRANS serve as block delimiters
  15873.  for multiple ISAM data exchange calls, you can use save points to delimit
  15874.  smaller data-exchange blocks within a transaction. The savepoint function
  15875.  takes no argument, but returns an integer that identifies the save point
  15876.  that was set.
  15877.  
  15878.   ROLLBACK uses two forms, as shown in the following table:
  15879.  
  15880. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  15881.  ────────────────────────────────────────────────────────────────────────────
  15882.   ROLLBACK   savepoint                   The  savepoint is an integer
  15883.                                          identifier corresponding to a save
  15884.                                          point returned by the  SAVEPOINT
  15885.                                          function. The effect of a
  15886.                                          ROLLBACK statement is the
  15887.                                          restoration of the database to the
  15888.                                          state it was at the named save
  15889.                                          point. If no  savepoint is
  15890.                                          specified, the rollback proceeds
  15891.                                          to the next available save point.
  15892.  
  15893.   ROLLBACK ALL                           Restores the database to the state
  15894.                                          it had when the most recent
  15895.                                          begintrans was executed.
  15896.  
  15897.  
  15898.  
  15899.  
  15900.  
  15901.  If your program executes a  ROLLBACK statement outside a transaction block,
  15902.  specifies a non-existent save point, or executes a qualified  ROLLBACK when
  15903.  there are no save points within the transaction block, a trappable error is
  15904.  generated. Data dictionary operations (for example,  DELETEINDEX) cannot be
  15905.  rolled back, since no record of them is kept in the transaction log.
  15906.  
  15907.  A transaction can be committed without an explicit  COMMITTRANS being
  15908.  executed. For example, if there is an error in an attempt to open an ISAM
  15909.  table or database, a  CLOSE statement is implicitly executed on the table or
  15910.  database. Any time an ISAM  CLOSE is performed (either explicitly or
  15911.  implicitly), any pending transaction is committed. It is not good practice
  15912.  to execute table-level and database-level operations within a transaction,
  15913.  since errors can commit the transaction indirectly. Even if an error doesn't
  15914.  occur, an implict  CLOSE may occur, committing the transaction. For example,
  15915.  if you delete a table from a database, and there is no other table open
  15916.  within the database, an implicit  CLOSE is performed on the database. Such a
  15917.   CLOSE causes all pending transactions (even in another open database) to be
  15918.  committed. You should limit the use of transactions as a programming tool
  15919.  for controlling record-level operations.
  15920.  
  15921.  The following example code illustrates a transaction block abstracted from
  15922.  the program BOOKLOOK.BAS. The initial fragment from the module-level code
  15923.  intercepts the code representing what the user wants to do and calls the
  15924.  EditCheck procedure to determine whether a transaction is pending, to be
  15925.  begun, or to be committed.
  15926.  
  15927.  The EDITRECORD case in the BOOKLOOK.BAS module-level code is the only case
  15928.  that uses transactions. Each time the user presses enter after editing a
  15929.  field in a table, a savepoint statement is executed just before the record
  15930.  is updated. The value returned by savepoint is saved in an element of the
  15931.  array variable Marker, as long as the user keeps editing fields, without
  15932.  performing other menu operations, such as displaying or searching for a new
  15933.  record. savepoint statements are executed after each succeeding edit. When
  15934.  the user makes a menu choice other than Edit, the transaction is committed.
  15935.  Any time prior to the commitment, the user can choose to Undo edits within
  15936.  the transaction, either as a group (rollback all), or singly in the reverse
  15937.  order from which they were entered, by pressing U (Undo) or Ctrl+U (Undo
  15938.  All).
  15939.  
  15940.  
  15941.  Maintaining Physical and Logical Data Integrity
  15942.  
  15943.  The "physical integrity" of a database is what guarantees you will be able
  15944.  to use the database. ISAM maintains this physical integrity as a matter of
  15945.  course whenever you use the database. However, circumstances can intervene
  15946.  that corrupt a database. For example, power to your system could be
  15947.  interrupted while ISAM is in the process of actually writing data to disk.
  15948.  When such a "crash" occurs while a file is open, the consequences are
  15949.  unpredictable. For example, even if no drastic damage is done, the crash may
  15950.  occur before all relevant indexes in the database can be updated. Similarly,
  15951.  some physical mishap could corrupt the file while you are not working on the
  15952.  database. If someone opened the file with another program such as a word
  15953.  processor, and modified it, its physical integrity would be compromised. In
  15954.  these types of situations, you can use the ISAMREPR utility to recover the
  15955.  undamaged parts of the database and restore its physical integrity. Making
  15956.  frequent backups of database files is an important element of maintaining
  15957.  physical integrity. You can do this with any commercial backup program, or
  15958.  simply with the operating system COPY command, since all the parts of an
  15959.  ISAM database are contained within a single disk file.
  15960.  
  15961.  
  15962.  ISAM speed depends on the fact that it writes changes to the disk
  15963.  periodically, rather than immediately. At the same time, something like an
  15964.  equipment failure could occur between the time a change is made in a table
  15965.  and the time the change is written to disk. In such a case, the data would
  15966.  be lost. This type of loss can occur when the program sits idle for a while
  15967.  after changes are made to a table. To minimize the danger, BASIC checks the
  15968.  amount of time that passes and compares that to the number of times the
  15969.  keyboard is polled while a program is sitting in an "idle loop." For
  15970.  example, when  INKEY$ or an  INPUT statement  is used to poll the keyboard
  15971.  for input, if a certain number of keyboard checks are made, or a certain
  15972.  amount of time passes without a keystroke, ISAM writes all changed buffers
  15973.  to disk. As soon as a keystroke occurs or buffers are flushed, the checking
  15974.  process starts anew.
  15975.  
  15976.  During transactions, the changes are actually made to the file on the normal
  15977.  basis. Their purpose is as a programming aid, rather than a form of
  15978.  integrity insurance. If changes are rescinded by rollbacks before the
  15979.  transaction is committed, the transaction log is used to restore the
  15980.  database to the proper state. If an equipment failure occurs before a
  15981.  transaction is committed, the disk file represents the state to which the
  15982.  transaction had progressed, rather than the state prior to the transaction.
  15983.  This means that the changes cannot be rescinded by rollbacks if the failure
  15984.  occurs within the transaction. Conversely, when a transaction concludes, not
  15985.  everything is necessarily written immediately to the physical disk file. The
  15986.  ISAM engine performs disk writes using algorithms that give priority to
  15987.  performance. Therefore, there may be a lag between the time when a
  15988.  transaction is committed and time the final pieces of data are written to
  15989.  disk. As in other situations, if no keyboard input occurs within a certain
  15990.  period after the transaction is committed, ISAM automatically writes the
  15991.  state of the tables to disk. In the event of a loss of power between the end
  15992.  of the transaction and automatic disk write, changes not yet written to disk
  15993.  can be lost. This could include some part of the end of the transaction.
  15994.  Therefore, although this eventuality is very unlikely, ISAM internally
  15995.  cannot guarantee a per-transaction level of data integrity.
  15996.  
  15997.  In a program compiled with the /D option, an implicit  CHECKPOINT statement
  15998.  is performed each time a  DELETE,  INSERT,  UPDATE, or ISAM  CLOSE statement
  15999.  is executed. If you are very concerned with obtaining maximum data
  16000.  integrity, and are willing to sacrifice speed, compile your program with /D.
  16001.  
  16002.  You can write code to enhance the logical and physical integrity of your
  16003.  database. The  CHECKPOINT statement forces a physical write to disk of all
  16004.  data in the ISAM buffers. However, with a large database, placing
  16005.  CHECKPOINT statements  in too many points in a program can significantly
  16006.  inhibit performance.
  16007.  
  16008.  
  16009.  Record Variables as Subsets of a Table's Columns
  16010.  
  16011.  You can open a table with a  tabletype that is a subset of a record within
  16012.  the table. To associate the subset data type with the columns in the table,
  16013.  you specify it as the  tabletype argument to the  OPEN statement you use to
  16014.  open the table. When you actually fetch a record from the table, you specify
  16015.  the variable (of type  tabletype) in a  RETRIEVE statement. That variable
  16016.  must have the same type as the  tabletype argument. If it is not the same
  16017.  type (even though it may be a valid subset in the sense that the element
  16018.  names are precisely the same, etc.), a trappable error occurs. Note however,
  16019.  that error checking in the QBX environment is more elaborate than in
  16020.  programs compiled from the command line. In a separately compiled program
  16021.  such an error may not be generated. In the BOOKLOOK.BAS example discussed
  16022.  earlier, if you wanted to open the table BookStock, but not access values in
  16023.  the Publisher and Price columns, you could declare a user-defined type as
  16024.  follows:
  16025.  
  16026.  TYPE SmallStock
  16027.  IDnumAS DOUBLE
  16028.  Title AS STRING * 50
  16029.  Author AS STRING * 36
  16030.  END TYPE
  16031.  
  16032.  The order in which the elements are specified is unimportant as long as the
  16033.  names are the same. You can still use indexes based on all the columns in
  16034.  the table, but you would not be able to transfer values to and from fields
  16035.  in the Publisher and Price columns.
  16036.  
  16037.  ISAM transfers values between a table and its corresponding structured
  16038.  variable by name, rather than by position. Therefore, in creating a subset
  16039.  the order of the elements can vary, as long as the names are the same. In
  16040.  other words, you can subtract columns as long as you preserve the original
  16041.  names precisely, regardless of their position. If the data types associated
  16042.  with the element names do not correspond to those in the table, or if a
  16043.  column name is spelled differently, a trappable error occurs. Therefore, you
  16044.  cannot simply change the data type, or the name, of a column.
  16045.  
  16046.  Note that if the length of one of the strings was greater than the length
  16047.  originally declared in BookStock, an error would be generated. Once the
  16048.  subset type is declared, you can open the BookStock table in BOOKS.MDB as
  16049.  follows:
  16050.  
  16051.  OPEN "books.mdb" FOR ISAM SmallStock  "BookStock" AS #1
  16052.  
  16053.  Using Multiple Files: "Relational" Databases
  16054.  
  16055.  Because ISAM maintains everything you need for a database in the multiple
  16056.  tables in the single database file, you rarely need to work with other
  16057.  files. Since a single ISAM database file can be as large as 128 megabytes,
  16058.  most of what other database systems do with multiple files can be handled in
  16059.  a single ISAM database file. In a program that accesses multiple files,
  16060.  table names in different files can be identical because an ISAM table name
  16061.  is maintained internally as a combination of the database name plus the
  16062.  table name.
  16063.  
  16064.  When you open multiple database files, the number of tables that can be
  16065.  opened simultaneously depends on how many database files are open, as shown
  16066.  in the following table:
  16067.  
  16068. ╓┌──────────────┌─────────────────────────────┌──────────────────────────────╖
  16069.  Number of      open databases                Maximum tables among
  16070.                                               databases
  16071.  ────────────────────────────────────────────────────────────────────────────
  16072.  /I             Specifies that an ISAM table
  16073.                 is to be created from  an
  16074.                 ASCII text file. /I stands
  16075.                 for "import."
  16076.  
  16077.  /E             Specifies that an ASCII text
  16078.                 file is to be created from
  16079.                 an ISAM table. /E stands for
  16080.                 "export.""
  16081.  
  16082.  /H or /?       Displays help for using the
  16083.                 ISAMIO utility. Anything
  16084.                 following these options in
  16085.                 the command line is ignored.
  16086.  
  16087.   asciifile     Names the ASCII file to be
  16088.  Number of      open databases                Maximum tables among
  16089.                                               databases
  16090.  asciifile     Names the ASCII file to be
  16091.                 imported (/I) or exported
  16092.                 (/E).
  16093.  
  16094.   databasename  Names the database file into
  16095.                 which the table should be
  16096.                 placed (/I) or from which
  16097.                 the data for the ASCII file
  16098.                 should be taken (/E).
  16099.  
  16100.   tablename     Names the table within the
  16101.                 database file into which the
  16102.                 records from the ASCII file
  16103.                 should be placed (/I), or
  16104.                 the table within the
  16105.                 database from which the data
  16106.                 for the ASCII file should be
  16107.                 taken (/E).
  16108.  
  16109.  Number of      open databases                Maximum tables among
  16110.                                               databases
  16111. 
  16112.  /A             Specifies that data being
  16113.                 imported (/I) should be
  16114.                 appended to  tablename. If
  16115.                 tablename does not exist, an
  16116.                 error message is displayed.
  16117.                 If /A is not specified,
  16118.                 ISAMIO imports the data into
  16119.                 the named table based on the
  16120.                 table description given in
  16121.                 specfile (described later in
  16122.                 this table). If no  specfile
  16123.                 is named (or found), an
  16124.                 error message is displayed.
  16125.  
  16126.  /C             When an ISAM table is being
  16127.                 imported (/I) from an ASCII
  16128.                 file,  /C specifies that the
  16129.                 table's column names should
  16130.  Number of      open databases                Maximum tables among
  16131.                                               databases
  16132.                table's column names should
  16133.                 be taken from  the first row
  16134.                 of data in the ASCII file.
  16135.                 If any of the specified
  16136.                 column names are
  16137.                 inconsistent with the ISAM
  16138.                 naming convention, ISAMIO
  16139.                 terminates and displays an
  16140.                 error message. When an ISAM
  16141.                 table is being exported (/E),
  16142.                 /C  specifies that the
  16143.                 table's column names should
  16144.                 appear in the ASCII file as
  16145.                 the first row of data (when
  16146.                 an ASCII file is being
  16147.                 created from an ISAM table).
  16148.                 If /A and /C are specified,
  16149.                 an error message appears. If
  16150.                 /C is not specified when a
  16151.  Number of      open databases                Maximum tables among
  16152.                                               databases
  16153.                /C is not specified when a
  16154.                 table is imported, ISAMIO
  16155.                 interprets the first row in
  16156.                 asciifile as the beginning
  16157.                 of the data records, and
  16158.                 looks for column names in
  16159.                 specfile. If /C is not
  16160.                 specified when a table is
  16161.                 exported, the column names
  16162.                 are not exported.
  16163.  
  16164.  /F :  width    Stipulates the data being
  16165.                 imported (/I) is of fixed
  16166.                 width, or that data being
  16167.                 exported (/E) should be
  16168.                 exported in fixed-width
  16169.                 format (i.e., no separators
  16170.                 appear in the data file).
  16171.                 The size of the fixed width
  16172.  Number of      open databases                Maximum tables among
  16173.                                               databases
  16174.                The size of the fixed width
  16175.                 fields are specified in the
  16176.                 first field of the  specfile,
  16177.                 if /F is specified. If you
  16178.                 don't use /F, the fields are
  16179.                 assumed to be comma
  16180.                 delimited, with double
  16181.                 quotation marks enclosing
  16182.                 string data. When exporting
  16183.                 fixed width,  width
  16184.                 specifies the width of
  16185.                 binary fields. The default
  16186.                 width is 512.
  16187.  
  16188.   specfile      A file that specifies the
  16189.                 data type and size (for
  16190.                 strings, arrays, and
  16191.                 user-defined types) for each
  16192.                 column of a table. The
  16193.  Number of      open databases                Maximum tables among
  16194.                                               databases
  16195.                column of a table. The
  16196.                 file's format is as follows:
  16197.                  fixedwidthsize,  type ,
  16198.                 size  ,  columnname Fields
  16199.                 can be separated with spaces
  16200.                 or commas. The
  16201.                 fixedwidthsize may only
  16202.                 appear if the /F option was
  16203.                 specified. The other
  16204.                 arguments appear only if the
  16205.                 /A option was not specified
  16206.                 (otherwise, it is ignored).
  16207.                 The  type is one of the
  16208.                 indexable ISAM data types.
  16209.                 In the case of arrays,
  16210.                 user-defined types, and
  16211.                 strings longer than 255
  16212.                 characters, specify  type as
  16213.                 binary. The  columnname is
  16214.  Number of      open databases                Maximum tables among
  16215.                                               databases
  16216.                binary. The  columnname is
  16217.                 any valid ISAM column name,
  16218.                 but is ignored if the /C
  16219.                 option is given. If
  16220.                 specfile  is not valid, a
  16221.                 descriptive error message is
  16222.                 displayed. Valid
  16223.                 designations for  type
  16224.                 include binary, integer,
  16225.                 long, real, and currency;
  16226.                 you can also specify
  16227.                 variabletext (vt), and
  16228.                 variablestring (vs). If the
  16229.                 type is one of the latter,
  16230.                 the  size field must appear.
  16231.                 If  specfile appears on the
  16232.                 command line when exporting,
  16233.                 a  specfile suitable for
  16234.                 importing is created. To see
  16235.  Number of      open databases                Maximum tables among
  16236.                                               databases
  16237.                importing is created. To see
  16238.                 an example of a  specfile,
  16239.                 you can export an existing
  16240.                 table, such as one of the
  16241.                 system tables, with a
  16242.                 command line having the
  16243.                 following form:isamio /e nul
  16244.                  databasename msysobjects
  16245.                 conThis line sends the
  16246.                 contents of the system table
  16247.                 to NUL, then prints the
  16248.                 specfile to the screen.
  16249.  
  16250.  /D             Specifies that a db/LIB file
  16251.                 is to be converted.
  16252.  
  16253.  /M             Specifies that an MS/ISAM
  16254.                 file is to be converted.
  16255.  
  16256.  Number of      open databases                Maximum tables among
  16257.                                               databases
  16258. 
  16259.  /B             Specifies that a Btrieve
  16260.                 database is to be converted.
  16261.  
  16262.   filename      The name of a data file to
  16263.                 be converted.
  16264.  
  16265.   tablename     The name of the ISAM table
  16266.                 into which the converted
  16267.                 records will be organized.
  16268.                 This name must follow the
  16269.                 ISAM naming convention.
  16270.  
  16271.   databasename  The name of the ISAM
  16272.                 database file into which the
  16273.                 table will be placed.
  16274.  
  16275.   specfile      You must supply this file      BASICtype,  size,
  16276.                 when converting Btrieve and   columnnameThe BASIC type is
  16277.  Number of      open databases                Maximum tables among
  16278.                                               databases
  16279.                when converting Btrieve and   columnnameThe BASIC type is
  16280.                 MS/ISAM files. It has the     the term used by Btrieve to
  16281.                 following form:               identify the data type; the
  16282.                                               size is the length of the
  16283.                                               field in the Btrieve format.
  16284.                                               The  columnname is any valid
  16285.                                               ISAM column name. The  size
  16286.                                               is ignored for all types
  16287.                                               except String.
  16288.  
  16289.  
  16290.  
  16291.  
  16292.  
  16293.  If  databasename does not exist, it is created. The utility uses the file
  16294.  utilities supplied with the database package that created the file. For
  16295.  example, the Btrieve TSR must be loaded when conversion is attempted. If the
  16296.  other-product file utilities are not available to ISAMCVT, a message is
  16297.  displayed. To convert the indexes of the original Btrieve, MS/ISAM, or
  16298.  db/LIB file, run ISAMCVT on the file that contains the index and name the
  16299.  ISAM table and database to which the index applies.
  16300.  
  16301.  Example entries in a Btrieve  specfile might look as follows:
  16302.  
  16303.  string 4 StringCol
  16304.  integer 2 IntColumn
  16305.  Long 10 LongColumn
  16306.  Double 5 DoubleCol
  16307.  
  16308.  In addition to Double and Single, DMBF and SMBF (for the corresponding
  16309.  Microsoft binary format) are also valid Btrieve column types.
  16310.  
  16311.  When the conversion is done, you can open the tables from within a BASIC
  16312.  program and begin using them right away. The following table describes how
  16313.  the data types associated with the old file map to the  TYPE... END TYPE
  16314.  variables you will be using in your BASIC program:
  16315.  
  16316.  db/LIB BtrieveMS/ISAMBASIC's ISAM
  16317.  The Repair Utility
  16318.  
  16319.  The Microsoft BASIC package includes the (ISAMREPR.EXE) utility to help
  16320.  recover databases that become corrupted. ISAMREPR can only restore physical
  16321.  integrity to a database (i.e., consistency among the tables in the
  16322.  database). ISAM does not pre-image changes made in a database and write them
  16323.  to a temporary file. Therefore, it is not possible to restore individual
  16324.  records entered if a crash occurs between the time the records are entered
  16325.  in the table and the next physical disk write. If this type of situation is
  16326.  a major concern, you can reduce the chance of losing such records by
  16327.  compiling programs with the /D option and making judicious use of checkpoint
  16328.  statements in your program.
  16329.  
  16330.  When ISAMREPR restores the database, it systematically goes through every
  16331.  table and index and recreates the database, using every piece of internally
  16332.  consistent information in the file. If anything is found that cannot be
  16333.  reconciled with the other information in the file, it is deleted. This
  16334.  restores consistency to the database. There is a chance that you will need
  16335.  to recreate some indexes. Similarly, it is possible that some blocks of data
  16336.  will never be reconciled with the rest of the database, and will therefore
  16337.  be lost.
  16338.  
  16339.  The syntax for ISAMREPR.EXE is as follows:
  16340.  
  16341.  ISAMREPR  databasename
  16342.  
  16343.  The  databasename  is the filename of the database you need to repair.
  16344.  ISAMREPR restores physical integrity to the database and prints messages to
  16345.  the screen whenever it takes an action that results in the loss of data.
  16346.  These messages describe the types of problems that were discovered and
  16347.  corrected. You can redirect this output to a file. The messages that may
  16348.  appear, with descriptions, are included in the following table:
  16349.  
  16350. ╓┌─────────────────────────────────────┌─────────────────────────────────────╖
  16351.  Message                               Explanation
  16352.  ────────────────────────────────────────────────────────────────────────────
  16353.  Table  name was truncated: data lost  During structural analysis of the
  16354.                                        table's data pages, an inconsistent
  16355.                                        page caused the table to be
  16356.                                        truncated as of the last uncorrupted
  16357.                                        page. This message is only given
  16358.                                        once for any affected table.
  16359.  
  16360.  One or more records were deleted      One of ISAM file's internal tables
  16361.  Message                               Explanation
  16362.  ────────────────────────────────────────────────────────────────────────────
  16363. One or more records were deleted      One of ISAM file's internal tables
  16364.  from table  name                      (MSysObjects, MSysIndexes, or
  16365.                                        MSysColumns) was found to have
  16366.                                        inconsistent data, or during the
  16367.                                        structural analysis of a table's
  16368.                                        data pages, an inconsistent data
  16369.                                        page was removed from the table
  16370.                                        (which resulted in the deletion of
  16371.                                        any table records on that page).
  16372.  
  16373.  One or more long values were deleted  "Long values" refer to strings
  16374.  from table  name                      longer than 255 characters, arrays,
  16375.                                        and user-defined types (not to the
  16376.                                        LONG data type). Their connections
  16377.                                        to the table were corrupted, so they
  16378.                                        were deleted . This message is only
  16379.                                        given once for any affected table.
  16380.  
  16381.  Cannot repair  name: Not a database   Repair process has been aborted
  16382.  Message                               Explanation
  16383.  ────────────────────────────────────────────────────────────────────────────
  16384. Cannot repair  name: Not a database   Repair process has been aborted
  16385.  file ,                                 because the file was not
  16386.                                        recognizable as a database.
  16387.  
  16388.  Cannot repair database  name:         Repair process has been aborted
  16389.  Uncorrectable problems                because the database cannot be
  16390.                                        repaired. Some common reasons
  16391.                                        include: system tables not found in
  16392.                                        the expected locations; any of the
  16393.                                        system tables is structurally
  16394.                                        inconsistent; information to
  16395.                                        reconstruct system data is
  16396.                                        unavailable; rebuilt system data is
  16397.                                        inconsistent; records describing
  16398.                                        system tables, columns, or indexes
  16399.                                        are inconsistent or missing.
  16400.  
  16401.  Repair of  name completed             Repair process completed.
  16402.  successfully
  16403.  Message                               Explanation
  16404.  ────────────────────────────────────────────────────────────────────────────
  16405. successfully
  16406.  
  16407.  
  16408.  
  16409.  
  16410.  
  16411.  A repair may also be aborted for reasons having nothing to do with the state
  16412.  of the database. Messages resulting in such cases include (but are not
  16413.  limited to): Disk full, Out of memory, and File not found.
  16414.  
  16415.  When you use the ISAMREPR utility it requires additional space within your
  16416.  database to accomplish its work. This adds at least 32K to the size of the
  16417.  database. Do not run the utility if your disk does not have this amount of
  16418.  space available in the current working directory. ISAMREPR deletes
  16419.  inconsistent records in tables, but does not compact after doing so.
  16420.  Compacting a database is described in the next section.
  16421.  
  16422.  
  16423.  The ISAMPACK Utility
  16424.  
  16425.  When tables or records are deleted from a database (either by your program,
  16426.  or the ISAMREPR utility), the size of your disk file does not change.
  16427.  Instead, the deleted data is marked, and ISAM begins to reuse the space in
  16428.  the file as you add to the database. The ISAMPACK utility performs two
  16429.  functions. First, if there is a total of 32K of data marked for deletion,
  16430.  ISAMPACK actually shrinks the disk file in increments of 32K. If there is
  16431.  not 32K of data marked for deletion, ISAMPACK has no effect on the size of
  16432.  the disk file. However, any time you run ISAMPACK, it removes records marked
  16433.  for deletion and then copies the database, table by table, and index by
  16434.  index into a database having the same name (if no  newdatabasename is
  16435.  specified). The effect of compaction is improved performance, in the same
  16436.  way that compacting a hard disk improves performance.
  16437.  
  16438.  As it compacts the database, ISAMPACK prints a report to the screen that
  16439.  lists the database's tables (including the types and maximum lengths of each
  16440.  of their columns), and the number of records in each table. It also lists
  16441.  (by table), all the database's indexes, the columns they are based on, and
  16442.  whether or not each one is unique. You can redirect this report to a file if
  16443.  you choose. The syntax for ISAMPACK.EXE is as follows:
  16444.  
  16445.  ISAMPACK  databasename   newdatabasename
  16446.  
  16447.  The  databasename is the filename of the ISAM disk file. The
  16448.  newdatabasename is an optional alternate name for the compacted database. If
  16449.  no  newdatabasename is  given, the original database file is renamed with
  16450.  the filename extension .BAK either appended to  databasename or replacing
  16451.  the original extension.
  16452.  
  16453.  
  16454.  Converting Btrieve Code
  16455.  
  16456.  If you have been using Btrieve as a database file manager, you may find that
  16457.  the ISAM integrated into Microsoft BASIC is a convenient and far less
  16458.  complicated substitute.
  16459.  
  16460.  If you've read the preceding portions of this chapter, you probably have a
  16461.  good idea already of how using ISAM can clean up your file-access code. With
  16462.  ISAM, the interface between your program and your database consists only of
  16463.  the ISAM statements and the structured variables you define to transfer
  16464.  values between your program and database tables. Using ISAM requires no
  16465.  elaborate initialization, and using ISAM statements is much more direct than
  16466.  passing a long list of arguments to BTRV. When you use ISAM, you don't need
  16467.  any of the following:
  16468.  
  16469.    ■    DEF SEG
  16470.  
  16471.    ■    ISAM manages memory addressing for you.
  16472.  
  16473.    ■    OPEN nul
  16474.  
  16475.    ■    With ISAM, you only have to worry about your actual database file.
  16476.  
  16477.    ■    FIELD statements
  16478.  
  16479.    ■    ISAM lets you use real, structured variables for records.
  16480.  
  16481.    ■   Operation codes
  16482.  
  16483.    ■    ISAM provides easy-to-use (and understand) statements for database
  16484.        access and manipulation.
  16485.  
  16486.    ■   Status codes
  16487.  
  16488.    ■    Errors in ISAM are trapped like any other BASIC errors.
  16489.  
  16490.    ■   FCB addresses and buffer lengths
  16491.  
  16492.    ■    ISAM handles all DOS interactions invisibly.
  16493.  
  16494.    ■   Key buffers and key numbers
  16495.  
  16496.    ■    ISAM uses indexes and maintains them for you.
  16497.  
  16498.    ■   Position blocks
  16499.  
  16500.    ■    ISAM handles file position invisibly.
  16501.  
  16502.  
  16503.  Your database files are created from within your BASIC programs, as are all
  16504.  tables and indexes. Although you do need to invoke a TSR before loading QBX
  16505.  when you want to develop database code within the environment, when you
  16506.  create a stand-alone version of the database program, you can have all file
  16507.  management support built into the executable file, so your user never has to
  16508.  do anything but fire up the program to work with a database.
  16509.  
  16510.  Similarly, because the ISAM data dictionary and all your tables of data are
  16511.  saved within the same disk file, you don't have to worry about keeping track
  16512.  of multiple files. Btrieve's transaction processing is limited to enhancing
  16513.  data integrity. ISAM's savepoint and rollback features help insure data
  16514.  integrity, but even more importantly, they simplify programming in which you
  16515.  want to allow a user to rescind a block of data exchanges.
  16516.  
  16517.  However, Btrieve offers the following features not yet available in ISAM:
  16518.  
  16519.    ■   Support for multi-user networks
  16520.  
  16521.    ■    Some versions of Btrieve support simultaneous multiple-user access to
  16522.        the same file, while the ISAM in BASIC does not.
  16523.  
  16524.    ■   Automatic logical integrity protection
  16525.  
  16526.    ■    Btrieve uses a pre-imaging system of temporary files for ensuring
  16527.        logical record integrity and consistency among files. ISAM guarantees
  16528.        only physical integrity. If your system crashes in the middle of a
  16529.        database operation, your ISAM file automatically maintains its
  16530.        internal consistency, but the one or two most recent edits to records
  16531.        may be lost. Since updates to records take place simultaneously in the
  16532.        record tables and the data dictionary, the possibility of inconsistent
  16533.        files is reduced greatly. The worst that can happen to a ISAM file is
  16534.        the loss of the latest edits to several recently modified records. You
  16535.        can minimize the effects of such losses by careful use of the
  16536.        CHECKPOINT statement, but unwritten records can be lost as a result of
  16537.        system crashes.
  16538.  
  16539.    ■   Same database across different disks
  16540.  
  16541.    ■    Btrieve permits you to extend a database across several different
  16542.        disks. While the maximum size (128 megabytes) of a single ISAM file
  16543.        means you will probably never have to partition a database in this
  16544.        way, if you have been using this Btrieve feature, you will have to
  16545.        redesign your database for ISAM.
  16546.  
  16547.    ■   Seek in descending order
  16548.  
  16549.    ■    When you use Get Lower from somewhere other than the second record in
  16550.        an index, Btrieve moves to the first match it makes by descending down
  16551.        the records in the index. ISAM does not offer an equivalent statement.
  16552.        If your code relies heavily on Btrieve's descending-order seeking, you
  16553.        can substitute combinations of  SEEKEQ and  MOVEPREVIOUS.
  16554.  
  16555.    ■   Null key
  16556.  
  16557.    ■    In Btrieve, when you designate a key as NULL, it is omitted from the
  16558.        index. There is no way to omit records having no value from the
  16559.        sorting order of a given index in Microsoft ISAM. Records with zero
  16560.        value in the current ISAM index simply sort as though they had the
  16561.        lowest value for that index. If your program relies on null keys in
  16562.        Btrieve, you will need to recode in ISAM to produce the same behavior.
  16563.  
  16564.  
  16565.  
  16566.  The following table illustrates the correspondence between Btrieve operation
  16567.  codes and the ISAM statements and functions:
  16568.  
  16569. ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
  16570.  Btrieve code             Description              BASIC  equivalent
  16571.  Btrieve code             Description              BASIC  equivalent
  16572.  ────────────────────────────────────────────────────────────────────────────
  16573.   0 (Open)                Makes file available      OPEN statement makes
  16574.                           for access.              tables accessible within
  16575.                                                    the database file.
  16576.  
  16577.   1 (Close)               Releases Btrieve file.    CLOSE statement closes
  16578.                                                    ISAM tables (and its
  16579.                                                    database file).
  16580.  
  16581.   2 (Insert)              Inserts a new record in   INSERT statement
  16582.                           the file.                inserts record into ISAM
  16583.                                                    table.
  16584.  
  16585.   3 (Update)              Overwrites current        UPDATE statement.
  16586.                           record.
  16587.  
  16588.   4 (Delete)              Deletes current record.   DELETE statement.
  16589.  
  16590.   5 (Get Equal)           Fetches the first         SEEKEQ + retrieve
  16591.                           record  whose field      statements fetches the
  16592.  Btrieve code             Description              BASIC  equivalent
  16593.  ────────────────────────────────────────────────────────────────────────────
  16594.                          record  whose field      statements fetches the
  16595.                           value matches the        first matching record in
  16596.                           specified key value..     the current index.
  16597.  
  16598.   6 (Get Next)            Fetches the record        MOVENEXT +  RETRIEVE
  16599.                           immediately following    statements.
  16600.                           the current record.
  16601.  
  16602.   7 (Get Previous)        Fetches the record        MOVEPREVIOUS +
  16603.                           immediately preceding    RETRIEVE statements.
  16604.                           the current record.
  16605.  
  16606.   8 (Get Greater)         Fetches the first         SEEKGT + retrieve
  16607.                           record whose field       statements fetch the
  16608.                           value exceeds the        first matching record in
  16609.                           specified key value.     the current index.
  16610.  
  16611.   9 (Get Greater or       Fetches the first         SEEKGE + retrieve
  16612.  Equal)                   record whose field       statements fetch the
  16613.  Btrieve code             Description              BASIC  equivalent
  16614.  ────────────────────────────────────────────────────────────────────────────
  16615. Equal)                   record whose field       statements fetch the
  16616.                           value equals or exceeds  first matching record in
  16617.                           the specified key value.  the current index.
  16618.  
  16619.   10 (Get Less Than)      Fetches the first         SEEKGE +  MOVEPREVIOUS
  16620.                           record whose field       + retrieve fetch the
  16621.                           value is less than the   first matching record in
  16622.                           specified key value.     the current index.
  16623.  
  16624.  
  16625.  
  16626.  
  16627.  
  16628.  
  16629. ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
  16630.  ────────────────────────────────────────────────────────────────────────────
  16631.   11 (Get Less Than or    Fetches the first         SEEKGT +  MOVEPREVIOUS
  16632.  Equal)                   record whose field       + retrieve fetch the
  16633.                           value is less than or    first matching record in
  16634.  ────────────────────────────────────────────────────────────────────────────
  16635.                          value is less than or    first matching record in
  16636.                           equals the specified     the current index.
  16637.                           key value.
  16638.  
  16639.   12 (Get Lowest)         Fetches the first         MOVEFIRST +  RETRIEVE
  16640.                           record.                  statements.
  16641.  
  16642.   13 (Get Highest)        Fetches the last record.   MOVELAST +  RETRIEVE
  16643.                                                    statements..
  16644.  
  16645.   14 (Create)             Creates a Btrieve file.   OPEN statement. In
  16646.                                                    BASIC the database files
  16647.                                                    (and tables) are created
  16648.                                                    by the  OPEN statement,
  16649.                                                    if they don't already
  16650.                                                    exist.
  16651.  
  16652.   15 (Stat)               Returns number of         LOF returns the number
  16653.                           records in the file,     of records in the
  16654.                           plus other file          specified table.
  16655.  ────────────────────────────────────────────────────────────────────────────
  16656.                          plus other file          specified table.
  16657.                           characteristics.
  16658.  
  16659.   16 (Extend)             Allows a file to be      No equivalent.
  16660.                           continuous across two
  16661.                           drives.
  16662.  
  16663.   17 (Set Directory)      Change current            CHDRIVE,  CHDIR.
  16664.                           directory.
  16665.  
  16666.   18 (Get Directory)      Returns current           CURDIR$.
  16667.                           directory.
  16668.  
  16669.   19 (Begin Transaction)  Marks start of a block    BEGINTRANS statement.
  16670.  .                         of related operations.
  16671.  
  16672.   20 (End Transaction)    Marks end of a block of   COMMITTRANS statement.
  16673.                           related operations.
  16674.  
  16675.   21 (Abort Transaction)  Restores file to its      ROLLBACKALL.
  16676.  ────────────────────────────────────────────────────────────────────────────
  16677.  21 (Abort Transaction)  Restores file to its      ROLLBACKALL.
  16678.                           condition prior to the
  16679.                           beginning of the
  16680.                           transaction.
  16681.  
  16682.   22 (Get Position)       Returns position of the  ISAM has no equivalent
  16683.                           current record.          because there are no
  16684.                                                    record numbers in ISAM.
  16685.  
  16686.   23 (Get Direct)         Fetches the record with  ISAM has no equivalent
  16687.                           the specified record     because there are no
  16688.                           number.                  record numbers in ISAM.
  16689.  
  16690.   24 (Step Direct)        Fetches the record in    ISAM has no equivalent
  16691.                           the next physical        because physical
  16692.                           location, regardless of  location is not a
  16693.                           the index.               meaningful mapping in
  16694.                                                    ISAM.
  16695.  
  16696.  
  16697.  
  16698.  
  16699.  
  16700.  
  16701. ╓┌──────────────────┌───────────────────────────┌────────────────────────────╖
  16702.  ────────────────────────────────────────────────────────────────────────────
  16703.   25 (Stop)         Unloads Btrieve record      In compiled BASIC programs
  16704.                     manager.                    use of an external database
  16705.                                                 manager is optional. After
  16706.                                                 using ISAM within the QBX
  16707.                                                 environment, you should
  16708.                                                 unload the TSR with its /D
  16709.                                                 option.
  16710.  
  16711.   26 (Version)      Returns the Btrieve         No equivalent.
  16712.                     version number.
  16713.  
  16714.   27 -  30   +100
  16715.  
  16716.  
  16717.  
  16718.  
  16719.  
  16720.  
  16721.  
  16722.  Run-Time Error Messages and Codes
  16723.  
  16724.  The following BASIC errors may occur as a result of ISAM statements:
  16725.  
  16726. ╓┌───┌───────────────┌───────────────┌──────┌───────────────┌────────────────╖
  16727.                      Special ISAM    Code   Error message   explanation (if
  16728.                                                             any)
  16729.  ────────────────────────────────────────────────────────────────────────────
  16730.  2   Syntax error    Some syntax
  16731.                      errors are not
  16732.                      detected until
  16733.                      run time. For
  16734.                      example,
  16735.                      supplying too
  16736.                      few  keyvalues
  16737.                      to a seek
  16738.                      operand
  16739.                      Special ISAM    Code   Error message   explanation (if
  16740.                                                             any)
  16741.  ────────────────────────────────────────────────────────────────────────────
  16742.                     operand
  16743.                      statement.
  16744.  
  16745.  5   Illegal         Many possible
  16746.      function call   causes.
  16747.  
  16748.  6   Overflow        Can occur when
  16749.                      automatic
  16750.                      coercion is
  16751.                      performed
  16752.                      between
  16753.                      integer and
  16754.                      long data
  16755.                      entering the
  16756.                      ISAM file.
  16757.  
  16758.  7   Out of memory   /Ib: or /Ii
  16759.                      set too small
  16760.                      Special ISAM    Code   Error message   explanation (if
  16761.                                                             any)
  16762.  ────────────────────────────────────────────────────────────────────────────
  16763.                     set too small
  16764.                      or too large;
  16765.                      or after EMS
  16766.                      has been
  16767.                      allocated for
  16768.                      ISAM, there is
  16769.                      not enough
  16770.                      left for QBX
  16771.                      to use for
  16772.                      text tables.
  16773.  
  16774.  10  Duplicate       An attempt was
  16775.      definition      made to
  16776.                      execute a
  16777.                      createindex
  16778.                      statement for
  16779.                      an index that
  16780.                      already exists
  16781.                      Special ISAM    Code   Error message   explanation (if
  16782.                                                             any)
  16783.  ────────────────────────────────────────────────────────────────────────────
  16784.                     already exists
  16785.                      in the
  16786.                      database.
  16787.  
  16788.  13  Type mismatch   Elements of
  16789.                      the
  16790.                      recordvariable
  16791.                      are
  16792.                      inconsistent
  16793.                      with the types
  16794.                      of the columns
  16795.                      in the table.
  16796.  
  16797.  
  16798.  
  16799.  
  16800.  
  16801.  
  16802. ╓┌───┌───────────────────────────────────┌───────────────────────────────────╖
  16803.  ────────────────────────────────────────────────────────────────────────────
  16804.  16  String formula too complex
  16805.  
  16806.  52  Bad file mode                       Attempted an ISAM operation on a
  16807.                                          non-ISAM file.
  16808.  
  16809.  54  Bad filename or number              The specified file number does not
  16810.                                          identify an ISAM table or database
  16811.                                          file.
  16812.  
  16813.  55  File already open                   The specified table or file is
  16814.                                          already open; or you specified a
  16815.                                          non-ISAM file in a deletetable
  16816.                                          statement.
  16817.  
  16818.  64  Bad filename                        Table name or database name
  16819.                                          exceeds legal length or contains
  16820.                                          illegal character.
  16821.  
  16822.  67  Too many files                      You have tried to open more than
  16823.  ────────────────────────────────────────────────────────────────────────────
  16824. 67  Too many files                      You have tried to open more than
  16825.                                          the maximum number of files. There
  16826.                                          are no more file handles available.
  16827.  
  16828.  70  Permission denied                   You attempted to open a file that
  16829.                                          was locked, or attempted to
  16830.                                          perform a file operation on a
  16831.                                          read-only file
  16832.  
  16833.  73  Feature unavailable                 User forgot to start the ISAM TSR
  16834.                                          before starting program, or tried
  16835.                                          to perform a data-dictionary
  16836.                                          operation using the reduced TSR
  16837.                                          (PROISAM, rather than PROISAMD) or
  16838.                                          .LIB configuration.
  16839.  
  16840.  76  Path not found                      The path was invalid; for example,
  16841.                                          a named directory did not exist.
  16842.  
  16843.  81  Invalid name                        Table or index name is too long or
  16844.  ────────────────────────────────────────────────────────────────────────────
  16845. 81  Invalid name                        Table or index name is too long or
  16846.                                          contains illegal characters.
  16847.  
  16848.  82  Table not found                     A table was specified that is not
  16849.                                          in the database, for example, in a
  16850.                                          deletetable statement.
  16851.  
  16852.  
  16853.  
  16854.  
  16855.  
  16856.  
  16857. ╓┌───┌───────────────────────────────────┌───────────────────────────────────╖
  16858.  ────────────────────────────────────────────────────────────────────────────
  16859.  83  Index not found                     The index specified by  SETINDEX
  16860.                                          was not associated with the
  16861.                                          specified table.
  16862.  
  16863.  84  Invalid column                      The name specified for a column in
  16864.                                          a createindex statement does not
  16865.  ────────────────────────────────────────────────────────────────────────────
  16866.                                         a createindex statement does not
  16867.                                          exist.
  16868.  
  16869.  85  No current record                   Occurs typically following an
  16870.                                          unsuccessful seek operand
  16871.                                          statement or move dest to end of
  16872.                                          file or beginning of file.
  16873.  
  16874.  86  Duplicate value for unique index    An attempt was made to create a
  16875.                                          unique index on a column that
  16876.                                          already contained duplicate
  16877.                                          values; or a user attempted to
  16878.                                          enter a duplicate value in a
  16879.                                          column for which a unique index
  16880.                                          exists.
  16881.  
  16882.  87  Invalid operation on NULL index     For example, it is illegal to
  16883.                                          execute a seek operand statement
  16884.                                          while the NULL index is current.
  16885.  
  16886.  ────────────────────────────────────────────────────────────────────────────
  16887. 
  16888.  88  Database inconsistent               There is a problem in the database
  16889.                                          -- run the ISAMREPR utility.
  16890.  
  16891.  
  16892.  
  16893.  
  16894.  
  16895.  
  16896.  
  16897.   ───────────────────────────────────────────────────────────────────────────
  16898.  Chapter 11:  Advanced String Storage
  16899.  
  16900.  This chapter explains when and how to use far strings as well as how
  16901.  to manipulate far strings to accomplish the following programming tasks:
  16902.  
  16903.    ■   Read and write far string data using  PEEK,  POKE,  BSAVE, and  BLOAD.
  16904.  
  16905.    ■   Make pointers used for mixed-language programming.
  16906.  
  16907.    ■   Maximize string storage space.
  16908.  
  16909.  
  16910.  
  16911.  Far Strings Vs. Near Strings
  16912.  
  16913.  In previous versions of Microsoft BASIC, all variable-length string data was
  16914.  stored in near memory, or what assembly language programmers call DGROUP.
  16915.  This is a relatively small portion of total memory (a maximum of 64K).
  16916.  Besides containing variable-length strings, DGROUP also contained the rest
  16917.  of the simple variables -- integers, floating-point numbers and fixed
  16918.  strings, all constants, and the stack. Even when the only variables you used
  16919.  were  variable-length strings, your maximum data capacity was limited to
  16920.  approximately 40K.
  16921.  
  16922.  This version of Microsoft BASIC supports "far strings" -- variable-length
  16923.  strings stored outside of DGROUP in multiple segments of far memory. This
  16924.  gives you 64K for far string processing in the main module, plus several
  16925.  additional 64K blocks depending on the specific program you write. And, by
  16926.  removing variable-length strings from DGROUP, you create more room for other
  16927.  simple variables as well.
  16928.  
  16929.  The following table shows the key differences between near and far string
  16930.  storage.
  16931.  
  16932.  As you can see, you can have up to 192K of far string storage: 64K for
  16933.  module-level strings, 64K for procedure-level strings, and 64K for
  16934.  strings declared with the  COMMON statement. If you are doing a
  16935.  recursive procedure, you can actually use additional segments as
  16936.  well -- one for each invocation. The exact programming techniques needed
  16937.  to achieve these increased capacities are explained in the section
  16938.  "Maximizing String Storage Space" later in this chapter.
  16939.  
  16940.  
  16941.  Note
  16942.  
  16943.  This chapter pertains only to variable-length strings. Fixed-length strings
  16944.  have not changed with this release of BASIC. For ease of reading, the word
  16945.  "string" is always used in this chapter to mean "variable-length string."
  16946.  
  16947.  
  16948.  When to Use Far Strings
  16949.  
  16950.  For many programming applications, using far strings is the preferred
  16951.  storage method. It gives you more space for variable-length strings and
  16952.  frees DGROUP to handle more integers, floating-pointing numbers, and
  16953.  fixed-length strings.
  16954.  
  16955.  If your variable-length string requirements are limited, however, there are
  16956.  times when you are better off with near strings. One such case is when you
  16957.  need all available memory for code. Another is when you have very large
  16958.  arrays that are memory intensive. For these instances, using near strings
  16959.  frees at least 64K. (For details on how BASIC stores far strings, see the
  16960.  section "Data Structure and Space Allocation.")
  16961.  
  16962.  Where you have very few strings and want to decrease code size, you can use
  16963.  near strings instead of the longer code for far strings.
  16964.  
  16965.  
  16966.  Selecting Far Strings
  16967.  
  16968.  When compiling from within QBX, far strings are the default. If you want
  16969.  variable-length strings stored in DGROUP, cancel the Strings in Far Memory
  16970.  selection in the Make EXE File dialog box.
  16971.  
  16972.  When compiling programs from the command line, DGROUP storage is the
  16973.  default. To use far strings, add the /Fs option to the BASIC Compiler (BC)
  16974.  command line.
  16975.  
  16976.  All programs running within the QBX environment use far string storage;  no
  16977.  other option is available. All Quick libraries must be made using the /Fs
  16978.  option as well.
  16979.  
  16980.  
  16981.  Direct Far-String Processing
  16982.  
  16983.  For most applications, far-string programming requires no new techniques.
  16984.  BASIC automatically takes care of far-string memory management. However, if
  16985.  you are using either the  BLOAD,  BSAVE,  POKE, or  PEEK statement for
  16986.  direct processing of far strings, you must first set the current segment
  16987.  address to the address of the far string being manipulated. You can use the
  16988.  SSEG ( stringvariable$) function to return the segment address of
  16989.  stringvariable$.
  16990.  
  16991.  As an example of direct
  16992.  processing, suppose you have initialized the following string:
  16993.  
  16994.  
  16995.  A$ = STRING$(1024,65)
  16996.  
  16997.  To save it on disk using  BSAVE, you do this:
  16998.  
  16999.  DEFINT A-Z
  17000.  DEF SEG = SSEG(a$)
  17001.  Offset = SADD(a$)
  17002.  Length = LEN(a$)
  17003.  BSAVE "bigstg.txt", Offset, Length
  17004.  You must also set the current segment when using  BLOAD with a far string.
  17005.  
  17006.  DEFINT A-Z
  17007.  ' Intialize a string variable to the correct length
  17008.  ' by computing the source size.
  17009.  OPEN "bigstg.txt" FOR INPUT AS #1
  17010.  Length = LOF(1)
  17011.  A$ = STRING$(Length, 0)
  17012.  ' Calculate location of destination.
  17013.  DEF SEG = SSEG(A$)
  17014.  offset = SADD(A$)
  17015.  BLOAD "bigstg.txt", Offset
  17016.  The following two examples use  DEF SEG in conjunction with   PEEK and  POKE
  17017.  on far string data. These examples insert one string into another and are a
  17018.  direct-processing emulation of the  MID$ statement.
  17019.  
  17020.  DEFINT A-Z
  17021.  ' Create a string of As and Bs.
  17022.  A$ = STRING$(20, 65)
  17023.  B$ = STRING$(40, 66)
  17024.  ' Calculate their offsets.
  17025.  Offset1 = SADD(A$)
  17026.  Offset2 = SADD(B$)
  17027.  ' Insert 10 As in the string of Bs.
  17028.  ' Same as MID$(B$, 11, 10) = A$.
  17029.  DEF SEG = SSEG(A$)
  17030.  FOR I = 11 to 20
  17031.  Temp = PEEK(Offset1)
  17032.  POKE Offset2 + I - 1, Temp
  17033.  NEXT I
  17034.  
  17035.  The preceding example needed only one use of the  DEF SEG statement. That is
  17036.  because both strings were in the same segment. In the next example, one
  17037.  string is in the main-module string segment and the other is in the segment
  17038.  declared  COMMON. This requires a separate  DEF SEG statement for the source
  17039.  and the destination.
  17040.  
  17041.  DEFINT A-Z
  17042.  
  17043.  COMMON A$
  17044.  ' Create a string of As and Bs.
  17045.  A$ = STRING$(20, 65)
  17046.  B$ = STRING$(40, 66)
  17047.  ' Calculate their offsets.
  17048.  Offset1 = SADD(A$)
  17049.  Offset2 = SADD(B$)
  17050.  ' Insert 10 As in the string of Bs.
  17051.  ' Same as MID$(b$, 11, 10) = A$.
  17052.  SourceSegment = SSEG(A$)
  17053.  DestinationSegment = SSEG(B$)
  17054.  FOR I = 11 to 20
  17055.  DEF SEG = SourceSegment
  17056.  Temp = PEEK(Offset1 + I - 11)
  17057.  DEF SEG = DestinationSegment
  17058.  POKE Offset2 + I - 1, Temp
  17059.  NEXT I
  17060.  Important
  17061.  
  17062.  Although shown here in examples, direct manipulation of far strings is
  17063.  recommended only as a last resort when standard BASIC programming techniques
  17064.  cannot achieve the desired result. Extreme caution is advised in these cases
  17065.  for the following reasons:
  17066.  
  17067.    ■   BASIC moves string locations during run time. Therefore the  SSEG and
  17068.        SADD functions need to be executed immediately before using  BLOAD,
  17069.        BSAVE,  PEEK, and  POKE.
  17070.  
  17071.    ■   BASIC can detect whether a string has changed length. Never use  POKE
  17072.        on data beyond the last character in a far string or you will get a
  17073.        String Space Corrupt error. If this occurs in the QBX environment, QBX
  17074.        will terminate, and you should reboot your computer before restarting
  17075.        QBX.
  17076.  
  17077.    ■   Far string descriptors have a different format than near string
  17078.        descriptors. If you attempt to locate far string data by using  PEEK
  17079.        to look at the descriptor, you will not be able to find the data. If
  17080.        your applications pass far strings extensively or accessed the string
  17081.        descriptor in the past to obtain information, see Chapter 13,
  17082.        "Mixed-Language Programming with Far Strings," for the correct new way
  17083.        to do this.
  17084.  
  17085.  
  17086.  
  17087.  Calculating Far-String Memory Space
  17088.  
  17089.  When used with far strings, the  FRE function provides new information to
  17090.  give you more program control.  FRE( stringexpression$) compacts string
  17091.  storage space and then returns the space remaining in the segment containing
  17092.   stringvexpression$.  FRE(" stringliteral")  also compacts string storage
  17093.  space, but it returns the space available for temporary string storage.
  17094.  Temporary string storage is used whenever a string expression is created,
  17095.  typically to the right or left of the equal sign or as an argument to a
  17096.  function or statement. Here are some examples:
  17097.  
  17098.  PRINT A$ + B$
  17099.  
  17100.  CALL StringManipulator( (A$) )
  17101.  A$ = A$ + "$"
  17102.  In certain instances, you may want to use the  FRE function to see if there
  17103.  is enough string space for a given operation. Suppose you are going to load
  17104.  a string from a file and then combine it with another string. You can check
  17105.  the space requirements this way:
  17106.  
  17107.  OPEN "bigstg1" FOR INPUT AS #1
  17108.  ' Skip I/O operation if out of space.
  17109.  IF LOF(1) <= FRE(A$) THEN
  17110.  INPUT #1, A$
  17111.  ' Before concatenating, make two checks.
  17112.  ' First see if there's enough temporary storage space.
  17113.  IF FRE("") >= LEN(A$) + LEN(B$) THEN
  17114.  ' Second, see if A$'s segment has room to store the new variable.
  17115.  IF FRE(A$) >= LEN(B$) THEN
  17116.  ' There's room to do the operation, so do it.
  17117.  A$ = A$ + B$
  17118.  END IF
  17119.  END IF
  17120.  END IF
  17121.  Note
  17122.  
  17123.  The output of the  FRE function changes, depending on whether or not far
  17124.  strings are selected. The following table shows the differences.
  17125.  
  17126.   FRE(-2)Unused stack spaceUnused stack space FRE(-3)Available EMSAvailable
  17127.  EMS FRE(any other numeric)Unused space in DGROUPCan't use with far
  17128.  strings
  17129.  
  17130.  Using Far-String Pointers
  17131.  
  17132.  Far strings are passed to procedures written in other languages through the
  17133.  use of data pointers. These pointers are obtained with the  SSEG, SADD, and
  17134.  SSEGADD functions.
  17135.  
  17136.  To pass separate segment and offset pointers, use  SSEG for the segment and
  17137.  SADD for the offset. This is shown in the following example, where BASIC
  17138.  passes a string to MASM, which prints it on the screen:
  17139.  
  17140.  DEFINT A-Z
  17141.  DECLARE SUB PrintMessage(BYVAL Segment, BYVAL Offset)
  17142.  ' Create the message with the "$" terminator--
  17143.  ' the DOS print service routine requires it.
  17144.  A$ = "This is a short example of a message" + "$"
  17145.  ' Call the MASM procedure with pointers
  17146.  CALL PrintMessage(SSEG(A$), SADD(A$))
  17147.  
  17148.  ;************************ PRINT MESSAGE ****************
  17149.  ; This MASM procedure prints a BASIC far string on the screen.
  17150.  ; Define use and ordering of segments so it's compatible with BASIC.
  17151.  .modelmedium, basic
  17152.  ; Set up some stack space--necessary if making this into a quick library.
  17153.  .stack
  17154.  .code
  17155.  ; Define a public procedure that inputs two word
  17156.  ; variables.
  17157.  publicprintmessage
  17158.  printmessageprocuses ds, segmnt, offst
  17159.  ; Tell DOS print routine where the string is
  17160.  movax, segmnt
  17161.  movds, ax
  17162.  movdx, offst
  17163.  ; Call DOS print routine and return to BASIC
  17164.  movah, 9
  17165.  int21h
  17166.  ret
  17167.  printmessageendp
  17168.  end
  17169.  
  17170.  Note
  17171.  
  17172.  This example uses features of MASM version 5.1, including the  .MODEL
  17173.  directive which establishes compatible naming, calling, and passing
  17174.  conventions. It also uses simplified segment directives which eliminate the
  17175.  need to separate  GROUP and  ASSUME directives. The new  PROC directive is
  17176.  employed. It includes new arguments that specify automatically saved
  17177.  registers, define arguments to procedures, and set up test macros to use for
  17178.  the arguments. The  PROC directive also causes the proper type of return to
  17179.  be generated automatically based on the chosen memory model and cleans up
  17180.  the stack.
  17181.  
  17182.  In the next example, a
  17183.  far pointer to a string is passed to a C routine, which prints the string
  17184.  data. The far pointer is returned by the  SSEGADD function. The far pointer
  17185.  is a double word with the segment contained in the high word and the offset
  17186.  contained in the low word.
  17187.  
  17188.  
  17189.  ' Declare external C procedure using correct naming
  17190.  ' and parameter passing conventions
  17191.  DEFINT A-Z
  17192.  DECLARE SUB PrintMessage CDECL (BYVAL FarString AS LONG)
  17193.  ' Create the message as an ASCIIZ string, as
  17194.  ' required by the C printf function.
  17195.  A$ = "This is a short example of a message" + CHR$(0)
  17196.  ' Tell C to print the string addressed by the far pointer
  17197.  CALL PrintMessage(SSEGADD(A$))
  17198.  /********************** PRINT MESSAGE *******************
  17199.  * This C routine prints a BASIC far string on the screen.
  17200.  * Use standard i/o header */
  17201.  #include <stdio.h>
  17202.  
  17203.  /* Define a procedure which inputs a string  far  pointer */
  17204.  void printmessage (char far *farpointer)
  17205.  {
  17206.  /* print the string addressed by the far pointer */
  17207.  printf( "%s\n", farpointer);
  17208.  }
  17209.  Notice that near and far pointers passed to FORTRAN, C, MASM, and other
  17210.  languages are treated by those languages as unsigned values, whereas BASIC
  17211.  has no such data type ( INTEGER,  LONG,  SINGLE, and  DOUBLE data types are
  17212.  signed). This presents no problem as long as the pointers are assigned their
  17213.  values from the  SSEG,  SADD and  SSEGADD functions. There can be problems,
  17214.  however, if pointers are assigned values directly from certain types of
  17215.  expressions. For example, suppose A$ is a string that exists in segment
  17216.  42000 at offset 40000. The following code passes the string to MASM:
  17217.  
  17218.  DEFINT A-Z
  17219.  DECLARE SUB PrintMessage(BYVAL Segment, BYVAL Offset)
  17220.  Segment = SSEG(A$)
  17221.  Offset = SADD(A$)
  17222.  CALL PrintMessage(Segment, Offset)
  17223.  The preceding method works correctly, but what if you directly assign the
  17224.  pointers with the following code?
  17225.  
  17226.  Segment = 42000
  17227.  Offset = 40000
  17228.  In this case, these
  17229.  lines produce an Overflow error message because the maximum value for an
  17230.  integer data type is 32,767. To make direct assignments, use hexadecimal
  17231.  numbers:
  17232.  
  17233.  
  17234.  ' set the segment using the hex equivalent of 40000 decimal.
  17235.  Segment = &H9C40
  17236.  ' Set the offset using the hex equivalent of 42000 decimal.
  17237.  Offset = &HA410
  17238.  
  17239.  Maximizing String Storage Space
  17240.  
  17241.  For applications requiring 128K of strings, the easiest way to create this
  17242.  much space is to keep half in the module-level string segment and half in
  17243.  the string segment declared with  COMMON. For example:
  17244.  
  17245.  COMMON C$, D$
  17246.  A$ = STRING$(32700,65)
  17247.  B$ = STRING$(32700,66)
  17248.  C$ = STRING$(32700,67)
  17249.  D$ = STRING$(32700,68)
  17250.  
  17251.  To get another 64K, call a procedure to create the rest of the strings. If
  17252.  you need to refer to procedure- and module-level strings at the same time,
  17253.  then share them, and do your string processing in the procedure. For
  17254.  example:
  17255.  
  17256.  SUB BigStrings
  17257.  SHARED A$, B$, C$, D$
  17258.  E$ = STRING$(32700,69)
  17259.  F$ = STRING$(32700,70)
  17260.  ' Place to do processing of a$, b$, c$, d$, e$ and F$
  17261.  .
  17262.  .
  17263.  .
  17264.  END SUB
  17265.  
  17266.  One problem with using large procedure-level strings is that it uses up
  17267.  temporary string storage space -- the place where string expressions are
  17268.  kept -- because both occupy the same data segment. Therefore, the largest
  17269.  string expression plus the total procedure-level string space cannot exceed
  17270.  64K. To prevent Out of String Space errors, use the methods described in
  17271.  the section "Calculating Far-String Memory Space" later in this chapter.
  17272.  
  17273.  Another way to avoid an
  17274.  error is to create far string arrays while in the procedure. They get their
  17275.  own 64K segment:
  17276.  
  17277.  
  17278.  SUB BigStrings
  17279.  SHARED A$, B$, C$, D$
  17280.  DIM E$(9), F$(9)
  17281.  FOR I% = 0 to 9
  17282.  E$(I%) = STRING$(3210,69 + i%)
  17283.  F$(I%) = STRING$(3210,70 + i%)
  17284.  NEXT I%
  17285.  ' Place to do processing of a$, B$, C$, D$, E$() and F$()
  17286.  .
  17287.  .
  17288.  .
  17289.  END SUB
  17290.  The previous method can be expanded upon to fill all available memory space
  17291.  with strings. It works because BASIC creates a new string segment for every
  17292.  invocation of a procedure -- in other words, each time the procedure is
  17293.  called during recursion. Here is the idea:
  17294.  
  17295.  DEFINT A-Z
  17296.  DECLARE SUB ManyStrings (n)
  17297.  ' Compute the # of 64K blocks available.
  17298.  N = FRE(-1) \ 65536
  17299.  CALL ManyStrings(N)
  17300.  
  17301.  SUB ManyStrings(N)
  17302.  DIM G$(1 to 1), H$(1 to 1)
  17303.  G$(1) = STRING$(32700, 71)
  17304.  H$(1) = STRING$(32700, 72)
  17305.  N = N - 1
  17306.  IF N > 0 THEN CALL ManyStrings(n)
  17307.  END SUB
  17308.  This creates 64K of strings for each recursion. A limitation is that the
  17309.  only strings that can be accessed are the ones that are dimensioned at the
  17310.  current level of recursion. In theory, this can be overcome by passing the
  17311.  strings from the previous level of recursion when the next call is made. But
  17312.  in reality, this makes for complex code, as demonstrated by the following:
  17313.  
  17314.  ' Define arrays which will be passed to each new level
  17315.  ' of recursion.
  17316.  DECLARE SUB BigStrings (N%, S1$(), S2$(), S3$(), S4$())
  17317.  DEFINT A-Z
  17318.  DIM S1$(1 TO 2), S2$(1 TO 2), S3$(1 TO 2), S4$(1 TO 2)
  17319.  ' Compute the # of 64K
  17320.  blocks available in far memory.
  17321.  
  17322.  N = FRE(-1) \ 65536
  17323.  CLS
  17324.  ' Quit if not enough memory.
  17325.  IF N < 1 THEN
  17326.               PRINT "Not enough memory for operation."
  17327.               END
  17328.  END IF
  17329.  
  17330.  ' Start the recursion.
  17331.  CALL BigStrings(N, S1$(), S2$(), S3$(), S4$())
  17332.  
  17333.  SUB BigStrings (N, S1$(), S2$(), S3$(), S4$())
  17334.  ' Create a new array (up to 64K) for each level of recursion.
  17335.  DIM A$(1 TO 2)
  17336.  ' Have N keep track of recursion level.
  17337.  SELECT CASE N
  17338.  ' When at highest recursion level, process the strings.
  17339.          CASE 0
  17340.  PRINT S1$(1); S1$(2); S2$(1); S2$(2); S3$(1); S3$(2); S4$(1); S4$(2)
  17341.          CASE 1
  17342.                  A$(1) = "Each "
  17343.                  A$(2) = "word "
  17344.                  S1$(1) = A$(1)
  17345.                  S1$(2) = A$(2)
  17346.          CASE 2
  17347.                  A$(1) = "pair "
  17348.                  A$(2) = "comes "
  17349.                  S2$(1) = A$(1)
  17350.                  S2$(2) = A$(2)
  17351.          CASE 3
  17352.                  A$(1) = "from "
  17353.                  A$(2) = "separate "
  17354.                  S3$(1) = A$(1)
  17355.                  S3$(2) = A$(2)
  17356.          CASE 4
  17357.                  A$(1) = "recursive "
  17358.                  A$(2) = "procedures."
  17359.                  S4$(1) = A$(1)
  17360.                  S4$(2) = A$(2)
  17361.  END SELECT
  17362.  
  17363.  ' Keep going until
  17364.  we're out of memory.
  17365.  
  17366.  IF N > 0 THEN
  17367.                  N = N - 1
  17368.  ' For each recursion, pass in previously created arrays.
  17369.                  CALL BigStrings(N, S1$(), S2$(), S3$(), S4$())
  17370.  END IF
  17371.  
  17372.  END SUB
  17373.  
  17374.  Output
  17375.  
  17376.  Each word pair comes from separate recursive procedures.
  17377.  
  17378.  In this last example, the variable N has several important functions. In the
  17379.  beginning it contains the total number of 64K blocks available for strings.
  17380.  Thus, during execution of the  SELECT  CASE code in the  SUB procedure, it
  17381.  can prevent Out of String Space errors. The N variable also keeps track of
  17382.  the level of recursion and ends the recursion at the appropriate time.
  17383.  
  17384.  As you can see, once the code reaches the highest recursive level, the user
  17385.  can process all the strings he has created. This occurs when CASE 0 is true.
  17386.  In this example, all the created strings are printed on the screen.
  17387.  
  17388.  As stated earlier, each string array can be up to 64K. They are kept short
  17389.  here to make the demonstration practical.
  17390.  
  17391.  
  17392.  Far Strings and Older Versions of BASIC
  17393.  
  17394.  If you want to use far strings in modules written with previous versions of
  17395.  BASIC, recompile them using the /Fs option. (See the preceding section,
  17396.  "Selecting Far Strings," for details.) The only time you have to change any
  17397.  code is when your module uses the  VARSEG or  VARPTR function. If the
  17398.  VARSEG function is used to obtain the string data segment, replace it with
  17399.  the  SSEG function. If you use the  VARPTR function to access the string
  17400.  descriptor, remember that a far string descriptor has a different format
  17401.  than a near string descriptor. You will have to make code changes. (For
  17402.  suggestions on how to handle this, see Chapter 13, "Mixed-Language
  17403.  Programming with Far Strings.")
  17404.  
  17405.  If you are linking new code containing far strings with older code
  17406.  containing near strings, you must recompile the old code using the /Fs
  17407.  option. Otherwise the program will return an error message Low Level
  17408.  Initialization and terminate.
  17409.  
  17410.  
  17411.  Data Structure and Space Allocation
  17412.  
  17413.  This section provides details about far strings which may prove helpful when
  17414.  doing mixed language programming or direct processing of far string data.
  17415.  Additional information can be found in Chapters 13, "Mixed-Language
  17416.  Programming with Far Strings" and 15, "Optimizing Program Size and Speed."
  17417.  
  17418.  
  17419.  
  17420.  
  17421.  
  17422.  Far-string data structure consists, in part, of a 4-byte string descriptor
  17423.  located in DGROUP and the data located in multiple segments of far memory.
  17424.  The string descriptor contains information that BASIC uses to manage the
  17425.  data as it changes length and location during run time. The exact structure
  17426.  of the string descriptor is unavailable.
  17427.  
  17428.  Whenever a far string (such as A$) is used in a program, the string refers
  17429.  to the offset address in DGROUP of the string descriptor. Thus if the string
  17430.  descriptor of A$ is at offset &H2000, that is what gets pushed on the stack
  17431.  during the following call:
  17432.  
  17433.  CALL DemoSub (A$)
  17434.  
  17435.  In assembly language, the equivalent would be:
  17436.  
  17437.  movax, 2000H
  17438.  pushax
  17439.  callDemoSub
  17440.  
  17441.  For all far string arrays, the array descriptor and all string descriptors
  17442.  -- one for each element in the array--reside in DGROUP. All string data is
  17443.  in far memory.
  17444.  
  17445.  Each string segment, besides storing the string data, contains a small
  17446.  amount of overhead used for string management. The overhead consists of 64
  17447.  bytes plus an additional 6 bytes per string in the segment.
  17448.  
  17449.  The exact number of 64K segments used to store string data is dependent on
  17450.  where the strings are created and how they are declared. This can be
  17451.  summarized as follows:
  17452.  
  17453.    ■   All far string data declared with  COMMON resides in a separate 64K
  17454.        segment.
  17455.  
  17456.    ■   All other far strings created at the module-level, whether simple or
  17457.        in arrays, reside in a separate 64K segment.
  17458.  
  17459.    ■   All string arrays created at the procedure level reside in a separate
  17460.        64K segment. The arrays are local to the procedure and exist only
  17461.        until the procedure is exited. During recursion, arrays created at all
  17462.        levels exist up to and within the most deeply nested level. During the
  17463.        exit process, when the routine returns to a previous level, arrays
  17464.        used in the exited level are cleared.
  17465.  
  17466.    ■   All simple strings created in any procedure reside in a single,
  17467.        separate 64K segment.
  17468.  
  17469.  
  17470.  Note
  17471.  
  17472.  
  17473.  The segment for procedure-level strings is also used for temporary strings.
  17474.  Temporary strings are created for all string expressions that appear
  17475.  anywhere in BASIC code. When using large string expressions, therefore, you
  17476.  may have to reduce the number of procedure-level strings to avoid running
  17477.  out of space in this segment. For an example of how to monitor this
  17478.  activity, see the section "Calculating Far-String Memory Space."
  17479.  
  17480.  
  17481.   ────────────────────────────────────────────────────────────────────────────
  17482.  Chapter 12:  Mixed-Language Programming
  17483.  ────────────────────────────────────────────────────────────────────────────
  17484.  
  17485.  Mixed-language programming is the process of combining programs from two or
  17486.  more source languages. For example, mixed-language programming allows you to
  17487.  use Microsoft Macro Assembler (MASM) to enhance your BASIC programs. You can
  17488.  develop most of your program rapidly using BASIC, then use assembly language
  17489.  for routines that are executed many times and must run with utmost speed.
  17490.  Similarly, you can call your own Microsoft C, Pascal, and Fortran routines
  17491.  from within BASIC programs.
  17492.  
  17493.  This chapter assumes that you know the languages you wish to combine, and
  17494.  that you know how to write, compile, and link multiple-module programs with
  17495.  these languages. When you finish this chapter, you will understand:
  17496.  
  17497.    ■   General issues important in mixed-language programming.
  17498.  
  17499.    ■   Calling between BASIC and other Microsoft high-level languages.
  17500.  
  17501.    ■   Passing parameters in interlanguage calls.
  17502.  
  17503.    ■   Differences in how BASIC, Pascal, FORTRAN, and C handle numeric and
  17504.        string data.
  17505.  
  17506.    ■   Calling between BASIC and assembly language.
  17507.  
  17508.        Information in this chapter assumes you are compiling your BASIC
  17509.        modules using the command-line compiler, using "near strings" (that
  17510.        is, without the /Fs option). If you do not plan to use the /Fs option,
  17511.        the information in this chapter should be all you need to combine
  17512.        modules written in Microsoft BASIC, C, Pascal, FORTRAN, and Macro
  17513.        Assembler.
  17514.  
  17515.    ■   Using "far strings" (compiling with the /Fs option) vastly increases
  17516.        the amount of space you can use for strings. However, because the
  17517.        string descriptors used for near strings and far strings are
  17518.        completely different, rules for mixed-language programming differ
  17519.        depending on which string model is used. Microsoft BASIC includes a
  17520.        set of mixed-language string-handling routines you can use in all your
  17521.        programming to assure portability among modules. For information on
  17522.        "far strings" and using the mixed-language string routines, see
  17523.        Chapters 11, "Advanced String Storage" and 13, "Mixed-Language
  17524.        Programming with Far Strings."
  17525.  
  17526.    ■   Note also that data storage differs for some kinds of data depending
  17527.        on whether you are using the command-line compiler or working within
  17528.        the QBX program-development environment. A table in the section
  17529.        "Special Data Types" later in this chapter summarizes these
  17530.        differences.
  17531.  
  17532.  
  17533.  Warning
  17534.  
  17535.  Routines intended for use in Quick libraries must use far strings. Non-BASIC
  17536.  Quick libraries written for QuickBASIC version 4.5 and earlier may have to
  17537.  be rewritten to use far strings before they can be used in the QBX
  17538.  environment. Also, you cannot use the /Ea option in QBX if you are using a
  17539.  Quick library that contains a non-BASIC routine that receives a BASIC array.
  17540.  
  17541.  
  17542.  Note
  17543.  
  17544.  These restrictions apply when creating mixed-language programs with
  17545.  Microsoft BASIC:
  17546.  
  17547.  Some combinations of languages may produce a Symbol defined more than once
  17548.  error when compiled for use with the BASIC run-time module. To avoid this,
  17549.  compile your program as a stand-alone executable (use the /O compiler
  17550.  option).
  17551.  
  17552.  Modules created with Microsoft QuickPascal are not compatible with any
  17553.  other language and cannot be linked into a mixed-language program
  17554.  
  17555.  You cannot link Pascal modules compiled with /Fpa with BASIC modules.
  17556.  Programs that include Pascal modules cannot use alternate math.
  17557.  
  17558.  
  17559.  Organizing Mixed-Language Programs
  17560.  
  17561.  The way you organize mixed-language programs depends on whether you run your
  17562.  program from within the QBX program-development environment, or compile from
  17563.  the command line using BASIC Compiler (BC). When your program calls
  17564.  other-language routines from within the QBX environment, the other-language
  17565.  routines must first be compiled and linked into a Quick library, and the
  17566.  Quick library must be loaded in QBX, as described in Chapter 19, "Creating
  17567.  and Using Quick Libraries."
  17568.  
  17569.  If you compile and link your program from the DOS command line, your
  17570.  other-language routines do not have to be part of a library. However, the
  17571.  Microsoft Library Manager (LIB) is provided for this purpose if you find it
  17572.  more convenient. See Chapters 16, "Compiling with BC," 17, "About Linking
  17573.  and Libraries," and 18,"Using LINK and LIB," for information on compiling,
  17574.  linking, and managing libraries.
  17575.  
  17576.  Note
  17577.  
  17578.  It is especially important that other-language procedures be thoroughly
  17579.  debugged before being incorporated in a Quick library. The QBX tracing
  17580.  commands do not step into Quick-library procedures when tracing through a
  17581.  program, so debugging them from within the environment is not possible. For
  17582.  source-level debugging of mixed-language programs, compile and link the
  17583.  modules from the command line with the /Zi and /CO options (or using the
  17584.  appropriate settings in the QBX Make EXE dialog box). You can then use the
  17585.  Microsoft CodeView debugger to debug the mixed-language program at their
  17586.  source level.
  17587.  
  17588.  
  17589.  Mixed-Language Programming Elements
  17590.  
  17591.  Microsoft languages have special keywords that facilitate mixed-language
  17592.  programming. To use these keywords, you must understand certain fundamental
  17593.  issues.
  17594.  
  17595.  After explaining the context of a mixed-language call, the following
  17596.  sections describe: how the languages differ and how to resolve these
  17597.  differences. The three fundamental mixed-language programming requirements
  17598.  are discussed:
  17599.  
  17600.    ■   The naming convention
  17601.  
  17602.    ■   The calling convention
  17603.  
  17604.    ■   Parameter passing
  17605.  
  17606.  
  17607.  
  17608.  Finally, issues relating to compiling and linking are discussed (including
  17609.  use of different memory models with C-language routines).
  17610.  
  17611.  
  17612.  Making Mixed-Language Calls
  17613.  
  17614.  Mixed-language programming always involves a function or procedure call. For
  17615.  example, a BASIC main module may need to execute a specific task that you
  17616.  would like to program separately. However, instead of calling a BASIC
  17617.  subprogram, you decide to call a C function.
  17618.  
  17619.  Mixed-language calls necessarily involve multiple modules. Instead of
  17620.  compiling all of your source modules with the same compiler, you use
  17621.  different compilers. In the situation mentioned earlier, you could compile
  17622.  the main-module source file with BC, another source file (written in C) with
  17623.  the C compiler, and then link the two object files using LINK.
  17624.  Alternatively, you could compile the C function, and then link it into a
  17625.  Quick library and call the function from a program running within the QBX
  17626.  environment.
  17627.  
  17628.  Any mixed language program that includes a BASIC module must have a BASIC
  17629.  main module. This is because BASIC requires that the environment be
  17630.  initialized in a unique way. No other language performs this initialization.
  17631.  
  17632.  Figure 12.1 illustrates the syntax of a mixed-language call in which a BASIC
  17633.  main module calls a C function.
  17634.  
  17635.  46f1nt??
  17636.  
  17637.  
  17638.  See the  DECLARE statement in the  BASIC Language Reference for more
  17639.  information.
  17640.  
  17641.  Despite syntactic differences, BASIC  FUNCTION and  SUB procedures are very
  17642.  similar to subroutines, procedures and functions in other Microsoft
  17643.  languages. The principal difference is that C, Pascal, FORTRAN functions,
  17644.  and BASIC  FUNCTION procedures (and assembly language procedures) can all
  17645.  return values, whereas the BASIC  SUB procedure, FORTRAN  SUBROUTINE, and
  17646.  Pascal procedure cannot. Table 12.1 shows the correspondence between routine
  17647.  calls in different languages.
  17648.  
  17649.  For example, a BASIC module can make a  SUB procedure call to a C function
  17650.  declared with the  void keyword in place of a return type. BASIC should make
  17651.  a  FUNCTION procedure call in order to call a C function that returns a
  17652.  value; otherwise, the return value is lost.
  17653.  
  17654.  Note
  17655.  
  17656.  In this chapter, "routine" refers to any C function, BASIC  SUB or  FUNCTION
  17657.  procedure, or assembly language procedure that can be called from another
  17658.  module.
  17659.  
  17660.  BASIC  DEF FN functions and  GOSUB subroutines cannot be called from another
  17661.  language.
  17662.  
  17663.  
  17664.  Naming Convention Requirement
  17665.  
  17666.  The calling program and the called routine must agree on the names of
  17667.  identifiers. Identifiers can refer to routines (functions, procedures, and
  17668.  subroutines) or to variables that have a public or global scope. Each
  17669.  language alters the names of identifiers.
  17670.  
  17671.  "Naming convention" refers to the way a compiler alters the name of the
  17672.  routine (or a public variable) before placing it in an object file.
  17673.  Languages may alter the identifier names differently. You can choose between
  17674.  several naming conventions to ensure that the names in the calling routine
  17675.  agree with those in the called routine. If the names of public variables or
  17676.  called routines are stored differently in any of the object files being
  17677.  linked, LINK will not be able to find a match. It will instead report
  17678.  unresolved external references.
  17679.  
  17680.  It is important that you adopt a compatible naming convention when you issue
  17681.  a mixed-language call. If the name of the called routine is stored
  17682.  differently in any of the object files being linked, then LINK is unable to
  17683.  find a match and reports an unresolved external reference.
  17684.  
  17685.  
  17686.  Microsoft compilers place machine code into object files; but they also
  17687.  place there the names of all routines and variables that need to be accessed
  17688.  publicly. That way, LINK can compare the name of a routine called in one
  17689.  module to the name of a routine defined in another module and recognize a
  17690.  match. Names are stored in ASCII format.
  17691.  
  17692.  BASIC, Pascal, and FORTRAN translate each letter to uppercase. BASIC drops
  17693.  its type-declaration characters ( %,  &,  !,  #,  @,  $). BASIC preserves
  17694.  the first 40 characters of any name; FORTRAN and Pascal recognize the first
  17695.  31 characters.
  17696.  
  17697.  Note
  17698.  
  17699.  Microsoft FORTRAN prior to version 5.0 truncated identifiers to six
  17700.  characters. As of version 5.0, FORTRAN retains up to 31 characters of
  17701.  significance unless you use the /4Yt option. Microsoft Pascal prior to
  17702.  version 4.0 preserved only the first eight characters of a name. As of
  17703.  version 5.0, Pascal preserves the first 31 characters.
  17704.  
  17705.  The C compiler does not translate any letters to uppercase, but it inserts a
  17706.  leading underscore
  17707.  
  17708.  ( _ ) in front of the name of each routine. C preserves only the first 31
  17709.  characters of a name.
  17710.  
  17711.  If a name is longer than the language recognizes, additional characters are
  17712.  simply not placed in the object file. Also, when the mixed-language keyword
  17713.  CDECL is specified in the BASIC  DECLARE statement, periods within a name
  17714.  are converted to underscores by BASIC (in addition to adding the leading
  17715.  underscore).
  17716.  
  17717.  Differences in naming conventions are dealt with automatically by
  17718.  mixed-language keywords, as long as you follow two rules:
  17719.  
  17720.    n
  17721.  
  17722.        If you use any FORTRAN routines that were compiled with the  $TRUNCATE
  17723.        metacommand enabled or with the /4Yt command-line option, make all
  17724.        names six characters or less. Make all names six characters or less
  17725.        when using FORTRAN routines compiled with versions of the FORTRAN
  17726.        compiler prior to version 5.0.
  17727.  
  17728.    n
  17729.  
  17730.        Do not use the /NOIGNORECASE (/NOI) LINK option (which causes LINK to
  17731.        treat identifiers in a case-sensitive manner). With C modules, this
  17732.        means that you must be careful not to rely upon differences between
  17733.        uppercase and lowercase letters when programming.
  17734.  
  17735.  
  17736.  
  17737.        The Microsoft C compiler drivers CL and QCL always set the /NOI option
  17738.        for the link stage when compiling and linking. This can be a problem
  17739.        if your C module contains mixed-case identifiers. For example, the C
  17740.        compiler translates the identifier Name to _Name, preserving the
  17741.        capital N. When BASIC, Pascal, and FORTRAN implement the C convention,
  17742.        they don't preserve case, they simply translate the characters to
  17743.        lowercase -- so in this case, they would translate Name to _name. The
  17744.        identifiers _Name and _name do not match when /NOI is set. To avoid
  17745.        problems, make all your C-program identifiers lowercase, or link as a
  17746.        separate stage (i.e. use LINK, rather than CL or QCL to link), and
  17747.        make sure not to specify /NOI.
  17748.  
  17749.  
  17750.  Note
  17751.  
  17752.  You use the command-line option /Gc (generate Pascal-style function calls)
  17753.  when you compile your C modules, or if you declare a function or variable
  17754.  with the  pascal keyword, the compiler will translate your identifiers to
  17755.  uppercase.
  17756.  
  17757.  In the preceding figure, the BASIC Compiler inserts a leading underscore in
  17758.  front of Prn as it places the name into the object file, because the  CDECL
  17759.  keyword directs the BASIC Compiler to use the C naming convention. BASIC
  17760.  will also convert all letters to lowercase when this keyword is used.
  17761.  (Converting letters to lowercase is not part of the C naming convention;
  17762.  however, it is consistent with the programming style of many C programs.)
  17763.  
  17764.  
  17765.  Calling-Convention Requirement
  17766.  
  17767.  "Calling convention" refers to the way a language implements a call. The
  17768.  choice of calling convention affects the actual machine instructions that a
  17769.  compiler generates in order to execute (and return from) a function or
  17770.  procedure call.
  17771.  
  17772.  The calling convention is a low-level protocol. It is crucial that the two
  17773.  routines concerned (the routine issuing a call and the routine being called)
  17774.  recognize the same protocol. Otherwise, the processor may receive
  17775.  inconsistent instructions, thus causing unpredictable behavior.
  17776.  
  17777.  The use of a calling convention affects programming in two ways:
  17778.  
  17779.  
  17780.        The calling routine uses a calling convention to determine in what
  17781.        order to pass arguments (parameters) to another routine. This
  17782.        convention can either be the default for the language, or specified in
  17783.        a mixed-language interface. In the following example, the  CDECL
  17784.        keyword in the BASIC declaration of the C function overrides the
  17785.        default BASIC convention and causes the parameters to be passed in the
  17786.        order in which a C function normally expects to receive them:
  17787.  
  17788.  
  17789.  DECLARE Func1 CDECL (N%, M%)
  17790.  
  17791.  
  17792.        The called routine uses a calling convention to determine in what
  17793.        order to receive the parameters passed to it. With a C function, this
  17794.        convention can be specified in the function definition. In the
  17795.        following example the  fortran keyword in the function definition
  17796.        overrides the default C convention, and causes the C function to
  17797.        receive the parameters consistent with the default
  17798.        BASIC/FORTRAN/Pascal convention:
  17799.  
  17800.  
  17801.  int fortran func2 (int x, int y)
  17802.  {
  17803.        /* body of C function would go here */
  17804.        }
  17805.  
  17806.  
  17807.  In other words, the way the function is declared in BASIC determines which
  17808.  calling convention BASIC uses. However, in C the calling convention can be
  17809.  specified in the function definition. The two conventions must be
  17810.  compatible. It is simplest to adopt the convention of the called routine.
  17811.  For example, a C function would use its own convention to call another C
  17812.  function but must use the BASIC convention to call BASIC. This is because
  17813.  BASIC always uses its own convention to receive parameters. Because the
  17814.  BASIC and C calling conventions are different, you can change the calling
  17815.  convention in either the caller or the called routine, but not in both.
  17816.  
  17817.  
  17818.  Effects of Calling Conventions
  17819.  
  17820.  Calling conventions dictate three things:
  17821.  
  17822.  
  17823.        The way parameters are communicated from one routine to another (in
  17824.        Microsoft mixed-language programming, parameters or pointers to the
  17825.        parameters are passed on the stack)
  17826.  
  17827.  
  17828.        The order in which parameters are passed from one routine to another
  17829.  
  17830.  
  17831.        The part of the program responsible for adjusting the stack
  17832.  
  17833.  
  17834.  
  17835.  Order in Which Arguments Are Pushed (BASIC, FORTRAN, Pascal)
  17836.  
  17837.  The BASIC, FORTRAN and Pascal calling conventions push parameters onto the
  17838.  stack in the order in which they appear in the source code. For example, the
  17839.  following BASIC statement pushes argument A onto the stack first, then B,
  17840.  and then C:
  17841.  
  17842.  CALL Calc( A, B, C )
  17843.  
  17844.  These conventions also specify that the stack is adjusted by the called
  17845.  routine just before returning control to the caller. Figures 12.3 and 12.4
  17846.  illustrate how the calling conventions work at the assembly language level.
  17847.  Note that the stack grows downward.
  17848.  
  17849.  
  17850.  Order in Which Arguments Are Pushed (C)
  17851.  
  17852.  The C calling convention pushes parameters onto the stack in the reverse
  17853.  order from their appearance in the source code. For example, the following C
  17854.  function call pushes c onto the stack, then b and finally a:
  17855.  
  17856.  calc( a, b, c );
  17857.  
  17858.  
  17859.  In contrast with the other high-level languages, the C calling convention
  17860.  specifies that a calling routine always adjusts the stack immediately after
  17861.  the called routine returns control.
  17862.  
  17863.  The BASIC, FORTRAN, and Pascal conventions produce slightly less object
  17864.  code. However, the C convention makes calling with a variable number of
  17865.  parameters possible. (Because the first parameter is always the last one
  17866.  pushed, it is always on the top of the stack; therefore it has the same
  17867.  address relative to the frame pointer, regardless of how many parameters
  17868.  were actually passed.)
  17869.  
  17870.  Note
  17871.  
  17872.  The C-compiler  fastcall keyword, which specifies that parameters are to be
  17873.  passed in registers, is incompatible with programs written in other
  17874.  languages. Avoid using  fastcall or the /Gr command-line option for C
  17875.  functions that you intend to make public to BASIC, FORTRAN, or Pascal
  17876.  programs.
  17877.  
  17878.  
  17879.  Parameter-Passing Requirements
  17880.  
  17881.  The routines in program must agree on the calling convention and the naming
  17882.  convention; they must also agree on the method in which they pass
  17883.  parameters. It is important that your routines send parameters in the same
  17884.  way to ensure proper data transmission and correct program results.
  17885.  
  17886.  
  17887.  Microsoft compilers support three methods for passing a parameter:
  17888.  
  17889. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  17890.  Method                                  Description
  17891.  ────────────────────────────────────────────────────────────────────────────
  17892.  Near reference                          Passes a variable's near (offset)
  17893.                                          address. This address is expressed
  17894.  Method                                  Description
  17895.  ────────────────────────────────────────────────────────────────────────────
  17896.                                         address. This address is expressed
  17897.                                          as an offset from the beginning of
  17898.                                          the default data segment (DGROUP).
  17899.                                          This method gives the called
  17900.                                          routine direct access to the
  17901.                                          variable itself. Any change the
  17902.                                          routine makes to the parameter
  17903.                                          changes the variable in the
  17904.                                          calling routine. BASIC passes by
  17905.                                          near reference as the default. See
  17906.                                          Chapters 11, "Advanced String
  17907.                                          Storage," and 13, "Mixed Language
  17908.                                          Programming with Far Strings," for
  17909.                                          information on passing strings
  17910.                                          stored in far memory.
  17911.  
  17912.  Far reference                           Passes a variable's far (segmented)
  17913.                                          address.This method is similar to
  17914.                                          passing by near reference, except
  17915.  Method                                  Description
  17916.  ────────────────────────────────────────────────────────────────────────────
  17917.                                         passing by near reference, except
  17918.                                          that a longer address is passed.
  17919.                                          This method is slower than passing
  17920.                                          by near reference but is necessary
  17921.                                          when you pass data that is stored
  17922.                                          outside the default data segment.
  17923.                                          (This is an issue in BASIC or
  17924.                                          Pascal only if you have
  17925.                                          specifically requested far memory.
  17926.                                          See Table 12.5 in the section
  17927.                                          "Special Data Types," later in
  17928.                                          this chapter and Chapters 11,
  17929.                                          "Advanced String Storage," and 13,
  17930.                                          "Mixed-Language Programming with
  17931.                                          Far Strings," for information on
  17932.                                          when BASIC and QBX store data in
  17933.                                          far memory).
  17934.  
  17935.  Value                                   Passes only the variable's value,
  17936.  Method                                  Description
  17937.  ────────────────────────────────────────────────────────────────────────────
  17938. Value                                   Passes only the variable's value,
  17939.                                          not its address. With this method,
  17940.                                          the called routine knows the value
  17941.                                          of the parameter but has no access
  17942.                                          to the original variable. Changes
  17943.                                          to a value passed by a parameter
  17944.                                          have no affect on the value of the
  17945.                                          parameter in the calling routine.
  17946.  
  17947.  
  17948.  
  17949.  
  17950.  
  17951.  These different parameter-passing methods mean that you must consider the
  17952.  following when programming with mixed languages:
  17953.  
  17954.    ■   You need to make sure that, for each parameter, the called routine and
  17955.        the calling routine use the same method for passing and receiving the
  17956.        argument. In most cases, you will need to check the parameter-passing
  17957.        defaults used by each language and possibly make adjustments. Each
  17958.        language has keywords or language features that allow you to change
  17959.        parameter-passing methods.
  17960.  
  17961.    ■   You may want to choose a specific parameter-passing method rather than
  17962.        using the defaults of any language. Table 12.2
  17963.  
  17964.  
  17965.  
  17966.  1 When a Pascal or C attribute is applied to a FORTRAN routine, passing by
  17967.  value becomes the default.
  17968.  
  17969.  See Chapters 11, "Advanced String Storage," and 13, "Mixed-Language
  17970.  Programming with Far Strings," for information on how strings in far memory
  17971.  are passed.
  17972.  
  17973.  Using the  BYVAL keyword for a parameter in the BASIC declaration of an
  17974.  other-language routine enables you to perform a true "pass by value" to the
  17975.  other-language routine, as explained in the section "Using the Parameter
  17976.  List" later in this chapter.
  17977.  
  17978.  
  17979.  Compiling and Linking
  17980.  
  17981.  After you have written your source files and decided on a naming convention,
  17982.  a calling convention, and a parameter-passing convention, you are ready to
  17983.  compile and link individual modules.
  17984.  
  17985.  
  17986.  Compiling with Correct Memory Models
  17987.  
  17988.  With BASIC, FORTRAN, and Pascal, no special options are required to compile
  17989.  source files that are part of a mixed-language program. With C, not all
  17990.  memory models are compatible with other languages.
  17991.  
  17992.  BASIC, FORTRAN, and Pascal use only far (segmented) code addresses.
  17993.  Therefore, you must use one of two techniques with C programs that call one
  17994.  of these languages: compile C modules in medium, large, or huge model (using
  17995.  the /A x command-line options), because these models also use far code
  17996.  addresses; or apply the  far keyword to the definitions of C functions you
  17997.  make public. If you use the /A x command-line option to specify medium,
  17998.  large, or huge model, all your function calls become far by default. This
  17999.  means you don't have to declare your functions explicitly with the  far
  18000.  keyword. (Note that you must also declare  extern functions  far, as well as
  18001.  public functions.)
  18002.  
  18003.  Choice of memory model affects the default data pointer size in C and
  18004.  FORTRAN, although this default can be overridden with the  near and  far
  18005.  keywords. With C and FORTRAN, the choice of memory model also affects
  18006.  whether data items are located in the default data segment; if a data item
  18007.  is not located in the default data segment, it cannot be passed by near
  18008.  reference.
  18009.  
  18010.  
  18011.  For more information about code and data address sizes in C, refer to the
  18012.  Microsoft C documentation.
  18013.  
  18014.  
  18015.  Linking with Language Libraries
  18016.  
  18017.  In most cases, you can easily link modules compiled with different
  18018.  languages. However, if any module in a program is a BASIC module, the main
  18019.  module of the program must be a BASIC module. When you link a program that
  18020.  contains a BASIC module, the BASIC main module must appear first on the LINK
  18021.  command line. Do any of the following to ensure that all required libraries
  18022.  link in the correct order:
  18023.  
  18024.    ■   Put all language libraries in the same directory as the source files.
  18025.  
  18026.    ■   List directories containing all needed libraries in the LIB
  18027.        environment variable.
  18028.  
  18029.    ■   Let LINK prompt you for libraries.
  18030.  
  18031.  
  18032.  In each of these cases (assuming the BASIC module appeared first on the LINK
  18033.  command line), LINK finds libraries in the order that it requires them. If
  18034.  you enter the library names on the command line, make sure you enter them in
  18035.  an order that allows LINK to resolve your program's external references.
  18036.  Here are some points to observe when specifying libraries on the command
  18037.  line:
  18038.  
  18039.    ■   If you are listing BASIC libraries on the LINK command line, specify
  18040.        those libraries first.
  18041.  
  18042.    ■   If you are using FORTRAN to write one of your modules, you need to
  18043.        link with the /NOD (no default libraries) option, and explicitly
  18044.        specify all the libraries you need on the LINK command line. You can
  18045.        also specify these libraries with an automatic-response file (or batch
  18046.        file), but you cannot use a default-library search.
  18047.  
  18048.    ■   If your program uses FORTRAN and C, specify the library for the most
  18049.        recent of the two language products first. In addition, make sure that
  18050.        you choose a C-compatible library when you install FORTRAN.
  18051.  
  18052.  
  18053.  The following example shows how to link three modules, mod1, mod2, and mod3,
  18054.  with a user library, GRAFX; the BASIC run-time library, BCL70ENR.LIB; the C
  18055.  run-time library, LLIBCE; and the FORTRAN run-time library, LLIBFORE:
  18056.  
  18057.  LINK /NOD mod1 mod2 mod3,,,BCL70ENR+GRAFX+LLIBCE+LLIBFORE
  18058.  
  18059.  Important
  18060.  
  18061.  Microsoft QuickC version 1.0 used medium model by default when you chose
  18062.  Compile from the Run menu and Obj from the Output Options. QuickC version
  18063.  2.0 uses small model by default. Also when compiling from the command line
  18064.  with either the QCL or CL commands, you must specify the correct memory
  18065.  model. When small model is used, your C object files will not be compatible
  18066.  with your BASIC object files.
  18067.  
  18068.  Linking with a C library containing graphics will result in Duplicate
  18069.  definition errors. Don't include graphics in C libraries that will be linked
  18070.  with BASIC.
  18071.  
  18072.  
  18073.  BASIC Calls to High-Level Languages
  18074.  
  18075.  Microsoft BASIC supports calls to routines written in Microsoft C, FORTRAN,
  18076.  and Pascal. This section describes the necessary syntax for calling these
  18077.  languages, then gives examples of each combination of BASIC with other
  18078.  languages. For simplicity in illustrating concepts, only integers are used
  18079.  as parameters in these examples. The section ends with a description of
  18080.  restrictions on the use of functions from the C standard library. Consult
  18081.  this section if the C functions called in your program use any system or
  18082.  memory-allocation library functions.
  18083.  
  18084.  See the section "Handling Data in Mixed-Language Programming" later in this
  18085.  chapter for information on how to pass specific kinds of data.
  18086.  
  18087.  
  18088.  The BASIC Interface to Other Languages
  18089.  
  18090.  The BASIC  DECLARE statement provides a flexible and convenient interface to
  18091.  other languages. It was introduced in Microsoft QuickBASIC version 4.0.
  18092.  Earlier versions of BASIC that do not provide the  DECLARE statement also do
  18093.  not provide libraries that are compatible with other languages. The  DECLARE
  18094.  statement is summarized in the following section.
  18095.  
  18096.  
  18097.  The DECLARE Statement
  18098.  
  18099.  The  DECLARE statement's syntax differs slightly for  FUNCTION and  SUB
  18100.  procedures. For  FUNCTION procedures, the  DECLARE statement's syntax is as
  18101.  follows:
  18102.  
  18103.   DECLARE FUNCTION  name  CDECL  ALIAS " aliasname"( parameterlist)
  18104.  
  18105.  For  SUB procedures, use this syntax for the  DECLARE statement:
  18106.  
  18107.   DECLARE SUB  name  CDECL  ALIAS " aliasname"( parameterlist)
  18108.  
  18109.  The  name argument is the name that appears in the BASIC source file for the
  18110.   SUB or  FUNCTION procedure you wish to call. Here are the recommended steps
  18111.  for using the  DECLARE statement to call other languages:
  18112.  
  18113.     1. For each distinct interlanguage routine you plan to call, include a
  18114.        DECLARE statement at the beginning of the module-level code of any
  18115.        module in which the routine is called. (QBX cannot automatically
  18116.        generate   DECLARE statements for other-language routines.) For
  18117.        example, your program may call the subprogram Maxparam five different
  18118.        times, each time with different arguments. However, you need to
  18119.        declare Maxparam just once for each module. The   DECLARE statements
  18120.        must be placed near the beginning of the module, preceding all
  18121.        executable statements. A good way to do this is with an include file.
  18122.  
  18123.     2. If you are calling a routine defined in a C module, use  CDECL in the
  18124.        DECLARE statement (unless the C routine is defined with the  pascal or
  18125.         fortran keyword). The  CDECL keyword directs BASIC to use the C
  18126.        naming and calling conventions during each subsequent call to  name.
  18127.  
  18128.  
  18129.  
  18130.     3. If you are calling a C function with a name containing characters that
  18131.        would be illegal in BASIC (for example, the underscore), you can use
  18132.        the  ALIAS feature, discussed in the next section. If you use the
  18133.        CDECL keyword, you can use a period in place of the underscore. BASIC
  18134.        then replaces the period with an underscore.
  18135.  
  18136.     4. Use the parameter list to specify how each parameter is to be passed.
  18137.        See the section "Using the Parameter List" later in this chapter for
  18138.        information on how to use a parameter list.
  18139.  
  18140.     5. Once the routine is properly declared, call it just as you would a
  18141.        BASIC sub or function procedure.
  18142.  
  18143.  
  18144.  The other syntax elements are explained in the following sections.
  18145.  
  18146.  
  18147.  Using ALIAS
  18148.  
  18149.  As noted in the preceding section, the use of the  ALIAS keyword may be
  18150.  necessary if you want to use an underscore as part of the C identifier.
  18151.  Similarly, though it is not likely to be a problem, C, FORTRAN, and Pascal
  18152.  place fewer characters of a name into an object file than BASIC (31 for all
  18153.  versions of C, FORTRAN version 5.0, and Pascal version 4.0, in addition to
  18154.  the leading underscore), which places up to 40 characters of a name into an
  18155.  object file.
  18156.  
  18157.  Note
  18158.  
  18159.  You do not need the  ALIAS feature to remove the type-declaration characters
  18160.  ( %,  &,  !,  #,  @,  $). BASIC automatically removes these characters when
  18161.  it generates object code. Thus, Fact% in BASIC matches fact in C.
  18162.  
  18163.  The  ALIAS keyword directs BASIC to place  aliasname into the object file,
  18164.  instead of  name. The BASIC source file still contains calls to  name.
  18165.  However, these calls are interpreted as if they were actually calls to
  18166.  aliasname.
  18167.  
  18168.  Example
  18169.  
  18170.  In the following example, BASIC places the  aliasname quad_result, rather
  18171.  than the name QuadResult, into the object code. This avoids the use of a
  18172.  mixed-case identifier for the C function, but provides the same type of
  18173.  recognizability as the BASIC name.
  18174.  
  18175.  DECLARE FUNCTION QuadResult% ALIAS "quad_result" (a, b, c)
  18176.  
  18177.  Using the Parameter List
  18178.  
  18179.  The following is the syntax for  parameterlist. Note that you can use  BYVAL
  18180.  or  SEG, but not both:
  18181.  
  18182.  { BYVAL |  SEG}  variable  AS  type ,{ BYVAL |  SEG}  variable  AS  type...
  18183.  
  18184.  Use the  BYVAL keyword to declare a value parameter. In each subsequent
  18185.  call, the corresponding argument will be passed by value (the default method
  18186.  for C modules).
  18187.  
  18188.  
  18189.  Note
  18190.  
  18191.  BASIC provides two ways of "passing by value." In BASIC-only programs you
  18192.  can simulate passing by value by enclosing the argument in parentheses, as
  18193.  follows:
  18194.  
  18195.  CALL Holm((A))
  18196.  
  18197.  This method actually creates a temporary value, whose address is passed. The
  18198.   BYVAL keyword provides the only true method of passing by value, because
  18199.  the value itself is passed, not an address. Using  BYVAL is the only way to
  18200.  make a BASIC program compatible with a non-BASIC routine that expects a
  18201.  value parameter.  BYVAL is only for interlanguage calls; it cannot be used
  18202.  in calls between BASIC routines.
  18203.  
  18204.  Use the  SEG keyword to declare a far-reference parameter. In each
  18205.  subsequent call, the far (segmented) address of the corresponding argument
  18206.  will be passed. See the  DECLARE statement in the  BASIC Language Reference
  18207.  for information and cautions on the use of the  SEG keyword.
  18208.  
  18209.  You can choose any legal name for  variable, but only the type associated
  18210.  with the name has any significance to BASIC. As with other variables, the
  18211.  type can be indicated with a type-declaration character ( %,  &,  !,  #,  @,
  18212.   $), in an  AS  type clause, or by implicit declaration.
  18213.  
  18214.  The  AS  type clause overrides the default type declaration of variable. The
  18215.   type field can be  INTEGER,  LONG,  SINGLE,  DOUBLE,  STRING,  CURRENCY or
  18216.  a user-defined type. Or it can be  ANY, which directs BASIC to permit any
  18217.  type of data to be passed as the argument.
  18218.  
  18219.  Examples
  18220.  
  18221.  In the following example, Calc2 is declared as a C routine that takes three
  18222.  arguments: the first two are integers passed by value, and the last is a
  18223.  single-precision real number passed by value.
  18224.  
  18225.  DECLARE FUNCTION Calc2! CDECL (BYVAL A%, BYVAL B%, BYVAL C!)
  18226.  
  18227.  The following example declares a subprogram, Maxout, that takes an integer
  18228.  passed by far reference and a double-precision real number passed by value.
  18229.  
  18230.  DECLARE SUB Maxout (SEG Var1 AS INTEGER, BYVAL Var2 AS DOUBLE)
  18231.  
  18232.  Alternative BASIC Interfaces
  18233.  
  18234.  Though the  DECLARE statement provides a particularly convenient interface,
  18235.  there are other methods of implementing mixed-language calls.
  18236.  
  18237.  Instead of modifying the behavior of BASIC with  CDECL, you can modify the
  18238.  behavior of C by applying the  pascal or  fortran keyword to the function
  18239.  definition. (These two keywords are functionally equivalent.) Or, you can
  18240.  compile the C module with the /Gc option, which specifies that all C
  18241.  functions, calls, and public symbols use the BASIC/FORTRAN/Pascal
  18242.  convention.
  18243.  
  18244.  
  18245.  For example, the following C function uses the BASIC/FORTRAN/Pascal
  18246.  conventions to receive an integer parameter:
  18247.  
  18248.  int pascal fun1(n)
  18249.   int n;
  18250.   {
  18251.    }
  18252.  
  18253.  You can specify parameter-passing methods even though you omit the  DECLARE
  18254.  statement or omit the parameter list, or both, as follows:
  18255.  
  18256.    ■   You can make the call with the  CALLS statement. The  CALLS statement
  18257.        causes each parameter to be passed by far reference.
  18258.  
  18259.    ■   You can use the  BYVAL and  SEG keywords in the argument list when you
  18260.        make the call.
  18261.  
  18262.  
  18263.  In the following example,  BYVAL and  SEG have the same meaning that they
  18264.  have in a BASIC  DECLARE statement. When you use  BYVAL and  SEG this way,
  18265.  however, you need to be careful because neither the type nor the number of
  18266.  parameters will be checked (as they would be if there were a  DECLARE
  18267.  statement). Also note that, if you do not use a  DECLARE statement, you must
  18268.  use either the  fortran or  pascal keyword in the C function definition, or
  18269.  compile the C function with the /Gc option.
  18270.  
  18271.  CALL Fun2(BYVAL Term1, BYVAL Term2, SEG Sum);
  18272.  
  18273.  Note
  18274.  
  18275.  BASIC provides a system-level function,  B_OnExit, that can be called from
  18276.  other-language routines to log a termination procedure that will be called
  18277.  when a BASIC program terminates or is restarted when a Quick library is
  18278.  present. See the section "B_OnExit Routine" later in this chapter for more
  18279.  information.
  18280.  
  18281.  
  18282.  BASIC Calls to C
  18283.  
  18284.  This section applies the steps outlined earlier to two example programs. An
  18285.  analysis of programming considerations follows each example.
  18286.  
  18287.  
  18288.  Calling C from BASIC with No Return Value
  18289.  
  18290.  The following example demonstrates a BASIC main module calling a C function,
  18291.  maxparam. The function maxparam returns no value, but adjusts the lower of
  18292.  two arguments to equal the higher argument.
  18293.  
  18294.  ' BASIC source file - calls C function returning no value
  18295.  '
  18296.  ' DECLARE Maxparam as subprogram, since there is no return value
  18297.  ' CDECL keyword causes Maxparam call to be made with C
  18298.  ' conventions. Integer parameters passed by near reference
  18299.  ' (BASIC default).
  18300.  
  18301.  DECLARE SUB Maxparam CDECL (A AS INTEGER, B AS INTEGER)
  18302.  '
  18303.  X% = 5
  18304.  Y% = 7
  18305.  PRINT USING "X% = ## Y% = ##";X% ;Y%  ' X% and Y% before call
  18306.  CALL Maxparam(X%, Y%)                 ' Call C function
  18307.  PRINT USING "X% = ## Y% = ##";X% ;Y%  ' X% and Y% after call END
  18308.  
  18309.  /* C source file */
  18310.  /* Compile in MEDIUM or LARGE memory model */
  18311.  /* Maxparam declared VOID because no return value */
  18312.  void maxparam(p1, p2)
  18313.  int near *p1; /* Integer params received by near ref. */
  18314.  int near *p2; /* NEAR keyword not needed in MEDIUM model. */
  18315.  {
  18316.  if (*p1 > *p2)
  18317.  *p2 = *p1;
  18318.  else
  18319.  *p1 = *p2;
  18320.  }
  18321.  
  18322.  You should keep the following programming considerations in mind when
  18323.  calling C from BASIC with no return value:
  18324.  
  18325.  nNaming conventions
  18326.  
  18327.    The  CDECL keyword causes Maxparam to be called with the C naming
  18328.  convention (as _maxparam).
  18329.  
  18330.        ■   Calling conventions
  18331.  
  18332.        ■   The  CDECL keyword causes Maxparam to be called with the C calling
  18333.            convention, which pushes parameters in the reverse order to the
  18334.            way they appear in the source code.
  18335.  
  18336.        ■   Parameter-passing methods
  18337.  
  18338.        ■   Since the C function maxparam may alter the value of either
  18339.            parameter, both parameters must be passed by reference. In this
  18340.            case, near reference was chosen; this method is the default for
  18341.            BASIC (so neither  BYVAL nor  SEG is used) and is specified in C
  18342.            by using near pointers.
  18343.  
  18344.  
  18345.  Far reference could have been specified by applying  SEG to each argument in
  18346.  the  DECLARE statement. In that case, the C parameter declarations would use
  18347.  far pointers.
  18348.  
  18349.  
  18350.  Calling C from BASIC with a Function Call
  18351.  
  18352.  The following example demonstrates a BASIC main module calling a C function,
  18353.  fact. This function returns the factorial of an integer value.
  18354.  
  18355.  ' BASIC source file - calls C function with return value
  18356.  '
  18357.  ' DECLARE Fact as function returning integer (%)
  18358.  ' CDECL keyword causes Fact% call to be made with
  18359.  ' C conventions. Integer parameter passed by value.
  18360.  
  18361.  DECLARE FUNCTION Fact% CDECL (BYVAL N AS INTEGER)
  18362.  '
  18363.  X% = 3
  18364.  Y% = 4
  18365.  PRINT USING "The factorial of X% is ####"; Fact%(X%)
  18366.  PRINT USING "The factorial of Y% is ####"; Fact%(Y%)
  18367.  PRINT USING "The factorial of X%+Y% is ####"; Fact%(X%+Y%)
  18368.  END
  18369.  
  18370.  /* C source file */
  18371.  /* Compile in MEDIUM or LARGE model */
  18372.  /* Factorial function, returning integer */
  18373.  int fact(n)
  18374.  int n; /* Integer passed by value, the C default */
  18375.  {
  18376.  int result = 1;
  18377.  while (n > 0)
  18378.  result *= n--; /* Parameter n modified here */
  18379.  return(result);
  18380.  }
  18381.  
  18382.  You should keep the following programming considerations in mind when
  18383.  calling C from BASIC with a function call:
  18384.  
  18385.    ■   Naming conventions
  18386.  
  18387.        The  CDECL keyword causes Fact to be called with the C naming
  18388.        convention (as _fact).
  18389.  
  18390.        n
  18391.  
  18392.            Calling conventions
  18393.  
  18394.        The  CDECL keyword causes Fact to be called with the C calling
  18395.        convention, which pushes parameters in reverse order.
  18396.  
  18397.  
  18398.  
  18399.    ■   Parameter-passing methods
  18400.  
  18401.        The preceding C function should receive the parameter by value.
  18402.        Otherwise the function will corrupt the parameter's value in the
  18403.        calling module. True passing by value is achieved in BASIC only by
  18404.        applying  BYVAL to the parameter (in the  DECLARE statement in this
  18405.        example); in C, passing by value is the default (except for arrays).
  18406.  
  18407.  
  18408.  
  18409.  BASIC Calls to FORTRAN
  18410.  
  18411.  This section applies the steps previously outlined to two example programs.
  18412.  An analysis of programming considerations follows each example.
  18413.  
  18414.  
  18415.  Calling FORTRAN from BASIC -- Subroutine Call
  18416.  
  18417.  The following example demonstrates a BASIC main module calling a FORTRAN
  18418.  subroutine, MAXPARAM. The subroutine returns no value, but adjusts the lower
  18419.  of two arguments to equal the higher argument.
  18420.  
  18421.  ' BASIC source file - calls FORTRAN subroutine
  18422.   '
  18423.   DECLARE SUB Maxparam ALIAS "MAXPAR" (A AS INTEGER, B AS INTEGER)
  18424.   '
  18425.   ' DECLARE as subprogram, since there is no return value
  18426.   ' ALIAS used because some FORTRAN versions recognize only the
  18427.   ' first 6 characters
  18428.   ' Integer parameters passed by near reference (BASIC default).
  18429.   '
  18430.   X% = 5
  18431.   Y% = 7
  18432.   PRINT USING "X% = ##  Y% = ##";X% ;Y%   ' X% and Y% before call.
  18433.   CALL Maxparam(X%, Y%)                   ' Call FORTRAN function
  18434.   PRINT USING "X% = ##  Y% = ##";X% ;Y%   ' X% and Y% after call.
  18435.   END
  18436.  
  18437.   C   FORTRAN source file, subroutine MAXPARAM
  18438.   C
  18439.   SUBROUTINE MAXPARAM (I, J)
  18440.   INTEGER*2 I NEAR
  18441.   INTEGER*2 J NEAR
  18442.   C
  18443.   C   I and J received by near reference, because of NEAR attribute
  18444.   C
  18445.   IF (I .GT. J) THEN
  18446.   J = I
  18447.   ELSE
  18448.   I = J
  18449.   ENDIF
  18450.   END
  18451.  
  18452.    ■   Naming conventions
  18453.  
  18454.    ■    By default, BASIC places all eight characters of Maxparam into the
  18455.        object file, yet some versions of FORTRAN place only the first six.
  18456.        This potential conflict is resolved with the  ALIAS feature: both
  18457.        modules place MAXPAR into the object file.
  18458.  
  18459.    ■   Calling conventions
  18460.  
  18461.    ■    BASIC and FORTRAN use the same convention for calling.
  18462.  
  18463.    ■   Parameter-passing methods
  18464.  
  18465.    ■    Since the subprogram Maxparam may alter the value of either parameter,
  18466.        both arguments must be passed by reference. In this case, near
  18467.        reference was chosen; this method is the default for BASIC (so neither
  18468.         BYVAL nor  SEG is used) and is specified in FORTRAN by applying the
  18469.        NEAR attribute to each of the parameter declarations.
  18470.  
  18471.  
  18472.  Far reference could have been specified by applying  SEG to each argument in
  18473.  the  DECLARE statement. In that case, the  NEAR attribute would be omitted
  18474.  from the FORTRAN code.
  18475.  
  18476.  
  18477.  Calling FORTRAN from BASIC -- Function Call
  18478.  
  18479.  The following example demonstrates a BASIC main module calling a FORTRAN
  18480.  function, FACT. This function returns the factorial of an integer value.
  18481.  
  18482.  ' BASIC source file - calls FORTRAN function
  18483.   '
  18484.   DECLARE FUNCTION Fact% (BYVAL N AS INTEGER)
  18485.   '
  18486.   ' DECLARE as function returning integer(%).
  18487.   ' Integer parameter passed by value.
  18488.   '
  18489.   X% = 3
  18490.   Y% = 4
  18491.   PRINT USING "The factorial of X%    is ####"; Fact%(X%)
  18492.   PRINT USING "The factorial of Y%    is ####"; Fact%(Y%)
  18493.   PRINT USING "The factorial of X%+Y% is ####"; Fact%(X%+Y%)
  18494.   END
  18495.  
  18496.   C   FORTRAN source file - factorial function
  18497.   C
  18498.  
  18499.  FUNCTION FACT (N)
  18500.  INTEGER*2 I
  18501.   INTEGER*2 TEMP
  18502.  TEMP = 1
  18503.  DO 100 I = 1, N
  18504.   TEMP = TEMP * I
  18505.     100       CONTINUE
  18506.  FACT = TEMP
  18507.   RETURN
  18508.   END
  18509.    ■   Naming conventions
  18510.  
  18511.    ■    There are no conflicts with naming conventions because the function
  18512.        name, FACT, does not exceed the number of characters recognized by any
  18513.        version of FORTRAN. The type declaration character (%) is not placed
  18514.        in the object code.
  18515.  
  18516.    ■   Calling conventions
  18517.  
  18518.    ■    BASIC and FORTRAN use the same convention for calling.
  18519.  
  18520.    ■   Parameter-passing methods
  18521.  
  18522.    ■    When a parameter is passed that should not be changed, it is generally
  18523.        safest to pass the parameter by value. True passing by value is
  18524.        specified in BASIC by applying  BYVAL to an argument in the  DECLARE
  18525.        statement; in FORTRAN, the  VALUE attribute in a parameter declaration
  18526.        specifies that the routine will receive a value rather than an
  18527.        address.
  18528.  
  18529.  
  18530.  
  18531.  BASIC Calls to Pascal
  18532.  
  18533.  This section applies the steps outlined previously to two example programs.
  18534.  An analysis of programming considerations follows each example.
  18535.  
  18536.  
  18537.  Calling Pascal from BASIC -- Procedure Call
  18538.  
  18539.  The following example demonstrates a BASIC main module calling a Pascal
  18540.  procedure, Maxparam. Maxparam returns no value, but adjusts the lower of two
  18541.  arguments to equal the higher argument.
  18542.  
  18543.  ' BASIC source file - calls Pascal procedure
  18544.   '
  18545.   ' DECLARE as subprogram, since there is no return value.
  18546.   ' Integer parameters passed by near reference (BASIC default).
  18547.   '
  18548.   DECLARE SUB Maxparam (A AS INTEGER, B AS INTEGER)
  18549.   X% = 5
  18550.   Y% = 7
  18551.   PRINT USING "X% = ##  Y% = ##";X% ;Y%   ' X% and Y% before call.
  18552.   CALL Maxparam(X%, Y%)                   ' Call Pascal function.
  18553.   PRINT USING "X% = ##  Y% = ##";X% ;Y%   ' X% and Y% after call.
  18554.   END
  18555.  
  18556.  {  Pascal source code - Maxparam procedure. }
  18557.  
  18558.   module Psub;
  18559.    procedure Maxparam(var a:integer; var b:integer);
  18560.  
  18561.   {  Two integer parameters are received by near reference. }
  18562.   {  Near reference is specified with the VAR keyword. }
  18563.  
  18564.    begin
  18565.       if a > b then
  18566.           b := a
  18567.       else
  18568.           a := b
  18569.    end;
  18570.   end.
  18571.    ■   Naming conventions
  18572.  
  18573.    ■    Note that name length is not an issue because Maxparam does not exceed
  18574.        eight characters.
  18575.  
  18576.    ■   Calling conventions
  18577.  
  18578.    ■    BASIC and Pascal use the same calling convention.
  18579.  
  18580.    ■   Parameter-passing methods
  18581.  
  18582.    ■    Since the procedure Maxparam may alter the value of either parameter,
  18583.        both parameters must be passed by reference. In this case, near
  18584.        reference was chosen; this method is the default for BASIC (so neither
  18585.         BYVAL nor  SEG is used) and is specified in Pascal by declaring
  18586.        parameters as  VAR.
  18587.  
  18588.  
  18589.  Far reference could have been specified by applying  SEG to each argument in
  18590.  the  DECLARE statement. In that case, the  VARS keyword would be required
  18591.  instead of  VAR.
  18592.  
  18593.  
  18594.  Calling Pascal from BASIC -- Function Call
  18595.  
  18596.  The following example demonstrates a BASIC main module calling a Pascal
  18597.  function, Fact. This function returns the factorial of an integer value.
  18598.  
  18599.  ' BASIC source file - calls Pascal function
  18600.   '
  18601.   ' DECLARE as function returning integer (%).
  18602.   ' Integer parameter passed by value.
  18603.   '
  18604.   DECLARE FUNCTION Fact% (BYVAL N AS INTEGER)
  18605.   '
  18606.  
  18607.  X% = 3
  18608.   Y% = 4
  18609.   PRINT USING "The factorial of X%    is ####"; Fact%(X%)
  18610.   PRINT USING "The factorial of Y%    is ####"; Fact%(Y%)
  18611.   PRINT USING "The factorial of X%+Y% is ####"; Fact%(X%+Y%)
  18612.   END
  18613.  
  18614.   {  Pascal source code - factorial function. }
  18615.  
  18616.   module Pfun;
  18617.    function Fact (n : integer) : integer;
  18618.  
  18619.   {  Integer parameters received by value, the Pascal default. }
  18620.  
  18621.    begin
  18622.       Temp := 1;
  18623.       while n > 0 do
  18624.           begin
  18625.               Temp := Temp * n;
  18626.               n := n - 1;          { Parameter n altered here. }
  18627.           end;
  18628.    end;
  18629.    Fact := Temp;
  18630.  end.
  18631.    ■   Naming conventions
  18632.  
  18633.    ■    Note that name length is not an issue because fact does not exceed
  18634.        eight characters.
  18635.  
  18636.    ■   Calling conventions
  18637.  
  18638.    ■    BASIC and Pascal use the same calling convention.
  18639.  
  18640.    ■   Parameter-passing methods
  18641.  
  18642.    ■    The Pascal function in the preceding example should receive a
  18643.        parameter by value. Otherwise the function will corrupt the
  18644.        parameter's value in the calling module. True passing by value is
  18645.        achieved in BASIC only by applying  BYVAL to the parameter; in Pascal,
  18646.        passing by value is the default.
  18647.  
  18648.  
  18649.  
  18650.  Restrictions on Calls from BASIC
  18651.  
  18652.  BASIC has a much more complex environment and initialization procedure than
  18653.  the other high-level languages. Interlanguage calling between BASIC and
  18654.  other languages is possible only because BASIC intercepts a number of
  18655.  library function calls from the other language and handles them in its own
  18656.  way. In other words, BASIC creates a host environment in which the C, Pascal
  18657.  and FORTRAN routines can function.
  18658.  
  18659.  
  18660.  However, BASIC is limited in its ability to handle some C function calls.
  18661.  Also FORTRAN and Pascal sometimes perform automatic memory allocation that
  18662.  can cause errors that are hard to diagnose.The following sections consider
  18663.  three kinds of limitations: C memory-allocation functions, which may require
  18664.  a special declaration, implicit memory allocation performed by Pascal and
  18665.  FORTRAN, and a few specific C-library functions, which cannot be called at
  18666.  all.
  18667.  
  18668.  
  18669.  Memory Allocation
  18670.  
  18671.  If your C module is medium model and allocates memory dynamically with
  18672.  malloc(), or if you execute explicit calls to  nmalloc() with any memory
  18673.  model, then you need to include the following lines in your BASIC source
  18674.  code before you call C:
  18675.  
  18676.  DIM mallocbuf%(0 TO 2047)
  18677.  COMMON SHARED /NMALLOC/ mallocbuf%()
  18678.  
  18679.  The array can have any name; only the size of the array is significant.
  18680.  However, the name of the common block must be NMALLOC. In QBX environments,
  18681.  you need to put this declaration in a module that you incorporate into a
  18682.  Quick library. See Chapter 19, "Creating and Using Quick Libraries," for
  18683.  more information on Quick libraries.
  18684.  
  18685.  The preceding example has the effect of reserving 4K of space ( 2 bytes *
  18686.  2048) in the common block NMALLOC. When BASIC intercepts C  malloc calls,
  18687.  BASIC allocates space out of this common block.
  18688.  
  18689.  Warning
  18690.  
  18691.  This common block is also used by FORTRAN and Pascal routines that perform
  18692.  dynamic memory allocation in connection with things like opening files or
  18693.  declaring global strings. Depending on the circumstances, however, the 4K of
  18694.  space may not be sufficient, and the error Insufficient heap space may be
  18695.  generated. If this happens, increase the amount of space in NMALLOC using
  18696.  increments of 512 or 1024.
  18697.  
  18698.  When you make far-memory requests in mixed-language programs, you may find
  18699.  it useful to call the BASIC intrinsic function  SETMEM first. This function
  18700.  can be used to reduce the amount of memory BASIC is using, thus freeing
  18701.  memory for far allocations. (An example of this use of  SETMEM appears in
  18702.  the  BASIC Language Reference and in the online Help for  SETMEM.)
  18703.  
  18704.  Important
  18705.  
  18706.  When you call the BASIC  CLEAR statement, all space allocated with near
  18707.  malloc calls is lost. If you use  CLEAR at all, use it only before any calls
  18708.  to  malloc.
  18709.  
  18710.  
  18711.  Incompatible Functions
  18712.  
  18713.  The following C functions are incompatible with BASIC and should be avoided:
  18714.  
  18715.    ■   All forms of  spawn() and  exec()
  18716.  
  18717.    ■    system()
  18718.  
  18719.    ■    getenv()
  18720.  
  18721.    ■    putenv()
  18722.  
  18723.  
  18724.  Calling these functions results in the BASIC error message Advanced feature
  18725.  unavailable.
  18726.  
  18727.  In addition, you should not link with the  xVARSTK.OBJ modules (where  x is
  18728.  a memory model) which C provides to allocate memory from the stack.
  18729.  
  18730.  Note
  18731.  
  18732.  The global C run-time variables environ and _pgmptr are defined as NULL. All
  18733.  functionality of these variables, as well as the functions noted previously,
  18734.  can be emulated using BASIC statements and intrinsic functions.
  18735.  
  18736.  
  18737.  Allocating String Space
  18738.  
  18739.  Other-language routines can allocate dynamic string space by calling the
  18740.  GetSpace$  FUNCTION procedure:
  18741.  
  18742.  FUNCTION GetSpace$ (x) STATIC
  18743.  GetSpace$ = STRING$(x, CHR$(0))
  18744.  END FUNCTION
  18745.  
  18746.  The GetSpace$ procedure returns a near pointer to a string descriptor that
  18747.  points to x bytes of string space. Because this space is managed by BASIC,
  18748.  it can move any time BASIC language code is executed. Therefore, the space
  18749.  must be accessed through the string descriptor, and the string descriptor
  18750.  must not be modified by other-language code. To release this space, pass the
  18751.  near pointer to the string descriptor to the FreeSpace  SUB procedure:
  18752.  
  18753.  SUB FreeSpace(a$) STATIC
  18754.  A$ = ""
  18755.  END SUB
  18756.  
  18757.  Note that the preceding procedures deal with pointers to string descriptors,
  18758.  not string data. String descriptors are always in DGROUP, and therefore
  18759.  always accessed through near pointers. Although variable-length string data
  18760.  is stored in far memory within the QBX environment (or when you compile a
  18761.  program with /Fs) the string descriptors are still in DGROUP. For more
  18762.  information on far strings and mixed-language programming, see Chapters 11,
  18763.  "Advanced String Storage," and 13, "Mixed-Language Programming with Far
  18764.  Strings."
  18765.  
  18766.  
  18767.  To return a string that is the result of a routine in another language, you
  18768.  can return a near pointer to a static string descriptor that is declared in
  18769.  the other-language code. A better method is to use the mixed-language string
  18770.  routines described in Chapter 13, "Mixed Language Programming with Far
  18771.  Strings." Although they are described in relation to far strings (for which
  18772.  they are mandatory), they are just as useful for near strings and should be
  18773.  used with new code. Because BASIC moves such strings around, the static
  18774.  string descriptor allocated by the other-language code becomes invalid after
  18775.  the function returns (or makes a call to any BASIC procedure).
  18776.  
  18777.  Calling BASIC from other languages is described in the section "Calls to
  18778.  BASIC from Other Languages" later in this chapter. Constraints on
  18779.  dynamic-memory allocation in other-language routines (see the sections
  18780.  "Restrictions on Calls from BASIC" and "Memory Allocation" earlier in this
  18781.  chapter) still apply despite the use of a function like GetSpace$.
  18782.  
  18783.  
  18784.  Performing I/O on BASIC Files
  18785.  
  18786.  Other-language routines can perform input and output on files opened by the
  18787.  BASIC  OPEN statement by calling BASIC procedures. The following example is
  18788.  a BASIC  SUB that can be called to print an integer to a BASIC file opened
  18789.  as Fileno%:
  18790.  
  18791.  SUB DoPrint(Fileno%, X%) STATIC
  18792.  PRINT #Fileno%, X%
  18793.  END SUB
  18794.  
  18795.  For constraints on direct file I/O in other-language routines, see the
  18796.  sections "Restrictions on Calls from BASIC" and "Memory Allocation" earlier
  18797.  in this chapter.
  18798.  
  18799.  
  18800.  Events and Errors
  18801.  
  18802.  BASIC events including  COM, key,  TIMER, and  PLAY may occur during
  18803.  execution of other-language code. The other-language code can allow such
  18804.  events to be handled by periodically calling a BASIC routine (this routine
  18805.  could be empty), or as follows:
  18806.  
  18807.    ■   When compiling with the BC command, compile the BASIC procedure with
  18808.        the /V or /W option selected .
  18809.  
  18810.    ■   Within the QBX environment, simply specify event-handling syntax in
  18811.        the procedure itself, then use the Run menu's Make EXE File or Make
  18812.        Library command to create an object file, or to incorporate the
  18813.        procedure into a Quick library.
  18814.  
  18815.  
  18816.  The following BASIC  SUB procedure lets you create BASIC errors:
  18817.  
  18818.  SUB MakeError(X%) STATIC
  18819.  ERROR X%
  18820.  END SUB
  18821.  
  18822.  
  18823.  When your other-language routine passes the error number to this procedure,
  18824.  the  ERROR statement is executed and BASIC recovers the stack back to the
  18825.  previous call to non-BASIC code. The BASIC statement containing the error
  18826.  ERROR X% is the statement that  RESUME would re-execute.  RESUME NEXT would
  18827.  re-execute at the following statement. See Chapter 8, "Error Handling" for
  18828.  more information on using new Microsoft BASIC error-handling features.
  18829.  
  18830.  Calling BASIC from other languages is described in the following section
  18831.  "Calls to BASIC from Other Languages."
  18832.  
  18833.  
  18834.  Calls to BASIC from Other Languages
  18835.  
  18836.  Microsoft C, FORTRAN, and Pascal can call routines written in Microsoft
  18837.  BASIC, if the main program is in BASIC. The following sections describe the
  18838.  necessary syntax for calling BASIC from other languages. Only simple
  18839.  parameter lists are used.
  18840.  
  18841.  See the section "Handling Data in Mixed-Language Programming" later in this
  18842.  chapter for information on how to pass particular kinds of data.
  18843.  
  18844.  
  18845.  Other Language Interfaces to BASIC
  18846.  
  18847.  Because they share similar calling conventions, calling BASIC procedures
  18848.  from Pascal and FORTRAN is straightforward. With FORTRAN, you need only
  18849.  write an interface for each BASIC procedure that will be called, then call
  18850.  them as needed. When calling BASIC from Pascal, declare the BASIC routine
  18851.  with an  extern procedure or function declaration (whichever is
  18852.  appropriate).
  18853.  
  18854.  Remember that, although BASIC can pass data in several ways, it can only
  18855.  receive data that is passed by near reference. Therefore, data passed to any
  18856.  BASIC procedure must be passed as a near pointer. If your version of FORTRAN
  18857.  recognizes only the first 6 characters of a name, you should use BASIC's
  18858.  ALIAS feature if the routine you are calling has a name longer than FORTRAN
  18859.  can recognize.
  18860.  
  18861.  Observe the following rules when you call BASIC from C, FORTRAN or Pascal:
  18862.  
  18863.        Start in a BASIC main module. You need to use the  DECLARE statement
  18864.        to provide an interface to the other-language module.
  18865.  
  18866.        If the other language is C or Pascal , declare the BASIC routine as
  18867.        extern, and include type information for parameters. Use either the
  18868.        fortran or  pascal keyword in the C declaration of the BASIC procedure
  18869.        to override the default C calling convention. If the other language is
  18870.        FORTRAN, use the  INTERFACE statement to create the interfaces to
  18871.        BASIC routines.
  18872.  
  18873.        Make sure that all data is passed as near pointers. BASIC can pass
  18874.        data in a variety of ways, but it is unable to receive data in any
  18875.        form other than near reference.
  18876.  
  18877.         With near pointers, the program assumes that the data is in the
  18878.         default data segment (DGROUP). If you want to pass data that is not
  18879.         in the default data segment, then first copy the data to a variable
  18880.         that is in the default data segment (this is only a consideration
  18881.         with large-model C programs).
  18882.  
  18883.        Compile the C language modules in medium or large memory models.
  18884.  
  18885.  
  18886.  Note
  18887.  
  18888.  All other-language-to-BASIC calling for programs within the QBX environment
  18889.  must be confined within a Quick library. In other words, a C function can
  18890.  call a BASIC procedure within the Quick library, but it cannot call a
  18891.  procedure defined within the QBX environment itself.
  18892.  
  18893.  
  18894.  Calling BASIC from C
  18895.  
  18896.  The C interface to BASIC is more complicated than the FORTRAN or Pascal
  18897.  interfaces. It uses standard C prototypes, with the  fortran or  pascal
  18898.  keyword. Using either of these keywords causes the routine to be called with
  18899.  the BASIC/FORTRAN/Pascal naming and calling conventions. The following steps
  18900.  are recommended for executing a mixed-language call from C:
  18901.  
  18902.     1. Write a prototype for each mixed-language routine called. The
  18903.        prototype should declare the routine  extern for the purpose of
  18904.        program documentation.
  18905.  
  18906.        Instead of using the  fortran or  pascal keyword, you can simply
  18907.        compile with the Pascal calling convention option (/Gc). The /Gc
  18908.        option causes all functions in the module to use the
  18909.        BASIC/FORTRAN/Pascal naming and calling conventions (except where you
  18910.        apply the  cdecl keyword).
  18911.  
  18912.     2. Pass near pointers to variables when calling a BASIC routine. You can
  18913.        obtain a pointer to a variable with the address-of ( &) operator.
  18914.  
  18915.         In C, array names are always translated into pointers to the first
  18916.         element of the array; hence, arrays are always passed by reference.
  18917.         However, although BASIC arrays are referenced through near pointers,
  18918.         the pointer points to an array descriptor, not the array data itself.
  18919.  
  18920.  
  18921.         Therefore, other-language arrays cannot be passed directly to BASIC.
  18922.         The prototype you declare for your function ensures that you are
  18923.         passing the correct length address (that is, near or far). In BASIC
  18924.         the address must be near.
  18925.  
  18926.     3. Issue a function call in your program as though you were calling a C
  18927.        function.
  18928.  
  18929.     4. Always compile the C module in either medium, large, or huge model, or
  18930.        use the  far keyword in your function prototype. This ensures that a
  18931.        far (intersegment) call is made to the routine.
  18932.  
  18933.  
  18934.  
  18935.  There are two rules of syntax that apply when you use the  fortran or
  18936.  pascal keyword:
  18937.  
  18938.        The  fortran and  pascal keywords modify only the item immediately to
  18939.        their right.
  18940.  
  18941.        The  near and  far keywords can be used with the  fortran and  pascal
  18942.        keywords in prototypes. The sequences  fortran far and  far fortran
  18943.        are equivalent.
  18944.  
  18945.  
  18946.  The keywords  pascal and  fortran have the same effect on the program; using
  18947.  one or the other makes no difference except for internal program
  18948.  documentation. Use  fortran to declare a FORTRAN routine,  pascal to declare
  18949.  a Pascal routine, and either keyword to declare a BASIC routine.
  18950.  
  18951.  The following example declares func to be a BASIC, Pascal, or FORTRAN
  18952.  function taking two  short parameters and returning a  short value.
  18953.  
  18954.  extern short pascal far func( short near * sarg1, short near * sarg2 );
  18955.  
  18956.  The following example declares  func to be pointer to a BASIC, Pascal, or
  18957.  FORTRAN procedure that takes a  long parameter and returns no value. The
  18958.  keyword  void is appropriate when the called routine is a BASIC sub
  18959.  procedure, Pascal procedure, or FORTRAN subroutine, since it indicates that
  18960.  the function returns no value.
  18961.  
  18962.  extern void ( fortran far * func )( long near * larg );
  18963.  
  18964.  The following example declares func to be a  far BASIC  FUNCTION procedure,
  18965.  Pascal function, or FORTRAN function. The routine receives a  double
  18966.  parameter by reference (because it expects a pointer to a  double) and
  18967.  returns a  int value.
  18968.  
  18969.  int far pascal func( near double * darg );
  18970.  
  18971.  The following example is equivalent to the preceding example ( pascal far is
  18972.  equivalent to  far pascal).
  18973.  
  18974.  int pascal far func( near double * darg );
  18975.  
  18976.  When you call a BASIC procedure, you must use the FORTRAN/Pascal conventions
  18977.  to make the call. (However, if your C function calls FORTRAN or Pascal, you
  18978.  have a choice. You can make C adopt the conventions described in the
  18979.  previous section, or you can make the FORTRAN or Pascal routine adopt the C
  18980.  conventions.) The call must be a far call. You can insure this either by
  18981.  compiling in medium (or larger) model, or by using the  far keyword, as
  18982.  shown in the preceding example.
  18983.  
  18984.  
  18985.  Example
  18986.  
  18987.  The following example demonstrates a BASIC program that calls a C function.
  18988.  The C function then calls a BASIC function that returns twice the number
  18989.  passed it and a BASIC subprogram that prints two numbers.
  18990.  
  18991.  ' BASIC source
  18992.  DEFINT A-Z
  18993.  DECLARE SUB Cprog CDECL()
  18994.  CALL Cprog
  18995.  END
  18996.  ' This is the BASIC FUNCTION called in Cprog().
  18997.  FUNCTION Dbl(N) STATIC
  18998.  Dbl = N*2
  18999.  END FUNCTION
  19000.  ' This is the BASIC SUB called in Cprog().
  19001.  SUB Printnum(A,B) STATIC
  19002.  PRINT "The first number is ";A
  19003.  PRINT "The second number is ";B
  19004.  END SUB
  19005.  
  19006.  
  19007.  
  19008.  /* C source; compile in medium or large model to insure far calls*/
  19009.  extern int fortran dbl(int near *);
  19010.  extern void fortran printnum(int near *, int near *);
  19011.  void cprog()
  19012.  {
  19013.  int near a = 5; /* NEAR guarantees that the data */
  19014.  int near b = 6; /* will be placed in default */
  19015.  /* data segment (DGROUP) */
  19016.  printf("Two times 5 is %d\n", dbl(&a));
  19017.  printnum(&a, &b);
  19018.  }
  19019.  
  19020.  In the preceding example, note that the addresses of a and b are passed,
  19021.  since BASIC expects to receive addresses for parameters. Also note that the
  19022.  keyword  near is used to declare each pointer in the C function declaration
  19023.  of printnum; this keyword would be unnecessary if it was known that the C
  19024.  module was compiled in medium model rather than large.
  19025.  
  19026.  Calling and naming conventions are resolved by the  CDECL keyword in BASIC's
  19027.  declaration of Cprog, and by  fortran in C's declaration of dbl and
  19028.  printnum.
  19029.  
  19030.  
  19031.  Calling BASIC from FORTRAN
  19032.  
  19033.  The following example illustrates the process of calling a BASIC routine
  19034.  from FORTRAN. First, a call must be made from BASIC to FORTRAN, then the
  19035.  FORTRAN routine can call BASIC routines.
  19036.  
  19037.  Example
  19038.  
  19039.  In this example the FORTRAN subroutine calls a BASIC function that returns
  19040.  twice the number passed to it, then calls a BASIC sub procedure that prints
  19041.  two numbers.
  19042.  
  19043.  ' BASIC source
  19044.  defint a-z
  19045.  declare SUB Fprog ()
  19046.  call fprog
  19047.  END
  19048.  function dbl (N) static
  19049.  Dbl = N * 2
  19050.  end sub
  19051.  
  19052.  
  19053.  sub printnum(A,B)
  19054.  print "the first number is " ; A
  19055.  print "the second number is " ; B
  19056.  end sub
  19057.  
  19058.  cfortran subroutine
  19059.  CCalls a BASIC function that receives one integer
  19060.  Cand a BASIC sub that takes two integers.
  19061.  C
  19062.  interface to integer * 2 function dbl (n)
  19063.  integer * 2  n [neaR]
  19064.  end
  19065.  C
  19066.  calias attribute may necessary since BASIC recognizes more
  19067.   Cthan six characters of the name "Printnum" (but FORTRAN
  19068.   Cversion may not).
  19069.  C
  19070.  interface to subroutine printn [alias:  'Printn'] (N1, N2)
  19071.  integer * 2 N1 [neaR]
  19072.  integer * 2 N2 [neaR]
  19073.  end
  19074.  c
  19075.  cparameters must be declared NEAR in the parameter
  19076.  cdeclarations; BASIC receives only 2-byte pointers.
  19077.  c
  19078.  subroutine fprog
  19079.  integer * 2 dbl
  19080.  integer * 2 A, B
  19081.  a = 5
  19082.  b = 6
  19083.  write (*,*) 'Two times 5 is ' , dbl(a)
  19084.  call printn(a,b)
  19085.  end
  19086.  
  19087.  In the preceding example, note that the near attribute is used in the
  19088.  fortran routines, so that near addresses will be passed to basic instead of
  19089.  far addresses.
  19090.  
  19091.  Calling BASIC from Pascal
  19092.  
  19093.  The following example illustrates the process of calling a BASIC routine
  19094.  from Pascal. First, a call must be made from BASIC to Pascal, then the
  19095.  Pascal routine can call BASIC routines.
  19096.  
  19097.  Example
  19098.  
  19099.  In this example the Pascal procedure calls a BASIC function that returns
  19100.  twice the number passed to it, then calls a BASIC sub procedure that prints
  19101.  two numbers.
  19102.  
  19103.  ' BASIC source
  19104.  defint a-z
  19105.  declare SUB Fprog ()
  19106.  call fprog
  19107.  END
  19108.  function dbl (N) static
  19109.  Dbl = N * 2
  19110.  end sub
  19111.  
  19112.  sub printnum(A,B)
  19113.  print "the first number is " ; A
  19114.  print "the second number is " ; B
  19115.  end sub
  19116.  
  19117.  { *Pascal procedure *}
  19118.  { *Calls a BASIC function and a BASIC sub*}
  19119.  
  19120.  module pproc ;
  19121.  procedure pprog() ;
  19122.  
  19123.  
  19124.  function Dbl (var n:integer) : integer ; extern ;
  19125.  procedure Printnum (var n1,n2:integer) ; extern ;
  19126.  var a,b:integer ;
  19127.  begin
  19128.  a := 5
  19129.  b := 6 ;
  19130.  writeln ('Two times 5 is ' , Dbl (a) )
  19131.  Printnum(a,b)
  19132.  end
  19133.  end.
  19134.  
  19135.  Note that in the preceding example, every argument in the external
  19136.  declarations must be declared var, since BASIC can only receive near
  19137.  pointers as parameters.
  19138.  
  19139.  
  19140.  Handling Data in Mixed-Language Programming
  19141.  
  19142.  This section discusses naming and calling conventions in a mixed-language
  19143.  program. It also describes how various languages represent strings,
  19144.  numerical data, arrays, and logical data.
  19145.  
  19146.  
  19147.  Default Naming and Calling Conventions
  19148.  
  19149.  Each language has its own default naming and calling conventions, listed in
  19150.  Table 12.3
  19151.  
  19152.  
  19153.  BASIC Conventions
  19154.  
  19155.  When you call BASIC routines, you must pass all arguments by near reference
  19156.  (near pointer).
  19157.  
  19158.  You can modify the conventions observed by BASIC routines that call C
  19159.  functions by using the  DECLARE,  BYVAL,  SEG, and  CALLS keywords. For more
  19160.  information about the use of these keywords, see the  BASIC Language
  19161.  Reference.
  19162.  
  19163.  
  19164.  FORTRAN Conventions
  19165.  
  19166.  You can modify the conventions observed by FORTRAN routines that call BASIC
  19167.  by using the  INTERFACE keyword. For more information about the use of
  19168.  mixed-language keywords, see the  Microsoft FORTRAN  Reference.
  19169.  
  19170.  
  19171.  Pascal Conventions
  19172.  
  19173.  You can modify the conventions observed by Pascal routines that call BASIC
  19174.  by using the  VAR,  CONST,  ADR,  VARS,  CONSTS, and  ADS keywords. For more
  19175.  information about the use of these keywords, see the  Microsoft Pascal
  19176.  Compiler User's Guide.
  19177.  
  19178.  
  19179.  Passing Data by Reference or Value
  19180.  
  19181.  The preceding sections introduced the general concepts of passing by
  19182.  reference and passing by value. They also noted that, by default, BASIC
  19183.  passes by reference, and C passes by value.
  19184.  
  19185.  This section further describes language features that override the default.
  19186.  For example, using the  BYVAL keyword in a  DECLARE statement causes BASIC
  19187.  to pass a given parameter by value rather than by reference.
  19188.  
  19189.  The next section summarizes parameter-passing methods for BASIC, discussing
  19190.  how to pass arguments by value, by near reference, and by far reference.
  19191.  Then, the same issues are discussed for C. To write a successful
  19192.  mixed-language interface, you must consider how each parameter is passed by
  19193.  the calling routine, and how each is received by the called routine.
  19194.  
  19195.  
  19196.  BASIC Arguments
  19197.  
  19198.  The default for BASIC is to pass all arguments by near reference.
  19199.  
  19200.  Note
  19201.  
  19202.  Every BASIC  SUB or  FUNCTION procedure always receives data by near
  19203.  reference. The rest of this section summarizes how BASIC passes arguments.
  19204.  
  19205.  
  19206.  Passing BASIC Arguments by Value
  19207.  
  19208.  An argument is passed by value when the called routine is first declared
  19209.  with a  DECLARE statement in which the  BYVAL keyword is applied to the
  19210.  argument. Arrays and user-defined types cannot be passed by value in BASIC.
  19211.  
  19212.  
  19213.  Passing BASIC Arguments by Near Reference
  19214.  
  19215.  The BASIC default is to pass by near reference. Use of  SEG,  SSEG,  BYVAL,
  19216.  or  CALLS changes this default. Note that when you pass an array or a
  19217.  string, you are passing the descriptor (which is always in DGROUP), so even
  19218.  if the data is stored in far memory, the descriptor is passed by near
  19219.  reference.
  19220.  
  19221.  
  19222.  Passing BASIC Arguments by Far Reference
  19223.  
  19224.  Using  SEG to modify a parameter in a preceding  DECLARE statement causes
  19225.  BASIC to pass that parameter by far reference. When  CALLS is used to invoke
  19226.  a routine, BASIC passes each argument in a call by far reference.
  19227.  
  19228.  Examples
  19229.  
  19230.  The following example passes the first argument, A%, by value, the second
  19231.  argument, B%, by near reference, and the third argument, C%, by far
  19232.  reference:
  19233.  
  19234.  DECLARE SUB Test(BYVAL A%, B%, SEG C%)
  19235.  CALL Test(X%, Y%, Z%)
  19236.  
  19237.  The following example passes each argument by far reference:
  19238.  
  19239.  CALLS Test2(X%, Y%, Z%)
  19240.  
  19241.  C Arguments
  19242.  
  19243.  The default for C is to pass all arrays by reference (near or far, depending
  19244.  on the memory model) and all other data types by value. C uses far data
  19245.  pointers for compact, large, and huge models, and near data pointers for
  19246.  small and medium models.
  19247.  
  19248.  
  19249.  Passing C Arguments by Value
  19250.  
  19251.  The C default is to pass all nonarrays (which includes all data types other
  19252.  than those explicitly declared as arrays) by value.
  19253.  
  19254.  
  19255.  Passing C Arguments by Reference (Near or Far)
  19256.  
  19257.  In C, passing a pointer to an data item is equivalent to passing the data
  19258.  item by reference.
  19259.  
  19260.   After control passes to the called function, each reference to the
  19261.  parameter is prefixed by an asterisk (*).
  19262.  
  19263.  Note
  19264.  
  19265.  To pass a pointer to a data item, prefix the parameter in the call statement
  19266.  with an ampersand (&). To receive a pointer to an data item, prefix the
  19267.  parameter's declaration with an asterisk (*). In the latter case, this may
  19268.  mean adding a second asterisk (*) to a parameter which already has an
  19269.  asterisk (*). For example, to receive a pointer by value, declare it as
  19270.  follows:
  19271.  
  19272.  int *ptr;
  19273.  
  19274.  To receive the same pointer to an integer by reference, declare it as
  19275.  follows:
  19276.  
  19277.  int **ptr;
  19278.  
  19279.  The default for arrays is to pass by reference.
  19280.  
  19281.  
  19282.  Effect of Memory Models on Size of Reference
  19283.  
  19284.  In C, near reference is the default for passing pointers in small and medium
  19285.  models. Far reference is the default in the compact, large, and huge models.
  19286.  
  19287.  Near pointers can be specified with the  near keyword, which overrides the
  19288.  default pointer size. However, if you are going to override the default
  19289.  pointer size of a parameter, then you must explicitly declare the parameter
  19290.  type in function prototypes as well as function definitions.
  19291.  
  19292.  Far pointers can be specified with the  far keyword, which overrides the
  19293.  default pointer size.
  19294.  
  19295.  
  19296.  FORTRAN Arguments
  19297.  
  19298.  The FORTRAN default is to pass and receive all arguments by reference. The
  19299.  size of the address passed depends on the memory model.
  19300.  
  19301.  
  19302.  Passing FORTRAN Arguments by Value
  19303.  
  19304.  A parameter is passed by value when declared with the value attribute. This
  19305.  declaration can occur either in a fortran interface statement (which
  19306.  determines how to pass a parameter) or in a function or subroutine
  19307.  declaration (which determines how to receive a parameter).
  19308.  
  19309.  A function or subroutine declared with the Pascal or C attribute will pass
  19310.  by value all parameters declared in its parameter list (except for
  19311.  parameters declared with the reference attribute). This change in default
  19312.  passing method applies to function and subroutine definitions, as well as to
  19313.  an interface statement.
  19314.  
  19315.  
  19316.  Passing fortran Arguments by Reference (Near or Far)
  19317.  
  19318.  Passing by reference is the default, however, if either the c or Pascal
  19319.  attribute is applied to a function or subroutine declaration, then you need
  19320.  to apply the reference attribute to any parameter of the routine that you
  19321.  want passed by reference.
  19322.  
  19323.  
  19324.  Use of Memory Models and FORTRAN Reference Parameters
  19325.  
  19326.  Near reference is the default for medium-model FORTRAN programs; far
  19327.  reference is the default for large- and huge-model programs.
  19328.  
  19329.  Note
  19330.  
  19331.  Versions of fortran prior to 4.0 always compile in large memory model. You
  19332.  can apply the near attribute to reference parameters in order to specify
  19333.  near reference.You can apply the far attribute to reference parameters in
  19334.  order to specify far reference. These keywords enable you to override the
  19335.  default. They have no effect when they specify the same method as the
  19336.  default. You may need to apply more than one attribute to a given parameter.
  19337.  If so, enclose both attributes in brackets, separated by commas:
  19338.  
  19339.  REAL*4  X   [ near, reference]
  19340.  
  19341.  Pascal Arguments
  19342.  
  19343.  The Pascal default is to pass all arguments by value.
  19344.  
  19345.  
  19346.  Passing Pascal Arguments by Near Reference
  19347.  
  19348.  Parameters are passed by near reference when declared as var or const.
  19349.  
  19350.  Parameters are also passed by near reference when the adr of a variable, or
  19351.  a pointer to a variable, is passed by value. In other words, the address of
  19352.  the variable is first determined. Then, this address is passed by value.
  19353.  (This is essentially the same method employed in C.)
  19354.  
  19355.  
  19356.  Passing Pascal Arguments by Far Reference
  19357.  
  19358.  Parameters are passed by far reference when declared as vars or consts.
  19359.  Parameters are also passed by far reference when the ads of a variable is
  19360.  passed by value.
  19361.  
  19362.  
  19363.  Numeric and String Data
  19364.  
  19365.  This section discusses passing and receiving different kinds of data.
  19366.  Discussion includes the differences in string format and methods of passing
  19367.  strings between BASIC and other languages.
  19368.  
  19369.  
  19370.  Integer and Real Numbers
  19371.  
  19372.  Integer and real numbers are usually the simplest kinds of data to pass
  19373.  between languages. However, the type of numeric data is named differently in
  19374.  each language; furthermore, not all data types are available in every
  19375.  language, and another type may have to be substituted in some cases.
  19376.  
  19377.  Table 12.4 shows equivalent data types in BASIC, C, FORTRAN, and Pascal.
  19378.  
  19379.  
  19380.  Warning
  19381.  
  19382.  As noted in Table 12.4, C sometimes performs automatic data conversions
  19383.  which other languages do not perform. You can prevent C from performing such
  19384.  conversions by declaring a variable as the only member of a structure and
  19385.  then passing this structure. For example, you can pass a variable x of type
  19386.  float by first declaring the structure as follows:
  19387.  
  19388.  struct {
  19389.   float x;
  19390.     } x_struct;
  19391.  
  19392.  If you pass a variable of type char or float by value and do not take this
  19393.  precaution, then the C conversion may cause the program to fail.
  19394.  
  19395.  
  19396.  Strings
  19397.  
  19398.  Strings are stored in a variety of formats. Therefore, some transformation
  19399.  is frequently required to pass strings between languages. This section
  19400.  presents the string format(s) used in each language, and then describes
  19401.  methods for passing strings within specific combinations of languages.
  19402.  Microsoft BASIC includes several special string manipulation routines
  19403.  designed to simplify passing strings between modules written in different
  19404.  languages. They are described in Chapter 13, "Mixed-Language Programming
  19405.  with Far Strings."
  19406.  
  19407.  
  19408.  BASIC String Format
  19409.  
  19410.  Near strings (strings generated by the command-line compiler when you do not
  19411.  specify the /Fs option) are stored in BASIC as 4-byte string descriptors, as
  19412.  shown in Figure 12.7.
  19413.  
  19414.  The first field of the string descriptor contains a 2-byte integer
  19415.  indicating the length of the actual string text. The second field contains
  19416.  the near address of this text. This address is an offset into the default
  19417.  data area (DGROUP).
  19418.  
  19419.  
  19420.  Within the QBX environment the near-string model is never used. Instead, QBX
  19421.  (and the command-line compiler when using the /Fs option) stores the data of
  19422.  variable-length strings in far memory. Using far strings is described in
  19423.  detail in Chapters 11, "Advanced String Storage," and 13, "Mixed-Language
  19424.  Programming with Far Strings." Briefly, however, the difference is that with
  19425.  far strings, the address of the string data cannot fit into the 2 bytes
  19426.  normally used for the string-data address in a near-string descriptor.
  19427.  Instead, the DGROUP string descriptor for a far string contains "handles"
  19428.  (pointers representing two levels of indirection) to the information
  19429.  necessary to retrieve the string. Although the size and location of both
  19430.  types of string descriptors is the same, the information in a descriptor of
  19431.  a far string is totally different from the information in a string
  19432.  descriptor for a near string. BASIC retains the convention of having all
  19433.  descriptors in DGROUP, and at the same time increase the amount of available
  19434.  string space.
  19435.  
  19436.  Note
  19437.  
  19438.  You cannot mix BASIC modules compiled for near strings with modules compiled
  19439.  for far strings. You can use BASIC modules compiled with the /Fs option in a
  19440.  mixed-language program if the other-language routines never try to
  19441.  manipulate BASIC strings by mimicking a BASIC string descriptor. If your
  19442.  other-language routines need to manipulate BASIC strings, you need to
  19443.  rewrite the source code since old methods of mimicking near-string
  19444.  descriptors in other languages will fail when the BASIC module uses far
  19445.  strings. You can guarantee portability among all modules by always using the
  19446.  Microsoft BASIC mixed-language string routines, and always assuming modules
  19447.  will be compiled with the /Fs option. If mixed-language source code is
  19448.  written assuming far strings, and for some reason you want to recompile the
  19449.  modules using the near string model, they will still work properly. In
  19450.  near-string code, addresses are assigned by BASIC's string-space management
  19451.  routines. These management routines need to be available to reassign this
  19452.  address whenever the length of the string changes or memory is compacted,
  19453.  yet these management routines are only available to BASIC. Therefore, other
  19454.  languages should not alter the length, or the address, of a BASIC string.
  19455.  See Chapter 13, "Mixed-Language Programming with Far Strings," for more
  19456.  information on far strings and mixed-language programming.
  19457.  
  19458.  
  19459.  C String Format
  19460.  
  19461.  C stores strings as simple arrays of bytes and uses a terminating null
  19462.  character (numerical 0, ASCII NUL) as a delimiter. For example, consider the
  19463.  string declared as follows:
  19464.  
  19465.  char str[] = "String of text"
  19466.  
  19467.  The string is stored in 15 bytes of memory as shown in Figure 12.8:
  19468.  
  19469.  Since str is an array like any other, it is passed by reference, just as
  19470.  other C arrays are.
  19471.  
  19472.  
  19473.  FORTRAN String Format
  19474.  
  19475.  FORTRAN stores strings as a series of bytes at a fixed location in memory.
  19476.  There is no delimiter at the end of the string. Consider the string declared
  19477.  as follows:
  19478.  
  19479.  STR = "String of text"
  19480.  
  19481.  The string is stored in memory as shown in Figure 12.9:
  19482.  
  19483.  FORTRAN passes strings by reference, as it does all other data.
  19484.  
  19485.  Note
  19486.  
  19487.  Be careful using FORTRAN's variable length strings in mixed-language
  19488.  programming. The temporary variable used to communicate string length is not
  19489.  accessible to other languages. When passing such a string to another
  19490.  language, you need to design a method by which the target routine can find
  19491.  the end of the string. For example, if the target routine were in C, you
  19492.  could append an ASCII NUL to terminate the string before passing it.
  19493.  
  19494.  
  19495.  Pascal String Format
  19496.  
  19497.  Pascal has two types of strings, each of which uses a different format: a
  19498.  fixed-length type  STRING and the variable-length type  LSTRING. Fixed
  19499.  length string format is exactly like the FORTRAN format described in the
  19500.  preceding section. The variable-length strings are stored with the length of
  19501.  the string in the first byte, followed immediately by the string data
  19502.  itself. For example, consider the  LSTRING declared as follows:
  19503.  
  19504.  VAR STR:LSTRING(14);
  19505.  STR := 'String of text'
  19506.  
  19507.  The string is stored in 15 bytes of memory, as shown in Figure 12.10:
  19508.  
  19509.  Passing Strings Between BASIC and Other Languages
  19510.  
  19511.  When a BASIC string (such as A$) appears in an argument list, BASIC passes
  19512.  the address of a string descriptor rather than the actual string data. The
  19513.  BASIC string descriptor is not compatible with the string formats of other
  19514.  languages. Because no other language handles strings the way BASIC does, you
  19515.  cannot pass strings between BASIC and the other languages in their native
  19516.  forms. In previous versions of BASIC you had two choices: pass the address
  19517.  of the BASIC string data to the other language or mimic the form of the
  19518.  BASIC string descriptor in the other language, then use that to access the
  19519.  string as BASIC would access one of its own strings. Either of these methods
  19520.  worked, but greatly limited how you could work with strings in each
  19521.  language. You can still use either method when working with near strings in
  19522.  BASIC, but you must use BASIC's new string manipulation routines (described
  19523.  in Chapter 13, "Mixed-Language Programming with Far Strings") when working
  19524.  with far strings. These routines also improve the reliability and
  19525.  flexibility of interlanguage manipulation of near strings, and should always
  19526.  be used for new code. Although the old methods are described in succeeding
  19527.  sections, they are not recommended for new code.
  19528.  
  19529.  Warning
  19530.  
  19531.  When you pass a string from BASIC to another language, the called routine
  19532.  should under no circumstances alter the length or address of the string.
  19533.  Other languages lack BASIC's string-space management routines. Therefore,
  19534.  altering the length of a BASIC string is liable to corrupt parts of the
  19535.  BASIC string space. Changes that do not affect length and address, however,
  19536.  are safe. If the routine that receives the string calls any BASIC routine,
  19537.  the address of the string data may change. The second field of the string
  19538.  descriptor maintained by BASIC is then updated with the new address of the
  19539.  string text.
  19540.  
  19541.  
  19542.  Passing Strings from BASIC
  19543.  
  19544.  When you compile BASIC modules without the /Fs option, both the string
  19545.  descriptor and the string data are stored in DGROUP. Within the QBX
  19546.  environment (and when you compile a program using the /Fs compiler option),
  19547.  Microsoft BASIC stores variable-length string information in far memory.
  19548.  There is still a string descriptor in DGROUP, but it contains different
  19549.  information than a descriptor for a near string. Microsoft BASIC includes
  19550.  two new functions sseg and ssegadd you can use to retrieve the segment of a
  19551.  string, and the complete far address of far string data, respectively. You
  19552.  can use  SSEG in combination with  SADD (or just use  SSEGADD by itself) to
  19553.  obtain values for directly addressing the data of strings in far memory.
  19554.  
  19555.  Note
  19556.  
  19557.  For far strings, ssegadd performs the same role as sadd does for near
  19558.  strings. Both of these (and sseg) return values. The seg keyword is used
  19559.  differently. It is neither a statement nor a function, and is simply used to
  19560.  indicate that an address that is being passed is in fact a far (4-byte)
  19561.  address. The section "The BASIC Interface to Other Languages" earlier in
  19562.  this chapter describes using seg in interlanguage declarations and calls.
  19563.  
  19564.  
  19565.  The  SADD,  SSEG,  SSEGADD and  LEN functions extract parts of BASIC string
  19566.  descriptors. sseg returns the segment of part of the address of the data of
  19567.  a variable-length string.  SADD extracts the complete address of the data of
  19568.  a string stored in DGROUP (near memory), or the offset of the string data of
  19569.  a string stored in far memory.  SSEGADD returns the complete (segment and
  19570.  offset) address of a string whose data is stored in far memory.  LEN
  19571.  extracts the length of any variable-length string. The results of these
  19572.  functions can then be passed to other languages.
  19573.  
  19574.  BASIC should pass the result of the  SADD  SSEG, or ssegadd  functions by
  19575.  value. Bear in mind that the string's address, not the string itself, is
  19576.  passed by value. This amounts to passing the string itself by reference. The
  19577.  BASIC module passes the string address, and the other module receives the
  19578.  string address. The addresses returned by  SADD and sseg are declared as
  19579.  type integer; the address returned by  SSEGADD is declared as a long. These
  19580.  are equivalent to C's near and far pointer types, respectively.
  19581.  
  19582.  Pass LEN(A$) as you would normally pass a 2-byte integer. Use values
  19583.  returned by  SADD, sseg or  SSEGADD immediately because several BASIC
  19584.  operations may cause movement of strings in memory. Note that  SADD cannot
  19585.  be used with fixed-length strings. See Appendix B, "Data Types, Constants,
  19586.  Variables, and Arrays," for more information.
  19587.  
  19588.  
  19589.  Passing BASIC Strings to C
  19590.  
  19591.  Before attempting to pass a BASIC string to C, you should first make the
  19592.  string conform to C-string format by appending a null byte on the end with
  19593.  an expression as follows:
  19594.  
  19595.  A$ = A$ + CHR$(0)
  19596.  
  19597.  You can use either of the following two methods for passing a near string
  19598.  from BASIC to C:
  19599.  
  19600.        Pass the string address and string length as separate arguments, using
  19601.        the  SADD and  LEN functions. (If you are linking to a C run-time
  19602.        library routine, this is the only workable method.) In the following
  19603.        example, SADD(A$) returns the near address of the string data. This
  19604.        address must be passed by value, since it is equivalent to a pointer
  19605.        (even though it is treated by BASIC as an integer). Passing by
  19606.        reference would attempt to pass the address of the address, rather
  19607.        than the address itself.
  19608.  
  19609.  DECLARE SUB Test CDECL(BYVAL S%, BYVAL N%)
  19610.  CALL Test(SADD(A$), LEN(A$))
  19611.  
  19612.        void Test(s, n)
  19613.        char near *s;
  19614.        int n;
  19615.       {  /* body of function */
  19616.  
  19617.  }
  19618.  
  19619.    ■   C must receive a near pointer since only the near (offset) address is
  19620.        being passed by BASIC. Near pointers are the default pointer size in
  19621.        medium-model C.
  19622.  
  19623.            If the string is a near string, pass the string descriptor itself,
  19624.            with a call statement as follows:
  19625.  
  19626.  CALL Test2(A$)      In this case, the C function must declare a structure
  19627.  for the parameter that has the appropriate fields (address and length) for a
  19628.  BASIC string descriptor. The C function should then expect to receive a
  19629.  pointer to a structure of this type.
  19630.  
  19631.  Note that any calls back to BASIC from within the preceding C function may
  19632.  invalidate the string-descriptor information. This method of mimicking BASIC
  19633.  string descriptors should not be used in new code. Use the BASIC
  19634.  string-manipulation routines described in Chapter 13, "Mixed-Language
  19635.  Programming with Far Strings," instead.
  19636.  
  19637.  
  19638.  Passing C Strings to BASIC
  19639.  
  19640.  When a C string appears in an argument list, C passes the address of the
  19641.  string. (A C string is just an array and so is passed by reference.) C can
  19642.  still pass near string data to BASIC in the form of a string descriptor.
  19643.  However, when the BASIC program uses far strings (/Fs option when compiling
  19644.  from command line, or by default in QBX), the special string manipulation
  19645.  routines described in Chapter 13, "Mixed-Language Programming with Far
  19646.  Strings," must be used.
  19647.  
  19648.  To use the string descriptor method (for near strings only), first allocate
  19649.  a string in C; then, create a structure that mimics to a BASIC string
  19650.  descriptor. Pass this structure by near reference, as in the following
  19651.  example:
  19652.  
  19653.  char cstr[] = "ABC";
  19654.  struct {
  19655.  int sd_len;
  19656.  char *sd_addr;
  19657.  } str_des;
  19658.  str_des.sd_len = strlen(cstr);
  19659.  str_des.sd_addr = cstr;
  19660.  bsub(&str_des);
  19661.  
  19662.  
  19663.  As noted previously, this method still works for BASIC programs that do not
  19664.  use the far strings feature of Microsoft BASIC. If you've successfully used
  19665.  this method with old code, it will still work. However, the Microsoft BASIC
  19666.  routines discussed in Chapter 13, "Mixed-Language Programming with Far
  19667.  Strings," can be used for both near and far strings, and make passing
  19668.  strings between language modules simpler and more reliable. In all cases,
  19669.  make sure that the string originates in C, not in BASIC. Strings originating
  19670.  in BASIC are subject to being moved around in memory during BASIC string
  19671.  management.
  19672.  
  19673.  
  19674.  Passing BASIC Strings to Fortran
  19675.  
  19676.  Fortran's variable-length strings are unique and cannot be a part of a
  19677.  mixed-language interface. Use  SADD to pass the address of the BASIC string.
  19678.  The FORTRAN routine should declare a character variable of the same length
  19679.  (which is fixed).
  19680.  
  19681.  DECLARE SUB Test(BYVAL S%)    ' or use S# if the address is far
  19682.  A$ = "abcd"
  19683.  CALL Test (SADD(A$))' or SSEGADD if string is far
  19684.  .
  19685.  .
  19686.  .
  19687.  C FORTRAN SOURCE
  19688.  C
  19689.  SUBROUTINE    TEST(STRINGA)
  19690.  CHARACTER*4 STRINGA [NEAR]
  19691.  
  19692.  In the preceding example, SADD(A$) must be passed by value, since it is
  19693.  actually an address, not an integer. Note that the fortran declaration
  19694.  CHARACTER*4 STRINGA [NEAR] declares a fixed-length parameter received by
  19695.  near reference. See Chapter 13, "Mixed-Language Programming with Far
  19696.  Strings," for information on passing BASIC far strings.
  19697.  
  19698.  
  19699.  Passing Fortran Strings to BASIC
  19700.  
  19701.  FORTRAN cannot directly pass strings to BASIC because BASIC expects to
  19702.  receive a string descriptor when passed a string. Yet there is an indirect
  19703.  method for passing FORTRAN strings to BASIC, if the BASIC program does not
  19704.  use far strings. First, allocate a fixed-length string in FORTRAN, declare
  19705.  an array of two 2-byte integers, and treat the array as a string descriptor.
  19706.  Next, assign the length of the string to the first element, and assign the
  19707.  address of the string to the second element (using the  LOC function).
  19708.  Finally, pass the integer array itself by reference. BASIC can receive and
  19709.  process this array just as it would a string descriptor
  19710.  
  19711.  If you've successfully used this method with old code, it will still work
  19712.  with near-string programs. However, the Microsoft BASIC routines discussed
  19713.  in Chapter 13, "Mixed-Language Programming with Far Strings," can be used
  19714.  for near and far strings, and make passing strings between language modules
  19715.  simpler and more reliable. In all cases, make sure that the string
  19716.  originates in FORTRAN, not in BASIC. Strings originating in BASIC are
  19717.  subject to being moved around in memory during BASIC string management.
  19718.  
  19719.  
  19720.  Passing BASIC Strings to Pascal
  19721.  
  19722.  The same technique used for passing strings to FORTRAN can be used to pass a
  19723.  BASIC string to Pascal when the BASIC program uses near strings. However,
  19724.  the Pascal routine should declare the string as a  VAR parameter in order to
  19725.  receive the string by near reference. See Chapter 13, "Mixed-Language
  19726.  Programming with Far Strings." The Pascal code must declare the fixed-length
  19727.  type string (4) in a separate statement, then use the declared type in a
  19728.  procedure declaration as show by the following example:
  19729.  
  19730.  DECLARE SUB Test(BYVAL S%)' or use S& if the address is far
  19731.  A$ = "abcd"
  19732.  CALL Test(SADD(A$))' or use SSEGADD if string is far
  19733.  type stype4=string(4);
  19734.  procedure Test (VAR StringA:stype4);{near string}
  19735.  
  19736.  Passing Pascal Strings to BASIC
  19737.  
  19738.  To pass a Pascal string to near-string BASIC program, you can first allocate
  19739.  a string in Pascal. Next, create a record identical to a BASIC string
  19740.  descriptor. Initialize this record with the string length and address, and
  19741.  then pass the record by near reference. If the BASIC program uses far
  19742.  strings, use the string manipulation routines described in Chapter 13,
  19743.  "Mixed-Language Programming with Far Strings."
  19744.  
  19745.  If you've successfully used this method with old code, it will still work
  19746.  with near-string programs. However, the Microsoft BASIC routines discussed
  19747.  in Chapter 13, "Mixed-Language Programming with Far Strings," can be used
  19748.  for both near and far strings, and make passing strings between language
  19749.  modules simpler and more reliable. In all cases, make sure that the string
  19750.  originates in Pascal, not in BASIC. Strings originating in BASIC are subject
  19751.  to being moved around in memory during BASIC string management.
  19752.  
  19753.  
  19754.  Special Data Types
  19755.  
  19756.  This section considers special types of data that are either arrays or
  19757.  structured types (that is, data types that contain more than one field).
  19758.  
  19759.  
  19760.  Arrays
  19761.  
  19762.  When you program in only one language, arrays do not present special
  19763.  problems; the language is consistent in its handling of arrays. When you
  19764.  program with more than one language, however, you need to be aware of two
  19765.  special problems that may arise with arrays:
  19766.  
  19767.    ■   Arrays are implemented differently in BASIC than in other Microsoft
  19768.        languages, so that you must take special precautions when you pass an
  19769.        array from BASIC to another language (including assembly language). A
  19770.        reference to an array in BASIC is really a reference to an array
  19771.        descriptor. Array descriptors are always in DGROUP. Array data
  19772.        however, is sometimes stored in DGROUP and sometimes in far memory.
  19773.        Further, array-data storage differs slightly in QBX from array-data
  19774.        storage in the command-line compiler. Table 12.5 summarizes array-data
  19775.        storage for both QBX and the command-line compiler.
  19776.  
  19777.  
  19778.  
  19779.    Remember, string arrays are arrays of string descriptors, not arrays of
  19780.  strings themselves. No matter where the actual strings are stored, the array
  19781.  descriptor is always in DGROUP. The array of the string descriptors referred
  19782.  to by the array descriptor is in DGROUP as well. With arrays of near strings
  19783.  (the default in a program compiled from the command line), the strings
  19784.  themselves are also always in DGROUP, but with far strings the actual
  19785.  strings are in far memory. Since QBX always uses far strings, the actual
  19786.  strings are always in far memory in QBX.
  19787.  
  19788.        Arrays are declared and indexed differently in each language.
  19789.  
  19790.  
  19791.  
  19792.  Passing Arrays from BASIC
  19793.  
  19794.  Most Microsoft languages permit you to reference arrays directly. In C, for
  19795.  example, an array name is equivalent to the address of the first element.
  19796.  
  19797.  This simple implementation is possible because the location of data for an
  19798.  array never changes.
  19799.  
  19800.  BASIC uses an array descriptor, however, which is similar in some respects
  19801.  to a BASIC string descriptor, but far more complicated. The array descriptor
  19802.  is necessary because BASIC may shift the location of array data in memory,
  19803.  as BASIC handles memory allocation for arrays dynamically. See Appendix B,
  19804.  "Data Types, Constants, Variables, and Arrays," for specific information and
  19805.  cautions about passing BASIC arrays.
  19806.  
  19807.  C, FORTRAN, and Pascal have no equivalent of the BASIC array descriptor.
  19808.  More importantly, they lack access to BASIC's space-management routines for
  19809.  arrays. Therefore, you may safely pass arrays from BASIC only if you follow
  19810.  three rules:
  19811.  
  19812.  
  19813.        Pass the array's address by applying the varptr function to the first
  19814.        element of the array, then pass the result by value. To pass the
  19815.        address of data stored in far memory, get the segment with varseg, the
  19816.        offset with varptr, then pass both by value. The target language
  19817.        receives the address of the first element, and considers it to be the
  19818.        address of the entire array. It can then access the array with its
  19819.        normal array-indexing syntax. The example following this list
  19820.        illustrates this approach.
  19821.  
  19822.  
  19823.  
  19824.    Alternatively, only a far reference to an array can be passed by passing
  19825.  its first element by far reference as in the following fragments:
  19826.  
  19827.  
  19828.  CALLS Proc1 (A(0,0))
  19829.  CALL Proc2 (SEG A(0,0))  n
  19830.  
  19831.        The routine that receives the array must not, under any circumstances,
  19832.        make a call back to BASIC. If it does, then the location of the array
  19833.        data may change, and the address that was passed to the routine
  19834.        becomes meaningless.
  19835.  
  19836.        BASIC may pass any member of an array by value. With this method, the
  19837.        preceding precautions do not apply.
  19838.  
  19839.  
  19840.  Example
  19841.  
  19842.  The following example passes an array from BASIC to FORTRAN:
  19843.  
  19844.  ' basic source file
  19845.  defint a-z
  19846.  dim a( 1 to 20)
  19847.  declare sub ArrFix(byval Addr as integer)
  19848.  .
  19849.  .
  19850.  .
  19851.  call ArrFix (varptr (a(1)) )
  19852.  print a(1)
  19853.  end
  19854.  
  19855.  cfortran source file
  19856.  
  19857.  c
  19858.  
  19859.  subroutine arrfix (arr)
  19860.  
  19861.  integer * 2  arr  [neaR]  (20)
  19862.  
  19863.  arr(1) = 5
  19864.  
  19865.  end
  19866.  
  19867.  
  19868.  In the preceding example, assuming the program is compiled from the command
  19869.  line, BASIC considers that the argument passed is the near address of an
  19870.  array element. FORTRAN considers it to be the near address of the array
  19871.  itself. Both assumptions are correct. You can use essentially the same
  19872.  method for passing BASIC arrays to Pascal or C. The parameter was declared
  19873.  BYVAL and  AS INTEGER because a near (2-byte) address needed to be passed.
  19874.  To pass a far address, you could use the following code instead:
  19875.  
  19876.  declare sub ArrFix(byval SegAdd AS Integer, byval Addr as integer)
  19877.   call ArrFix (varseg( A(0) ), varptr( a(0) )  )
  19878.  
  19879.  
  19880.  The first field is the segment returned by varseg. If you use cdecl then be
  19881.  sure to pass the offset address before the segment address, because cdecl
  19882.  causes parameters to be passed in reverse order:
  19883.  
  19884.  declare sub ArrFix(byval Addr as integer byval,SegAdd AS Integer)
  19885.   call ArrFix ( varptr( a(0) ) , varseg( A(0) ) )
  19886.  
  19887.  Note
  19888.  
  19889.  You can apply the  LBOUND and  UBOUND functions to a BASIC array, to
  19890.  determine lower and upper bounds, and then pass the results to another
  19891.  routine. This way, the size of the array does not need to be determined in
  19892.  advance. See the  BASIC Language Reference for more information on the
  19893.  LBOUND and  UBOUND functions.
  19894.  
  19895.  
  19896.  Array Indexing and Declaration
  19897.  
  19898.  Each language varies somewhat in the way that arrays are declared and
  19899.  indexed. Array indexing is a source-level consideration and involves no
  19900.  transformation of data. There are three differences in the way elements are
  19901.  indexed by each language:
  19902.  
  19903.  
  19904.        The way an array's lower bound is specified differs among Microsoft
  19905.        languages.
  19906.  
  19907.         By default, FORTRAN indexes the first element of an array as 1. BASIC
  19908.         and C index it as 0. Pascal lets you begin indexing at any integer
  19909.         value. Recent versions of BASIC and FORTRAN also give you the option
  19910.         of specifying lower bounds at any integer value.
  19911.  
  19912.  
  19913.        The way the number of elements in an array is declared is different in
  19914.        BASIC than for the other languages. For example, in BASIC, the
  19915.        constants that are used in the array declaration DIM Arr%(5,5)
  19916.        represent the upper bounds of the array. Therefore, the last element
  19917.        is indexed as Arr%(5,5). The constants used in a C array declaration
  19918.        represent the actual number of elements in each dimension, not upper
  19919.        bounds as they do in BASIC. Therefore, the last element in the C array
  19920.        declared as int arr[5][5] is indexed as arr[4][4], rather than as
  19921.        arr[5][5].
  19922.  
  19923.  
  19924.        Some languages vary subscripts in row-major order; others vary
  19925.        subscripts in column-major order. This issue only affects arrays with
  19926.        more than one dimension. When you traverse an array in row-major
  19927.        order, you access each column of a row before moving on to the next
  19928.        row. Therefore, with row-major order (the only choice with C and
  19929.        Pascal) each element of the rightmost subscript is accessed before the
  19930.        leftmost subscript changes. When you traverse an array in column-major
  19931.        order, you access each row of a column before moving on to the next
  19932.        column. Thus, with column-major order (used by BASIC by default, and
  19933.        the only choice in FORTRAN), each element of the leftmost subscript is
  19934.        accessed before the rightmost subscript changes. Thus, in C the first
  19935.        four elements of an array declared as X [3][3], are:
  19936.  
  19937.  
  19938.  X[0][0] X[0][1] X[0][2] X[1][0]
  19939.  
  19940.  In BASIC, the corresponding four elements are:
  19941.  
  19942.  X(0,0) X(1,0) X(2,0) X(0,1)
  19943.  
  19944.  Similarly, the following references both refer to the same place in memory
  19945.  for an array:
  19946.  
  19947.  arr1[2][8] /* in C */
  19948.  Arr1(8,2) ' in BASIC
  19949.  
  19950.  The preceding examples assume that the C and BASIC arrays use lower bounds
  19951.  of 0.
  19952.  
  19953.  
  19954.  Declaring Arrays
  19955.  
  19956.  Table 12.6 shows equivalences for array declarations in each language. In
  19957.  this table,  i represents the number of elements in the leftmost dimension
  19958.  for column-major arrays and  I  is used for the leftmost dimension of
  19959.  row-major arrays. Similarly,  J represents the number of elements in the
  19960.  rightmost dimension for column-major arrays and  j is used for row-major
  19961.  arrays.
  19962.  
  19963.  
  19964.  Compiling BASIC Modules for Row-Major Array Storage
  19965.  
  19966.  When you compile a BASIC program with the bc compiler, you can select the /R
  19967.  compile option, which specifies that row-major order is to be used, rather
  19968.  than the default column-major order. BASIC is the only Microsoft language
  19969.  that permits you to specify how arrays should be stored. The /R option is
  19970.  available only when compiling from the command line. The only choice for
  19971.  array storage in QBX (or when compiling from within QBX using the Make EXE
  19972.  command) is column-major storage.
  19973.  
  19974.  
  19975.  Array Data in Memory
  19976.  
  19977.  The following code could be used in BASIC to fill a 4 x 5 array with
  19978.  integers from 0 through 19:
  19979.  
  19980.  DIM Arr%(3,4) AS integer
  19981.  for I% = 0 to 3
  19982.  for J% = 0 to 4
  19983.  Arr%(I%, J%) = number%
  19984.  print Arr%(I%, J%)
  19985.  Number%= Number%+1
  19986.  Next J%
  19987.  next I%
  19988.  
  19989.  Because BASIC uses column-major array storage by default, the numbers would
  19990.  be stored in contiguous memory locations. However, the order of the data, as
  19991.  placed in memory, is not the same as the numeric sequence (0\- - 19) of the
  19992.  data. Because the arrangement in memory is column major, it would be as
  19993.  shown in Figure 12.11.
  19994.  
  19995.  5f17bfffHowever, if compiled from the command line with BASIC's /R option,
  19996.  the array shown in the preceding section would be stored as shown in Figure
  19997.  12.12:
  19998.  
  19999.  5f14bfffWithin the QBX environment only the default can be used; the /R
  20000.  option is not available.
  20001.  
  20002.  
  20003.  Passing Arrays Between Modules
  20004.  
  20005.  Only BASIC allows you to choose the order in which arrays are stored.
  20006.  Therefore, when you pass an array from BASIC to a language that expects
  20007.  arrays to be stored in row-major order, the easiest thing to do is to use
  20008.  the /R option when compiling the BASIC module. The next easiest thing to do
  20009.  is to reverse the order of the dimensions in the array declaration of one of
  20010.  the languages. For example, if you wanted to pass the BASIC array described
  20011.  in the preceding section to C function contained in a Quick library loaded
  20012.  in the QBX environment, this would be the only option.
  20013.  
  20014.  
  20015.  Given the address of the first element of the BASIC array shown in the
  20016.  preceding BASIC code, a C function could use the following code to print out
  20017.  its elements in the order in which they would be printed out in the BASIC
  20018.  code:
  20019.  
  20020.  int arr[5][4] ;/* Note the number of elements = 5 */
  20021.  int number = 0  , I = 0, j = 0 ;/* in the major dimension, but the */
  20022.  for (I =0; I = 4 ; I++)/* major-dimension upper bound = 4 */
  20023.  for (j =0; j = 3 ; j++)
  20024.  printf( "%d ", arr[I][j] ) ;
  20025.  
  20026.  The data stored by BASIC is not actually reordered by the C code. Instead
  20027.  the dimensions in the C array are declared in reverse order from those in
  20028.  the BASIC declaration. To access an element in an array, compilers use a
  20029.  formula similar to the following:
  20030.  
  20031.  starting address + ((MajorUpperBound * MajorSubscript) + MinorSubscript) *
  20032.  scale
  20033.  
  20034.   MajorUpperBound is the number of elements in the major dimension (in the
  20035.  default BASIC case, the right, or column dimension).  MajorSubscript is the
  20036.  actual major index value of the element you want to access (with the BASIC
  20037.  default, the right index value).  MinorSubscript is the actual minor index
  20038.  value of the element you want to access (in the case of BASIC, the left
  20039.  index value). The  scale is the size of each data element, for example an
  20040.  integer is 2 bytes, a long integer is 4 bytes, etc.
  20041.  
  20042.  Example
  20043.  
  20044.  The following references all refer to the same place in memory for an array:
  20045.  
  20046.  arr1[2][8]/* in C */
  20047.  
  20048.  Arr1[3,9]{  in Pascal, assuming lower bounds of 1  }
  20049.  
  20050.  ARR1(8,2)    ' in BASIC, assuming default array storage & C lower bounds
  20051.  
  20052.  CIn FORTRAN, assuming lower bounds of 1
  20053.  ARR1(9,3)
  20054.  
  20055.  Arrays with More than Two Dimensions
  20056.  
  20057.  Describing arrays in terms of rows and columns is a convenient analogy for
  20058.  understanding two-dimensional arrays. Actual storage is simply by contiguous
  20059.  memory locations. However, the format of the declarations shown earlier in
  20060.  Table 12.6 can be extended to any number of dimensions that you may use. For
  20061.  example, the following C declaration:
  20062.  
  20063.  int arr1[2][10][15][20] ;
  20064.  
  20065.  Is equivalent to the following BASIC declaration:
  20066.  
  20067.  DIM Arr1%(19, 14, 9, 1)
  20068.  
  20069.  
  20070.  These are equivalent in the sense that the C array element represented by
  20071.  the following:
  20072.  
  20073.  arr1[k][l][m][n]
  20074.  
  20075.  Refers to the same memory location as the following BASIC array element:
  20076.  
  20077.  Arr1(n, m, l, k)
  20078.  
  20079.  Structures, Records, and User-Defined Types
  20080.  
  20081.  The C  struct type, the BASIC user-defined type, the FORTRAN record (defined
  20082.  with the  STRUCTURE keyword), and the Pascal  record type are equivalent.
  20083.  Therefore, these data types can be passed between C, FORTRAN, Pascal, and
  20084.  BASIC.
  20085.  
  20086.  These types can be affected by the storage method. By default, C, FORTRAN,
  20087.  and Pascal use word alignment for types shorter than one word (type  char
  20088.  and  unsigned char). This storage method specifies that occasional bytes can
  20089.  be inserted as padding so that word and double-word data items start on an
  20090.  even boundary. (In addition, all nested structures and records start on a
  20091.  word boundary.)
  20092.  
  20093.  If you are passing a structure or record across a mixed-language interface,
  20094.  your calling routine and called routine must agree on the storage method and
  20095.  parameter-passing convention. Otherwise, data will not be interpreted
  20096.  correctly.
  20097.  
  20098.  Because Pascal, FORTRAN, and C use the same storage method for structures
  20099.  and records, you can exchange data between routines without taking any
  20100.  special precautions unless you modify the storage method. Make sure the
  20101.  storage methods agree before changing data between C, FORTRAN, and Pascal.
  20102.  
  20103.  BASIC packs user-defined types, so your other-language routines must also
  20104.  pack structures (using the /Zp command-line option or the  pack pragma, in
  20105.  C) to agree. Figure 12.13 contrasts packed and word-aligned storage.
  20106.  
  20107.  In C and Pascal, you can pass structures as parameters by value or by
  20108.  reference. In BASIC, structures (called user-defined types) can only be
  20109.  passed by reference. Both the calling program and the called program must
  20110.  agree on the parameter-passing convention. See the section
  20111.  "Parameter-Passing Requirements" earlier in this chapter for more
  20112.  information about the language you are using.
  20113.  
  20114.  
  20115.  External Data
  20116.  
  20117.  External data refers to data that is static and public; that is, the data is
  20118.  stored in a set place in memory as opposed to being allocated on the stack,
  20119.  and the data is visible to other modules. Although BASIC has static data,
  20120.  BASIC has no support for public data. Therefore there is no truly external
  20121.  data in BASIC. (See the section "Common Blocks" later in this chapter for
  20122.  more information about sharing external data with BASIC and FORTRAN
  20123.  programs.)
  20124.  
  20125.  
  20126.  Pointers and Address Variables
  20127.  
  20128.  Rather than passing data directly, you may want to pass the address of a
  20129.  piece of data. Passing the address amounts to passing the data by reference.
  20130.  In some cases, such as in BASIC arrays, there is no other way to pass a data
  20131.  item as a parameter.
  20132.  
  20133.  BASIC and FORTRAN do not have formal address types. However, they do provide
  20134.  ways for storing and passing addresses.
  20135.  
  20136.  BASIC programs can access a variable's segment address with the  VARSEG
  20137.  function and its offset address with the  VARPTR function. The values
  20138.  returned by these intrinsic functions should then be passed or stored as
  20139.  ordinary integer variables. If you pass them to another language, pass by
  20140.  value. Otherwise you will be attempting to pass the address of the address,
  20141.  rather than the address itself. See Chapters 13, "Mixed-Language Programming
  20142.  with Far Strings," and 11, "Advanced String Storage," for information on
  20143.  passing addresses of far strings.
  20144.  
  20145.  To pass a near address, pass only the offset; if you need to pass a far
  20146.  address, you may have to pass the segment and the offset separately. Pass
  20147.  the segment address first, unless you have used  CDECL in the BASIC  DECLARE
  20148.  statement.
  20149.  
  20150.  FORTRAN programs can determine near and far addresses with the  LOC and
  20151.  LOCFAR functions. Store the result of the  LOC function as  INTEGER*2 and
  20152.  the result of the  LOCFAR function as  INTEGER*4. As with BASIC, if you pass
  20153.  the result of  LOC or  LOCFAR to another language, be sure to pass by value.
  20154.  
  20155.  C programs always pass array variables by address. All other types are
  20156.  passed by value unless you use the address-of ( &) operator to obtain the
  20157.  address.
  20158.  
  20159.  The Pascal  ADR and  ADS types are equivalent to C's near and far pointers,
  20160.  respectively. You can pass  ADR and  ADS variables as  ADRMEM or  ADSMEM.
  20161.  
  20162.  
  20163.  Common Blocks
  20164.  
  20165.  You can pass individual members of a BASIC or FORTRAN common block in an
  20166.  argument list, just as you can with any data. However, you can also give a
  20167.  different language module access to the entire common block at once.
  20168.  
  20169.  C modules can refer to the items of a common block by first declaring a
  20170.  structure or record with fields that correspond to the common-block
  20171.  variables. Having defined a structure or user-defined type with the
  20172.  appropriate fields, the C module must then connect with the common block
  20173.  itself.
  20174.  
  20175.  To pass the address of a common block, simply pass the address of the first
  20176.  variable in the block. (In other words, pass the first variable by
  20177.  reference.) The receiving C module should expect to receive a structure by
  20178.  reference.
  20179.  
  20180.  Example
  20181.  
  20182.  In the following example, the C function initcb receives the address of the
  20183.  variable N, which it considers to be a pointer to a structure with three
  20184.  fields:
  20185.  
  20186.  ' BASIC SOURCE CODE
  20187.  '
  20188.   COMMON /Cblock/ N%,X#,Y#
  20189.   DECLARE SUB INITCB CDECL (N%)
  20190.  .
  20191.  .
  20192.  .
  20193.   CALL INITCB(N%)
  20194.  .
  20195.  .
  20196.  .
  20197.  /* C source code */
  20198.  struct block_type {
  20199.   int N;
  20200.   double x;
  20201.   double y;
  20202.  };
  20203.  void initcb(block_hed)
  20204.  struct block_type *block_hed;
  20205.  {
  20206.   block_hed->n = 1;
  20207.   block_hed->x = 10.0;
  20208.   block_hed->y = 20.0;
  20209.  }
  20210.  
  20211.  Using a Varying Number of Parameters
  20212.  
  20213.  Some C functions, most notably  printf, can be called with a different
  20214.  number of arguments each time. To call such a function from another
  20215.  language, you need to suppress the type-checking that normally forces a call
  20216.  to be made with a fixed number of parameters. In BASIC, you can remove this
  20217.  type checking by omitting the parameter list from the  DECLARE statement, as
  20218.  noted in the section "Alternative BASIC Interfaces" earlier in this chapter.
  20219.  
  20220.  In FORTRAN or Pascal you can call routines with a variable number of
  20221.  parameters by using the  VARYING attribute in your interface to the routine
  20222.  along with the  C attribute. You must use the  C attribute because a
  20223.  variable number of parameters is feasible only with the C calling
  20224.  convention.
  20225.  
  20226.  Because the number of parameters is not fixed, the routine you call should
  20227.  have some mechanism for determining how many parameters to expect. Often
  20228.  this information is indicated by the first parameter. For example, the C
  20229.  function  printf scans the format string passed as the first parameter. The
  20230.  number of fields in the format string determines how many additional
  20231.  parameters the function should expect. The following examples illustrate two
  20232.  ways of calling the C-library function  printf.
  20233.  
  20234.  Examples
  20235.  
  20236.  In the first example a fixed number of arguments is declared, and the
  20237.  keyword  BYVAL precedes each parameter in the  DECLARE statement to insure
  20238.  that the true address of the string argument is passed. The example assumes
  20239.  you are using near strings in BASIC and compiling the C module in medium
  20240.  model.
  20241.  
  20242.  DEFINT A-Z
  20243.  DECLARE SUB printf CDECL (BYVAL Format, BYVAL Value)
  20244.  String1$ = "Value passed to the formatted string is %d"+CHR$(0)
  20245.  Number = 19
  20246.  CALL printf(SADD(String1$), Number)
  20247.  END
  20248.  
  20249.  The following variation of the preceding example uses a special form of the
  20250.  DECLARE statement to declare the  printf function. The parentheses that
  20251.  normally contain the formal parameters are omitted from the declaration.
  20252.  This informs BASIC of two facts:
  20253.  
  20254.  
  20255.        The procedure is not written in BASIC.
  20256.  
  20257.  
  20258.        The procedure can have a variable number of arguments. The  BYVAL
  20259.        keyword is still used in the program, but this time it is passed with
  20260.        each argument in the  printf call.
  20261.  
  20262.  
  20263.        In both examples, the C-language format character  %d is used in the
  20264.        string passed to  printf, which replaces it with the value of the
  20265.        second argument.
  20266.  
  20267.  
  20268.  DEFINT A-Z
  20269.  DECLARE SUB printf CDECL
  20270.  String1$ = "Value passed to the formatted string is %d"+CHR$(0)
  20271.  Number = 19
  20272.  CALL printf(BYVAL SADD(String1$), BYVAL Number)
  20273.  END
  20274.  
  20275.  The preceding example describes the BASIC call to  printf. When you link the
  20276.  object file created by such a program from the command line,  LINK resolves
  20277.  the reference to the  printf function when the executable file is created.
  20278.  However, if you want to call  printf from within the QBX environment, you
  20279.  must make the function available within a Quick library. The simplest way to
  20280.  do this is to write a "dummy" C function that calls  printf, and compile it
  20281.  (make sure you are compiling in medium model, as noted previously). Then,
  20282.  instead of linking it with a BASIC program on the link command line,
  20283.  incorporate the object file into a Quick library as shown in the following
  20284.  example:
  20285.  
  20286.  link /q dummy.obj,,,qbxqlb.lib /NOE;
  20287.  
  20288.  In this case a Quick library called dummy.qlb is created; it contains only
  20289.  the  printf function. (Note that  LINK will prompt you for the path to the
  20290.  correct C library if it cannot find it.) The /NOE option keeps  LINK from
  20291.  misinterpreting certain symbols common to the two libraries as duplicate
  20292.  definitions. You can then make calls to printf if you load this library when
  20293.  you invoke BASIC, as follows:
  20294.  
  20295.  qbx /l dummy
  20296.  
  20297.  Using this method, you can create Quick libraries that include useful
  20298.  functions from the standard C library, as well as other-language routines
  20299.  you write yourself. See Chapter 19, "Creating and Using Quick Libraries,"
  20300.  for more information.
  20301.  
  20302.  
  20303.  B_OnExit Routine
  20304.  
  20305.  You can use  B_OnExit when your other-language routines take special actions
  20306.  that need to be undone before program termination or rerunning of the
  20307.  program. For example, within the BASIC environment, an executing program
  20308.  that calls other-language routines in a Quick library may not always run to
  20309.  normal termination. If such routines need to take special actions at
  20310.  termination (for example, de-installation of previously installed interrupt
  20311.  vectors), you can guarantee that your termination routines will always be
  20312.  called if you include an invocation of  B_OnExit in the routine. The
  20313.  following example illustrates such a call (for simplicity, the example omits
  20314.  error-handling code). Note that such a function would be compiled in C in
  20315.  large model.
  20316.  
  20317.  #include <malloc.h>
  20318.  extern pascal far B_OnExit(); /* Declare the routine */
  20319.  int *p_IntArray;
  20320.  void InitProc()
  20321.  {
  20322.   void TermProc(); /* Declare TermProc function */
  20323.  
  20324.   /* Allocate far space for 20-integer array: */
  20325.   p_IntArray = (int *)malloc(20*sizeof(int));
  20326.  
  20327.  
  20328.  /* Log termination routine (TermProc) with BASIC: */
  20329.   B_OnExit(TermProc);
  20330.  }
  20331.  /* The TermProc function is */
  20332.  void TermProc() /* called before any restarting */
  20333.  { /* or termination of program. */
  20334.   free(p_IntArray); /* Release far space allocated */
  20335.  } /* previously by InitProc. */
  20336.  
  20337.  If the InitProc function were in a Quick library, the call to  B_OnExit
  20338.  would insure proper release of the space reserved in the call to  malloc,
  20339.  should the program end before normal termination could be performed. The
  20340.  routine could be called several times, since the program can be executed
  20341.  several times from the BASIC environment. However, the TermProc function
  20342.  itself would be called only once each time the program runs.
  20343.  
  20344.  The following BASIC program is an example of a call to the InitProc
  20345.  function:
  20346.  
  20347.  DECLARE SUB InitProc CDECL
  20348.  X = SETMEM(-2048) ' Make room for the malloc memory
  20349.  ' allocation in C function.
  20350.  CALL InitProc
  20351.  END
  20352.  
  20353.  If more than 32 routines are registered,  B_OnExit returns NULL, indicating
  20354.  there is not enough space to register the current routine. Note that
  20355.  B_OnExit has the same return values as the Microsoft C run-time library
  20356.  routine  onexit.
  20357.  
  20358.   B_OnExit can be used with assembly language or any other-language routines
  20359.  you place in a Quick library. With programs compiled and linked completely
  20360.  from the command line,  B_OnExit is optional.
  20361.  
  20362.  
  20363.  Assembly Language-to-BASIC Interface
  20364.  
  20365.  With MASM you can write assembly language modules that can be linked to
  20366.  modules developed with Microsoft BASIC, Pascal, FORTRAN, and C. This section
  20367.  outlines the recommended programming guidelines for writing assembly
  20368.  language routines compatible with Microsoft BASIC.
  20369.  
  20370.  Writing assembly language routines for Microsoft high-level languages is
  20371.  easiest when you use the simplified segment directives provided with the
  20372.  MASM version 5.0, and later. This manual assumes that you have version 5.0
  20373.  or later.
  20374.  
  20375.  
  20376.  Writing the Assembly Language Procedure
  20377.  
  20378.  You can call assembly language procedures using essentially the same
  20379.  conventions as for compiler-generated code. This section describes how you
  20380.  use those conventions to call assembly language procedures. Procedures that
  20381.  observe these conventions can be called recursively and can be debugged with
  20382.  the Microsoft CodeView debugger. The standard assembly language interface
  20383.  method, described in the following sections, consists of the following
  20384.  steps:
  20385.  
  20386.    ■    1. Set up the procedure.
  20387.  
  20388.    ■    2. Enter the procedure.
  20389.  
  20390.    ■    3. Allocate local data (optional).
  20391.  
  20392.    ■    4. Preserve register values.
  20393.  
  20394.    ■    5. Access parameters.
  20395.  
  20396.    ■    6. Return a value (optional).
  20397.  
  20398.    ■    7. Exit the procedure.
  20399.  
  20400.  
  20401.  Note
  20402.  
  20403.  The MASM, version 5.0 and later, provide an include file, MIXED.INC, that
  20404.  automatically performs many of the stack-maintenance chores described in the
  20405.  next few sections. Version 5.1 and later include several keywords that
  20406.  simplify creation of a mixed-language interface. Part 1 of the  Microsoft
  20407.  QuickAssembler Programmer's Guide describes the mechanics of mixed-language
  20408.  programming using the mixed-language keywords.
  20409.  
  20410.  
  20411.  Setting Up the Procedure
  20412.  
  20413.  LINK cannot combine the assembly language procedure with the calling program
  20414.  unless compatible segments are used and unless the procedure itself is
  20415.  declared properly. The following four points may be helpful:
  20416.  
  20417.  
  20418.        Use the  .MODEL directive at the beginning of the source file. This
  20419.        directive automatically causes the appropriate kind of returns to be
  20420.        generated ( NEAR for small or compact model,  FAR otherwise). Modules
  20421.        called from BASIC should be  .MODEL MEDIUM. If you have a version of
  20422.        the MASM previous to 5.0, declare the procedure  FAR.
  20423.  
  20424.  
  20425.        Use the simplified segment directives . CODE to declare the code
  20426.        segment and . DATA to declare the data segment. (Having a code segment
  20427.        is sufficient if you do not have data declarations.) If you are using
  20428.        a version of the assembler earlier than 5.0, declare the segments
  20429.        using the  SEGMENT,  GROUP, and  ASSUME directives (described in the
  20430.        Microsoft Macro Assembler manual).
  20431.  
  20432.  
  20433.        Use the  PUBLIC directive to declare the procedure label public. This
  20434.        declaration makes the procedure visible to other modules. Also, any
  20435.        data you want to make public to other modules must be declared as
  20436.        PUBLIC.
  20437.  
  20438.  
  20439.        Use the  EXTRN directive to declare any global data or procedures
  20440.        accessed by the routine as external. The safest way to use code  EXTRN
  20441.        declarations is to place the directive outside of any segment
  20442.        definition. However, place near data inside the data segment.
  20443.  
  20444.  
  20445.  Note
  20446.  
  20447.  If you want to be able to call the assembly language procedure from both
  20448.  BASIC and C, you can create the common interface using the  CDECL keyword in
  20449.  the BASIC declaration, then following the C naming and calling conventions
  20450.  (as explained in the section "Using  CDECL in calls from BASIC" earlier in
  20451.  this chapter).
  20452.  
  20453.  
  20454.  Entering the Procedure
  20455.  
  20456.  If your procedure accepts arguments or has local (automatic) variables on
  20457.  the stack, you need to use the following two instructions to set up the
  20458.  stack frame and begin the procedure:
  20459.  
  20460.  push bp
  20461.  mov bp,sp
  20462.  
  20463.  This sequence establishes  BP as the framepointer. The "framepointer" is
  20464.  used to access parameters and local data, which are located on the stack.
  20465.  The value of the base register  BP should remain constant throughout the
  20466.  procedure (unless your program changes it), so that each parameter can be
  20467.  addressed as a fixed displacement off of  BP.  SP cannot be used for this
  20468.  purpose because it is not an index or base register. Also, the value of  SP
  20469.  may change as more data is pushed onto the stack.
  20470.  
  20471.  The instruction push bp preserves the value of  BP. This value will be
  20472.  needed by the calling procedure as soon as the current procedure terminates.
  20473.  The instruction mov bp,sp captures the value that the stack pointer had at
  20474.  the time of entry to the procedure. This establishes that the parameter can
  20475.  be addressed.
  20476.  
  20477.  
  20478.  Allocating Local Data (Optional)
  20479.  
  20480.  An assembly procedure can use the same technique for allocating temporary
  20481.  storage for local data that is used by high-level languages. To set up local
  20482.  data space, simply decrease the contents of  SP immediately after setting up
  20483.  the stack frame. To ensure correct execution, you should always increase or
  20484.  decrease  SP by an even amount. Decreasing  SP reserves space on the stack
  20485.  for the local data. The space must be restored at the end of the procedure
  20486.  as shown by the following:
  20487.  
  20488.  push bp
  20489.  mov bp,sp
  20490.  sub sp,space
  20491.  
  20492.  In the preceding code fragment, space is the total size in bytes of the
  20493.  local data. Local variables are then accessed as fixed, negative
  20494.  displacements off of  BP.
  20495.  
  20496.  
  20497.  Example
  20498.  
  20499.  The following example uses two local variables, each of which is 2 bytes.
  20500.  SP is decreased by 4, since there are 4 bytes total of local data. Later,
  20501.  each of the variables is initialized to 0.
  20502.  
  20503.  push bp; Save old stack frame
  20504.  mov bp,sp; Set up new stack frame
  20505.  sub sp,4; Allocate 4 bytes local storage
  20506.  
  20507.  mov WORD PTR [bp-2],0
  20508.  mov WORD PTR [bp-4],0
  20509.  
  20510.  Local variables are also called dynamic, stack, or automatic variables.
  20511.  
  20512.  
  20513.  Preserving Register Values
  20514.  
  20515.  A procedure called from any of the Microsoft high-level languages should
  20516.  preserve the direction flag and the values of  SI,  DI,  SS, and  DS (in
  20517.  addition to  BP, which is already saved). Any register values that your
  20518.  procedure alters should be pushed onto the stack after you set up the stack
  20519.  frame, but before the main body of the procedure. If the procedure does not
  20520.  change the value of any of these registers, then the registers do not need
  20521.  to be pushed.
  20522.  
  20523.  Warning
  20524.  
  20525.  Routines that your assembly language procedure calls must not alter the  SI,
  20526.   DI,  SS,  DS, or  BP registers. If they do, and you have not preserved the
  20527.  registers, they can corrupt the calling program's register variables,
  20528.  segment registers, and stack frame, causing program failure.
  20529.  
  20530.  If your procedure modifies the direction flag using the  STD or  CLD
  20531.  instructions, you must preserve the flags register.
  20532.  
  20533.  The following example shows an entry sequence that sets up a stack frame,
  20534.  allocates 4 bytes of local data space on the stack, then preserves the  SI,
  20535.  DI, and flags registers.
  20536.  
  20537.  push    bp        ; Save caller's stack frame.
  20538.   mov     bp,sp     ; Establish new stack frame.
  20539.   sub     sp,4      ; Allocate local data space.
  20540.   push    si        ; Save SI and DI registers.
  20541.   push    di
  20542.   pushf             ; Save the flags register.
  20543.   .
  20544.   .
  20545.   .
  20546.  
  20547.  
  20548.  In the preceding example, you must exit the procedure with the following
  20549.  code:
  20550.  
  20551.  popf              ; Restore the flags register.
  20552.   pop    di         ; Restore the old value in the DI register.
  20553.   pop    si         ; Restore the old value in the SI register.
  20554.   mov    sp,bp      ; Restore the stack pointer.
  20555.   pop    bp         ; Restore the frame pointer.
  20556.   ret               ; Return to the calling routine.
  20557.  
  20558.  If you do not issue the preceding instructions in the order shown, you will
  20559.  place incorrect data in registers. The rules for restoring the calling
  20560.  program's registers, stack pointer, and frame pointer are:
  20561.  
  20562.    ■   Pop all registers that you preserve in the reverse order from which
  20563.        they were pushed onto the stack. So, in the example above,  SI and  DI
  20564.        are pushed, and  DI and  SI are popped.
  20565.  
  20566.    ■   Restore the stack pointer by transferring the value of  BP into  SP
  20567.        before restoring the value of the frame pointer.
  20568.  
  20569.    ■   Always restore the frame pointer last.
  20570.  
  20571.  
  20572.  
  20573.  Accessing Parameters
  20574.  
  20575.  Once you have established the procedure's framepointer, allocated local data
  20576.  space (if desired), and pushed any registers that need to be preserved, you
  20577.  can write the main body of the procedure. To write instructions that can
  20578.  access parameters, consider the general picture of the stack frame after a
  20579.  procedure call, as illustrated in Figure 12.14.
  20580.  
  20581.  6a7abfffThe stack frame
  20582.  for the procedure is established by the following sequence of events:
  20583.  
  20584.     1. The calling program pushes each of the parameters on the stack, after
  20585.        which  SP points to the last parameter pushed.
  20586.  
  20587.     2. The calling program issues a  CALL instruction, which causes the
  20588.        return address (the place in the calling program to which control
  20589.        ultimately returns) to be placed on the stack. Because BASIC always
  20590.        uses a  FAR call, this address is 4 bytes long.  SP now points to this
  20591.        address. With a language such as C, the address may be 2 bytes, if the
  20592.        call is a near call.
  20593.  
  20594.     3. The first instruction of the called procedure saves the old value of
  20595.        BP, with the instruction push bp. Now  SP points to the saved copy of
  20596.        BP.
  20597.  
  20598.     4.  BP is used to capture the current value of  SP, with the instruction
  20599.        mov bp,sp.  BP therefore now points to the old value of  BP (saved on
  20600.        the stack).
  20601.  
  20602.     5. Whereas  BP remains constant throughout the procedure,  SP is often
  20603.        decreased to provide room on the stack for local data or saved
  20604.        registers.
  20605.  
  20606.  
  20607.  In general, the displacement (off of  BP) for a parameter X is equal to 2
  20608.  plus the size of return address plus the total size of parameters between X
  20609.  and  BP.
  20610.  
  20611.  
  20612.  For example, consider a procedure that has received one parameter, a 2-byte
  20613.  address. Since the size of the return address is always 4 bytes in BASIC,
  20614.  the displacement of the parameter would be calculated as follows:
  20615.  
  20616.   argument's displacement = 2 +  size of return address
  20617.  
  20618.  = 2 + 4
  20619.       = 6
  20620.  
  20621.  In other words, the argument's displacement equals 2 plus 4, or 6. The
  20622.  argument can thus be loaded into  BX with the following instruction:
  20623.  
  20624.  mov bx,[bp+6]
  20625.  
  20626.  Once you determine the displacement of each parameter, you may want to use
  20627.  the  EQU directive or structures to refer to the parameter with a single
  20628.  identifier name in your assembly source code. For example, the preceding
  20629.  parameter at BP+6 can be conveniently accessed if you put the following
  20630.  statement at the beginning of the assembly source file:
  20631.  
  20632.  Arg1EQU[bp+6]
  20633.  
  20634.  You could then refer to this parameter as Arg1 in any instruction. Use of
  20635.  this feature is optional.
  20636.  
  20637.  Note
  20638.  
  20639.  For far (segment plus offset) addresses, Microsoft high-level languages push
  20640.  segment addresses before pushing offset address. Furthermore, when pushing
  20641.  arguments larger than 2 bytes, high-order words are always pushed before
  20642.  low-order words and parameters longer than 2 bytes are stored on the stack
  20643.  in most-significant, least-significant order. This standard for pushing
  20644.  segment addresses before pushing offset addresses facilitates the use of the
  20645.   LES (load extra segment) and  LDS (load data segment) instructions.
  20646.  
  20647.  
  20648.  Returning a Value (Optional)
  20649.  
  20650.  BASIC has a straightforward convention for receiving return values when the
  20651.  data type to be returned is simple (that is, not a floating-point value, an
  20652.  array, or structured type) and is no more than 4 bytes. This includes all
  20653.  pointers and all parameters passed by reference, as shown in the following
  20654.  list:
  20655.  
  20656. ╓┌───────────────────┌───────────────────────────────────────────────────────╖
  20657.  ────────────────────────────────────────────────────────────────────────────
  20658.  2-byte integer( %)   AX
  20659.  4-byte integer( &)  High-order portion in  DX; low-order portion in  AX
  20660.  All other types     Near offset in  AX
  20661.  
  20662.  
  20663.  
  20664.  
  20665.  
  20666.  An assembly language procedure called by BASIC must use a special convention
  20667.  to return floating-point values, user-defined types and arrays, and values
  20668.  larger than 4 bytes, as explained in the following section:
  20669.  
  20670.  
  20671.  Numeric Return Values Other Than 2- and 4-Byte Integers
  20672.  
  20673.  In order to create an interface for numeric return values that are neither
  20674.  2-byte integers ( %) nor 4-byte integers ( &), BASIC modules take the
  20675.  following actions before they call your procedure (assuming that the BASIC
  20676.  declarations do not specify the  CDECL keyword):
  20677.  
  20678.     1. When the call to your procedure is made, an extra parameter is passed;
  20679.        this parameter contains the offset address of the actual return value.
  20680.        This parameter is placed immediately above the return address. (In
  20681.        other words, this parameter is the last one pushed.)
  20682.  
  20683.     2. The segment address of the return value is contained in  SS and  DS.
  20684.  
  20685.         The extra parameter (which contains the offset address of the return
  20686.         value) is always located at  BP+6. Furthermore, its presence
  20687.         automatically increases the displacement of all other parameters by
  20688.         two, as shown in Figure 12.15.
  20689.  
  20690.  Your assembly language procedure can successfully return numeric values
  20691.  other than 2- and
  20692.  
  20693.   4-byte integers if you follow these steps:
  20694.  
  20695.     1. Put the data for the return value at the location pointed to by the
  20696.        return-value offset.
  20697.  
  20698.     2. Copy the return-value offset (located at  BP+6) to  AX. This is
  20699.        necessary because the calling module expects  AX to point to the
  20700.        return value.
  20701.  
  20702.     3. Exit the procedure as described in the next section.
  20703.  
  20704.  
  20705.  
  20706.  Exiting the Procedure
  20707.  
  20708.  Several steps may be involved in terminating the procedure:
  20709.  
  20710.     1. If any of the registers  SS,  DS,  SI, or  DI have been saved, these
  20711.        must be popped off the stack in the reverse order from that in which
  20712.        they were saved. If they are popped in any other order, program
  20713.        behavior is unpredictable.
  20714.  
  20715.     2. If local data space was allocated at the beginning of the procedure,
  20716.        SP must be restored with the instruction  mov sp,bp.
  20717.  
  20718.     3. Restore  BP with the instruction  pop bp. This step is always
  20719.        necessary.
  20720.  
  20721.     4. Since the BASIC calling convention is being used, you must use the
  20722.        RET  n form of the instruction to adjust the stack if any parameters
  20723.        were pushed by the caller.
  20724.  
  20725.  
  20726.  Examples
  20727.  
  20728.  The following example shows the simplest possible exit sequence. No
  20729.  registers were saved, no local data space was allocated, and no parameters
  20730.  were passed to the routine.
  20731.  
  20732.  pop bp
  20733.  ret
  20734.  
  20735.  The following example shows an exit sequence for a procedure that has
  20736.  previously saved  SI and  DI, allocated 4 bytes of local data space, used
  20737.  the BASIC calling convention, and received 6 bytes of parameters. The
  20738.  procedure must therefore use ret 6  to restore the 6 bytes of parameters on
  20739.  the stack.
  20740.  
  20741.  push bp
  20742.  movbp,sp
  20743.  subsp,4
  20744.  pushsi
  20745.  pushdi
  20746.   .
  20747.   .
  20748.   .
  20749.   popdi; pop saved registers
  20750.   pop si
  20751.   mov sp,bp ; Free local data space.
  20752.   pop bp ; Restore old stack frame.
  20753.   ret 6 ; Exit, and remove 6 bytes of args.
  20754.  
  20755.  Note
  20756.  
  20757.  If the preceding routine had been declared with  CDECL, only the  RET
  20758.  (without a specification of  n) would be used because with the C calling
  20759.  convention, the caller cleans up the stack.
  20760.  
  20761.  
  20762.  Calls from BASIC
  20763.  
  20764.  A BASIC program can call an assembly language procedure in another source
  20765.  file with the  CALL or  CALLS statement. Proper use of the  DECLARE
  20766.  statement is also important. In addition to the steps outlined in the
  20767.  preceding sections, the following guidelines may be helpful:
  20768.  
  20769.  
  20770.        Declare procedures called from BASIC as  FAR.
  20771.  
  20772.  
  20773.        Observe the BASIC calling conventions:
  20774.  
  20775.  
  20776.            Parameters are placed on the stack in the same order in which they
  20777.            appear in the BASIC source code. The first parameter is highest in
  20778.            memory (because it is also the first parameter to be placed on the
  20779.            stack, and the stack grows downward).
  20780.  
  20781.  
  20782.            By default, BASIC parameters are passed by reference as 2-byte
  20783.            addresses. (See Chapter 13, "Mixed-Language Programming with Far
  20784.            Strings," for information on passing strings stored in far
  20785.            memory.)
  20786.  
  20787.  
  20788.            Upon exit, the procedure must reset  SP to the value it had before
  20789.            the parameters were placed on the stack. This is accomplished with
  20790.            the instruction ret  n, where  n is the total size in bytes of all
  20791.            the parameters.
  20792.  
  20793.  
  20794.        Observe the BASIC naming convention.
  20795.  
  20796.  
  20797.  BASIC outputs symbolic names in uppercase characters, which is also the
  20798.  default behavior of the assembler. BASIC recognizes up to 40 characters of a
  20799.  name, whereas the assembler recognizes only the first 31 (this should rarely
  20800.  create a problem).
  20801.  
  20802.  Note
  20803.  
  20804.  Microsoft BASIC provides a Quick library (called QBX.QLB and an include file
  20805.  called QBX.BI). These files contain several routines that facilitate calling
  20806.  assembly language routines from within the BASIC environment. See Chapter
  20807.  11, "Advanced String Storage," for information on using this library.
  20808.  
  20809.  Examples
  20810.  
  20811.  In the following example, BASIC calls an assembly language procedure that
  20812.  calculates  A * 2B, where  A and  B are the first and second parameters,
  20813.  respectively. The calculation is performed by shifting the bits in  A to the
  20814.  left,  B times.
  20815.  
  20816.  ' BASIC program
  20817.  '
  20818.  DEFINT A-Z
  20819.  '
  20820.  DECLARE FUNCTION Power2(A%,B%)
  20821.  '
  20822.  PRINT "3 times 2 to the power of 5 is ";
  20823.  PRINT Power2(3,5)
  20824.  END
  20825.  
  20826.  
  20827.  To understand how to write the assembly language procedure, recall how the
  20828.  parameters are placed on the stack, (shown in Figure 12.16):
  20829.  
  20830.  The return address is 4 bytes because procedures that are called
  20831.  from BASIC must be  FAR. Arg 1 is higher in memory than Arg 2 because BASIC
  20832.  pushes arguments in the same order in which they appear. Also, each argument
  20833.  is passed as a 2-byte offset address, the BASIC default.
  20834.  
  20835.  The assembly language procedure can be written as follows:
  20836.  
  20837.  .MODEL MEDIUM
  20838.  .CODE
  20839.   PUBLIC Power2
  20840.  Power2 PROC
  20841.   push bp ; Entry sequence - saved old BP
  20842.   mov bp,sp ; Set stack framepointer
  20843.  ;
  20844.   mov bx,[bp+8]  ; Set BX equal to address of Arg1
  20845.   mov ax,[bx] ; Load value of Arg1 into AX
  20846.   mov bx,[bp+6] ; Set BX equal to address of Arg2
  20847.   mov cx,[bx] ; Load value Arg2 into CX
  20848.   shl ax,cl ; AX = AX * (2 to power of CX)
  20849.   ; Leave return value in AX
  20850.   pop bp ; Exit sequence - restore old BP
  20851.   ret 4 ; Return, and restore 4 bytes
  20852.  Power2 ENDP
  20853.   END
  20854.  
  20855.  
  20856.  Note that each parameter must be loaded in a two-step process because the
  20857.  address of each is passed rather than the value. Also, note that the stack
  20858.  is restored with the instruction ret 4 since the total size of the
  20859.  parameters is 4 bytes. (The preceding example is simplified to illustrate
  20860.  the interlanguage interface. Code for handling possible errors, such as
  20861.  overflow, is not included, but is a significant consideration with such
  20862.  procedures.)
  20863.  
  20864.  
  20865.  Using CDECL in Calls from BASIC
  20866.  
  20867.  You can use the  CDECL keyword in the  DECLARE statement in your BASIC
  20868.  module to call an assembly language routine. If you do so, the C calling
  20869.  conventions, rather than those of BASIC, determine the order of the
  20870.  arguments as received in the assembly language routine, and also the manner
  20871.  in which returns are handled. The primary advantage of using  CDECL is that
  20872.  then you can call the assembly language routine with a variable number of
  20873.  arguments. The technique is analogous to calling a C function from BASIC
  20874.  using  CDECL. In using  CDECL, observe the C calling convention:
  20875.  
  20876.  
  20877.        Parameters are placed on the stack in the reverse order to that in
  20878.        which they appear in the BASIC source code. This means the first
  20879.        parameter is lowest in memory (because the stack grows downward, and
  20880.        it is the last parameter to be placed on the stack).
  20881.  
  20882.  
  20883.        Return with a simple ret instruction. Do not restore the stack with
  20884.        ret  n, since using  CDECL causes the calling routine to restore the
  20885.        stack itself as soon as the calling routine resumes control. When the
  20886.        return value is not a 2-byte or 4-byte numeric value, a procedure
  20887.        called by BASIC with  CDECL in effect must allocate space for the
  20888.        return value, and then place its address in  AX. A simple way to
  20889.        create space for the return value is to declare it in a data segment.
  20890.  
  20891.  
  20892.        If  CDECL is used in the BASIC  DECLARE statement, you must name the
  20893.        assembler procedure with a leading underscore, unless you use the
  20894.        ALIAS feature.
  20895.  
  20896.  
  20897.  
  20898.  The Microsoft Segment Model
  20899.  
  20900.  If you use the simplified segment directives by themselves, you do not need
  20901.  to know the names assigned for each segment. However, versions of MASM prior
  20902.  to 5.0 do not support these directives. With older versions of the
  20903.  assembler, you should use the  SEGMENT,  GROUP,  ASSUME, and  ENDS
  20904.  directives equivalent to the simplified segment directives.
  20905.  
  20906.  Table 12.7 shows the default segment names created by each directive for
  20907.  medium model, the only model applicable to BASIC. Use of these segments
  20908.  ensures compatibility with Microsoft languages and helps you to access
  20909.  public symbols. This table is followed by a list of three steps,
  20910.  illustrating how to make the actual declarations.
  20911.  
  20912.  DATA_DATAWORDPUBLIC'DATA'DGROUPInitialized data.
  20913.  CONSTCONSTWORDPUBLIC'CONST'DGROUPUninitialized data. Microsoft compilers
  20914.  store uninitialized data separately because it can be more efficiently
  20915.  stored than initialized data. DATA?_BSSWORDPUBLIC'BSS'DGROUPConstant data.
  20916.  Microsoft compilers use this segment for such items as string and
  20917.  floating-point constants. STACKSTACKPARASTACK'STACK'DGROUPStack. Normally,
  20918.  this segment is declared in the main module for you and should not be
  20919.  redeclared.
  20920.  The following steps describe how to use Table 12.7 to create directives:
  20921.  
  20922.     1. Use Table 12.7 to determine the segment name, align type, combine
  20923.        type, and class for your code and data segments. Use all of these
  20924.        attributes when you define a segment. For example, the code segment is
  20925.        declared as follows:
  20926.  
  20927.  
  20928.  name_TEXT SEGMENT
  20929.        WORD
  20930.        PUBLIC
  20931.        'CODE'
  20932.  
  20933.    The name_TEXT and all the attributes are taken from Table 12.7. You
  20934.    substitute your module name for name. If the combine type is private,
  20935.    simply do not use any combine type.
  20936.  
  20937.     2. If you have segments in  DGROUP, put them into  DGROUP with the  GROUP
  20938.        directive, as in:
  20939.  
  20940.  
  20941.     3. Use  ASSUME and  ENDS as you would normally. Upon entry,  DS and  SS
  20942.        both point to  DGROUP; therefore, a procedure that makes use of
  20943.        DGROUP should include the following  ASSUME directive:
  20944.  
  20945.  
  20946.  ASSUME CS:name_TEXT,
  20947.  
  20948.        DS:DGROUP,
  20949.  
  20950.        SS:DGROUP
  20951.  
  20952.  
  20953.  
  20954.  Note
  20955.  
  20956.  If your assembly language procedures use real numbers, they must use
  20957.  IEEE-format numbers to be compatible with QBX. This is the default for MASM,
  20958.  version 5.0 and later. With earlier versions, you must specify the  IR
  20959.  command-line option or the  8087 directive.
  20960.  
  20961.  
  20962.   ────────────────────────────────────────────────────────────────────────────
  20963.  Chapter 13:  Mixed-Language Programming with Far Strings
  20964.  
  20965.  
  20966.  This chapter provides new techniques for passing strings between BASIC
  20967.  and another language. These techniques supplement the general method
  20968.  of programming outlined in Chapter 12, "Mixed-Language Programming."
  20969.  To get the most out of the present chapter, you should understand the
  20970.  material presented in that guide -- most importantly, be familiar with the
  20971.  naming, calling, and passing conventions of BASIC and the other languages
  20972.  you are using.
  20973.  
  20974.  This chapter contains the following information:
  20975.  
  20976.    ■   A description of BASIC's string-processing routines.
  20977.  
  20978.    ■   A general string-passing model.
  20979.  
  20980.    ■   Specific examples of passing strings between Microsoft BASIC, Macro
  20981.        Assembler (MASM), C, Pascal, and FORTRAN.
  20982.  
  20983.  
  20984.  
  20985.  Considerations When Using Strings
  20986.  
  20987.  When passing strings between BASIC and another language, several
  20988.  complications arise. First of all, BASIC's sending and receiving parameters
  20989.  can only be variable-length strings; other languages use fixed-length
  20990.  strings. Furthermore, whenever a BASIC  SUB procedure is receiving a string,
  20991.  it expects to be passed the address of a variable-length string descriptor,
  20992.  a data structure unique to BASIC.
  20993.  
  20994.  If the BASIC program is using far strings, a further complication arises
  20995.  because the structure of the far string descriptor is proprietary. It is not
  20996.  possible for the external routine to get information directly from the
  20997.  descriptor, or to create a far string descriptor for existing fixed-string
  20998.  data, and have BASIC recognize it as one of its own far strings.
  20999.  
  21000.  To make it easier for you to pass strings in these situations, routines in
  21001.  the BASIC run-time and stand-alone libraries are provided with Microsoft
  21002.  BASIC. These routines are described in the next section. After that, a
  21003.  general method for passing strings is outlined. This same method works for
  21004.  near and far strings. Specific examples of using this method with MASM, C,
  21005.  Pascal, and FORTRAN are then given.
  21006.  
  21007.  
  21008.  String-Processing Routines
  21009.  
  21010.  The routines described in the following sections are designed to transfer
  21011.  string data between languages, deallocate string space, and to determine the
  21012.  length and location of variable-length strings.
  21013.  
  21014.  The routines can be used by any language including BASIC and behave like any
  21015.  other external call. (See the  DECLARE statement in the  BASIC Language
  21016.  Reference for more information.) They are declared as external procedures
  21017.  and loaded from the appropriate BASIC run-time or stand-alone library during
  21018.  linking. For running and debugging within QBX, they need to be in a Quick
  21019.  library. The section "Passing Strings in QBX" later in this chapter
  21020.  describes how to do this.
  21021.  
  21022.  
  21023.  Transferring String Data
  21024.  
  21025.  The  StringAssign routine lets you transfer string data from one language
  21026.  memory space to another. Typically it is used to transfer a BASIC
  21027.  variable-length string to a second language's fixed-length string and vice
  21028.  versa. The syntax is:
  21029.  
  21030.   CALL  StringAssign( sourceaddress&, sourcelength%, destaddress&,
  21031.  destlength% )
  21032.  
  21033.  The  sourceaddress& argument is a far pointer to the string descriptor if
  21034.  the source is a variable-length string, or it is a far pointer to the start
  21035.  of string data if the source is a fixed-length string. The  sourcelength%
  21036.  argument is 0 for variable-length strings, otherwise it contains the length
  21037.  of the fixed-length string source in bytes. The  destaddress& argument is a
  21038.  far pointer to the string descriptor if the destination is a variable-length
  21039.  string, or it is a far pointer to the start of string data if the
  21040.  destination is a fixed-length string. The  destlength% argument is 0 for
  21041.  variable-length strings, otherwise it contains the length of the
  21042.  fixed-length string destination.
  21043.  
  21044.  Arguments are passed to  StringAssign by value using the  BYVAL keyword. The
  21045.  following is an example of the declaration:
  21046.  
  21047.  DECLARE SUB StringAssign(BYVAL Src&, BYVAL SrcLen%, BYVAL Dest&,_
  21048.  BYVAL DestLen%)
  21049.  
  21050.  
  21051.  When far pointers are not available, they can be generated by passing the
  21052.  segment and the offset separately. For example, assume that the segment,
  21053.  offset, and length of an external fixed-length string are contained in the
  21054.  variables FixedSeg%, FixedOff%, and FixedLength%. The string can be assigned
  21055.  to variable-length string A$ as follows:
  21056.  
  21057.  DECLARE SUB StringAssign(BYVAL SrcSeg%,BYVAL SrcOff%,BYVAL SrcLen%,_
  21058.  BYVAL DestSeg%,BYVAL DestOff%,BYVAL DestLen%)
  21059.  CALL StringAssign(FixedSeg%,FixedOff%,FixedLength%,VARSEG(A$),_
  21060.  VARPTR(A$),0)
  21061.  
  21062.  To assign the variable-length string A$ back to the fixed-length string,
  21063.  BASIC does the reverse:
  21064.  
  21065.  CALL StringAssign(VARSEG(A$),VARPTR(A$),0,FixedSeg%,FixedOff%,_
  21066.  FixedLength%)
  21067.  
  21068.  MASM, C, Pascal, and FORTRAN deal only with fixed-length strings. When
  21069.  programming in these languages you can use  StringAssign to create a new
  21070.  BASIC variable-length string and transfer fixed-string data to it. To
  21071.  transfer a MASM string containing the word "hello" to a BASIC string, for
  21072.  example, you use this data structure:
  21073.  
  21074.  fixedstring  db  "Hello"  ; source of data
  21075.  descriptor   dd  0; descriptor for destination
  21076.  
  21077.  The second data element, descriptor, is a 4-byte string descriptor
  21078.  initialized to zero. BASIC interprets this to mean that it should create a
  21079.  new variable-length string and associate it with the address descriptor.
  21080.  Assigning the fixed-length string to it is accomplished by the following:
  21081.  
  21082.  pushds;segment of near fixed string
  21083.  leaax, fixedstring;offset of near fixed string
  21084.  pushax
  21085.  movax, 5;fixed string length
  21086.  pushax
  21087.  pushds;segment of descriptor
  21088.  leaax, descriptor  ;offset of descriptor
  21089.  pushax
  21090.  xorax, ax  ;0 means that destination
  21091.  pushax;is a variable-length string
  21092.  extrnstringassign:  proc far;transfer the data
  21093.  callstringassign
  21094.  
  21095.  When the call to  StringAssign is made, BASIC will fill in the double-word
  21096.  descriptor with the correct string descriptor.
  21097.  
  21098.  
  21099.  Note
  21100.  
  21101.  When creating a new variable-length string you must allocate 4-bytes of
  21102.  static data for a string descriptor as shown in the preceding procedure.
  21103.  Allocating the data on the stack will not work.
  21104.  
  21105.  A new variable-length string that is created with  StringAssign can be used
  21106.  as an argument to a procedure. The address of the string descriptor is
  21107.  pushed on the stack and thus becomes a procedure parameter that can be
  21108.  processed like any other variable-length string. Examples of creating
  21109.  strings in all languages with the  StringAssign routine can be found in the
  21110.  code examples which follow this section.
  21111.  
  21112.  
  21113.  Deallocating String Data
  21114.  
  21115.  The  StringRelease routine deallocates variable-length strings created in a
  21116.  non-BASIC language by  StringAssign. This frees the space in BASIC's string
  21117.  data area. You can use this routine to deallocate strings after your string
  21118.  processing tasks are completed.  StringRelease has the following syntax:
  21119.  
  21120.   CALL  StringRelease( string-descriptor% )
  21121.  
  21122.  The  string-descriptor% argument is a near pointer to the variable-length
  21123.  string descriptor. The pointer is passed by value.
  21124.  
  21125.  To perform the release with MASM, assuming a descriptor for the
  21126.  variable-length string NoLongerNeeded exists at offset descriptor1, write
  21127.  the following code:
  21128.  
  21129.  leaax, descriptor1
  21130.  pushax
  21131.  extrnstringrelease: proc far
  21132.  callstringrelease
  21133.  
  21134.  Important
  21135.  
  21136.   StringRelease is only for variable-length strings created by a language
  21137.  other than BASIC. Never use it on strings that are created by BASIC. Doing
  21138.  so will cause unpredictable results.
  21139.  
  21140.  
  21141.  Computing String Data Addresses
  21142.  
  21143.   StringAddress is a routine that returns a far pointer to variable-length
  21144.  string data. It is the equivalent of  SSEGADD. Assume that a descriptor for
  21145.  a BASIC variable-length string MissingString$ exists at offset descriptor1.
  21146.  MASM can find the far address of the string data with the following
  21147.  fragment:
  21148.  
  21149.  leaax, descriptor1
  21150.  pushax
  21151.  extrnstringaddress: proc far
  21152.  callstringaddress
  21153.  
  21154.  The far pointer is returned in DX:AX. DX holds the segment and AX holds the
  21155.  offset.
  21156.  
  21157.  
  21158.  Computing String Data Length
  21159.  
  21160.   StringLength is a routine function that returns the length of
  21161.  variable-length string data. It is the equivalent of  LEN. Assume that a
  21162.  descriptor for a BASIC variable-length string LongString$ exists at the
  21163.  label descriptor1. MASM can find the length of string data with the
  21164.  following fragment:
  21165.  
  21166.  leaax, descriptor1
  21167.  pushax
  21168.  extrnstringlength: proc far
  21169.  call stringlength
  21170.  
  21171.  The length of LongString$ is returned in AX.
  21172.  
  21173.  
  21174.  Passing Variable-Length Strings
  21175.  
  21176.  Any time you need to pass variable-length strings from one language to
  21177.  another, process them, and then pass the string data back for further
  21178.  processing, the following general method may prove useful. This method uses
  21179.  the  StringAssign routine which works for any calling language and also
  21180.  works whether you are passing near or far strings.
  21181.  
  21182.     1. The first module makes the call, providing string pointers (and length
  21183.        if calling an external procedure).
  21184.  
  21185.     2. The second module assigns the data to its own string type using the
  21186.        StringAssign routine.
  21187.  
  21188.     3. The second module processes the data.
  21189.  
  21190.     4. The second module assigns the output string data to the first module's
  21191.        string type using the  StringAssign routine.
  21192.  
  21193.     5. The second module returns from the call, providing string pointers
  21194.        (and length if returning to an external procedure).
  21195.  
  21196.     6. The first module continues processing.
  21197.  
  21198.  
  21199.  
  21200.  BASIC Calling MASM
  21201.  
  21202.  This example shows how to pass variable-length strings between BASIC and
  21203.  MASM using the general method explained previously. The first module,
  21204.  MXSHKB.BAS, creates the strings A$ and B$, then passes their
  21205.  string-descriptor far pointers and data lengths to a MASM procedure named
  21206.  AddString. This procedure is contained in the file MXADSTA.ASM. The
  21207.  AddString procedure transfers the data to its own work area and then
  21208.  concatenates the two strings. Finally, AddString transfers the output to a
  21209.  BASIC variable-length string. Upon return, the BASIC module prints the
  21210.  output string.
  21211.  
  21212.  
  21213.  Important
  21214.  
  21215.   StringAssign,  StringRelease,  StringAddress, and  StringLength may change
  21216.  the contents of  the  AX,  BX,  CX,  DX, and  ES registers and may change
  21217.  any flags other than the direction flag. These registers and flag should be
  21218.  saved before calling any of these string routines if their values are
  21219.  important to your MASM program.
  21220.  
  21221.  This MASM code uses the  .MODEL directive which establishes compatible
  21222.  naming, calling, and passing conventions for BASIC, and it also uses
  21223.  simplified segment directives. This eliminates the need for separate  GROUP
  21224.  and  ASSUME directives. See Section 8.2, "Declaring Symbols External," of
  21225.  the  Microsoft Macro Assembler 5.1 Programmer's Guide manual for a
  21226.  comparison of this method with one using full segment definitions. The
  21227.  version 5.1  PROC directive is employed. It includes new arguments that
  21228.  specify automatically saved registers, define arguments to the procedure,
  21229.  and set up text macros to use for the arguments. The  PROC directive
  21230.  automatically generates the proper type of return based on the chosen memory
  21231.  model and cleans up the stack.
  21232.  
  21233.  '******************************MXSHKB.BAS*******************************
  21234.  DEFINT A-Z
  21235.  
  21236.  'Define a non-BASIC procedure.
  21237.  DECLARE FUNCTION AddString$(SEG S1$,BYVAL S1Length,SEG S2$,BYVAL S2Length)
  21238.  
  21239.  'Create the data.
  21240.  A$ = "To be or not to be;"
  21241.  B$ = " that is the question."
  21242.  
  21243.  'Use non-BASIC function to add two BASIC far strings.
  21244.  C$ = (A$, LEN(A$), B$, LEN(B$))
  21245.  
  21246.  'Print the result on the screen.
  21247.  PRINT C$
  21248.  
  21249.  ;  This procedure accepts two far strings, concatenates them, and
  21250.  ;  returns the result in the form of a far string.
  21251.  ;
  21252.  .modelmedium,basic;define memory model to match BASIC
  21253.  .stack
  21254.  .data?
  21255.  maxst = 50;maximum bytes reserved for strings
  21256.  inbuffer1dbmaxst dup(0);room for first fixed-length string
  21257.  inbuffer2dbmaxst dup(0);and second one
  21258.  outbufferdb2*maxst dup(0);work area for string processing
  21259.  .data
  21260.  shdd0;output string descriptor
  21261.  .code
  21262.  addstring   procuses si di ds, s1:far ptr, s1len, s2:far ptr, s2len
  21263.  
  21264.  ;First get BASIC to convert BASIC strings into standard form:
  21265.  lesax,s1;Push far pointer
  21266.  pushes;to input string
  21267.  pushax;descriptor.
  21268.  xorax,ax;Push a zero to indicate
  21269.  pushax;it is variable length.
  21270.  pushds;Push far pointer
  21271.  leaax, inbuffer1;to destination
  21272.  pushax;string.
  21273.  movax,maxst;Push length of destination
  21274.  pushax;fixed-length string.
  21275.  extrnstringassign:proc far
  21276.  callstringassign;Call BASIC to assign
  21277.  ;variable-length string
  21278.  ;to fixed length string.
  21279.  lesax,s2;Push far pointer to
  21280.  pushes;second input string
  21281.  pushax;descriptor.
  21282.  xorax,ax;Push a zero to indicate
  21283.  pushax;it is variable length.
  21284.  pushds;Push far pointer
  21285.  leaax,inbuffer2;to second
  21286.  pushax;destination string.
  21287.  movax,maxst;Push length of destination
  21288.  pushax;fixed-length string.
  21289.  
  21290.  extrnstringassign:proc far
  21291.  callstringassign; Call BASIC to assign
  21292.  ; variable-length string
  21293.  ; to fixed length string.
  21294.  ; Concatenate strings:
  21295.  leasi,inbuffer1; Copy first string to buffer.
  21296.  leadi,outbuffer
  21297.  movax,ds
  21298.  moves,ax
  21299.  movcx,s1len
  21300.  repmovsb
  21301.  leasi,inbuffer2; Concatenate second string to
  21302.  movcx,s2len; end of first.
  21303.  repmovsb
  21304.  
  21305.  ;Get BASIC to convert result back into a BASIC string:
  21306.  pushds;Push far pointer to
  21307.  leaax,outbuffer;fixed-length result
  21308.  pushax;string.
  21309.  movax,s1len;Compute total length
  21310.  movbx,s2len;of fixed-length
  21311.  addax,bx;result string.
  21312.  pushax;Push length.
  21313.  pushds;Push far pointer
  21314.  lea ax,sh;to sh (BASIC will use
  21315.  pushax;this in StringAssign).
  21316.  xorax,ax;Push a zero for length
  21317.  pushax;indicating variable-length.
  21318.  callstringassign;Call BASIC to assign the
  21319.  ;result to sh.
  21320.  leaax,sh;Return output string pointer
  21321.  ;in ax and go back to BASIC.
  21322.  ret
  21323.  
  21324.  addstringendp
  21325.  end
  21326.  
  21327.  
  21328.  When returning to BASIC with only one string output, it is convenient for
  21329.  the MASM procedure to be a function, as in the preceding example. To pass
  21330.  back more than one string, do the following:
  21331.  
  21332.     1. Have BASIC declare the procedure as a BASIC  SUB procedure with output
  21333.        parameters included in the calling list.
  21334.  
  21335.     2. Then call the  SUB procedure with the output arguments.
  21336.  
  21337.     3. In the MASM data segment, eliminate descriptor1 and add another output
  21338.        data block.
  21339.  
  21340.     4. Add output parameters to the proc statement:
  21341.  
  21342.     5. Then transfer each fixed-string output to one of the passed-in
  21343.        variable-length strings using the  StringAssign routine.
  21344.  
  21345.  
  21346.  
  21347.  MASM Calling BASIC
  21348.  
  21349.  This example shows how to pass variable-length strings between MASM and
  21350.  BASIC using the general method explained in the preceding section. The first
  21351.  module is the MXSHKA.ASM file. For proper initialization, however, all
  21352.  mixed-language programs involving BASIC must start in BASIC. So startup
  21353.  begins in the second module, MXADSTB.BAS, which then calls the MASM
  21354.  procedure shakespeare.
  21355.  
  21356.  The shakespeare procedure creates the strings phrase1 and phrase2. For each
  21357.  string it also creates a data block that contains the length and a near
  21358.  pointer to the data. The elements of the data block are then passed to the
  21359.  BASIC procedure AddString by reference, along with a similar data block for
  21360.  output. The AddString procedure transfers the data to its own work area and
  21361.  then concatenates the two strings. It then transfers the output to a MASM
  21362.  fixed-length string sentence. Upon return, the MASM module prints the output
  21363.  string.
  21364.  
  21365.  ;*************************** SHAKESPEARE ******************************
  21366.  ; This program is found in file MXSHKA.ASM.
  21367.  ; It creates two strings and passes them to a BASIC procedure called
  21368.  ; AddString (in file MXADSTB.BAS).  This procedure concatenates
  21369.  ; the strings and passes the result to MASM which prints it.
  21370.  
  21371.  .model medium,basic;Use same memory model as BASIC
  21372.  .stack
  21373.  .data
  21374.  
  21375.  ;Create the data
  21376.  phrase1db"To be or not to be;"
  21377.  phrase1lendw$-phrase1
  21378.  phrase1offdwphrase1
  21379.  phrase2 db" that is the question."
  21380.  phrase2lendw$-phrase2
  21381.  phrase2offdwphrase2
  21382.  sentencedb100 dup(0);Make room for return data
  21383.  sentencelen dw0;and a length indicator.
  21384.  sentenceoff dwsentence
  21385.  
  21386.  .code
  21387.  shakespeare procuses si
  21388.  
  21389.  
  21390.  ;First call BASIC to concatenate strings:
  21391.  leaax,phrase1off;Push far address of
  21392.  pushax;fixed-length string #1,
  21393.  lea ax,phrase1len;and its length.
  21394.  pushax
  21395.  lea ax,phrase2off;Do the same for the
  21396.  pushax;address of string #2,
  21397.  lea ax,phrase2len;and its length.
  21398.  pushax
  21399.  leaax,sentenceoff;Push far address of
  21400.  pushax;the return string,
  21401.  lea ax,sentencelen;and its length.
  21402.  pushax
  21403.  extrnaddstring:proc;Call BASIC function to
  21404.  calladdstring;concatenate the strings and
  21405.  ;put the result in the
  21406.  ;fixed-length return string.
  21407.  
  21408.  ; Call DOS to print string. The DOS string output routine (09H)
  21409.  ; requires that strings end with a "$" character.
  21410.  mov bx,sentencelen;Go to end of the result
  21411.  lea si,sentence ;string and add a
  21412.  mov byte ptr [bx + si],24h;"$" (24h) character.
  21413.  
  21414.  lea dx,sentence ;Set up registers
  21415.  mov ah,9;and  call DOS
  21416.  int 21h ;to print result string.
  21417.  ret
  21418.  
  21419.  shakespeare endp
  21420.  
  21421.  end
  21422.  '************************XDTBBS*************
  21423.  DEFINT A-Z
  21424.  
  21425.  'Start program in BASIC for proper initialization.
  21426.  ' Define external and internal procedures.
  21427.  DECLARE SUB shakespeare ()
  21428.  DECLARE SUB StringAssign(BYVAL srcsegment,BYVAL srcoffset,_
  21429.  BYVAL srclen,BYVAL destsegment,_
  21430.  BYVAL destoffset,BYVAL destlen)
  21431.  DECLARE SUB addstring (instrg1off,instrg1len,instrg2off,_
  21432.  instrg2len,outstrgoff,outstrglen)
  21433.  DECLARE SUB StringRelease (s$)
  21434.  
  21435.  'Go to main routine in second language
  21436.  CALL shakespeare
  21437.  
  21438.  'The non-BASIC program calls this SUB to add the two strings together
  21439.  SUB addstring (instrg1off,instrg1len,instrg2off,instrg2len,_
  21440.  outstrgoff,outstrglen)
  21441.  
  21442.  ' Create variable-length strings and transfer non-BASIC fixed strings
  21443.  ' to them. Use VARSEG() to compute the segment of the strings
  21444.  ' returned from the other language--this is the DGROUP segment,
  21445.  ' and all string descriptors are found in this segment (even
  21446.  ' though the far string itself is elsewhere).
  21447.  
  21448.  CALL StringAssign(VARSEG(a$), instrg1off, instrg1len, VARSEG(a$),_
  21449.  VARPTR(a$), 0)
  21450.  CALL StringAssign(VARSEG(b$), instrg2off, instrg2len, VARSEG(b$),_
  21451.  VARPTR(b$), 0)
  21452.  
  21453.  ' Process the strings--in this case, add them.
  21454.  c$ = a$ + b$
  21455.  
  21456.  ' Calculate the new output length.
  21457.  outstrglen = LEN(c$)
  21458.  
  21459.  ' Transfer string output to a non-BASIC fixed-length string.
  21460.  CALL StringAssign(VARSEG(c$), VARPTR(c$), 0, VARSEG(c$), outstrgoff,_
  21461.  outstrglen)
  21462.  END SUB
  21463.  
  21464.  BASIC Calling C
  21465.  
  21466.  This example shows how to pass variable-length strings between BASIC and C
  21467.  using the general method explained previously. The first module, MXSHKB.BAS,
  21468.  creates the strings A$ and B$, then passes their string-descriptor far
  21469.  pointers and data lengths to a C procedure called AddString. This procedure
  21470.  is contained in the file MXADSTC.C. The AddString procedure transfers the
  21471.  data to its own work area and then concatenates the two strings. It then
  21472.  transfers the output to a BASIC variable-length string. Upon return, the
  21473.  BASIC module prints the output string.
  21474.  
  21475.  '******************************MXSHKB.BAS*******************************
  21476.  DEFINT A-Z
  21477.  'Define non-basic procedures
  21478.  DECLARE FUNCTION addstring$(SEG s1$,BYVAL s1length,SEG s2$,BYVAL s2length)
  21479.  
  21480.  'Create the data
  21481.  A$ = "To be or not to be;"
  21482.  B$ = " that is the question."
  21483.  'Use Non-BASIC function to add two BASIC far strings.
  21484.  C$ = addstring(A$, LEN(A$), B$, LEN(B$))
  21485.  
  21486.  'Print the result on the screen.
  21487.  
  21488.  PRINT C$
  21489.  
  21490.  /*           MXADSTC.C           */
  21491.  #include <string.h>
  21492.  
  21493.  /* Function Prototypes force either correct data typing or compiler
  21494.   * warnings. Note all functions exported to BASIC and all BASIC (extern)
  21495.   * functions are declared with the far pascal calling convention.
  21496.   * WARNING: This must be compiled with the Medium memory model (/AM)
  21497.   */
  21498.  char * pascal addstring( char far *s1, int s1len,
  21499.  char far *s2, int s2len );
  21500.  extern void far pascal StringAssign( char far *source, int slen,
  21501.  char far *dest, int dlen );
  21502.  
  21503.  /* Declare global char array to contain new BASIC string descriptor.
  21504.   */
  21505.  char BASICDesc[4];
  21506.  
  21507.  char * pascal addstring( char far *s1, int s1len,
  21508.  char far *s2, int s2len )
  21509.  
  21510.  {
  21511.      char TS1[50];
  21512.      char TS2[50];
  21513.      char TSBig[100];
  21514.  
  21515.      /* Use the BASIC StringAssign routine to retrieve information
  21516.       * from the descriptors, s1 and s2, and place them in the temporary
  21517.       * arrays TS1 and TS2.
  21518.       */
  21519.      StringAssign( s1, 0, TS1, 49 );/* Get S1 as array of char */
  21520.      StringAssign( s2, 0, TS2, 49 );/* Get S2 as array of char */
  21521.  
  21522.      /* Copy the data from TS1 into TSBig, then append the data from
  21523.       * TS2.
  21524.       */
  21525.      memcpy( TSBig, TS1, s1len );
  21526.      memcpy( &TSBig[s1len], TS2, s2len );
  21527.  
  21528.      StringAssign( TSBig, s1len + s2len, BASICDesc, 0 );
  21529.  
  21530.      return BASICDesc;
  21531.  }
  21532.  
  21533.  When returning to BASIC with only one string output, it is convenient for
  21534.  the C program to be a function, as in the preceding example. To pass back
  21535.  more than one string, do the following:
  21536.  
  21537.     1. Have BASIC declare the procedure as a BASIC  SUB procedure with output
  21538.        parameters included in the calling list.
  21539.  
  21540.     2. Call the  SUB procedure with the output arguments C$ and D$.
  21541.  
  21542.  Suppose you want the original sentence returned in C$ and its reverse in D$.
  21543.  Make these modifications to the C code:
  21544.  
  21545.     1. Change the prototype and function header for AddString to include the
  21546.        outputs, and declare it a void.
  21547.  
  21548.     2. Eliminate the data block for the string descriptor BASICDesc[4].
  21549.        Change the  StringAssign call so that TSBig is assigned to s3 instead
  21550.        of BASICDesc.
  21551.  
  21552.     3. Add the following lines of code:
  21553.  
  21554.  TSBig[s1len + s2len] = '\0';
  21555.        strrev( TSBig );
  21556.        StringAssign( TSBig, s1len + s2len, s4, 0 );
  21557.  
  21558.     4. Delete the  return statement.
  21559.  
  21560.  
  21561.  
  21562.  C Calling BASIC
  21563.  
  21564.  This example shows how to pass variable-length strings between C and BASIC
  21565.  using the general method explained previously. The first module is the
  21566.  MXSHKC.C file. For proper initialization, however, all mixed-language
  21567.  programs involving BASIC must start in BASIC. So startup begins in a second
  21568.  module, MXADSTB.BAS, which then calls the C procedure shakespeare.
  21569.  
  21570.  The shakespeare procedure creates the strings s1 and s2. For each string it
  21571.  also creates a data block that contains the length and a near pointer to the
  21572.  data. The elements of the data block are then passed to the BASIC  SUB
  21573.  procedure AddString by reference, along with a similar data block for
  21574.  output. The AddString  SUB procedure transfers the data to its own work area
  21575.  and then concatenates the two strings. It then transfers the output to the C
  21576.  fixed-length string s3. Upon return, the C module prints the output string.
  21577.  
  21578.  /*                      MXSHKC.C                   */
  21579.  #include <stdio.h>
  21580.  #include <string.h>
  21581.  
  21582.  /* Prototype the shakespeare function (our function)
  21583.   * The prototypes force either correct data typing or compiler warnings.
  21584.   */
  21585.  void far pascal shakespeare( void );
  21586.  extern void far pascal addstring( char ** s1, int * s1len,
  21587.  char ** s2, int * s2len,
  21588.  char ** s3, int * s3len );
  21589.  
  21590.  
  21591.  void far pascal shakespeare( void )
  21592.  {
  21593.      char * s1 = "To be or not to be;";
  21594.      int  s1len;
  21595.      char * s2 = " that is the question.";
  21596.      int  s2len;
  21597.      char s3[100];
  21598.      int  s3len;
  21599.      char * s3ad = s3;
  21600.  
  21601.  s1len = strlen( s1 );
  21602.      s2len = strlen( s2 );
  21603.      addstring( &s1, &s1len, &s2, &s2len, &s3add, &s3len );
  21604.  
  21605.      s3[s3len] = '\0';
  21606.      printf("\n%s", s3 );
  21607.  }
  21608.  '*************************MXADSTRB.BAS***************************
  21609.  DEFINT A-Z
  21610.  
  21611.  'Start program in BASIC for proper initialization.
  21612.  ' Define external and internal procedures.
  21613.  DECLARE SUB shakespeare ()
  21614.  DECLARE SUB StringAssign(BYVAL srcsegment,BYVAL srcoffset,_
  21615.  BYVAL srclen,BYVAL destsegment,_
  21616.  BYVAL destoffset,BYVAL destlen)
  21617.  DECLARE SUB addstring (instrg1off,instrg1len,instrg2off,_
  21618.  instrg2len,outstrgoff,outstrglen)
  21619.  DECLARE SUB StringRelease (s$)
  21620.  
  21621.  'Go to main routine in second language
  21622.  CALL shakespeare
  21623.  
  21624.  'The non-BASIC program calls this SUB to add the two strings together
  21625.  SUB addstring (instrg1off,instrg1len,instrg2off,instrg2len,_
  21626.  outstrgoff,outstrglen)
  21627.  
  21628.  ' Create variable-length strings and transfer non-BASIC fixed strings
  21629.  ' to them. Use VARSEG() to compute the segment of the strings
  21630.  ' returned from the other language--this is the DGROUP segment,
  21631.  ' and all string descriptors are found in this segment (even
  21632.  ' though the far string itself is elsewhere).
  21633.  
  21634.  CALL StringAssign(VARSEG(a$), instrg1off, instrg1len, VARSEG(a$),_
  21635.  VARPTR(a$), 0)
  21636.  CALL StringAssign(VARSEG(b$), instrg2off, instrg2len, VARSEG(b$),_
  21637.  VARPTR(b$), 0)
  21638.  
  21639.  
  21640.  ' Process the strings--in this case, add them.
  21641.  c$ = a$ + b$
  21642.  
  21643.  ' Calculate the new output length.
  21644.  outstrglen = LEN(c$)
  21645.  ' Transfer string output to a non-BASIC fixed-length string.
  21646.  CALL StringAssign(VARSEG(c$), VARPTR(c$), 0, VARSEG(c$), outstrgoff,_
  21647.  outstrglen)
  21648.  END SUB
  21649.  
  21650.  
  21651.  BASIC Calling FORTRAN
  21652.  
  21653.  This example shows how to pass variable-length strings between BASIC and
  21654.  FORTRAN using the general method explained above. The first module,
  21655.  MXSHKB.BAS, creates the strings A$ and B$, then passes their
  21656.  string-descriptor far pointers and data lengths to a FORTRAN procedure
  21657.  called ADDSTR. This procedure is contained in the file MXADSTF.FOR. ADDSTR
  21658.  transfers the data to its own work area and then concatenates the two
  21659.  strings. It then transfers the output to a BASIC variable-length string.
  21660.  Upon return, the BASIC module prints the output string.
  21661.  
  21662.  '******************************MXSHKB.BAS*******************************
  21663.  DEFINT A-Z
  21664.  'Define non-BASIC procedures.
  21665.  DECLARE FUNCTION AddString$(SEG s1$,BYVAL s1length,SEG s2$,BYVAL s2length)
  21666.  
  21667.  
  21668.  'Create the data.
  21669.  A$ = "To be or not to be;"
  21670.  B$ = " that is the question."
  21671.  
  21672.  'Use Non-BASIC function to add two BASIC far strings.
  21673.  C$ = AddString(A$, LEN(A$), B$, LEN(B$))
  21674.  
  21675.  'Print the result on the screen.
  21676.  PRINT C$
  21677.  C ********************
  21678.  ADDSTRING  *********************
  21679.  C This program is in file MXADSTF.FOR
  21680.  C Declare interface to Stringassign subprogram. The pointer fields are
  21681.  C declared INTEGER*4, so that different types of far pointers can be
  21682.  C passed without conflict. The INTEGER*4 fields are essentially generic
  21683.  C pointers. [VALUE] must be specified, or FORTRAN will pass pointers to
  21684.  C pointers. INTEGER*2 also passed by [VALUE], to be consistent with
  21685.  C declaration of StringAssign.
  21686.  C
  21687.  INTERFACE TO SUBROUTINE STRASG [ALIAS:'STRINGASSIGN'] (S,SL,D,DL)
  21688.         INTEGER*4 S [VALUE]
  21689.         INTEGER*2 SL [VALUE]
  21690.         INTEGER*4 D [VALUE]
  21691.         INTEGER*2 DL [VALUE]
  21692.         END
  21693.  C
  21694.  C Declare heading of Addstring function in the same way as above: the
  21695.  C pointer fields are INTEGER*4
  21696.  C
  21697.         INTEGER*2 FUNCTION ADDSTR [ALIAS:'ADDSTRING'] (S1,S1LEN,S2,S2LEN)
  21698.         INTEGER*4 S1 [VALUE]
  21699.         INTEGER*2 S1LEN [VALUE]
  21700.         INTEGER*4 S2 [VALUE]
  21701.         INTEGER*2 S2LEN [VALUE]
  21702.  C
  21703.  C Local parameters TS1, TS2, and BIGSTR are temporary strings. STRDES is
  21704.  C a four-byte object into which Stringassign will put BASIC string
  21705.  C descriptor.
  21706.  C
  21707.         CHARACTER*50 TS1, TS2
  21708.         CHARACTER*100 BIGSTR
  21709.         INTEGER*4 STRDES
  21710.  
  21711.  TS1 = " "
  21712.  TS2 = " "
  21713.  STRDES = 0
  21714.  
  21715.  C
  21716.  C Use the LOCFAR function to take the far address of data. LOCFAR returns
  21717.  C a value of type INTEGER*4.
  21718.  C
  21719.         CALL STRASG (S1, 0, LOCFAR(TS1), S1LEN)
  21720.         CALL STRASG (S2, 0, LOCFAR(TS2), S2LEN)
  21721.         BIGSTR = TS1(1:S1LEN) // TS2(1:S2LEN)
  21722.         CALL STRASG (LOCFAR(BIGSTR), S1LEN+S2LEN, LOCFAR(STRDES), 0)
  21723.         ADDSTR = LOC(STRDES)
  21724.         RETURN
  21725.         END
  21726.  
  21727.  Instead of returning a string as the output of a function, you can also pass
  21728.  a string back as a subroutine parameter. In fact, to pass back more than one
  21729.  string, you must use this method. To do so, make these changes to the
  21730.  preceding code:
  21731.  
  21732.     1. Declare the subroutine as a BASIC  SUB procedure with the output
  21733.        parameter included in the calling list:
  21734.  
  21735.     2. Call the subprogram with output argument C$:
  21736.  
  21737.     3. In the FORTRAN module, change the  FUNCTION declaration to a
  21738.        SUBROUTINE declaration, in which the subroutine accepts an additional
  21739.        parameter not passed by value:
  21740.  
  21741.     4. Change the line of code that sets the following return value:
  21742.  
  21743.  ADDSTR = LOC(STRDES)  Into a statement that sets the value of the following
  21744.  output string:
  21745.  
  21746.  OUTS = STRDES
  21747.  In some cases, you may want to return several strings. To do so, simply add
  21748.  additional INTEGER*4 parameters to your procedure declaration.
  21749.  
  21750.  
  21751.  FORTRAN Calling BASIC
  21752.  
  21753.  This example shows how to pass variable-length strings between FORTRAN and
  21754.  BASIC using the general method explained previously. The first module is the
  21755.  MXSHKF.FOR file. For proper initialization, however, all mixed-language
  21756.  programs involving BASIC must start in BASIC. So startup begins in a second
  21757.  module, MXADSTB.BAS, which then calls the FORTRAN procedure SHAKES.
  21758.  
  21759.  The SHAKES procedure creates the strings STR1 and STR2. For each string it
  21760.  also creates a data block that contains the length and a near pointer to the
  21761.  data. The elements of the data block are then passed to the BASIC AddString
  21762.  procedure by reference, along with a similar data block for output. The
  21763.  AddString procedure transfers the data to its own work area and then
  21764.  concatenates the two strings. It then transfers the output to the FORTRAN
  21765.  fixed-length string STR3. Upon return, the FORTRAN module prints the output
  21766.  string.
  21767.  
  21768.  C *********************** SHAKESPEARE ****************
  21769.  C This program is in file MXSHKF.FOR
  21770.  C Declare interface to BASIC routine ADDSTRING.
  21771.  C All parameters must be passed NEAR, for compatibility with BASIC's
  21772.  C conventions.
  21773.  C
  21774.  
  21775.         INTERFACE TO SUBROUTINE ADDSTR[ALIAS:'ADDSTRING']
  21776.       * (S1,L1,S2,L2,S3,L3)
  21777.         INTEGER*2 S1 [NEAR]
  21778.         INTEGER*2 L1 [NEAR]
  21779.         INTEGER*2 S2 [NEAR]
  21780.         INTEGER*2 L2 [NEAR]
  21781.         INTEGER*2 S3 [NEAR]
  21782.         INTEGER*2 L3 [NEAR]
  21783.         END
  21784.  C
  21785.  C Declare subroutine SHAKESPEARE, which declares two strings, calls
  21786.  C BASIC subroutine ADDSTRING, and prints the result.
  21787.  C
  21788.         SUBROUTINE SHAKES [ALIAS:'SHAKESPEARE']
  21789.         CHARACTER*50 STR1, STR2
  21790.         CHARACTER*100 STR3
  21791.         INTEGER*2 STRLEN1, STRLEN2, STRLEN3
  21792.         INTEGER*2 TMP1, TMP2, TMP3
  21793.  
  21794.  C
  21795.  C The subroutine uses FORTRAN LEN_TRIM function, which returns the
  21796.  C length of string, excluding trailing blanks. (All FORTRAN strings
  21797.  C are initialized to blanks.)
  21798.  C
  21799.         STR1 = 'To be or not to be;'
  21800.         STRLEN1 = LEN_TRIM(STR1)
  21801.         STR2 = ' that is the question.'
  21802.         STRLEN2 = LEN_TRIM(STR2)
  21803.         TMP1 = LOC(STR1)
  21804.         TMP2 = LOC(STR2)
  21805.         TMP3 = LOC(STR3)
  21806.         CALL ADDSTR (TMP1, STRLEN1, TMP2, STRLEN2, TMP3, STRLEN3)
  21807.         WRITE (*,*) STR3
  21808.  END
  21809.  
  21810.       '*************************MXADSTRB.BAS***************************
  21811.  DEFINT A-Z
  21812.  
  21813.  'Start program in BASIC for proper initialization.
  21814.  ' Define external and internal procedures.
  21815.  DECLARE SUB shakespeare ()
  21816.  DECLARE SUB StringAssign(BYVAL srcsegment,BYVAL srcoffset,_
  21817.  BYVAL srclen,BYVAL destsegment,_
  21818.  BYVAL destoffset,BYVAL destlen)
  21819.  DECLARE SUB addstring (instrg1off,instrg1len,instrg2off,_
  21820.  instrg2len,outstrgoff,outstrglen)
  21821.  DECLARE SUB StringRelease (s$)
  21822.  
  21823.  'Go to main routine in second language
  21824.  CALL shakespeare
  21825.  
  21826.  'The non-BASIC program calls this SUB to add the two strings together
  21827.  SUB addstring (instrg1off,instrg1len,instrg2off,instrg2len,_
  21828.  outstrgoff,outstrglen)
  21829.  
  21830.  ' Create variable-length strings and transfer non-BASIC fixed strings
  21831.  ' to them. Use VARSEG() to compute the segment of the strings
  21832.  ' returned from the other language--this is the DGROUP segment,
  21833.  ' and all string descriptors are found in this segment (even
  21834.  ' though the far string itself is elsewhere).
  21835.  
  21836.  CALL StringAssign(VARSEG(a$), instrg1off, instrg1len, VARSEG(a$),_
  21837.  VARPTR(a$), 0)
  21838.  CALL StringAssign(VARSEG(b$), instrg2off, instrg2len, VARSEG(b$),_
  21839.  VARPTR(b$), 0)
  21840.  
  21841.  ' Process the strings--in this case, add them.
  21842.  c$ = a$ + b$
  21843.  
  21844.  ' Calculate the new output length.
  21845.  outstrglen = LEN(c$)
  21846.  
  21847.  ' Transfer string output to a non-BASIC fixed-length string.
  21848.  CALL StringAssign(VARSEG(c$), VARPTR(c$), 0, VARSEG(c$), outstrgoff,_
  21849.  outstrglen)
  21850.  END SUB
  21851.  
  21852.  
  21853.  BASIC Calling Pascal
  21854.  
  21855.  This example shows how to pass variable-length strings between BASIC and
  21856.  Pascal using the general method explained previously. The first module,
  21857.  MXSHKB.BAS, creates the strings A$ and B$, then passes their
  21858.  string-descriptor far pointers and data lengths to a Pascal procedure called
  21859.  ADDSTRING. This procedure is contained in the file MXADSTP.PAS. ADDSTRING
  21860.  transfers the data to its own work area and then concatenates the two
  21861.  strings. It then transfers the output to a BASIC variable-length string.
  21862.  Upon return, the BASIC module prints the output string.
  21863.  
  21864.  '******************************MXSHKB.BAS*******************************
  21865.  DEFINT A-Z
  21866.  'Define non-basic procedures.
  21867.  DECLARE FUNCTION AddString$(SEG s1$,BYVAL s1length,SEG s2$,_
  21868.  BYVAL s2length)
  21869.  
  21870.  'Create the data.
  21871.  A$ = "To be or not to be;"
  21872.  B$ = " that is the question."
  21873.  
  21874.  'Use Non-BASIC function to add two BASIC far strings.
  21875.  C$ = AddString(A$, LEN(A$), B$, LEN(B$))
  21876.  
  21877.  'Print the result on the screen.
  21878.  PRINT C$
  21879.  {
  21880.  **********************ADDSTRING ***********************
  21881.    This program is in file MXADSTP.PAS  }
  21882.  
  21883.  { Module MXADSTP--takes address and lengths of two BASIC
  21884.    strings, concatenates, and creates a BASIC string descriptor. }
  21885.  MODULE MAXADSTP;
  21886.  { Declare type ADSCHAR for all pointer types. For ease of programming,
  21887.    all address variables in this module are considered pointers to
  21888.    characters, and all strings and string descriptors are considered
  21889.    arrays of characters. Also, declare the BASIC string descriptor
  21890.    type as a simple array of four characters. }
  21891.  
  21892.  TYPE
  21893.      ADSCHAR = ADS OF CHAR;
  21894.      ADRCHAR = ADR OF CHAR;
  21895.      STRDESC = ARRAY[0..3] OF CHAR;
  21896.  VAR
  21897.      MYDESC : STRDESC;
  21898.  { Interface to procedure BASIC routine StringAssign. If source
  21899.    string is a fixed-length string, S points to string data and SL
  21900.    gives length. If source string is a BASIC variable-length string,
  21901.    S points to a BASIC string descriptor and SL is 0. Similarly for
  21902.    destination string, D and DL. }
  21903.  PROCEDURE STRINGASSIGN (S:ADSCHAR; SL:INTEGER;
  21904.  D:ADSCHAR; DL:INTEGER ); EXTERN;
  21905.  
  21906.  FUNCTION ADDSTRING (S1:ADSCHAR; S1LEN:INTEGER;
  21907.  S2:ADSCHAR; S2LEN:INTEGER) : ADRCHAR;
  21908.  
  21909.      VAR
  21910.  BIGSTR : ARRAY[0..99] OF CHAR;
  21911.  { Execute function by copying S1 to the array BIGSTR, appending S2
  21912.    to the end, and then copying combined data to the string descriptor. }
  21913.  
  21914.      BEGIN
  21915.  STRINGASSIGN (S1, 0, ADS BIGSTR[0], S1LEN);
  21916.  STRINGASSIGN (S2, 0, ADS BIGSTR[S1LEN], S2LEN);
  21917.  STRINGASSIGN (ADS BIGSTR[0], S1LEN+S2LEN, ADS MYDESC[0], 0);
  21918.  ADDSTRING := ADR MYDESC;
  21919.      END;  { End Addstring function,}
  21920.  END.  {End module.}
  21921.  
  21922.  
  21923.  Instead of returning a string as the output of a function,  you can also
  21924.  pass a string back as a procedure parameter. In fact, to pass back more than
  21925.  one string, you must use this method. To do so, make the following changes
  21926.  to the preceding code:
  21927.  
  21928.     1. Declare the procedure as a  BASIC  SUB procedure with the output
  21929.        parameter included in the calling list:
  21930.  
  21931.     2. Then call the subprogram with output argument C$:
  21932.  
  21933.     3. In the Pascal module, change the EXTERN declaration from a function to
  21934.        a procedure, in which the procedure accepts an additional VAR
  21935.        parameter:
  21936.  
  21937.     4. Change the line of code that sets the following return value:
  21938.  
  21939.  
  21940.  ADDSTRING:=MYDESC;  Into a statement that sets the value of the following
  21941.  output string:
  21942.  
  21943.  
  21944.  OUTSTR:=MYDESC;
  21945.  In some cases, you may want to return several strings. To do so, simply add
  21946.  additional  VAR parameters to your procedure declaration. Each formal
  21947.  argument (parameter) should be of type STRDESC. You can use another name for
  21948.  this type, but remember to define this type or some equivalent type
  21949.  (ARRAY[0..3] OF CHAR) at the beginning of your Pascal module.
  21950.  
  21951.  
  21952.  Pascal Calling BASIC
  21953.  
  21954.  This example shows how to pass variable-length strings between Pascal and
  21955.  BASIC using the general method explained previously. The first module is the
  21956.  MXSHKP.PAS file. For proper initialization, however, all mixed-language
  21957.  programs involving BASIC must start in BASIC. So startup begins in a second
  21958.  module, MXADSTB.BAS, which then calls the Pascal procedure shakespeare.
  21959.  
  21960.  The shakespeare procedure creates the strings STR1 and STR2. For each string
  21961.  it also creates a data block that contains the length and a near pointer to
  21962.  the data. The elements of the data block are then passed to the BASIC
  21963.  procedure AddString by reference, along with a similar data block for
  21964.  output. The AddString procedure transfers the data to its own work area and
  21965.  then concatenates the two strings. It then transfers the output to the
  21966.  Pascal fixed-length string STR3. Upon return, the Pascal module prints the
  21967.  output string.
  21968.  
  21969.  {
  21970.  ************************ SHAKESPEARE ******************
  21971.    This program is in file MXSHKP.PAS }
  21972.  
  21973.  MODULE MPAS;
  21974.  TYPE
  21975.      ADRCHAR = ADR OF CHAR;
  21976.  VAR
  21977.      S1, S2, S3 : LSTRING (100);
  21978.  S1LEN, S2LEN, S3LEN : INTEGER;
  21979.      TMP1, TMP2, TMP3 : ADRCHAR;
  21980.  { Declare interface to procedure ADDSTRING, which concatenates first
  21981.    two strings passed and places the result in the third string
  21982.    passed. }
  21983.  PROCEDURE ADDSTRING (VAR TMP1:ADRCHAR; VAR STR1LEN:INTEGER;
  21984.  VAR TMP2:ADRCHAR; VAR STR2LEN:INTEGER;
  21985.  VAR TMP3:ADRCHAR; VAR STR3LEN:INTEGER ); EXTERN;
  21986.  
  21987.  { Procedure Shakespeare declares two strings, calls BASIC procedure
  21988.    AddString to concatenate them, then prints results. With LSTRING
  21989.    type, note that element 0 contains length byte. String data starts
  21990.    with element 1. }
  21991.  PROCEDURE SHAKESPEARE;
  21992.      BEGIN
  21993.  S1:='To be or not to be;';
  21994.  S1LEN:=ORD(S1[0]);
  21995.  S2:=' that is the question.';
  21996.  S2LEN:=ORD(S2[0]);
  21997.  TMP1:=ADR(S1[1]);
  21998.  TMP2:=ADR(S2[1]);
  21999.  TMP3:=ADR(S3[1]);
  22000.  ADDSTRING (TMP1, S1LEN, TMP2, S2LEN, TMP3, S3LEN);
  22001.  S3[0]:=CHR(S3LEN);
  22002.  WRITELN(S3);
  22003.      END;
  22004.  END.
  22005.  
  22006.  '*************************MXADSTRB.BAS***************************
  22007.  DEFINT A-Z
  22008.  
  22009.  'Start program in BASIC for proper initialization.
  22010.  ' Define external and internal procedures.
  22011.  DECLARE SUB shakespeare ()
  22012.  DECLARE SUB StringAssign(BYVAL srcsegment,BYVAL srcoffset,_
  22013.  BYVAL srclen,BYVAL destsegment,_
  22014.  BYVAL destoffset,BYVAL destlen)
  22015.  DECLARE SUB addstring (instrg1off,instrg1len,instrg2off,_
  22016.  instrg2len,outstrgoff,outstrglen)
  22017.  DECLARE SUB StringRelease (s$)
  22018.  
  22019.  'Go to main routine in second language
  22020.  CALL shakespeare
  22021.  
  22022.  'The non-BASIC program calls this SUB to add the two strings together
  22023.  SUB addstring (instrg1off,instrg1len,instrg2off,instrg2len,_
  22024.  outstrgoff,outstrglen)
  22025.  
  22026.  ' Create variable-length strings and transfer non-BASIC fixed strings
  22027.  ' to them. Use VARSEG() to compute the segment of the strings
  22028.  ' returned from the other language--this is the DGROUP segment,
  22029.  ' and all string descriptors are found in this segment (even
  22030.  ' though the far string itself is elsewhere).
  22031.  
  22032.  CALL StringAssign(VARSEG(a$), instrg1off, instrg1len, VARSEG(a$),_
  22033.  VARPTR(a$), 0)
  22034.  CALL StringAssign(VARSEG(b$), instrg2off, instrg2len, VARSEG(b$),_
  22035.  VARPTR(b$), 0)
  22036.  
  22037.  ' Process the strings--in this case, add them.
  22038.  c$ = a$ + b$
  22039.  
  22040.  ' Calculate the new output length.
  22041.  outstrglen = LEN(c$)
  22042.  
  22043.  ' Transfer string output to a non-BASIC fixed-length string.
  22044.  CALL StringAssign(VARSEG(c$), VARPTR(c$), 0, VARSEG(c$), outstrgoff,_
  22045.  outstrglen)
  22046.  END SUB
  22047.  
  22048.  
  22049.  Passing Strings in QBX
  22050.  
  22051.  If you have a BASIC module that calls an external procedure, as in the
  22052.  preceding examples, you may want to debug the BASIC code in QBX. If you are
  22053.  using any of the string-processing routines, you'll need to make a Quick
  22054.  library that contains the routines. Here's how to do this:
  22055.  
  22056.     1. Compile the external procedure.
  22057.  
  22058.     2. Link the object file with the necessary libraries plus the QBXQLB.LIB
  22059.        library. Use the /Q option. For instance, for the first C example:
  22060.  
  22061.     3. Load the new Quick library when starting up QBX by using the /L option
  22062.        as shown here:
  22063.  
  22064.  
  22065.  QBX /L MXADSTC.QLB
  22066.  If you just want to test the new string routines in QBX, without calling an
  22067.  external procedure, load the QBX.QLB Quick library when starting QBX.
  22068.  
  22069.  
  22070.  Passing Variable-Length String Arrays
  22071.  
  22072.  To manage variable-length string arrays, BASIC creates a data block in
  22073.  DGROUP containing a series of variable-length string descriptors -- one for
  22074.  each array element. The descriptors begin at address of the first element in
  22075.  the array, for example the first descriptor in a three dimensional
  22076.  zero-based array named A$() would be A$(0,0,0). The descriptors are in
  22077.  column-major order, where the rightmost dimension changes first. Note,
  22078.  however, that if you compile with the /R option the descriptors will be in
  22079.  row-major form. For examples of this, see the  Microsoft Mixed-Language
  22080.  Programming Guide.
  22081.  
  22082.  Using the  StringAssign routine, a non-BASIC language can copy a BASIC
  22083.  variable-length string array into its own workspace, modify any data element
  22084.  (even change its length), and copy the changed array back to BASIC.
  22085.  
  22086.  Assume, for example, that A$() is a one-dimensional BASIC string array that
  22087.  contains these elements indexed with numbers 1 to 10:
  22088.  
  22089.  A,BB,CCC,DDDD,EEEEE,FFFFFF,GGGGGGG,HHHHHHHH,IIIIIIIII,JJJJJJJJJJ
  22090.  
  22091.  The elements need to be changed to:
  22092.  
  22093.  jjjjjjjjjj,iiiiiiiii,hhhhhhhh,ggggggg,ffffff,eeeee,dddd,ccc,bb,a
  22094.  
  22095.  To accomplish this, BASIC could call a MASM procedure, passing it the
  22096.  address of the first string descriptor in the array:
  22097.  
  22098.  DECLARE SUB ChangeArray(S$)
  22099.  CALL ChangeArray(A$(1))
  22100.  
  22101.  
  22102.  The array transfer is accomplished by:
  22103.  
  22104.  .modelmedium,basic
  22105.  
  22106.  .data
  22107.  arraydw100 dup(0);Create space for 10 element array
  22108.  .code
  22109.  changearray  proc uses si di, arraydescriptor: near ptr ;pointer to array
  22110.  extrnstringassign:proc;declare BASIC callback
  22111.  movcx, 10  ;number of transfers
  22112.  movsi, arraydescriptor;first source
  22113.  leadi, array    ;first destination
  22114.  transferin:pushcx ;preserve cx during callback
  22115.  
  22116.  pushds;far pointer to source--
  22117.  pushsi
  22118.  xorax,ax   ;a variable-length string
  22119.  pushax
  22120.  pushds;far pointer to destination--
  22121.  pushdi;a fixed-length string
  22122.  movax, 10  ;10 bytes long
  22123.  pushax
  22124.  callstringassign;go transfer one string
  22125.  popcx;restore cx
  22126.  addsi, 4;update pointers
  22127.  adddi,10
  22128.  looptransferin;last transfer?
  22129.  
  22130.  ;Now, change the data to lower case
  22131.  
  22132.  movcx,100
  22133.  leabx, array
  22134.  more:cmpbyte ptr[bx], 0
  22135.  jzskip
  22136.  addbyte ptr[bx], 32
  22137.  skip:incbx
  22138.  loopmore
  22139.  
  22140.  ; and send it back out, last element first.
  22141.  
  22142.  movcx, 10;number of transfers
  22143.  leasi, array + 90 ;first source--the last element
  22144.  movdi, arraydescriptor;first destination
  22145.  transferout:pushcx;preserve cx during call
  22146.  
  22147.  pushds;far pointer to source--
  22148.  pushsi
  22149.  push cx;a fixed-length string
  22150.  pushds;far pointer to destination--
  22151.  pushdi;a variable-length
  22152.  xorax,ax;string.
  22153.  pushax
  22154.  callstringassign;go transfer one string
  22155.  popcx;restore variables
  22156.  subsi, 10;update pointers
  22157.  adddi, 4
  22158.  looptransferout;last transfer?
  22159.  
  22160.  ret
  22161.  
  22162.  changearrayendp
  22163.  end
  22164.  
  22165.  Passing Fixed-Length Strings
  22166.  
  22167.  If you want to pass BASIC fixed-length string data to and from another
  22168.  language, you can also use the  StringAssign routine. This works in spite of
  22169.  the fact that fixed-length strings are illegal as parameters in BASIC
  22170.  procedures. In other words, this is impossible:
  22171.  
  22172.  DECLARE FUNCTION PassFixed AS STRING * 10 (FixedString1 AS STRING * 10)
  22173.  
  22174.  You can, however, achieve the same result by using fixed-string arguments
  22175.  and variable-string parameters:
  22176.  
  22177.  ' Declare an external function.
  22178.  DECLARE FUNCTION PassFixed$(FixedString1$)
  22179.  DIM InputString AS STRING * 10, OutputString AS STRING * 20
  22180.  OutputString = PassFixed(InputString)
  22181.  
  22182.  When BASIC makes the call, it creates the variable-length string
  22183.  FixedString1$ and copies the data from the fixed string into it. It pushes
  22184.  the address of the FixedString1$ descriptor (remember, this is a
  22185.  variable-length string) onto the stack.
  22186.  
  22187.  
  22188.  From here on, processing is the same as for the preceding examples. The
  22189.  called function, before returning, creates a 4-byte string descriptor filled
  22190.  with zeros. It uses  StringAssign to transfer its output data to this newly
  22191.  created variable-length string. The address of the string's descriptor is
  22192.  placed in AX and the return is made.
  22193.  
  22194.  When the equal operator (=) is executed in the last code statement, BASIC
  22195.  assigns the data from the returned variable-length string to the the
  22196.  fixed-length string OutputString.
  22197.  
  22198.  
  22199.  Getting Online Help
  22200.  
  22201.  Online Help is available in QBX for mixed-language programming. For help
  22202.  with making an external call, see the  CALL,  CALLS, and  DECLARE non-BASIC
  22203.  Statement categories in the Help Keyword Index. More information can be
  22204.  found in the Mixed-Language Programming section listed in the Help Table of
  22205.  Contents.
  22206.  
  22207.  Sample code in the online Help screens can be copied and pasted to a file.
  22208.  All copied BASIC code samples will execute within QBX. Any of the code
  22209.  samples can also be compiled, linked into executable files or Quick
  22210.  libraries, or made into object-module libraries. See Chapters 18, "Using
  22211.  LINK and LIB" and 19, "Creating and Using Quick Libraries," for more
  22212.  information.
  22213.  
  22214.  
  22215.   ────────────────────────────────────────────────────────────────────────────
  22216.  Chapter 14:  OS/2 Programming
  22217.  ────────────────────────────────────────────────────────────────────────────
  22218.  
  22219.  Microsoft BASIC enables you to create programs for the OS/2 protected-mode
  22220.  environment as well as the real-mode (DOS) environment. This chapter
  22221.  explains how OS/2 protected-mode programs differ from BASIC programs written
  22222.  for DOS. You'll learn how to write, compile, and link programs that run
  22223.  under OS/2, as well as the following:
  22224.  
  22225.    ■   What libraries and include files you'll need.
  22226.  
  22227.    ■   How to call OS/2 functions in your program.
  22228.  
  22229.    ■   Limitations for BASIC programs.
  22230.  
  22231.    ■   Language changes for protected-mode-only programs.
  22232.  
  22233.    ■   How to prepare your programs for debugging.
  22234.  
  22235.  
  22236.  
  22237.  Creating Real or Protected-Mode Programs
  22238.  
  22239.  With BASIC you can create protected-mode programs or real-mode programs. You
  22240.  cannot create bound programs -- programs that run in real and protected
  22241.  modes. You also cannot bind a BASIC executable file after linking.
  22242.  
  22243.  To create an OS/2 program, BASIC provides the QuickBASIC Extended (QBX)
  22244.  programming environment.
  22245.  
  22246.  QBX can be run only in real mode, although you can create OS/2 programs that
  22247.  run in real or protected mode from QBX. The QBX environment contains
  22248.  context-sensitive online Help.
  22249.  
  22250.  While this chapter assumes you are writing, compiling, and linking from QBX
  22251.  you can, if you wish, invoke the BASIC Compiler (BC) and the LINK utility
  22252.  separately from the command line.
  22253.  
  22254.  
  22255.  Editing Source Code
  22256.  
  22257.  To edit source code, you can use the editing facilities built into QBX. Use
  22258.  the F1 key to access online Help for information about using specific
  22259.  editing features and commands.
  22260.  
  22261.  Except where noted, you may write your program using the BASIC statements
  22262.  and functions described in the  BASIC Language Reference. There are certain
  22263.  BASIC statements and functions, however, that behave differently or require
  22264.  extra caution in protected mode. These are described in the following
  22265.  sections.
  22266.  
  22267.  
  22268.  Language Changes for Protected Mode
  22269.  
  22270.  This section describes specific BASIC statements and functions that behave
  22271.  differently or require extra caution in protected mode. The most significant
  22272.  differences between real mode and protected mode concern memory management.
  22273.  Others concern BASIC statements that directly access the machine's hardware,
  22274.  an activity that is not always appropriate in a protected environment. Table
  22275.  14.1 lists and explains the statements and functions that are changed for
  22276.  protected mode in Microsoft BASIC.
  22277.  
  22278.  
  22279.  
  22280.  Making OS/2 Calls in Your Program
  22281.  
  22282.  Microsoft BASIC allows you to make direct calls to OS/2 functions when
  22283.  running in protected mode. When invoking an OS/2 function, it is necessary
  22284.  to use the syntax for calling a BASIC function, even if you are not
  22285.  interested in the error code or other information that might be returned by
  22286.  the OS/2 function.
  22287.  
  22288.  For example, the following program fragment invokes the OS/2 function
  22289.  DOSBEEP:
  22290.  
  22291.  ' Include the file BSEDOSPC.BI
  22292.  REM $INCLUDE: 'bsedospc.bi'
  22293.  ' Invoke the DOSBEEP function
  22294.  x = DOSBEEP(100, 200)
  22295.  
  22296.  OS/2 Include Files
  22297.  
  22298.  To provide support for both OS/2 function calls and type definitions for
  22299.  data structures used by OS/2 functions, Microsoft BASIC provides several
  22300.  OS/2 include files. You can insert include files into your program with a
  22301.  $INCLUDE metacommand.
  22302.  
  22303.  Microsoft BASIC provides the following OS/2 include files:
  22304.  
  22305.  BSEDOSFL.BI:    Device drivers, file management
  22306.  BSESUBMO.BI:    Mouse
  22307.  BSEDOSPE.BI:    National language, resource management,
  22308.                  module management, date/time and timer, memory management,
  22309.                  information segments
  22310.  
  22311.  These include files provide support only for OS/2 functions that are
  22312.  appropriate for BASIC. Many of the omitted functions involve multiple thread
  22313.  capabilities, while others duplicate existing BASIC support, such as
  22314.  keyboard and screen I/O. Because these include files provide a standard
  22315.  interface to OS/2 functions, you should avoid changing their contents
  22316.  unnecessarily.
  22317.  
  22318.  The process of calling OS/2 functions requires certain data types that are
  22319.  not intrinsic to BASIC. Because of this, the include files listed in the
  22320.  preceding table use standard methods for simulating those types. How various
  22321.  data types are simulated in the OS/2 include files for BASIC is described in
  22322.  the following sections.
  22323.  
  22324.  
  22325.  Unsigned Values
  22326.  
  22327.  Unsigned values are not intrinsic to BASIC. The signed version of the given
  22328.  type is used instead.
  22329.  
  22330.  
  22331.  Pointers in User-Defined Types
  22332.  
  22333.  In cases where OS/2 requires a pointer as a field of a user-defined type,
  22334.  the include files use the type ADDRESS, which is defined at the beginning of
  22335.  the include file BSEDOSPC.BI. You can fill in the fields of this type with
  22336.  VARSEG and  SADD in the case of a variable-length string, or  VARSEG and
  22337.  VARPTR in the case of other data objects.
  22338.  
  22339.  
  22340.  Far Character Pointers in Function Parameters
  22341.  
  22342.  The far character pointer for OS/2 functions (far char *) is simulated with
  22343.  two parameters: a segment and an offset. Note that both of these values are
  22344.  integers. Thus, if the original declaration would have been DOSXYZ( far char
  22345.  * ), it is declared in this form:
  22346.  
  22347.  DOSXYZ( BYVAL S1s AS INTEGER, BYVAL S1o AS INTEGER )
  22348.  
  22349.  You would call this function with a statement in the following form, where
  22350.  FixedLen is a fixed-length string:
  22351.  
  22352.  DOSXYZ( VARSEG(FixedLen), VARPTR(FixedLen) )
  22353.  
  22354.  You can call the function with a statement in the following form if VarLen
  22355.  is a variable-length string:
  22356.  
  22357.  DOSXYZ( VARSEG(VarLen), SADD(VarLen) )
  22358.  
  22359.  Pointer to a Function in a Function Parameter
  22360.  
  22361.  The pointer to a function is simulated with two parameters, just as in the
  22362.  case of a far character pointer. The first integer represents the segment
  22363.  and the second integer represents the offset (see the preceding section).
  22364.  BASIC itself has no means of finding the location of a function; however, if
  22365.  you find that location with a language procedure written in another
  22366.  language, the address can be used in an OS/2 function call from BASIC.
  22367.  
  22368.  
  22369.  Character in a Function Parameter
  22370.  
  22371.  A character in a function parameter is simulated with an integer. This
  22372.  method is safe because parameters are always passed as words. Thus, a
  22373.  character is extended to an integer before being passed in other languages.
  22374.  
  22375.  Example
  22376.  
  22377.  The following example prompts you for a file or set of files you want
  22378.  information on, and then uses the  DosFindFirst and  DosFindNext API
  22379.  functions to retrieve the information.
  22380.  
  22381.  
  22382.  ' This is an OS/2 protect mode program: compile with the /Lp switch
  22383.  
  22384.  CONST TRUE = -1
  22385.  CONST FALSE = 0
  22386.  ' $INCLUDE: 'bsedosfl.bi'
  22387.  
  22388.  DEFINT A-Z
  22389.  
  22390.  COLOR 15, 1
  22391.  DIM buffer AS FILEFINDBUF
  22392.  DIM Filelist(255) AS FILEFINDBUF
  22393.  DIM reserved  AS LONG
  22394.  
  22395.  CLS
  22396.  
  22397.  PRINT "Test of DOSFINDFIRST..."
  22398.  
  22399.  DO
  22400.  PRINT
  22401.  INPUT "Enter the Filename(s) : "; flname$
  22402.  flname$ = flname$ + CHR$(0)
  22403.  
  22404.  counter = 0
  22405.  atr = 0 + 2 + 4 + 16  'normal + hidden + system + subdirectory
  22406.  dirh = 1
  22407.  searchcount = 255
  22408.  bufflen = 36
  22409.  X = DosFindFirst%(VARSEG(flname$) SADD(flname$),dirh,_
  22410.   atr,buffer,bufflen,searchcount,reserved)
  22411.  IF (X = 0) THEN
  22412.  DO
  22413.  counter = counter + 1
  22414.  Filelist(counter) = buffer
  22415.  
  22416.  ' clear out buffer for call to DosFindNext
  22417.  buffer.achName = STRING$(13, 32) 'assign blanks
  22418.  buffer.fdateLastWrite = 0
  22419.  buffer.ftimeLastWrite = 0
  22420.  LOOP WHILE (DosFindNext%(dirh, buffer, bufflen, searchcount) = 0)
  22421.  ELSE
  22422.  PRINT "No MATCH was found"
  22423.  END
  22424.  END IF
  22425.  
  22426.  
  22427.  PRINT : PRINT counter; " matching files found:": PRINT
  22428.  FOR t = 1 TO counter
  22429.  PRINT USING "###"; t; SPC(2);
  22430.  PRINT Filelist(t).achName
  22431.  NEXT t
  22432.  
  22433.  PRINT : INPUT "Repeat (y/n)"; y$
  22434.  
  22435.  LOOP WHILE UCASE$(LEFT$(y$, 1)) = "Y"
  22436.  
  22437.  Note
  22438.  
  22439.  This program should be compiled using near strings; to use far strings, use
  22440.  the  SSEG and  SADD functions instead to return the segment and offset of
  22441.  the filename you want. For more information about compiling programs that
  22442.  use far strings, see Chapter 11, "Advanced String Storage."
  22443.  
  22444.  
  22445.  Creating Dynamic-Link Libraries
  22446.  
  22447.  You cannot create dynamic-link libraries from BASIC modules created with
  22448.  Microsoft BASIC, but a protected-mode BASIC program can invoke routines
  22449.  contained in a dynamic-link library.
  22450.  
  22451.  BASIC run-time and extended-run-time modules can be dynamic-link libraries.
  22452.  And user-created routines embedded in a protected-mode, extended run-time
  22453.  module are dynamically linked.
  22454.  
  22455.  
  22456.  Creating Multiple Threads
  22457.  
  22458.  A protected-mode BASIC program can call an external routine or execute a
  22459.  process that creates multiple threads. However, because Microsoft BASIC
  22460.  run-time routines are not re-entrant, you cannot create multiple threads
  22461.  from within a protected-mode BASIC program. For the same reason, threads
  22462.  created by external routines cannot call BASIC routines.
  22463.  
  22464.  
  22465.  Making Memory References
  22466.  
  22467.  Several BASIC statements and functions refer directly to addresses in the
  22468.  machine's physical memory. These include  CALL  ABSOLUTE,  DEF  SEG,  PEEK,
  22469.  POKE,  BLOAD,  BSAVE,  VARPTR, and  VARSEG.
  22470.  
  22471.  When using these statements in protected mode, you should take care not to
  22472.  refer to an illegal memory address. The selector portion of the address in
  22473.  question must refer to a memory segment for which your process has
  22474.  appropriate read and/or write permission. In addition, the segment must be
  22475.  large enough to contain the address referenced by the offset and the size of
  22476.  the object being accessed.
  22477.  
  22478.  If your program ignores these requirements, it may trigger a protection
  22479.  exception by the operating system or the BASIC error message Permission
  22480.  denied.
  22481.  
  22482.  
  22483.  The default  DEF SEG segment is safely addressable. Values returned by
  22484.  VARPTR and  VARSEG are valid; and you can safely access BASIC variables and
  22485.  arrays, provided you do not exceed the size of valid BASIC objects.
  22486.  
  22487.  
  22488.  Using Graphics
  22489.  
  22490.  In protected mode, all graphics operations are limited to BASIC screen modes
  22491.  1 and 2. Screen modes 3 and 7-13 are supported only in real mode. Microsoft
  22492.  BASIC does not support multiple screen pages in protected mode.
  22493.  
  22494.  
  22495.  Using Music, Sound, and Devices
  22496.  
  22497.  Except for the  BEEP statement, no music or sound statements are available
  22498.  in protected mode (you cannot use  SOUND or  PLAY). However, you can call
  22499.  the OS/2 function  DOSBEEP.
  22500.  
  22501.  You cannot use the light pen, joystick, and joystick triggers in protected
  22502.  mode.
  22503.  
  22504.  
  22505.  Creating Extended Run-Time Modules
  22506.  
  22507.  You can create extended run-time modules that can be used in real and
  22508.  protected mode. The program BUILDRTM.EXE is a bound program, so it can be
  22509.  run in either real or protected mode. As with the compiler, BUILDRTM creates
  22510.  an extended run-time module suitable for the environment you are in at the
  22511.  time. You can override the default environment by specifying either the /LP
  22512.  (protected mode) or /LR (real mode) compiler option in QBX.
  22513.  
  22514.  To avoid errors, it is important that all modules in a given application
  22515.  share the same target environment (real or protected mode).
  22516.  
  22517.  For information about the standard run-time modules and libraries, see
  22518.  "Using Standard Run-Time Modules and Libraries" later in this chapter.
  22519.  
  22520.  
  22521.  Compiling OS/2 Programs
  22522.  
  22523.  After writing your BASIC program, you can compile it from QBX. From QBX,
  22524.  choose Make EXE from the Run menu. For protected-mode programs, make sure to
  22525.  specify the /LP option to LINK.
  22526.  
  22527.  Unless you specify otherwise, the compiler creates an object file suitable
  22528.  for the environment in which it was created. If you run the compiler under
  22529.  DOS or real mode, it automatically creates an object file suitable for DOS
  22530.  and OS/2 real mode. Likewise, if you run the compiler in protected mode, it
  22531.  creates a protected-mode object file.
  22532.  
  22533.  
  22534.  You can override the default environment by using one of the options listed
  22535.  in Table 14.2.
  22536.  
  22537.  If you supply the /Lp option, the compiler creates a protected-mode object
  22538.  file no matter which environment you are in at the time. If you supply the
  22539.  
  22540.  /Lr option, the compiler creates a real-mode (DOS-compatible) object file.
  22541.  
  22542.  
  22543.  Linking OS/2 Programs
  22544.  
  22545.  Before linking OS/2 programs, you should read Chapter 18, "Using LINK and
  22546.  LIB." That chapter contains information about module definition files and
  22547.  import libraries, which are needed if your program makes calls to
  22548.  dynamic-link libraries.
  22549.  
  22550.  From the QBX environment, your program is automatically linked with the
  22551.  proper libraries when you choose the Make EXE option from the Run menu. By
  22552.  default, LINK uses the libraries created by the BASIC Setup program. These
  22553.  libraries use the following naming convention:
  22554.  
  22555.  BCL70 float string mode.LIB
  22556.  
  22557.  For example, if you specified the emulator floating-point option, near
  22558.  strings, and real-mode-only options during setup, your program would link,
  22559.  by default, with the BCL70ENR.LIB library. From QBX, you can change the
  22560.  default library by changing options in the Create EXE dialog box.
  22561.  
  22562.  For information about LINK options you can use, see Chapter 18, "Using LINK
  22563.  and LIB."
  22564.  
  22565.  
  22566.  Using Standard Run-Time Modules and Libraries
  22567.  
  22568.  The BASIC Setup program automatically creates a run-time module and run-time
  22569.  module library that match the operating environment and floating-point
  22570.  method you specify. Run-time modules use the following naming convention:
  22571.  
  22572.  Protected-mode run-time moduleBRT70 float string P.DLL Run-time libraryBRT70
  22573.  float string mode.LIB
  22574.  The possible values for each variable are the same as shown in the preceding
  22575.  section.
  22576.  
  22577.  If you create programs for more than one operating mode, you must make sure
  22578.  that the appropriate run-time module library is available when linking, and
  22579.  the appropriate run-time module is available at run time. This can be done
  22580.  by setting the LIB and PATH environment variables to the directories where
  22581.  your libraries and run time modules are kept, or by moving them into
  22582.  appropriate directories where they can be found when run.
  22583.  
  22584.  
  22585.  LINK Options for Real and Protected Modes
  22586.  
  22587.  The following options for the LINK utility can only be used when linking
  22588.  real-mode programs:
  22589.  
  22590.    ■   /CPARMAXALLOC ■   /DSALLOCATE
  22591.  
  22592.    ■   /HIGH ■   /NOGROUPASSOCIATION
  22593.  
  22594.    ■   /OVERLAYINTERRUPT
  22595.  The following options can only be used when linking protected-mode programs:
  22596.  
  22597.    ■   /ALIGNMENT: size  ■   /WARNFIXUP
  22598.  
  22599.  
  22600.  For descriptions of these and other LINK options, see Chapter 18, "Using
  22601.  LINK and LIB."
  22602.  
  22603.  
  22604.  Debugging OS/2 Programs
  22605.  
  22606.  Microsoft BASIC provides the CodeView debugger to help you debug your OS/2
  22607.  program. Two versions of CodeView are supplied: CVP.EXE, for debugging under
  22608.  protected mode and CV.EXE, for debugging under real mode.
  22609.  
  22610.  To prepare files for use with CodeView, you must specify the /Zi option of
  22611.  the compiler and the /CO option of LINK. From QBX, choose the CodeView
  22612.  Information option in the Make EXE File dialog box.
  22613.  
  22614.  
  22615.  Running BASIC Programs Under OS/2
  22616.  
  22617.  When you run a BASIC program in OS/2 protected mode, the system needs to
  22618.  find one or more dynamic-link libraries (.DLL). If a needed dynamic-link
  22619.  library cannot be found at run time, the system displays an error message.
  22620.  When searching for a dynamic-link library, OS/2 looks in the directories
  22621.  specified by the LIBPATH configuration command in your CONFIG.SYS file. For
  22622.  more about OS/2 configuration commands, see Part 2 in the  Microsoft
  22623.  Operating System/2 User's Guide.
  22624.  
  22625.  In OS/2 protected mode, it is possible to run a BASIC program as a detached
  22626.  program (using the  DETACH command). A detached program, however, cannot
  22627.  perform console input or output while it is detached. If input or output is
  22628.  attempted (including key trapping), a Device unavailable error message is
  22629.  generated. If a detached program causes an error, the error message appears
  22630.  in an OS/2 pop-up window.
  22631.  
  22632.  See the  Microsoft Operating System/2 User's Guide for more information
  22633.  about the  DETACH command.
  22634.  
  22635.  
  22636.  
  22637.  
  22638.  
  22639.   ────────────────────────────────────────────────────────────────────────────
  22640.  Chapter 15:  Optimizing Program Size and Speed
  22641.  
  22642.  Improved code generation and BASIC run-time management as well as
  22643.  higher-capacity development tools make it possible to write substantially
  22644.  larger BASIC programs, that can then be compiled into smaller and faster
  22645.  executables than previously possible.
  22646.  
  22647.  This chapter consists of programming hints and technical information to help
  22648.  you write faster, more efficient BASIC programs. The first half focuses on
  22649.  making the most of available random-access memory (RAM) and ways to reduce
  22650.  the size of programs in memory and on disk. The second half of this chapter,
  22651.  beginning with the section "Compiling Programs for Speed," is a series of
  22652.  tips to help you improve the performance of BASIC programs.
  22653.  
  22654.  
  22655.  Size and Capacity
  22656.  
  22657.  While there is no substitute for good program design, new features like far
  22658.  strings, overlays, stub files, and improved code generation can help
  22659.  significantly reduce executable size and increase data capacity.
  22660.  
  22661.  There are three major areas of BASIC size and capacity issues: BASIC
  22662.  variable space, BASIC program size, and the size of the compiled executable
  22663.  file on disk. An understanding of how BASIC programs allocate and use memory
  22664.  will help you to manage variable and program space to your advantage.
  22665.  
  22666.  
  22667.  BASIC Memory Use
  22668.  
  22669.  Under the DOS and OS/2 operating systems, RAM stores the resident portion of
  22670.  any BASIC program that is currently running, the various constants and data
  22671.  needed by the program, the variable space, and any other information needed
  22672.  by the computer while the program is running.
  22673.  
  22674.  RAM used by a BASIC program is divided into two categories: near memory and
  22675.  far memory. Near memory and far memory each contains a "heap," which is an
  22676.  area of memory used to store dynamic variables. "Near memory," also referred
  22677.  to as "DGROUP," is the single segment of memory (maximum size of 64K) that
  22678.  includes, but is not limited to, the near heap (where near strings and
  22679.  variables are stored), the stack, and state information about the BASIC
  22680.  run-time. "Far memory" is the multiple-segment area of memory outside of
  22681.  DGROUP that includes, but is not limited to, the BASIC program (run-time and
  22682.  generated code) and the far heap (where dynamic arrays and far strings are
  22683.  stored).
  22684.  
  22685.  DOS and OS/2 manage
  22686.  memory in fundamentally different ways. In DOS, application programs use
  22687.  physical addresses to directly access specific memory locations. In
  22688.  contrast, OS/2 supports virtual addressing. "Virtual addressing" is an
  22689.  advanced system of memory management whereby the operating system maintains
  22690.  an address space larger than physical memory by swapping data to and from
  22691.  disk (and within memory) as needed, while mapping address references in
  22692.  application programs onto the physical addresses of the actual memory
  22693.  locations. Under DOS, the upper boundary of standard memory is 640K. Under
  22694.  OS/2, program size is realistically limited only by available disk space.
  22695.  
  22696.  
  22697.  The memory map in Figure A.1 shows the high-level memory organization for a
  22698.  BASIC program. The diagram shows the different areas of memory in their
  22699.  correct relative positions for a stand-alone (compiled with /O) BASIC
  22700.  program running under DOS or OS/2 real mode. A program compiled without /O
  22701.  under DOS loads the run-time module in far memory (above DGROUP), not next
  22702.  to the generated code as pictured in Figure 15.1. Also, all references to
  22703.  strings in the diagram refer only to variable-length strings, since
  22704.  fixed-length strings are managed in memory the same way fixed-length data
  22705.  types (such as numerics) are managed.
  22706.  
  22707.  There are a few fundamental things you can do to maximize capacity in
  22708.  BASIC under DOS. If you are working with a large amount of code in a
  22709.  program, or if you want more far memory to be available for far string
  22710.  or dynamic array data, overlays will allow different module groups in
  22711.  your program to share the same memory space. Microsoft BASIC supports up
  22712.  to 64 overlays. Each overlay may be up to 256K for a maximum total of over
  22713.  15 megabytes of compiled code. See the section "Overlays" later in this
  22714.  chapter and Chapter 18, "Using LINK and LIB," for more information about
  22715.  overlays.
  22716.  
  22717.  
  22718.  If DGROUP (near memory) is constraining, compiling with the far string
  22719.  option (/Fs) will place variable-length string data in the far heap, leaving
  22720.  more room in DGROUP for other data. See Chapter 11, "Advanced String
  22721.  Storage," for more information on using strings.
  22722.  
  22723.  The  FRE() function can give you information about exactly how much memory
  22724.  is available in a particular area of memory. FRE(-2) returns the amount of
  22725.  stack space not yet used. FRE(-1) returns the amount of available far
  22726.  memory.  FRE() can also be used to determine the amount of string space
  22727.  available for near and far strings. See the  BASIC Language Reference or
  22728.  online Help for a complete description of the  FRE() function.
  22729.  
  22730.  
  22731.  Variable-Length String Storage
  22732.  
  22733.  Microsoft BASIC gives you two options for storing variable-length string
  22734.  data and string array data: near string storage in DGROUP and far string
  22735.  storage in the far heap. You can specify which option you want to use by
  22736.  compiling with or without the far string option (/Fs). The only data type
  22737.  affected by the /Fs option is the variable-length string data type. Table
  22738.  15.1 shows where variables and arrays are stored in memory based on the
  22739.  storage option you choose. This information can help you make better use of
  22740.  the space available in memory, and it can help you make speed/size and
  22741.  capacity tradeoffs in your BASIC code.
  22742.  
  22743.  
  22744.  Note
  22745.  
  22746.  When you compile from within QBX and a Quick library is loaded, the default
  22747.  compile option is far string (/Fs). When you compile from the command line,
  22748.  near string storage is the default, as it was in all previous versions of
  22749.  BASIC. To use far string storage instead, add the /Fs option to the BASIC
  22750.  Compiler (BC) command line.
  22751.  
  22752.  Since the QBX environment treats all strings as far strings, a program that
  22753.  uses near pointers to string data cannot run or be debugged in the QBX
  22754.  environment. However, the program may still work when compiled using the
  22755.  near string compiler option, and can be debugged with CodeView. On the other
  22756.  hand, far pointers will always work in QBX and in a program compiled with
  22757.  far strings. For detailed information about string storage, see Chapter 11,
  22758.  "Advanced String Storage."
  22759.  
  22760.  
  22761.  String Array Storage
  22762.  
  22763.  A string array has three parts in memory: the array descriptor, the array of
  22764.  string descriptors, and the string data. The array descriptor is always
  22765.  stored in DGROUP. Each element in the array of string descriptors is stored
  22766.  in DGROUP and contains the length and location in memory of the string data.
  22767.  The string data resides in the near heap if near string storage is specified
  22768.  at compile time, or in far heap if far string storage is specified at
  22769.  compile time. The 4-byte string descriptor for each variable-length string
  22770.  resides in DGROUP regardless of which string option (near or far) is used.
  22771.  Therefore, even with far strings, it is possible to run out of DGROUP by
  22772.  filling it with string descriptors. When this happens, the only remedy is to
  22773.  reduce the number of elements in the array.
  22774.  
  22775.  BASIC string arrays can be either static or dynamic. A "static string array"
  22776.  is an array of variable length strings whose descriptors reside in a
  22777.  permanently allocated array in DGROUP. This array of descriptors is fixed
  22778.  when compiled and cannot be altered while a program is running.
  22779.  
  22780.  A "dynamic string array" is a string array whose array of descriptors can be
  22781.  defined or changed during run-time with BASIC's  DIM,  REDIM, or  ERASE
  22782.  statements. Dynamic arrays that are declared local to a procedure are
  22783.  de-allocated when control leaves the procedure. As with static string
  22784.  arrays, dynamic string array descriptors also reside in DGROUP, but they may
  22785.  change in number and/or location during execution of a BASIC program.
  22786.  
  22787.  The location of the string data itself is independent of the static or
  22788.  dynamic status of a string array, and depends solely on whether the near or
  22789.  far string option is chosen when compiling.
  22790.  
  22791.  
  22792.  Numeric Array Storage
  22793.  
  22794.  Unlike string arrays, numeric arrays have a fixed amount of data associated
  22795.  with each element. All of the following information about numeric arrays is
  22796.  equally applicable to arrays of fixed-length strings or arrays of
  22797.  user-defined types, since they also have a fixed amount of data associated
  22798.  with each element in the array.
  22799.  
  22800.  A numeric array has two parts: an array descriptor and the numeric
  22801.  array data itself. The array descriptor contains information about the
  22802.  array including its type, dimensions, and the location of its data in
  22803.  memory. Array descriptors are always stored in DGROUP. The data in a
  22804.  numeric array may be stored entirely in DGROUP or entirely in the far
  22805.  heap, as shown in Table 15.1.
  22806.  
  22807.  As with string arrays, numeric arrays may be static or dynamic. A static
  22808.  numeric array has a fixed number of elements that are allocated when the
  22809.  program is compiled, while a dynamic numeric array is allocated and can be
  22810.  changed when the program is running. The compiler blocks out portions of
  22811.  DGROUP to contain all static array data, and this DGROUP space cannot be
  22812.  reclaimed while the program is running. Dynamic arrays are more flexible.
  22813.  They can be declared with a variable argument to the  DIM statement,
  22814.  re-sized using the  REDIM statement, and de-allocated altogether using the
  22815.  ERASE statement. Any space allocated for local dynamic arrays is reclaimed
  22816.  when BASIC leaves a particular procedure.
  22817.  
  22818.  Certain tradeoffs are involved in the choice between static and dynamic
  22819.  numeric arrays. As described previously, static arrays are unchanging and
  22820.  thus consume a constant amount of memory. Dynamic arrays, however, will
  22821.  reclaim memory space after an array is erased or redimensioned to be
  22822.  smaller, thus allowing the same far heap space to be used for different
  22823.  purposes at different times in the same program. Static arrays provide the
  22824.  advantage of faster referencing and fixed locations in memory (helpful for
  22825.  mixed-language programming and quick look-ups).
  22826.  
  22827.  
  22828.  Example
  22829.  
  22830.  In the following example, A and B start out the same size, but A can grow or
  22831.  shrink during program execution; if A is no longer needed, the space can be
  22832.  reclaimed:
  22833.  
  22834.  Index = 0
  22835.  DIM A (Index) AS INTEGER' A is dynamic,
  22836.  DIM B (10) AS SINGLE' B is static,
  22837.  Index = 20
  22838.  REDIM A (Index) AS INTEGER' A's size can change,
  22839.  .
  22840.  .
  22841.  .
  22842.  ERASE A' Or disappear.
  22843.  
  22844.  Huge Dynamic Array Storage
  22845.  
  22846.  Huge dynamic arrays allow you to create and store in memory arrays that
  22847.  contain more than 64K of data (64K is the limitation of standard dynamic
  22848.  arrays). Huge arrays are invoked by using the /Ah option when starting QBX
  22849.  or by using the /Ah option when compiling a program from the command line.
  22850.  
  22851.  Huge arrays are limited to 128K unless the individual element size of each
  22852.  record divides evenly into 64K. In other words, the number of bytes in each
  22853.  element must be an integer power of two in order for an array of these
  22854.  elements to be larger than 128K, as shown by the following example:
  22855.  
  22856.  TYPE ElementType
  22857.  a AS INTEGER' 2 bytes
  22858.  b AS LONG' 4 bytes
  22859.  c AS SINGLE' 4 bytes
  22860.  d AS DOUBLE' 8 bytes
  22861.  e AS CURRENCY' 8 bytes
  22862.  f AS STRING * 6' 6 bytes
  22863.  END TYPE'Total of 32 bytes in ElementType
  22864.  MaxElement% = 5000
  22865.  DIM HugeArray(1 to MaxElement%) AS ElementType
  22866.  
  22867.  The ElementType defined in the preceding example is a total of 32 bytes per
  22868.  element, so it will divide evenly into 64K and will enable BASIC to create
  22869.  the 160,000 byte array -- assuming the /Ah option on QBX or BC was used, and
  22870.  there is sufficient far heap available). However, if the fixed-length string
  22871.  F is changed to either 5 or 7 bytes, a Subscript out of range error will
  22872.  result.
  22873.  
  22874.  
  22875.  Note
  22876.  
  22877.  When compiling from within QBX, the /Ah compile option is automatically set
  22878.  only if you started QBX with the /Ah option. This can be changed only by
  22879.  exiting QBX and restarting it with the desired option.
  22880.  
  22881.  Using the /Ah option in QBX or when compiling forces all dynamic arrays in
  22882.  your program to be huge. Huge arrays have slower access times and require
  22883.  more memory than similar standard dynamic arrays.
  22884.  
  22885.  
  22886.  Tips on Conserving Data Space
  22887.  
  22888.  The following are some tips on coding style that will help to conserve data
  22889.  space:
  22890.  
  22891.    ■   Use constants.
  22892.  
  22893.    ■    Normal numeric variables in BASIC programs reside in DGROUP. However,
  22894.        when a program is compiled, constant values can be folded directly
  22895.        into the generated code, so no additional DGROUP is needed to support
  22896.        these constants. If a value does not need to change in a program,
  22897.        constants can frequently make a program execute faster and will use
  22898.        less memory than variables.
  22899.  
  22900.    ■   Use local variables in  SUB procedures.
  22901.  
  22902.    ■    Local variables within a  SUB procedure occupy DGROUP space only while
  22903.        the program is running within that  SUB procedure. BASIC keeps local
  22904.        variables on the stack, so that when the program returns from a
  22905.        procedure using local variables, the local stack variables are
  22906.        discarded and the stack is returned to the state it was in before the
  22907.        procedure was invoked. This is different from static variables, that
  22908.        occupy DGROUP space for the entire duration of the program.
  22909.  
  22910.  
  22911.    ■   Trade file I/O buffer size for string space.
  22912.  
  22913.    ■    File I/O buffers reside in the same area of memory as variable-length
  22914.        strings. If a program is compiled with /Fs, then the file I/O buffer
  22915.        will reside in far memory. Otherwise, the file I/O buffer will reside
  22916.        in DGROUP. The size of the file I/O buffer is defined by the  LEN=
  22917.        argument of the  OPEN statement, with the default buffer for
  22918.        sequential file I/O equal to 512 bytes (default for random file I/O is
  22919.        128 bytes). A larger buffer generally requires fewer disk accesses and
  22920.        results in a faster executing program; but a smaller file I/O buffer
  22921.        takes up less memory in either DGROUP or the far heap, depending on
  22922.        the string option chosen when compiled.
  22923.  
  22924.  
  22925.  
  22926.  Controlling Program Size
  22927.  
  22928.  BASIC programs consist of the BASIC run-time routines and code generated by
  22929.  the compiler. Knowing how to manage both of these will help you to keep
  22930.  program size down leaving more room for other data in memory and on disk.
  22931.  
  22932.  The BASIC run-time is the set of routines that support most of BASIC's
  22933.  underlying functionality. Since most compiled programs do not need all of
  22934.  the functionality available in the BASIC run-time routines, these routines
  22935.  are divided into many individual object modules and are included as
  22936.  necessary in the user's final program. The extent to which these routines
  22937.  are divided into individually accessible pieces is called "granularity."
  22938.  
  22939.  The BASIC run-time routines included in any given program can be minimized
  22940.  either through writing code that does not use certain BASIC functionality,
  22941.  or through the use of "stub" files. This will result in smaller executables
  22942.  in memory and on disk, when compiling stand-alone programs.
  22943.  
  22944.  
  22945.  Using Stub Files
  22946.  
  22947.  Stub files are the special object files shipped with Microsoft BASIC that
  22948.  block certain pieces of the BASIC run-time from being included in the final
  22949.  executable file when the program is linked. To understand how stub files
  22950.  work, it is necessary to understand what happens during the link step of the
  22951.  build process.
  22952.  
  22953.  When a BASIC source file is compiled into an object file, the object file
  22954.  contains many unresolved references. These references are simply calls into
  22955.  the BASIC run-time or other libraries on which the main BASIC program is
  22956.  depending, but are not present in the object file. During linking, the
  22957.  Microsoft Segmented-Executable Linker (LINK) matches up these calls to
  22958.  external procedures with the procedures themselves. LINK can be thought of
  22959.  as the matchmaker between the requests for certain functionality and the
  22960.  procedures that provide that functionality.
  22961.  
  22962.  In order to resolve these calls to external procedures, LINK first
  22963.  searches every object module that is in the object field of the LINK
  22964.  command. Then, if there are any remaining unresolved references, LINK
  22965.  searches the files in the library field of the LINK command. The main
  22966.  difference between the object field and the library field, besides the
  22967.  order in which they are searched, is that every module found in the
  22968.  object field will be linked into the final executable file, while only
  22969.  those modules that contain procedures necessary to resolve references will
  22970.  be linked in from the library field. Therefore, if you specify a module
  22971.  in the object field that resolves the references that would otherwise be
  22972.  resolved by a module in the library field, you can block the module in
  22973.  the library field from being included in the final executable file.
  22974.  
  22975.  
  22976.  Stub files accomplish that goal. Stub files contain dummy procedures, or
  22977.  "stubs," that have the same names as the BASIC run-time routines targeted
  22978.  for exclusion. The only functionality that exists in most of these dummy
  22979.  procedures is the ability to return a Feature Stubbed Out error message.
  22980.  Some stub files, however, contain a smaller, limited version of the
  22981.  functionality (as in SMALLERR and NOEDIT). Trading full-functionality
  22982.  routines for reduced or removed functionality routines reduces executable
  22983.  size.
  22984.  
  22985.  Stub files can be used either when the stand-alone library or run-time
  22986.  module is created with BUILDRTM (see Chapter 21, "Building Custom Run-Time
  22987.  Modules") or the Setup program (see  Getting Started ), or they can be used
  22988.  individually with each program on a case-by-case basis. Stub files are only
  22989.  of value on a case-by-case basis when compiling stand-alone executables
  22990.  (compiling with the /O option).
  22991.  
  22992.  It is important to remember to use the /NOE option when linking with stub
  22993.  files. This option causes LINK to ignore conflicts when a public symbol has
  22994.  been redefined, and instructs LINK to use only the specified object files
  22995.  (as opposed to using predefined dictionaries to identify libraries which
  22996.  will resolve references, as would happen if stub files were not being used).
  22997.  
  22998.  The following example builds a program called MYPROG.BAS into an executable
  22999.  file and excludes support for COM and LPT I/O:
  23000.  
  23001.  BC /O MYPROG.BAS;
  23002.  LINK /NOE MYPROG.OBJ NOCOM.OBJ NOLPT.OBJ;
  23003.  
  23004.  If you are certain all of your programs won't need some piece of
  23005.  functionality for which a stub file is provided, removing the functionality
  23006.  during setup is a way to reduce every program's size and thus increase room
  23007.  for other code or data competing for far memory space.
  23008.  
  23009.  Note
  23010.  
  23011.  Functionality that is excluded from the BASIC run-time modules during setup
  23012.  using stub files will not be available in any program, so these choices
  23013.  should be made cautiously. If you accidentally exclude needed functionality
  23014.  from the BASIC libraries during setup, run the Setup program again to add
  23015.  functionality back into the run-time module. See  Getting Started for more
  23016.  information.
  23017.  
  23018.  Table 15.2 lists the stub files that are shipped with Microsoft BASIC.
  23019.  
  23020.  
  23021. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  23022.  File                                    Description
  23023.  ────────────────────────────────────────────────────────────────────────────
  23024.  NOFLTIN.OBJ                             Allows a program to contain INPUT,
  23025.                                          VAL, and READ statements without
  23026.                                          support for parsing floating point
  23027.                                          numbers that would require the
  23028.                                          floating point math package to be
  23029.                                          included. If a program is linked
  23030.                                          with this stub file, all numbers
  23031.                                          recognized by INPUT, VAL, and READ
  23032.                                          must be legal long integers.
  23033.  
  23034.  NOEDIT.OBJ                              Reduces functionality of the
  23035.                                          editor provided with the INPUT
  23036.                                          statement to support only Enter
  23037.                                          and Backspace keys (no Home, End,
  23038.                                          etc.).
  23039.  File                                    Description
  23040.  ────────────────────────────────────────────────────────────────────────────
  23041.                                         etc.).
  23042.  
  23043.  NOCOM.OBJ                               Removes support for COM device I/O.
  23044.                                          COM support is included by LINK if
  23045.                                          an OPEN statement is used with a
  23046.                                          string variable in place of the
  23047.                                          file or device name, as in OPEN A$
  23048.                                          FOR OUTPUT AS #1 or if a string
  23049.                                          constant starting with COMn is
  23050.                                          used with an OPEN statement.
  23051.  
  23052.  NOLPT.OBJ                               Removes support for LPT device I/O.
  23053.                                          LPT support is included by LINK if
  23054.                                          an OPEN statement is used with a
  23055.                                          string variable in place of the
  23056.                                          file or device name, as in OPEN A$
  23057.                                          FOR OUTPUT AS #1 or if a string
  23058.                                          constant starting with LPTn: is
  23059.                                          used with an OPEN statement. Using
  23060.  File                                    Description
  23061.  ────────────────────────────────────────────────────────────────────────────
  23062.                                         used with an OPEN statement. Using
  23063.                                          this stub file also removes
  23064.                                          run-time support for screen
  23065.                                          printing using Ctrl+PrtSc.
  23066.  
  23067.  NOEVENT.OBJ                             Removes support for event trapping.
  23068.                                          This stub file is only effective
  23069.                                          if linked with the run-time
  23070.                                          module; it has no effect when
  23071.                                          linked into stand-alone
  23072.                                          executables.
  23073.  
  23074.  NOEMS.OBJ                               Prevents a program linked for
  23075.                                          overlays from using Expanded
  23076.                                          Memory Specification (EMS);
  23077.                                          instead, the program will be
  23078.                                          forced to swap to disk.
  23079.  
  23080.  OVLDOS21.OBJ                            Required in order for a program
  23081.  File                                    Description
  23082.  ────────────────────────────────────────────────────────────────────────────
  23083. OVLDOS21.OBJ                            Required in order for a program
  23084.                                          linked for overlays to work under
  23085.                                          DOS 2.1. Does not reduce the size
  23086.                                          of the executable.
  23087.  
  23088.  NOISAM.OBJ                              Removes ISAM functionality from
  23089.                                          BASIC run-time modules. This stub
  23090.                                          file is not useful when creating
  23091.                                          stand-alone executable files.
  23092.  
  23093.  SMALLERR.OBJ                            Reduces length of run-time error
  23094.                                          messages.
  23095.  
  23096.  87.LIB                                  Removes software coprocessor
  23097.                                          emulation, so that an 8087-family
  23098.                                          math coprocessor must be present
  23099.                                          in order for the program to
  23100.                                          perform any floating-point
  23101.                                          calculations, if floating-point
  23102.  File                                    Description
  23103.  ────────────────────────────────────────────────────────────────────────────
  23104.                                         calculations, if floating-point
  23105.                                          statements are used in the program.
  23106.  
  23107.  
  23108.  
  23109.  
  23110.  
  23111. ╓┌───────────────────────────────────────┌───────────────────────────────────╖
  23112.  File                                    Description
  23113.  ────────────────────────────────────────────────────────────────────────────
  23114.  NOTRNEMm.LIB1                           Removes support for any command
  23115.                                          using transcendental operation
  23116.                                          including: LOG, SQR SIN, COS, TAN,
  23117.                                          ATN, EXD, ^, CIRCLE statements
  23118.                                          with a start and /or stop angle,
  23119.                                          DRAW statements with A or T
  23120.                                          commands.
  23121.  
  23122.  TSCNIOsm.OBJ1,2                         These stub files limit the program
  23123.  File                                    Description
  23124.  ────────────────────────────────────────────────────────────────────────────
  23125. TSCNIOsm.OBJ1,2                         These stub files limit the program
  23126.                                          to text-only screen I/O with no
  23127.                                          support for special treatment of
  23128.                                          control characters. Each TSCNIOsm
  23129.                                          .OBJ stub file is a superset of
  23130.                                          all the graphics-related stub
  23131.                                          files that follow.
  23132.  
  23133.  NOGRAPH.OBJ                             Removes all support for graphics
  23134.                                          statements and non-zero SCREEN
  23135.                                          modes. This stub file is a
  23136.                                          superset of all the following
  23137.                                          graphic SCREEN mode stub files.
  23138.  
  23139.  NOCGA.OBJ                               Removes all support for CGA
  23140.                                          graphics SCREEN modes 1 and 2.
  23141.  
  23142.  NOHERC.OBJ                              Removes all support for Hercules
  23143.                                          graphics SCREEN mode 3.
  23144.  File                                    Description
  23145.  ────────────────────────────────────────────────────────────────────────────
  23146.                                         graphics SCREEN mode 3.
  23147.  
  23148.  NOOGA.OBJ                               Removes all support for Olivetti
  23149.                                          graphics SCREEN mode 4.
  23150.  
  23151.  NOEGA.OBJ                               Removes all support for EGA
  23152.                                          graphics SCREEN modes 7-10..
  23153.  
  23154.  NOVGA.OBJ                               Removes all support for VGA
  23155.                                          graphics SCREEN modes 11-13.
  23156.  
  23157.  
  23158.  
  23159.  
  23160.  
  23161.  
  23162.  2 s can be either N  or  F  for near or far strings.
  23163.  Exploiting Run-Time Granularity with Code Style
  23164.  
  23165.  Compiled BASIC programs will automatically leave out pieces of the run-time
  23166.  that LINK can determine are unnecessary. Since the run-time is pulled into a
  23167.  program in discrete pieces, understanding how to avoid pulling in large
  23168.  chunks of run-time support when they are not completely necessary can result
  23169.  in significantly smaller executable files, especially with smaller programs.
  23170.  
  23171.  
  23172.  Avoid Accidental Use of Floating-Point Math
  23173.  
  23174.  Depending on which math option is specified when compiling (/FPa for
  23175.  alternate math or /FPi for coprocessor/emulation), inadvertent inclusion of
  23176.  a floating-point math package can add over 10K to program size. Here are
  23177.  some tips to help you avoid pulling in a floating-point math package when it
  23178.  is not needed:
  23179.  
  23180.    ■   Use integers.
  23181.  
  23182.    ■    Start each program with DEFINT A-Z, or be sure to use integer
  23183.        variables (terminated with %) wherever possible. This will help you
  23184.        avoid the floating-point math package and speed up your program. Since
  23185.        the default data type in BASIC is  SINGLE (4-byte floating-point
  23186.        real), people frequently make the mistake of using the  SINGLE data
  23187.        type where integers would actually better suit their needs.
  23188.  
  23189.    ■   Use the integer division operator ( \ ) whenever doing integer
  23190.        division.
  23191.  
  23192.    ■    This division operator will not cause the floating-point math support
  23193.        to be pulled in if the math pack is not needed elsewhere in the
  23194.        program. Using the regular division operator ( / ) always pulls in the
  23195.        floating-point package, even when used with integers.
  23196.  
  23197.    ■   Avoid BASIC statements and functions that use the floating-point
  23198.        package.
  23199.  
  23200.    ■    Some of the BASIC statements and functions that pull in the floating
  23201.        point package are obvious, such as  SIN,  COS,  TAN,  ATN,  LOG, and
  23202.        EXP. The not-so-obvious BASIC functions that use the floating-point
  23203.        package are  VAL,  WINDOW,  DRAW,  TIMER,  RANDOM,  INPUT,  READ,
  23204.        PMAP,  POINT,  PRINT  USING, and  SOUND.
  23205.  
  23206.  
  23207.  
  23208.  Use Constants in SCREEN Statements
  23209.  
  23210.  Using a variable as an argument to the  SCREEN statement will cause support
  23211.  for all graphic screen modes to be pulled in, unless stub files are being
  23212.  used, whereas a  SCREEN statement with a constant only pulls in the support
  23213.  necessary for the specified screen mode.
  23214.  
  23215.  
  23216.  Minimizing Generated Code
  23217.  
  23218.  As a BASIC program gets longer, the compiled executable file becomes
  23219.  increasingly dominated by generated code. Since run-time library routines
  23220.  are included only once in any given program, the larger a program becomes,
  23221.  the more it benefits from the BASIC strategy of using run-time library
  23222.  routines for much of its functionality. The converse is also true -- the
  23223.  shorter a program is, the more its size is dominated by the run-time
  23224.  routines, making generated code size less important. However, if a program
  23225.  is compiled without the /O option, no run-time library routines are
  23226.  contained in the executable file, so the marginal space taken up on disk is
  23227.  dominated by code generation even for short BASIC programs.
  23228.  
  23229.  The following are some
  23230.  tips to help you keep your programs small by minimizing generated code:
  23231.  
  23232.  
  23233.    ■   Use procedures.
  23234.  
  23235.    ■    Designing a BASIC program to efficiently re-use code for repetitive
  23236.        operations will not only make your code smaller but it will also make
  23237.        it more understandable, easier to maintain and debug, and easier to
  23238.        use in other programs.
  23239.  
  23240.    ■   Beware of event trapping.
  23241.  
  23242.    ■    While it takes relatively little BASIC code to set up event trapping,
  23243.        the resultant compiler-generated code will be disproportionately
  23244.        large, since the compiler must generate tests for the specified event
  23245.        between each label or statement. Event-trapping code can be more
  23246.        precisely controlled with  EVENT ON and  EVENT OFF statements, which
  23247.        allow you to specify exactly where within a module event-trapping code
  23248.        is generated.
  23249.  
  23250.    ■   Compile modules that use  ON ERROR RESUME statements separately.
  23251.  
  23252.    ■    BASIC does not provide statements to turn error trapping on and off.
  23253.        Compiling with the /X option (to support  ON ERROR RESUME statements)
  23254.        generates 4 bytes of code per BASIC statement in a module compiled
  23255.        with /X. These 4 bytes contain the location to which the program
  23256.        should return if an error occurs at any particular time. To get the
  23257.        smallest size and best performance in a program with such error
  23258.        trapping, use local error handling and have all those procedures with
  23259.        local error handling in a separate module compiled with /X. This will
  23260.        prevent the performance degradations associated with /X from affecting
  23261.        those pieces of your program that do not require error handling.
  23262.  
  23263.  
  23264.  
  23265.  Overlays
  23266.  
  23267.  Modular programs that use overlays can execute in substantially less memory
  23268.  than programs that do not use overlays. The key to successful program design
  23269.  for overlays is minimizing the performance penalty inherent in swapping
  23270.  modules in and out of memory. This can be accomplished by designing the
  23271.  program in groups of interconnected modules that will primarily call each
  23272.  other, with only infrequent calls outside the group that would cause another
  23273.  overlay to be swapped back into memory.
  23274.  
  23275.  In general, event-handling routines and general-purpose routines should go
  23276.  in code which is not overlaid, unless events are expected to be rare. See
  23277.  Chapter 18, "Using LINK and LIB," for more information about using overlays
  23278.  in BASIC.
  23279.  
  23280.  
  23281.  Minimizing Executable File Size
  23282.  
  23283.  Compiling without /O is the easiest single thing which can be done to
  23284.  minimize the size of BASIC executables on disk. The resultant executable
  23285.  programs do not contain the BASIC run-time routines. Rather, these programs
  23286.  access the same run-time routines that exist in a single BASIC run-time file
  23287.  on disk (one of the files named BRT70 mso, where  mso stand for the math,
  23288.  string, and operating mode options). You can customize these run-time
  23289.  modules by adding modules of your own routines with the BUILDRTM utility.
  23290.  See Chapter 21, "Building Custom Run-Time Modules," for more information on
  23291.  how to use this feature.
  23292.  
  23293.  The primary tradeoff of compiling programs that require the presence of a
  23294.  BRT70 msofile is that the program will not work as a stand-alone,
  23295.  single-file executable. The ultimate end-user of the program must always
  23296.  have a copy of the appropriate BRT70 mso file present in order for the
  23297.  program to run. Other tradeoffs made in working with run-time modules are
  23298.  that these programs use more memory (since the entire run-time gets loaded
  23299.  into memory) and program initialization is slower.
  23300.  
  23301.  If the program is meant to be stand-alone and is compiled with /O, then all
  23302.  of the BASIC run-time management tips in the section "Controlling Program
  23303.  Size" earlier in this chapter are equally valuable in reducing the size of
  23304.  executables on disk. Minimizing generated code will also result in
  23305.  proportionately smaller executable files.
  23306.  
  23307.  LINK provides some options such as /EXEPACK, /PACKCODE, and
  23308.  /FARCALLTRANSLATION that help to compress the size of the executable and
  23309.  improve executable speed in some cases. See Chapter 18, "Using LINK and
  23310.  LIB," for more information on these and other LINK options.
  23311.  
  23312.  
  23313.  Compiling Programs for Speed
  23314.  
  23315.  This section and the rest of the sections in this chapter discuss ways to
  23316.  improve the performance of your BASIC programs so they execute in the
  23317.  shortest amount of time possible.
  23318.  
  23319.  
  23320.  Compiling for the Target System
  23321.  
  23322.  Knowing the hardware configuration of the system on which the program will
  23323.  ultimately run makes it possible to compile a program optimally for speed on
  23324.  that particular machine.
  23325.  
  23326.  The /G2 compiler option causes the compiler to generate code that takes
  23327.  advantage of the expanded instruction set of the Intel 80286 microprocessor.
  23328.  If the target system is definitely an IBM AT or compatible 286-based
  23329.  machine, the /G2 option will make compiled BASIC programs smaller and
  23330.  faster. PCs with backward-compatible microprocessors that support the 286
  23331.  instruction set (i.e. 386 and 486) will also run programs compiled with /G2
  23332.  faster than programs compiled without /G2.
  23333.  
  23334.  
  23335.  Math Options
  23336.  
  23337.  Depending on whether or not the target system has an 8087-family math
  23338.  coprocessor, one of two math packages will provide the fastest possible
  23339.  floating-point math support.
  23340.  
  23341.  80x87 Support
  23342.  
  23343.  If the target system is expected to have an 80x87-family math coprocessor,
  23344.  then compiling with the /FPi option (the default) will either precisely
  23345.  emulate the functionality of such a chip or will use the hardware directly
  23346.  to perform the desired functions. If you are absolutely certain that the
  23347.  target system will have such a coprocessor, then you may wish to LINK with
  23348.  87.LIB (the coprocessor math package which does not provide floating-point
  23349.  operation software). If math is used, this reduces executable size by over 9
  23350.  K.
  23351.  
  23352.  
  23353.  Alternate Math
  23354.  
  23355.  If the target system is not expected to have an 80x87-family math
  23356.  coprocessor, then compiling with the /FPa option will link in the alternate
  23357.  floating-point math pack, which does not attempt to emulate the 80x87
  23358.  hardware. Alternate math is an IEEE-compatible math package optimized for
  23359.  speed and size on systems without a floating-point coprocessor. The tradeoff
  23360.  of using the alternate math package is that a small amount of precision is
  23361.  lost compared with the true 80x87 emulator. For most applications, this loss
  23362.  of precision is negligible and the alternate math package will offer better
  23363.  math speed with no undesirable side effects.
  23364.  
  23365.  Specifically, all floating-point calculations performed by alternate math
  23366.  are performed in either 24-bit or 53-bit mantissa precision for  SINGLE or
  23367.  DOUBLE data types, respectively. With the 80x87/emulator, all intermediate
  23368.  results in an expression are calculated to 64-bits of mantissa precision.
  23369.  
  23370.  Simple operations involving only one calculation followed by an assignment,
  23371.  such as the following will yield the same result with either math package:
  23372.  
  23373.  A! = B! + C!
  23374.  A# = B# + C#
  23375.  
  23376.  The differences start appearing when multiple calculations are performed in
  23377.  a single expression, as in the following example:
  23378.  
  23379.  B! = 1000001!
  23380.  C! = 1!
  23381.  D! = 1!
  23382.  A! = (1000001! * B! + C!) * D! - (1000001! * B!)
  23383.  
  23384.  With the alternate math package, the intermediate results are calculated to
  23385.  24 bits of precision. The result of this expression to 24 bits is 0. With
  23386.  the 80x87/emulator, the result is exactly 1. The preceding single-precision
  23387.  calculation could be performed without losing any bits of significance.
  23388.  
  23389.  Transcendental functions computed with the emulator are accurate to between
  23390.  62 and 64 bits. The alternate math package is also accurate to within 2 bits
  23391.  of its normal mantissa precision for each data type in transcendental math.
  23392.  This means that the following expression could be accurate to 22 bits with
  23393.  alternate math, but with the 80x87/emulator it will usually be accurate to
  23394.  24 bits (it would be off by 1 bit in about 1 in 238 samples):
  23395.  
  23396.  A! = SIN(1.0!)
  23397.  
  23398.  Managing Data for Speed
  23399.  
  23400.  Variable types and storage options have a significant impact on program
  23401.  execution speed. Choosing data types and storage options intelligently can
  23402.  decrease execution time.
  23403.  
  23404.  
  23405.  Constants
  23406.  
  23407.  Use constants whenever possible to save space and make programs faster.
  23408.  References to named constants are resolved when compiled and the values are
  23409.  then embedded directly into the generated code (except for string and
  23410.  floating point constants). Where constants can be used, they are almost
  23411.  universally more efficient than variables.
  23412.  
  23413.  
  23414.  Integers
  23415.  
  23416.  Integers are the fastest data type available in BASIC. Integers are smaller
  23417.  than any other numeric BASIC data type (each integer uses only 2 bytes per
  23418.  instance), and integers can be handled efficiently without run-time calls
  23419.  through compiler-generated in-line code. A good programming habit is to put
  23420.  DEFINT A-Z at the top of each module and procedure and then only use other
  23421.  data types as necessary.
  23422.  
  23423.  The following tips on using integers can help your programs run faster:
  23424.  
  23425.    ■   Loop variables should usually be integers.
  23426.  
  23427.    ■    A common error made by BASIC programmers is to just use the default
  23428.        data type ( SINGLE 4-byte floating point real) for loop variables.
  23429.        This error could slow down loop speed significantly and cause program
  23430.        size to grow by unnecessarily pulling in a floating-point math package
  23431.        if it hasn't yet been pulled in.
  23432.  
  23433.    ■   Booleans should always be integers.
  23434.  
  23435.    ■    If a variable's purpose is to either specify true or false, or some
  23436.        other discrete set of values, integers will be by far the smallest and
  23437.        fastest data type suitable for this purpose.
  23438.  
  23439.    ■   BASIC statement and function arguments should be integers wherever
  23440.        possible.
  23441.  
  23442.    ■    Using integer arguments for calls to graphics functions, for example,
  23443.        will make the code execute more quickly and will not require the use
  23444.        of the floating-point math package.
  23445.  
  23446.  
  23447.  
  23448.  Currency
  23449.  
  23450.  Microsoft BASIC supports a new data type,  CURRENCY (specified with the  @
  23451.  suffix), that can be thought of as an extra-long integer that has been
  23452.  shifted to accommodate fixed decimal-place fractional values. Internally,
  23453.  the  CURRENCY data type is represented as an 8-byte integer that is then
  23454.  scaled by a factor of 10,000 to leave four decimal places to the right of
  23455.  the decimal point, and 15 places to the left. Its internal representation as
  23456.  an integer gives the  CURRENCY data type a significant advantage in speed
  23457.  over floating point for addition and subtraction and a disadvantage in speed
  23458.  for other mathematical operations. Where fixed-decimal precision across the
  23459.  CURRENCY data type's range of values is acceptable (for example in dollar
  23460.  and cent calculations),  CURRENCY should be seen as a desirable alternative
  23461.  to traditional floating point data types.
  23462.  
  23463.  Compared with binary-coded-decimal (BCD) data types, the  CURRENCY data type
  23464.  has superior range for a comparable number of bytes (since BCD data type
  23465.  only uses 100 out of 256 values in every byte, while  CURRENCY uses all
  23466.  256). The  CURRENCY data type is also faster in math operations such as
  23467.  addition and subtraction.
  23468.  
  23469.  
  23470.  Near Vs. Far Strings
  23471.  
  23472.  While far strings provide greater capacity, near strings are measurably
  23473.  faster for most operations, since it takes less time to access a near string
  23474.  than a far string. Depending on the program, string speed or capacity may be
  23475.  most desirable, and the choice between string packages should be made
  23476.  accordingly.
  23477.  
  23478.  
  23479.  Static Vs. Dynamic Arrays
  23480.  
  23481.  As with type of strings, the type of arrays used involves a tradeoff of
  23482.  capacity for speed. Static array referencing is always faster than
  23483.  referencing dynamic arrays, but dynamic arrays (since they exist in far
  23484.  memory) offer significantly higher capacity. Similarly, accessing a standard
  23485.  dynamic array is faster than accessing a huge array (compiled with the /Ah
  23486.  operator).
  23487.  
  23488.  
  23489.  Optimizing Control-Flow Structures for Speed
  23490.  
  23491.  BASIC provides three different levels of general purpose control-flow
  23492.  structures, each with its own advantages. They are the  GOTO,  GOSUB/ DEF
  23493.  FN procedures, and  SUB/ FUNCTION procedures. The speed with which each of
  23494.  these can cause your program to branch off to execute different pieces of
  23495.  code is largely a function of how much information must be passed and how
  23496.  much of the current context must be maintained.
  23497.  
  23498.  
  23499.  GOTO
  23500.  
  23501.  The  GOTO statement is the oldest and least structured of the BASIC
  23502.  branching controls, but is still unmatched in raw jumping speed. The simple
  23503.  mapping of the  GOTO statement onto the single-instruction  JMP in assembly
  23504.  language makes the  GOTO statement the fastest way to go from point A to
  23505.  point B in a program. The obvious tradeoff here is that  GOTO statements are
  23506.  unstructured, making code difficult to read, debug, and maintain, and are
  23507.  therefore considered poor programming style.
  23508.  
  23509.  
  23510.  GOSUB and DEF FN Subroutines
  23511.  
  23512.   GOSUB and  DEF FN procedures do little more for the user than the  GOTO
  23513.  statement, except to push a return address onto the stack so that after the
  23514.  procedure is finished, a return instruction is executed that returns the
  23515.  program to the statement after the assembly language  CALL instruction. This
  23516.  branching construct is slower than the  GOTO statement, but it adds the
  23517.  advantage of returning to the point after the branch.
  23518.  
  23519.  
  23520.  SUB and FUNCTION Procedure Calls
  23521.  
  23522.   SUB and  FUNCTION procedures are the powerful, fully structured BASIC
  23523.  constructs that allow passing of parameters, local variables, recursion,
  23524.  local error handling, inter-module calls, and mixed-language programming.
  23525.  The elegance of these modular language features leads some people to argue
  23526.  that the older constructs discussed above are completely obsolete, despite
  23527.  their speed advantage in some situations. The speed of a call to a procedure
  23528.  of this type is dominated by the amount of context and parameter information
  23529.  that must be stored on the stack (called the stack frame) before the branch
  23530.  can take place. The size of this stack frame is determined by the number and
  23531.  size of parameters being passed to the procedure, the amount of
  23532.  error-trapping information to be maintained, and the code that exists within
  23533.  the procedure itself.
  23534.  
  23535.  The following tips help you improve the speed of calls:
  23536.  
  23537.    ■   Minimize parameter passing.
  23538.  
  23539.    ■    While parameter passing is generally preferred programming style
  23540.        compared to using global variables, pushing parameters on the stack
  23541.        takes time. When call speed is critical, global variables reduce frame
  23542.        size and minimize the net time spent transferring control to a
  23543.        procedure.
  23544.  
  23545.    ■   Compile with the /Ot option when appropriate.
  23546.  
  23547.    ■    Compiling with the /Ot option will improve call speed if you are not
  23548.        also compiling with the /D or /Fs option and your program does not use
  23549.        local error handling. If a  SUB or  FUNCTION procedure uses local
  23550.        error handling, calls a  DEF FN or  GOSUB procedure, or contains a
  23551.        RETURN statement, then a full stack frame is generated and compiling
  23552.        with the /Ot option will not improve call speed.
  23553.  
  23554.    ■   Avoid setting up a large stack frame.
  23555.  
  23556.    ■    A BASIC stack frame that contains a large block of information about
  23557.        the current context of the program will be saved in some situations as
  23558.        soon as a  SUB or  FUNCTION procedure is invoked. The following things
  23559.        require a large stack frame and should be avoided if call speed is
  23560.        critical:
  23561.  
  23562.    ■   GOSUB and  DEF  FN invocations from
  23563.        within procedures
  23564.  
  23565.        Module-level error handlers (if only procedure-level
  23566.            error-handling routines are used, a BASIC stack frame is required
  23567.            only when calling those procedure containing local error-handling
  23568.            routines)
  23569.  
  23570.        Run-time error checking (compiling with the /D option for debug
  23571.            information)
  23572.  
  23573.         ON... GOSUB statements within procedures
  23574.  
  23575.        Compiling with the /Fs option
  23576.  
  23577.    ■   Pass parameters by value.
  23578.  
  23579.  
  23580.    ■   When passing dynamic array elements or fixed-length strings as
  23581.        parameters to BASIC procedures, putting parentheses around the
  23582.        expression within the parameter list will cause the parameter to be
  23583.        passed by value rather than by reference, which will result in faster
  23584.        calls. Also, when calling a non-BASIC procedure from BASIC, using the
  23585.        BYVAL attribute in the parameter list of the  DECLARE statement will
  23586.        cause the particular parameter to be passed by value, thus speeding up
  23587.        the call. Of course, passing parameters by value should be used only
  23588.        when the value of the parameter does not need to be changed globally
  23589.        within the procedure.
  23590.  
  23591.  
  23592.    ■   Use  STATIC  SUB statements to maximize call speed to  SUB procedures.
  23593.  
  23594.  
  23595.    ■   Variables will then be static by default and will exist between calls
  23596.        to the  SUB procedure; the compiler won't have to recreate them. The
  23597.        variables will require more space, however.
  23598.  
  23599.  
  23600.  
  23601.  Writing Faster Loops
  23602.  
  23603.  While looping is a straightforward task in BASIC, there are some things that
  23604.  can help speed up simple looping. The first thing to remember, once again,
  23605.  is always use integer loop counters. Second, remember to remove any and all
  23606.  "loop invariant code"-- code that doesn't execute differently during each
  23607.  iteration of the loop. Any operation whose result does not change through
  23608.  multiple iterations of the loop should be coded outside of the loop
  23609.  altogether. An example of removal of loop invariant code is:
  23610.  
  23611.  FOR I%=1 to LastLoop%
  23612.  A(I%)=SIN(B)
  23613.  NEXT I%
  23614.  
  23615.  The preceding example
  23616.  could be more efficiently recoded as:
  23617.  
  23618.  
  23619.  C=SIN(B)
  23620.  FOR I%=1 to LastLoop%
  23621.  A(I%)=C
  23622.  NEXT I%
  23623.  
  23624.  Optimizing Input and Output for Speed
  23625.  
  23626.  File and screen I/O are frequently the speed bottlenecks for BASIC programs,
  23627.  because of the relatively slow speed of disk access and writing to screen.
  23628.  But as with all the other areas of BASIC programming, there are ways to
  23629.  maximize the performance of these operations.
  23630.  
  23631.  
  23632.  Sequential File I/O
  23633.  
  23634.  The  OPEN statement has an optional  LEN= argument that can be used to
  23635.  specify the buffer size that will be used when opening a file for sequential
  23636.  file I/O. This sequential file I/O buffer resides in the same area of memory
  23637.  that contains variable-length strings, namely DGROUP if the program is
  23638.  compiled for near strings, or the far memory if the program is compiled with
  23639.  /Fs. The larger this buffer is, the smaller the number of disk accesses that
  23640.  will be necessary to perform a given quantity of file I/O with the disk. The
  23641.  tradeoff here is speed of file I/O in exchange for capacity in the area of
  23642.  memory (near or far) that contains variable length strings. The default
  23643.  sequential buffer size is 512 bytes.
  23644.  
  23645.  
  23646.  ISAM
  23647.  
  23648.  The BASIC ISAM statements and functions make structured file I/O much faster
  23649.  and simpler than using non-ISAM file I/O statements and functions and
  23650.  creating your own code in BASIC to handle structured file access. ISAM is
  23651.  optimized for working with very large numbers of records that must be
  23652.  accessed by indexes that do not necessarily correspond to the physical order
  23653.  of the records in the file. When using ISAM, however, compiling with /D can
  23654.  degrade performance. See Chapter 10, "Database Programming with ISAM," for
  23655.  more information on programming with the ISAM statements and functions.
  23656.  
  23657.  
  23658.  Printing to Screen
  23659.  
  23660.  The stub files TSCNIO sm.OBJ not only reduce executable size but also speed
  23661.  up printing characters to the screen. These stub files eliminate checking
  23662.  for special control characters when they are printed, so that all characters
  23663.  are then sent directly to the screen without filtering out and treating
  23664.  characters below ASCII 32 differently from the other characters.
  23665.  
  23666.  Sending text to the screen can be accelerated further through precise use of
  23667.  the  PRINT statement. The  PRINT statement comes with many automatic
  23668.  functions, among them are auto-scroll if there is no terminating semicolon.
  23669.  Putting a semicolon at the end of any  PRINT statement where a line feed is
  23670.  not needed will save an unnecessary call to the lower level print routines
  23671.  to start a new line. This code change could make printing to screen twice as
  23672.  fast.
  23673.  
  23674.  
  23675.  Other Hints for Speed
  23676.  
  23677.  The following sections include some general hints to help you improve
  23678.  program execution speed.
  23679.  
  23680.  
  23681.  Event Trapping
  23682.  
  23683.  Event trapping in BASIC programs forces the compiler to generate
  23684.  event-checking code that is executed either between statements or between
  23685.  labels. This makes the code larger and slower. Precise use of  EVENT  ON and
  23686.   EVENT OFF statements to limit the areas affected by event trapping, or
  23687.  avoiding use of event trapping altogether when possible, will substantially
  23688.  improve speed of the resultant executable.
  23689.  
  23690.  
  23691.  Line Labels
  23692.  
  23693.  Excessive numbers of line labels limit the optimizations that can be
  23694.  performed by the compiler. Programs run faster after extraneous line labels
  23695.  have been removed. The REMLINE.BAS program that comes with Microsoft BASIC
  23696.  can automatically remove all unneeded line labels and thus help the compiler
  23697.  perform more sophisticated optimizations, resulting in faster executables.
  23698.  
  23699.  
  23700.  Buying Speed with Memory
  23701.  
  23702.  A common technique used to gain speed in calculation-intensive programs is
  23703.  to set up arrays in which the results of a particular function or
  23704.  calculation, across the expected range of input values, are stored. The
  23705.  underlying assumption is that the time needed to refer to the array is
  23706.  shorter than the time needed to perform the calculation itself, and that the
  23707.  speed advantage is worth the price of additional memory used to store the
  23708.  array.
  23709.  
  23710.  For example, a program that repeatedly requires the tangents of angles to be
  23711.  calculated might run substantially faster if builds an array of values
  23712.  TAN(x) that can be quickly referred to, rather than calculating the tangent
  23713.  each time it is required.
  23714.  
  23715.  
  23716.  
  23717.   ────────────────────────────────────────────────────────────────────────────
  23718.  Chapter 16:  Compiling with BC
  23719.  
  23720.  This chapter explains how to compile your BASIC program
  23721.  using the BASIC Compiler (BC). You'll learn how to invoke BC from the
  23722.  command line as well as how to use command-line options to BC.
  23723.  
  23724.  While you can compile and link your program from QBX by choosing Make EXE
  23725.  File from the Run menu, you might run BC from the command line to create an
  23726.  object file. Then you can run the LINK utility to link object modules and
  23727.  libraries to create an executable file. You may want to invoke BC and LINK
  23728.  in separate steps for the following reasons:
  23729.  
  23730.    ■   To compile a program that is too large to compile in memory within the
  23731.        QBX environment
  23732.  
  23733.    ■   To debug your program with the Microsoft CodeView debugger
  23734.  
  23735.    ■   To use a different text editor
  23736.  
  23737.    ■   To create listing files for use in debugging a stand-alone executable
  23738.        program
  23739.  
  23740.    ■   To use options not available within the QBX environment, such as
  23741.        storing arrays in row order (/R)
  23742.  
  23743.    ■   To link your program with stub files, which reduce the size of
  23744.        executable files in programs that do not use a particular BASIC
  23745.        feature
  23746.  
  23747.  
  23748.  This chapter assumes that you will be using BC from the command line. If you
  23749.  are compiling your program from QBX, use online Help to learn how to invoke
  23750.  the compiler and specify compiler options.
  23751.  
  23752.  
  23753.  New Options and Features
  23754.  
  23755.  The following options and features have been added to this release of BC:
  23756.  
  23757.    ■   The /Fs option enables you to store string data in far memory.
  23758.  
  23759.    ■   The /G2 option generates instructions specific to the 80286 memory
  23760.        chip that result in smaller, faster executable code.
  23761.  
  23762.    ■   The /Ot option optimizes the performance of procedure calls.
  23763.  
  23764.    ■   The /I x: options control memory use in ISAM applications.
  23765.  
  23766.    ■   The DOS INCLUDE environment variable enables you to determine where BC
  23767.        will look for included files without changing the  $INCLUDE
  23768.        metacommand in your source file.
  23769.  
  23770.  
  23771.  
  23772.  
  23773.  Compiling with the BC Command
  23774.  
  23775.  You can compile with the BC command in either of the following ways:
  23776.  
  23777.    ■   Type all information on a single command line, using the following
  23778.        syntax:
  23779.  
  23780.    ■    BC  sourcefile , objectfile , listingfile  options;
  23781.  
  23782.    ■    You can let BC create default object and listing files for you by
  23783.        typing a semicolon or commas after  sourcefile. BC will generate an
  23784.        object file with the same name as your source file (without its
  23785.        extension), and will append an .OBJ (object file) or .LST (listing
  23786.        file) filename extension.
  23787.  
  23788.        Type the BC command and respond to the following prompts:
  23789.  
  23790.        BC
  23791.  
  23792.        Source Filename [.BAS]:
  23793.        Object Filename [filename.OBJ]:
  23794.        Source Listing [NUL.LST]:
  23795.  
  23796.        The argument  filename is the name of your source file
  23797.        without the .BAS filename extension.
  23798.  
  23799.        To accept the default filenames shown in brackets, type a carriage
  23800.        return or semicolon after the colon.
  23801.  
  23802.  
  23803.  Table 16.1 shows the input you must give on the BC command line or in
  23804.  response to each prompt.
  23805.  
  23806.  
  23807.  Specifying Filenames
  23808.  
  23809.  The BC command makes certain assumptions about the files you specify, based
  23810.  on the paths and extensions you use for the files. The following sections
  23811.  describe these assumptions and other rules for specifying filenames to the
  23812.  BC command.
  23813.  
  23814.  
  23815.  Uppercase and Lowercase Letters
  23816.  
  23817.  You can use any combination of uppercase and lowercase letters for
  23818.  filenames; the compiler accepts uppercase and lowercase letters
  23819.  interchangeably. Thus, the BC command considers the following three
  23820.  filenames to be equivalent:
  23821.  
  23822.  abcde.BAS
  23823.   ABCDE.BAS
  23824.   aBcDe.Bas
  23825.  
  23826.  Filename Extensions
  23827.  
  23828.  The name of a DOS file has two parts: the base filename, which includes
  23829.  everything up to (but not including) the period (.), and the filename
  23830.  extension, which includes the period and up to three characters following
  23831.  the period. In general, the extension identifies the type of file (for
  23832.  example, whether the file is a BASIC source file, an object file, an
  23833.  executable file, or an object-module library).
  23834.  
  23835.  BC uses the filename extensions described in the following list:
  23836.  
  23837.  .BAS    BASIC source file
  23838.  
  23839.  .OBJ    Object file
  23840.  
  23841.  .LST    Listing file produced by BC. The extension .LST is
  23842.          the default filename extension; it will be overridden if
  23843.          you provide an extension for sourcefile.
  23844.  
  23845.  
  23846.  Paths
  23847.  
  23848.  Any filename can include a full or partial path. A full path starts with the
  23849.  drive name; a partial path has one or more directory names preceding the
  23850.  filename, but does not include a drive name.
  23851.  
  23852.  Giving a path allows you to specify files in different paths as input to the
  23853.  BC command and lets you create files on different drives or in different
  23854.  directories on the current drive.
  23855.  
  23856.  Note
  23857.  
  23858.  For files that you are creating with BC, you can give a path ending in a
  23859.  backslash. When it creates the file, BC uses the default name for the file.
  23860.  
  23861.  You can use the DOS command  SET to change the INCLUDE environment variable
  23862.  determining a search path for files included in your BASIC source file. To
  23863.  do this enter a command with the following syntax before invoking BC:
  23864.  
  23865.   SET INCLUDE =  path ;  path...
  23866.  
  23867.  Using BC Command Options
  23868.  
  23869.  Options to the BC command consist of either a slash (/) or a dash (-)
  23870.  followed by one or more letters. (The slash and the dash can be used
  23871.  interchangeably. In this manual, forward slashes are used for options.) From
  23872.  QBX, you can modify BC options by choosing options in the Modify EXE File
  23873.  dialog box.
  23874.  
  23875.  
  23876.  Using Far Strings (/Fs)
  23877.  
  23878.  
  23879.  Using Floating-Point Options (/FPa and /FPi)
  23880.  
  23881.  Microsoft BASIC offers two main methods for handling floating-point math
  23882.  operations: in-line instructions (/FPi) or alternate math library (/FPa).
  23883.  Your choice of methods affects the speed and accuracy of floating-point
  23884.  operations, as well as the size of the executable file.
  23885.  
  23886.  Object files created with the /FPa option are not compatible with those
  23887.  created with the /FPi option. If you link files that use incompatible
  23888.  floating-point methods, BASIC detects the incompatibility and terminates
  23889.  with the error message Error during run-time initialization. When you link
  23890.  multiple-source files into a single executable file, you must ensure that
  23891.  every part of your program handles floating-point operations consistently.
  23892.  
  23893.  
  23894.  In-Line Instructions (/FPi)
  23895.  
  23896.  Specifying the /FPi option causes the compiler to create "in-line
  23897.  instructions" for use in floating-point operations. In-line instructions are
  23898.  machine-code instructions that a math coprocessor can execute. At run time,
  23899.  BASIC checks to see whether a math coprocessor is present. BASIC uses the
  23900.  coprocessor if it is present, or emulates its functions in software if it is
  23901.  not.
  23902.  
  23903.  This method of handling floating-point operations provides the fastest
  23904.  solution if you have a math coprocessor, and it offers the convenience of
  23905.  automatically checking for a coprocessor at run time. For these reasons,
  23906.  BASIC uses in-line instructions (/FPi) by default if you compile without
  23907.  choosing a floating-point option.
  23908.  
  23909.  If you choose the /FPi option, your program will link the emulator library
  23910.  (EMR.LIB or EMP.LIB), which provides a large subset of the functions of a
  23911.  math coprocessor in software. The emulator can perform basic operations to
  23912.  the same degree of accuracy as a math coprocessor. However, the emulator
  23913.  routines used for transcendental math functions differ slightly from the
  23914.  corresponding math coprocessor functions, causing a slight difference
  23915.  (usually within 2 bits) in the results of these operations when performed
  23916.  with the emulator instead of a math coprocessor.
  23917.  
  23918.  
  23919.  Alternate Math Library (/FPa)
  23920.  
  23921.  If you compile with the /FPa option, your program uses the alternate math
  23922.  library for floating-point operations. The alternate math library
  23923.  (BLIBFA.LIB or BLIBFP.LIB) is a software-only math package that uses a
  23924.  subset of the Institute of Electrical and Electronics Engineers, Inc. (IEEE)
  23925.  format numbers.
  23926.  
  23927.  This option offers the fastest math solution if your computer does not have
  23928.  a math coprocessor. It also creates a smaller executable file. However,
  23929.  using the alternate math library does sacrifice some accuracy in favor of
  23930.  speed and simplicity because infinity, "not-a-number" (NAN) values, and
  23931.  denormal numbers are not used. See Chapter 15, "Optimizing Program Size and
  23932.  Speed," for information about the performance gain and accuracy of the
  23933.  alternate math library. See Appendix B, "Data Types, Constants, Variables,
  23934.  and Arrays," for the range of values valid with alternate math.
  23935.  
  23936.  When you choose the alternate-math library, BASIC does not use a math
  23937.  coprocessor, even if one is present.
  23938.  
  23939.  
  23940.  Run-Time Modules and Floating-Point Methods
  23941.  
  23942.  Your choice of floating-point method has implications at run time for
  23943.  programs that use run-time modules and libraries. If you compile without the
  23944.  /O option, you must ensure that the appropriate run-time module is present
  23945.  at run time. (The /O option creates a stand-alone file that does not require
  23946.  the BASIC run-time module.) For each operating mode (real or protected),
  23947.  Microsoft BASIC offers several versions of the run-time module. Some
  23948.  versions use the in-line-instructions method for floating-point operations,
  23949.  while other versions use the alternate-math-library method. See Chapter 21,
  23950.  "Custom Run-Time Modules," for more information about run-time modules and
  23951.  libraries.
  23952.  
  23953.  
  23954.  Optimizing Procedure Calls (/Ot)
  23955.  
  23956.  Calls to  SUB,  FUCTION, and  DEF FN procedures can be optimized by
  23957.  compiling with the /Ot option under certain conditions. The /Ot option
  23958.  reduces the size of the stack frame generated by certain calls, thereby
  23959.  reducing the amount of information passed to the stack when a call is made.
  23960.  Reducing the amount of information passed to the stack reduces the amount of
  23961.  time required to execute. The /Ot option will improve execution speed as
  23962.  follows:
  23963.  
  23964.    ■    SUB and  FUNCTION procedures
  23965.  
  23966.    ■    A reduced stack frame is generated with /Ot if no module-level
  23967.        error-handling routines exist in the code and the /D or /Fs option is
  23968.        not used. The full stack frame is generated and no performance benefit
  23969.        results if your code uses local error handling, uses a  DEF FN or
  23970.        GOSUB statement, has a return, or contains an  ON  event  GOSUB
  23971.        statement.
  23972.  
  23973.    ■    DEF FN procedures
  23974.  
  23975.    ■    A full stack frame is generated and no benefit results if the /D, /Fs,
  23976.        /E, or /X option is used. A partial stack frame is generated if the /W
  23977.        or /V option is used. In all other cases, no stack frame is generated
  23978.        and a performance benefit results.
  23979.  
  23980.  
  23981.  
  23982.   ────────────────────────────────────────────────────────────────────────────
  23983.  
  23984.  Chapter 17:  About Linking and Libraries
  23985.  
  23986.  This chapter provides an overview of library and linking support provided
  23987.  with Microsoft BASIC. You'll learn about the types of libraries you can
  23988.  work with and how to link them into your BASIC programs. If you are
  23989.  interested in learning about how to use the LINK and LIB utilities and
  23990.  their options, see Chapter 18, "Using LINK and LIB." The Quick library,
  23991.  a special type of library used within the QBX environment, is described
  23992.  in Chapter 19, "Creating and Using Quick Libraries."
  23993.  
  23994.  
  23995.  What Is a Library?
  23996.  
  23997.  A library is an organized collection of object code; that is, a library
  23998.  contains functions and data that are already compiled (with a compiler or an
  23999.  assembler) and are ready to link with your program. The structure of a
  24000.  library supports the mass storage of common procedures -- procedures that
  24001.  can be called by a variety of programs. These common procedures, called
  24002.  "modules," can be added, deleted, changed, or copied.
  24003.  
  24004.  Libraries are typically used for one of three purposes:
  24005.  
  24006.    ■   To support high-level languages. For example, Microsoft BASIC performs
  24007.        input/output and floating-point operations by calling standard support
  24008.        routines. Because the support routines are available in a library, the
  24009.        compiler never needs to regenerate code for these routines.
  24010.  
  24011.    ■   To perform complex and specialized activities, such as financial
  24012.        functions or matrix math operations. Libraries containing such
  24013.        routines often are provided by the makers of the compilers or by
  24014.        third-party software vendors. Microsoft BASIC comes with several such
  24015.        libraries.
  24016.  
  24017.    ■   To support your own work. If you have created routines that you find
  24018.        useful for a variety of programs, you may want to put these routines
  24019.        into a library. That way, these routines do not need to be rewritten
  24020.        or recompiled. You save development time by using work you have
  24021.        already done, and disk space because you don't have to replicate
  24022.        source code.
  24023.  
  24024.  
  24025.  Note
  24026.  
  24027.  Because LIB and LINK assign special meaning to the at sign (@), it should
  24028.  never be used as the first character of a filename that is to be made into
  24029.  an executable or library file.
  24030.  
  24031.  
  24032.  
  24033.  Types of Libraries (.LIB and .QLB)
  24034.  
  24035.  Microsoft BASIC provides tools for creating two different types of
  24036.  libraries, which are identified by the following filename extensions:
  24037.  
  24038.  You can think of a Quick library as a group of procedures appended to QBX
  24039.  when the library is loaded into the environment, while an object-module
  24040.  library is a collection of independent, compiled procedures. Object-module
  24041.  libraries can be linked with a main module to create a file that is
  24042.  executable from the DOS command line.
  24043.  
  24044.  Both types of libraries are discussed in the following sections.
  24045.  
  24046.  
  24047.  Object-Module Libraries (.LIB )
  24048.  
  24049.  Object-module libraries can be linked with compiled object files to produce
  24050.  stand-alone executable programs. These libraries normally have the .LIB
  24051.  filename extension. Microsoft BASIC supplies the following types of
  24052.  object-module libraries:
  24053.  
  24054.    ■   Stand-alone libraries. These libraries allow your executable file to
  24055.        run alone, without run-time modules (see the following item).Your
  24056.        program will look for this type of library during linking if you
  24057.        compiled your program with the /O option of the BASIC Compiler (BC).
  24058.  
  24059.    ■   Run-time libraries. These libraries are used in conjunction with
  24060.        special modules called run-time modules. Your program will look for
  24061.        this type of library during linking if you have not used the /O option
  24062.        of BC when you compiled your program. Run-time modules and libraries
  24063.        are discussed in greater detail in Chapter 21, "Building Custom
  24064.        Run-Time Modules."
  24065.  
  24066.    ■   Add-on libraries and BASIC toolbox files. Microsoft BASIC supplies two
  24067.        add-on libraries: a financial function library and a date/time
  24068.        library. These are object-module libraries (.LIB) that contain
  24069.        special-purpose routines.
  24070.  
  24071.    ■    BASIC toolbox files are BASIC source files (.BAS) containing mouse,
  24072.        menu, window, graphics, and support routines. These files can be
  24073.        turned into stand-alone or Quick libraries. (See Chapters 18, "Using
  24074.        LINK and LIB" and 19, "Creating and Using Quick Libraries," for
  24075.        details about creating libraries.)
  24076.  
  24077.  
  24078.  The Setup program automatically creates stand-alone and run-time
  24079.  libraries during the setup operation. The exact libraries created
  24080.  depend upon which math package, mode (real or protected), and string
  24081.  support options you specify.
  24082.  
  24083.  
  24084.  You can also create your own custom library or turn BASIC source code
  24085.  modules into a library. To do this, separately compile source code modules
  24086.  that you want in the library, then use LIB to combine those object modules
  24087.  into one library. LIB also lets you add, delete, replace, copy, and move
  24088.  modules in the library as you choose. The QBX environment automates this
  24089.  process if you select the Make Library command from the Run menu.
  24090.  
  24091.  
  24092.  Stand-Alone Libraries
  24093.  
  24094.  Stand-alone libraries use the following naming convention:
  24095.  
  24096.  BCL70   float  string  mode.LIB
  24097.  
  24098.  The possible values for each variable are shown in Table 17.1.
  24099.  
  24100.  For example, if you specify the emulator floating-point math, far
  24101.  strings, and real mode options during setup, your program would search
  24102.  for and link with the BCL70EFR.LIB stand-alone library. From within QBX,
  24103.  you can change the library used for linking your program by changing
  24104.  options in the Make EXE dialog box.
  24105.  
  24106.  When you compile with the /O option of BC, an object file requiring a
  24107.  stand-alone library is produced. If you then run LINK, the proper
  24108.  stand-alone library is linked with your program to produce a stand-alone
  24109.  executable file. In the QBX environment, your program is automatically
  24110.  linked with a stand-alone library if you select the Stand-Alone EXE option
  24111.  from the Make EXE File dialog box.
  24112.  
  24113.  Stand-alone programs require more disk space than those requiring the run-tim
  24114.  variables listed in  COMMON statements as well as open files are not
  24115.  preserved when a  CHAIN statement transfers control to another program.
  24116.  Stand-alone programs do have the following advantages, however:
  24117.  
  24118.  
  24119.    ■   Stand-alone programs always require less memory than their run-time
  24120.        equivalents.
  24121.  
  24122.    ■   Execution of the program does not require that the run-time module be
  24123.        on the disk when the program is run. This is important when you write
  24124.        programs that users will copy, since an inexperienced user may not
  24125.        know to copy the run-time module as well.
  24126.  
  24127.  
  24128.  
  24129.  BASIC Run-Time Libraries
  24130.  
  24131.  You may choose to link with a run-time library instead of with a stand-alone
  24132.  library. If you compile and then link your program with a run-time library,
  24133.  the resulting executable file can only be run in the presence of a run-time
  24134.  module. This module contains code that implements the BASIC language.
  24135.  
  24136.  Using a run-time module and library provides the following advantages:
  24137.  
  24138.    ■   The executable file is much smaller. If several programs are kept on
  24139.        the disk, considerable space is saved.
  24140.  
  24141.    ■   Unnamed  COMMON variables and open files are preserved across  CHAIN
  24142.        statements. This can be valuable in systems of programs that use
  24143.        shared data.
  24144.  
  24145.    ■   Run-time modules reside in memory, so they do not need to be reloaded
  24146.        for each program in a system of chained programs.
  24147.  
  24148.  
  24149.  Run-time modules use the following naming conventions for real and protected
  24150.  modes and run-time libraries:
  24151.  
  24152.  BRT70  float  string  mode.EXE (Real mode)
  24153.  
  24154.  BRT70  float  string  mode.DLL (Protected mode)
  24155.  
  24156.  Run-time libraries use the following naming convention:
  24157.  
  24158.  BRT70  float  string  mode.LIB
  24159.  
  24160.  The possible values for each variable are the same as for stand-alone
  24161.  libraries (see Table 17.1). For example, if you specify the emulator
  24162.  math, far strings, and real mode options during the setup operation,
  24163.  your program would use the BRT70EFR.EXE run-time module and the
  24164.  BRT70EFR.LIB run-time library.
  24165.  
  24166.  
  24167.  In addition to the standard run-time module, Microsoft BASIC also lets
  24168.  you embed your own routines into the run-time module to create a custom
  24169.  run-time module. To do this, you write and compile your source modules;
  24170.  create a file called an export list to define object files, routines,
  24171.  and libraries you wish to add to the run-time module; invoke the BUILDRTM
  24172.  utility to create the custom run-time module and support files; and,
  24173.  finally, link the object files for your application with the support
  24174.  files for your custom run-time module. These steps are described in
  24175.  detail in Chapter 21, "Building Custom Run-Time Modules."
  24176.  
  24177.  
  24178.  Add-On Libraries
  24179.  
  24180.  Microsoft BASIC provides several additional libraries. These libraries
  24181.  provide additional support for various types of functions that you may want
  24182.  to include in your BASIC programs.
  24183.  
  24184.  
  24185.  BASIC Toolbox Files
  24186.  
  24187.  BASIC toolbox files are BASIC source files that you can use to build
  24188.  object-module or Quick libraries.
  24189.  
  24190.  After compiling these files into object files, you can use LIB to
  24191.  create an object-module library or LINK to create a Quick library.
  24192.  See Chapter 18, "Using LINK and LIB," for details on how to create an
  24193.  object-module library or Chapter 19, "Creating and Using Quick
  24194.  Libraries," for details on how to create a Quick library.
  24195.  
  24196.  
  24197.  
  24198.  Quick Libraries (.QLB)
  24199.  
  24200.  Microsoft BASIC supports another type of library called a Quick library.
  24201.  This type of library can only be used in the QBX environment -- it can't be
  24202.  linked with object modules to create an executable file. Quick libraries
  24203.  normally have the .QLB filename extension.
  24204.  
  24205.  Quick libraries contain one or more procedures that can be loaded and made
  24206.  available to your BASIC programs in the QBX environment. A Quick library can
  24207.  contain procedures written in BASIC or other Microsoft languages such as C.
  24208.  Procedures in a Quick library behave like QBX's own statements. Just like
  24209.  BASIC statements, Quick library procedures can be executed directly from the
  24210.  Immediate window or called from your BASIC program. This makes it easy to
  24211.  test them before using them in other programs.
  24212.  
  24213.  
  24214.  Advantages of Quick Libraries
  24215.  
  24216.  There are several reasons why you might want to use Quick libraries. Quick
  24217.  libraries facilitate program development and maintenance. As development
  24218.  progresses on a project and modules become stable components of your
  24219.  program, you can add them to a Quick library, then set aside the source
  24220.  files for the original modules until you want to improve or maintain those
  24221.  source files. Thereafter you can load the library along with QBX, and your
  24222.  program has instant access to all procedures in the library.
  24223.  
  24224.  If you develop programs with others, Quick libraries make it easy to
  24225.  update a pool of common procedures. If you wish to offer a library of
  24226.  original procedures for commercial distribution, all QBX programmers
  24227.  will be able to use them immediately to enhance their own work. You
  24228.  could leave your custom Quick library on a bulletin board for others
  24229.  to try before purchasing. Because Quick libraries contain no source
  24230.  code and can only be used within the QBX programming environment, your
  24231.  proprietary interests are protected while your marketing goals are
  24232.  advanced.
  24233.  
  24234.  BASIC procedures within Quick libraries represent code compiled with the
  24235.  command-line compiler. These procedures share significant characteristics
  24236.  with executable files. For example, executable files and Quick libraries
  24237.  perform floating-point arithmetic faster than the same calculations
  24238.  performed within the QBX environment.
  24239.  
  24240.  
  24241.  Note
  24242.  
  24243.  Quick libraries have the same function as user libraries in QuickBASIC
  24244.  versions 2.0 and 3.0. However, you cannot load a user library as a Quick
  24245.  library. You must recreate the library from the original source code, as
  24246.  described in the following section.
  24247.  
  24248.  
  24249.  Making Quick Libraries
  24250.  
  24251.  If your source modules are written in BASIC, you can create Quick libraries
  24252.  from either QBX or the command line. However, if you have source modules
  24253.  written in languages besides BASIC, you must create the Quick library from
  24254.  the command line.
  24255.  
  24256.  From QBX, load desired BASIC source modules into the environment and choose
  24257.  the Make Library option from the Run menu to build the library. QBX
  24258.  automatically compiles your BASIC source modules, then creates a Quick
  24259.  library (.QLB) and a parallel object-module library (.LIB) as shown in
  24260.  Figure 17.1.
  24261.  
  24262.  To make a Quick library from the command line, you must compile all
  24263.  source modules using the appropriate compiler (or assembler), then invoke
  24264.  LINK to create the Quick library. You should also create a parallel
  24265.  object-module library using the LIB.
  24266.  
  24267.  Since Quick libraries are managed by LINK, you can't manipulate objects in
  24268.  it as with object-module libraries. To remove, add, or modify modules in a
  24269.  Quick library, you must work with its parallel object-module library.
  24270.  
  24271.  Instructions for creating, modifying, and using Quick libraries are found in
  24272.  Chapter 19, "Creating and Using Quick Libraries."
  24273.  
  24274.  
  24275.  Overview of Linking
  24276.  
  24277.  Linking is the process of combining libraries and other object files to
  24278.  create an executable file or a Quick library. To do this, Microsoft BASIC
  24279.  provides the LINK utility. To create a Quick library, LINK must be invoked
  24280.  with the /Q (Quick library) option. To create an executable file, LINK is
  24281.  invoked without the /Q option.
  24282.  
  24283.  LINK can be invoked from either QBX or from the command line. From QBX, LINK
  24284.  is invoked automatically when you select either the Make EXE or Make Library
  24285.  option from the Run menu. From the command line, LINK is invoked by typing
  24286.  LINK on the command line followed by command-line arguments and options.
  24287.  
  24288.  
  24289.  Using LINK to Create Executable Files
  24290.  
  24291.  LINK can be used to create an executable file from object modules and
  24292.  libraries. You can write source modules in any standard Microsoft language
  24293.  such as BASIC, C, or MASM. Then compile the programs using the appropriate
  24294.  compiler to produce an object file (.OBJ). Finally, use LINK to combine the
  24295.  object modules with appropriate libraries.
  24296.  
  24297.  For example, say you have a written a BASIC program ONE.BAS, which calls a C
  24298.  program named TWO.C and an assembly language program named THREE.ASM. You
  24299.  would first compile each program separately, and then link the resulting
  24300.  object files to produce an executable file:
  24301.  
  24302.  The file COMBO.EXE can be run from the DOS command prompt. LINK also
  24303.  supports other features such as producing a map file and reading information
  24304.  from a module definition file (OS/2 programs). For details on using LINK,
  24305.  see Chapter 18, "Using LINK and LIB."
  24306.  
  24307.  
  24308.  Using LINK to Create Quick Libraries
  24309.  
  24310.  If you specify the /Q option, the LINK will create a Quick library instead
  24311.  of an executable file. You use object modules such as compiled source code
  24312.  or libraries as input to LINK.
  24313.  
  24314.  
  24315.  For example, the files ONE.BAS, TWO.C, and THREE.ASM could be compiled
  24316.  and then linked along with the library OLD.LIB to create a Quick library
  24317.  by specifying the /Q option when you invoke LINK:
  24318.  
  24319.  For more information about creating Quick libraries, see Chapter 19,
  24320.  "Creating and Using Quick Libraries."
  24321.  
  24322.  
  24323.  Linking with Stub Files
  24324.  
  24325.  Microsoft BASIC provides several special-purpose object files with which you
  24326.  can link to minimize the size of your executable file. These files, called
  24327.  "stub files," cause LINK to exclude (or in some cases, include a smaller
  24328.  version of) code that would normally be placed in your executable file.
  24329.  
  24330.  For example, if your program does not need editor support, you could link
  24331.  your program with the NOEDIT.OBJ stub file. LINK would then include code
  24332.  that replaces the editor used with the  INPUT and  LINE INPUT statements.
  24333.  Keep in mind that this process is appropriate only for programs compiled
  24334.  with the /O option of BC or when creating a custom run-time module.
  24335.  
  24336.  The stub files supplied with Microsoft BASIC and how to link with them are
  24337.  discussed in Chapter 18, "Using LINK and LIB."
  24338.  
  24339.  
  24340.  Linking to Create Overlays
  24341.  
  24342.  You can direct LINK to create an overlaid version of a program. In an
  24343.  overlaid version of a program, specified parts of the program (known as
  24344.  "overlays") are loaded only if and when they are needed. These parts share
  24345.  the same space in memory. Only code is overlaid; data is never overlaid.
  24346.  Programs that use overlays usually require less memory, but they run more
  24347.  slowly because of the time needed to read and reread the code from disk or
  24348.  Expanded Memory Specification (EMS) into conventional memory. Specifying
  24349.  overlays can be useful if you have compiled a program that is too large to
  24350.  load into memory. You can have only those portions of code loaded into
  24351.  memory that are currently needed. Overlays are discussed in Chapters 15,
  24352.  "Optimizing Program Size and Speed," and 18, "Using LINK and LIB."
  24353.  
  24354.  
  24355.   ────────────────────────────────────────────────────────────────────────────
  24356.  
  24357.  Chapter 18:  Using LINK and LIB
  24358.  
  24359.  This chapter describes the syntax and usage of the Microsoft Library
  24360.  Manager (LIB) and the Microsoft Segmented-Executable Linker (LINK).
  24361.  
  24362.  The syntax to invoke each utility is provided, as well as descriptions
  24363.  of command-line arguments and options. For an overview of linking and
  24364.  libraries, see Chapter 17, "About Linking and Libraries."
  24365.  
  24366.  
  24367.  Invoking and Using LIB
  24368.  
  24369.  The Microsoft Library Manager (LIB) helps you create and maintain
  24370.  object-module libraries. An object-module library is a collection of
  24371.  separately compiled or assembled object files combined into a single file.
  24372.  Object-module libraries provide a convenient source of commonly used
  24373.  routines. A program that calls library routines is linked with the library
  24374.  to produce the executable file. Only modules containing the necessary
  24375.  routines, not all library modules, are linked into the executable file.
  24376.  
  24377.  Library files are usually identified by their .LIB extension, although other
  24378.  extensions are allowed. In addition to accepting DOS object files and
  24379.  library files, LIB can read the contents of 286 XENIX archives and
  24380.  Intel-style libraries and combine their contents with DOS libraries.
  24381.  
  24382.  You can use LIB for the following tasks:
  24383.  
  24384.    ■   Create a new library file.
  24385.  
  24386.    ■   Add object files or the contents of a library to an existing library.
  24387.  
  24388.    ■   Delete library modules.
  24389.  
  24390.    ■   Replace library modules.
  24391.  
  24392.    ■   Copy library modules to object files.
  24393.  
  24394.  
  24395.  While the Microsoft BASIC Setup program creates libraries during
  24396.  installation, you will need to run LIB if you want to create new libraries
  24397.  (for example, from BASIC object modules) or if you want to modify the
  24398.  contents of an existing library. This lets you create custom libraries
  24399.  containing only those routines that you want.
  24400.  
  24401.  To invoke LIB, type the LIB command on the DOS command line. You can specify
  24402.  the input required in one of three ways:
  24403.  
  24404.    ■   Type it on the command line after the LIB command.
  24405.  
  24406.    ■   Respond to prompts.
  24407.  
  24408.    ■   Specify a file containing responses to prompts (called a "response
  24409.        file").
  24410.  
  24411.  
  24412.  This section describeshow to specify input to LIB on the command line. To
  24413.  use prompts or a response file to specify input, see the sections "LIB
  24414.  Prompts" or "LIB Response File" later in this chapter.
  24415.  
  24416.  The command-line syntax for LIB is as follows:
  24417.  
  24418.  LIB  oldlibrary [options%]@AE@%  [commands%]@AE@%,
  24419.      [listfile],  [newlibrary]    ;
  24420.  
  24421.  
  24422.  The individual fields are discussed in greater detail in the sections that
  24423.  follow.
  24424.  
  24425.  Type a semicolon (;) after any field except the  oldlibrary field to tell
  24426.  LIB to use the default responses for the remaining fields. The semicolon
  24427.  should be the last character on the command line. Typing a semicolon after
  24428.  the  oldlibrary field causes LIB to perform a consistency check on the
  24429.  library -- no other action is performed. LIB displays any consistency errors
  24430.  it finds and returns to the operating-system level (see the section
  24431.  "Consistency Check" later in this chapter for more information).
  24432.  
  24433.  You can terminate the library session at any time and return to the
  24434.  operating system by pressing Ctrl+C.
  24435.  
  24436.  
  24437.  Old Library File
  24438.  
  24439.  Use the  oldlibrary field to specify the name of the library to be created,
  24440.  modified, or operated upon. LIB assumes that the filename extension is .LIB,
  24441.  so if your library file has the .LIB extension, you can omit it. Otherwise,
  24442.  include the extension. You must give LIB the path of a library file if it is
  24443.  in another directory or on another disk.
  24444.  
  24445.  There is no default for the  oldlibrary field. This field is required and
  24446.  LIB issues an error message if you do not give a filename. If the library
  24447.  you name does not exist, LIB displays the following prompt:
  24448.  
  24449.  Library does not exist. Create? (y/n)
  24450.  
  24451.  Type  Y to create the library file, or  N to terminate the session. This
  24452.  message does not appear if a command, a comma, or a semicolon immediately
  24453.  follows the library name.
  24454.  
  24455.  
  24456.  Consistency Check
  24457.  
  24458.  If you type a library name and follow it immediately with a semicolon (;),
  24459.  LIB only performs a consistency check on the given library. A consistency
  24460.  check tells you whether all the modules in the library are in usable form.
  24461.  No changes are made to the library. It usually is not necessary to perform
  24462.  consistency checks because LIB automatically checks object files for
  24463.  consistency before adding them to the library. LIB prints a message if it
  24464.  finds an invalid object module; no message appears if all modules are
  24465.  intact.
  24466.  
  24467.  
  24468.  Creating a Library File
  24469.  
  24470.  To create a new library file, give the name of the library file you want to
  24471.  create in the  oldlibrary field of the command line (or at the "Library
  24472.  name" prompt). LIB supplies the .LIB extension, if needed.
  24473.  
  24474.  If the name of the new library file is the same as the name of an existing
  24475.  library file, LIB assumes that you want to change the existing file. If the
  24476.  name of the new library file is the same as the name of a file that is not a
  24477.  library, LIB issues an error message.
  24478.  
  24479.  When you give the name of a file that does not currently exist, LIB displays
  24480.  the following prompt:
  24481.  
  24482.  Library does not exist. Create? (y/n)
  24483.  
  24484.  Type  Y to create the file, or  N to terminate the library session. This
  24485.  message does not appear if the name is followed immediately by a command, a
  24486.  comma, or a semicolon.
  24487.  
  24488.  You can specify a page size for the library by specifying the /PA: number
  24489.  option when you create the library. The default page size is 16 bytes.
  24490.  
  24491.  Once you have given the name of the new library file, you can insert object
  24492.  modules into the library by using the add-command symbol (+) (described
  24493.  under "Commands" later in this chapter).
  24494.  
  24495.  Examples
  24496.  
  24497.  The following example causes LIB to perform a consistency check of the
  24498.  library file GRAPHIC.LIB:
  24499.  
  24500.  LIB GRAPHIC;
  24501.  
  24502.  The following example tells LIB to perform a consistency check of the
  24503.  library file GRAPHIC.LIB and to create SYMBOLS.LST, a
  24504.  cross-reference-listing file:
  24505.  
  24506.  LIB GRAPHIC ,SYMBOLS.LST;
  24507.  
  24508.  Options
  24509.  
  24510.  Specify options on the command line following the required library
  24511.  filename and preceding any commands.
  24512.  
  24513.  
  24514.  Ignoring Case of Symbols (/I)
  24515.  
  24516.  The /I option tells LIB to ignore case when comparing symbols, which is the
  24517.  default. Use this option when you are combining a library that is case
  24518.  sensitive (was created with the /NOI option) with others that are not case
  24519.  sensitive. The resulting library will not be case sensitive. The /NOI option
  24520.  is described later in this section.
  24521.  
  24522.  
  24523.  No Extended Dictionary (/NOE)
  24524.  
  24525.  The /NOE option tells LIB not to generate an extended dictionary. The
  24526.  extended dictionary is an extra part of the library that helps LINK process
  24527.  libraries faster.
  24528.  
  24529.  Use the /NOE option if you get the error message Insufficient memory or No
  24530.  more virtual memory, or if the extended dictionary causes problems with LINK
  24531.  (that is, if you receive the message Symbol multiply defined). For more
  24532.  information on how LINK uses the extended dictionary, see the description of
  24533.  the /NOE option for LINK.
  24534.  
  24535.  
  24536.  Using Case-Sensitive Symbols (/NOI)
  24537.  
  24538.  The /NOI option tells LIB not to ignore case when comparing symbols; that
  24539.  is, /NOI makes LIB case sensitive. By default, LIB ignores case. Using this
  24540.  option allows symbols that are the same except for case, such as Spline and
  24541.  SPLINE, to be put in the same library.
  24542.  
  24543.  Note that when you create a library with the /NOI option, LIB "marks" the
  24544.  library internally to indicate that /NOI is in effect. Earlier versions of
  24545.  LIB did not mark libraries in this way. If you combine multiple libraries
  24546.  and any one of them is marked /NOI, then /NOI is assumed to be in effect for
  24547.  the output library.
  24548.  
  24549.  
  24550.  
  24551.  Specifying Page Size (/PA:number)
  24552.  
  24553.  The /PA option specifies the library page size of a new library or changes
  24554.  the library page size of an existing library. The  number specifies the new
  24555.  page size. It must be an integer value representing a power of two between
  24556.  the values 16 and 32,768.
  24557.  
  24558.  A library's page size affects the alignment of modules stored in the
  24559.  library. Modules in the library are always aligned to start at a position
  24560.  that is a multiple of the page size (in bytes) from the beginning of the
  24561.  file. The default page size for a new library is 16 bytes; for an existing
  24562.  library, the default is its current page size. Because of the indexing
  24563.  technique used by LIB, a library with a large page size can hold more
  24564.  modules than a library with a smaller page size. For each module in the
  24565.  library, however, an average of  number / 2 bytes of storage space is
  24566.  wasted. In most cases, a small page size is advantageous; you should use a
  24567.  small page size unless you need to put a very large number of modules in a
  24568.  library.
  24569.  
  24570.  Another consequence of the indexing technique is that the page size
  24571.  determines the maximum possible size of the library file. Specifically, this
  24572.  limit is  number * 65,536. For example, /PA:16 means that the library file
  24573.  must be smaller than 1 megabyte (16 * 65,536 bytes).
  24574.  
  24575.  
  24576.  Commands
  24577.  
  24578.  LIB can perform a number of library-management functions, including creating
  24579.  a library file, adding an object file as a module to a library, deleting a
  24580.  module from a library, replacing a module in the library file, copying a
  24581.  module to a separate object file, and moving a module out of a library and
  24582.  into an object file.
  24583.  
  24584.  The  commands field allows you to specify the command symbols for
  24585.  manipulating modules. In this field, type a command symbol followed
  24586.  immediately by a module name or the name of an object file. The command
  24587.  symbols are the following:
  24588.  
  24589.  +       Adds an object file or library to the library.
  24590.  -       Deletes a module from the library.
  24591.  - +     Replaces a module in the library.
  24592.  *       Copies a module from the library to an object file.
  24593.  -*      Moves a module (copies the module and then deletes it).
  24594.  
  24595.  Each of these commands is described in the following sections. Note
  24596.  that LIB does not process commands in left-to-right order; it uses its
  24597.  own precedence rules for processing (described in the next section).
  24598.  
  24599.  You can specify more than one operation in the  commands field,
  24600.  in any order. LIB makes no changes to  oldlibrary if you
  24601.  leave this field blank.
  24602.  
  24603.  
  24604.  
  24605.  Order of Processing
  24606.  
  24607.  For each library session, LIB reads and interprets commands in the following
  24608.  order. It determines whether a new library is being created or an existing
  24609.  library is being examined or modified.
  24610.  
  24611.     1. LIB processes any deletion and move commands.
  24612.  
  24613.        LIB does not actually delete modules from the existing file. Instead,
  24614.        it marks the selected modules for deletion, creates a new library
  24615.        file, and copies only the modules  not marked for deletion into the
  24616.        new library file.
  24617.  
  24618.     2. LIB processes any addition commands.
  24619.  
  24620.        Like deletions, additions are not performed on the original library
  24621.        file. Instead, the additional modules are appended to the new library
  24622.        file. (If there were no deletion or move commands, a new library file
  24623.        would be created in the addition stage by copying the original library
  24624.        file.)
  24625.  
  24626.  
  24627.  How LIB Processes Commands
  24628.  
  24629.  As LIB carries out these commands, it reads the object modules in the
  24630.  library, checks them for validity, and gathers the information necessary to
  24631.  build a library index and a listing file. When you link a library with other
  24632.  object files, LINK uses the library index to search the library.
  24633.  
  24634.  LIB never makes changes to the original library; it copies the library and
  24635.  makes changes to the copy. Therefore, if you press Ctrl+C to terminate the
  24636.  session, you do not lose your original library. Because of this, when you
  24637.  run LIB, you must make sure your disk has enough space for the original
  24638.  library file and the copy.
  24639.  
  24640.  Once an object file is incorporated into a library, it becomes an "object
  24641.  module." An object file has a full path, including a drive designation,
  24642.  directory path, and filename extension (usually .OBJ) object modules have
  24643.  only a name. For example, B:\RUN\SORT.OBJ is an object filename, while SORT
  24644.  is an object module name.
  24645.  
  24646.  
  24647.  Add Command (+)
  24648.  
  24649.  Use the add-command symbol (+) to add an object module to a library. Give
  24650.  the name of the object file to be added, without the .OBJ extension,
  24651.  immediately following the plus sign.
  24652.  
  24653.  LIB uses the base name of the object file as the name of the object module
  24654.  in the library. For example, if the object file B:\CURSOR.OBJ is added to a
  24655.  library file, the name of the corresponding object module is CURSOR.
  24656.  
  24657.  Object modules are always added to the end of a library file.
  24658.  
  24659.  You can also use the
  24660.  plus sign to combine two libraries. When you give a library name following
  24661.  the plus sign, a copy of the contents of that library is added to the
  24662.  library file being modified. You must include the .LIB extension when you
  24663.  give a library filename. Otherwise, LIB uses the default .OBJ extension when
  24664.  it looks for the file. If both libraries contain a module with the same
  24665.  name, LIB ignores the second module of that name. For information on
  24666.  replacing modules, see the description of the replace-command symbol (- +)
  24667.  found later in this chapter.
  24668.  
  24669.  
  24670.  LIB adds the modules of the library to the end of the library being changed.
  24671.  Note that the added library still exists as an independent library because
  24672.  LIB copies the modules without deleting them.
  24673.  
  24674.  In addition to allowing DOS libraries as input, LIB also accepts 286 XENIX
  24675.  archives and Intel-format libraries. Therefore, you can use LIB to convert
  24676.  libraries from either of these formats to the DOS format.
  24677.  
  24678.  
  24679.  Examples
  24680.  
  24681.  The following example uses the add-command symbol (+) to instruct LIB to add
  24682.  the file STAR to the library GRAPHIC.LIB:
  24683.  
  24684.  LIB GRAPHIC +STAR;
  24685.  The semicolon at the end of the preceding command line causes LIB to use the
  24686.  default responses for the remaining fields. As a result, no listing file is
  24687.  created and the original library file is renamed GRAPHIC.BAK. The modified
  24688.  library is GRAPHIC.LIB.
  24689.  
  24690.  The following example adds the file FLASH.OBJ to the library MAINLIB.LIB:
  24691.  
  24692.  LIB MAINLIB +FLASH;
  24693.  The following example adds the contents of the library TRIG.LIB to the
  24694.  library MATH.LIB. The library TRIG.LIB is unchanged after this command is
  24695.  executed.
  24696.  
  24697.  LIB MATH +TRIG.LIB;
  24698.  
  24699.  Delete Command (-)
  24700.  
  24701.  Use the delete-command symbol (-) to delete an object module from a library.
  24702.  After the minus sign, give the name of the module to be deleted. Module
  24703.  names do not have paths or extensions. The contents of the deleted library
  24704.  are copied to another file having the same filename except with a .BAK
  24705.  extension.
  24706.  
  24707.  
  24708.  Example
  24709.  
  24710.  The following example deletes the module FLASH from the library MAINLIB.LIB:
  24711.  
  24712.  LIB MAINLIB -FLASH;
  24713.  
  24714.  Replace Command (- +)
  24715.  
  24716.  Use the replace-command symbol (- +) to replace a module in a library.
  24717.  Following the symbol, give the name of the module to be replaced. Module
  24718.  names do not have paths or extensions.
  24719.  
  24720.  To replace a module,
  24721.  LIB first deletes the existing module, then appends an object file that has
  24722.  the same name as the module. The object file is assumed to have the .OBJ
  24723.  extension and to reside in the current directory; if not, give the object
  24724.  filename with an explicit extension or path.
  24725.  
  24726.  
  24727.  Example
  24728.  
  24729.  This command replaces the module FLASH in the MAINLIB.LIB library with the
  24730.  contents of FLASH.OBJ from the current directory. Upon completion of this
  24731.  command, the file FLASH.OBJ still exists and the FLASH module is updated in
  24732.  MAINLIB.LIB.
  24733.  
  24734.  LIB MAINLIB -+FLASH;
  24735.  
  24736.  Copy Command (*)
  24737.  
  24738.  Use the copy-command symbol (*) followed by a module name to copy a module
  24739.  from the library into an object file of the same name. The module remains in
  24740.  the library. When LIB copies the module to an object file, it adds the .OBJ
  24741.  extension to the module name and places the file in the current directory.
  24742.  
  24743.  Example
  24744.  
  24745.  The following example copies the module FLASH from the MAINLIB.LIB library
  24746.  to a file called FLASH.OBJ in the current directory. Upon completion of this
  24747.  command, MAINLIB.LIB still contains the module FLASH.
  24748.  
  24749.  LIB MAINLIB *FLASH;
  24750.  
  24751.  Move Command (-*)
  24752.  
  24753.  Use the move-command symbol (-*) to move an object module from the library
  24754.  file to an object file. This operation is equivalent to copying the module
  24755.  to an object file, then deleting the module from the library.
  24756.  
  24757.  Example
  24758.  
  24759.  The following example moves the module FLASH from the MAINLIB.LIB library to
  24760.  a file called FLASH.OBJ in the current directory. Upon completion of this
  24761.  command, MAINLIB.LIB no longer contains the module FLASH.
  24762.  
  24763.  LIB MAINLIB -*FLASH;
  24764.  
  24765.  This following example instructs LIB to move the module JUNK from the
  24766.  library GRAPHIC.LIB to an object file named JUNK.OBJ. The module JUNK is
  24767.  removed from the library in the process.
  24768.  
  24769.  LIB GRAPHIC -*JUNK *STAR, ,SHOW
  24770.  
  24771.  In the preceding example, the module STAR is copied from the library to an
  24772.  object file named STAR.OBJ; the module remains in the library. No
  24773.  cross-reference listing file is produced. The revised library is named
  24774.  SHOW.LIB. It contains all the modules in GRAPHIC.LIB except JUNK, which was
  24775.  removed by using the move-command symbol (-*). The original library,
  24776.  GRAPHIC.LIB, remains unchanged.
  24777.  
  24778.  
  24779.  Cross-Reference Listing File
  24780.  
  24781.  The  listfile field allows you to specify a filename for a cross-reference
  24782.  listing file. You can give the listing file any name and any extension. To
  24783.  create it outside your current directory, supply a path. Note that LIB does
  24784.  not assume any defaults for this field on the command line. If you do not
  24785.  specify a name for the file, the file is not created.
  24786.  
  24787.  A cross-reference listing file contains the following two lists:
  24788.  
  24789.    ■   An alphabetical list of all public symbols in the library.
  24790.  
  24791.    ■   Each symbol name is followed by the name of the module in which it is
  24792.        defined. The following example output shows that the public symbol ADD
  24793.        is contained in the module junk and the public symbols CALC, MAKE, and
  24794.        ROLL are contained in the module dice:
  24795.  
  24796.  
  24797.  ADD...............junk CALC..............dice
  24798.  MAKE..............dice ROLL..............dice
  24799.  
  24800.    ■   A list of the modules in the library.
  24801.  
  24802.    ■   Under each module name is an alphabetical listing of the public
  24803.        symbols defined in that module. The following example output shows
  24804.        that the module dice contains the public symbols CALC, MAKE, and ROLL
  24805.        and the module junk contains the public symbol ADD:
  24806.  
  24807.  
  24808.  dice Offset: 00000010H Code and data size: 621H
  24809.  
  24810.   CALC MAKE ROLL
  24811.  
  24812.  junk Offset: 00000bc0H Code and data size: 118H
  24813.  
  24814.   ADD
  24815.  
  24816.  
  24817.  
  24818.  New Library
  24819.  
  24820.  If you specify a name in the  newlibrary field, LIB gives this name to the
  24821.  modified library it creates. This optional field is only used if you specify
  24822.  commands to change the library.
  24823.  
  24824.  If you leave this field blank, the original library is renamed with a .BAK
  24825.  extension and the modified library receives the original name.
  24826.  
  24827.  
  24828.  LIB Prompts
  24829.  
  24830.  If you type LIB at the DOS command line, the library manager prompts you for
  24831.  the input it needs by displaying the following four messages, one at a time:
  24832.  
  24833.  Library name:
  24834.  Operations:
  24835.  List file:
  24836.  Output library:
  24837.  
  24838.  The input for each prompt corresponds to each field of the LIB command.
  24839.  See the previous sections for descriptions of each LIB command field.
  24840.  
  24841.  
  24842.  LIB waits for you to respond to each prompt before printing the next prompt.
  24843.  If you notice that you have entered an incorrect response to a previous
  24844.  prompt, press Ctrl+C to exit LIB and begin again.
  24845.  
  24846.  
  24847.  Extending Lines
  24848.  
  24849.  If you have many operations to perform during a library session, use the
  24850.  ampersand symbol (&) to extend the operations line. Type the ampersand
  24851.  symbol after the name of an object module or object file; do not put the
  24852.  ampersand between a command symbol and a name.
  24853.  
  24854.  The ampersand causes LIB to display the Operations prompt again, allowing
  24855.  you to specify more operations.
  24856.  
  24857.  
  24858.  Default Responses
  24859.  
  24860.  Press the Enter key to choose the default response for the current prompt.
  24861.  Type a semicolon (;) and press Enter after any response except "Library
  24862.  name" to select default responses for all remaining prompts.
  24863.  
  24864.  The following list shows the defaults for LIB prompts:
  24865.  
  24866.  Operations        No operation; no change to library file
  24867.  List file         NUL; no listing file is produced
  24868.  Output library    The current library name
  24869.  
  24870.  
  24871.  LIB Response File
  24872.  
  24873.  Using a response file lets you conduct the library session without typing
  24874.  responses to prompts at the keyboard. To run LIB with a response file, you
  24875.  must first create the response file. Then type the following at the DOS
  24876.  command line:
  24877.  
  24878.  LIB @ responsefile
  24879.  
  24880.  The  responsefile is the name of a response file. Specify a
  24881.  path if the response file is not in the current directory.
  24882.  
  24883.  You can also enter @ responsefile at any position on a command line or after
  24884.  any of the prompts. The input from the response file is treated exactly as
  24885.  if it had been entered on a command line or after the prompts. A new-line
  24886.  character in the response file is treated the same as pressing the Enter key
  24887.  in response to a prompt.
  24888.  
  24889.  A response file uses one text line for each prompt. Responses must appear
  24890.  in the same order as the command prompts appear. Use command symbols
  24891.  in the response file the same way you would use responses typed on the
  24892.  keyboard. You can type an ampersand (&) at the end of the response to
  24893.  the Operations prompt, for instance, and continue typing operations on
  24894.  the next line.
  24895.  
  24896.  When you run LIB with a response file, the prompts are displayed with the
  24897.  responses from the response file. If the response file does not contain
  24898.  responses for all the prompts, LIB uses the default responses.
  24899.  
  24900.  Example
  24901.  
  24902.  Assume that a response file named RESPONSE in the directory B:\PROJ contains
  24903.  the following lines:
  24904.  
  24905.  GRAPHIC
  24906.  +CIRCLE+WAVE-WAVE*FLASH
  24907.  GRAPHIC.LST
  24908.  
  24909.  If you invoke LIB with the following command line, then LIB deletes the
  24910.  module WAVE from the library GRAPHIC.LIB, copies the module FLASH into an
  24911.  object file named FLASH.OBJ, appends the object files CIRCLE.OBJ and
  24912.  WAVE.OBJ as the last two modules in the library, and creates a
  24913.  cross-reference listing file named GRAPHIC.LST.
  24914.  
  24915.  LIB @B:\PROJ\RESPONSE
  24916.  
  24917.  
  24918.  
  24919.  Invoking and Using LINK
  24920.  
  24921.  This section describes how to use the Microsoft Segmented-Executable Linker
  24922.  (LINK). LINK allows you to link object files with appropriate libraries to
  24923.  create an executable file or Quick library.
  24924.  
  24925.  You can invoke LINK in several ways. If you are working in the QBX
  24926.  environment and have selected the Make EXE file option from the Run menu,
  24927.  LINK is automatically called after your program is compiled by BASIC. If you
  24928.  are compiling and linking your program from the command line, you must
  24929.  invoke LINK separately after compiling your program with BC. You can also
  24930.  invoke LINK and specify LINK options from within a MAKEFILE (described in
  24931.  Chapter 20, "Using NMAKE").
  24932.  
  24933.  Regardless of how you invoke LINK, you may press Ctrl+C at any time to
  24934.  terminate a LINK operation and exit to the operating system.
  24935.  
  24936.  You can specify the input required for the LINK command in one of three
  24937.  ways:
  24938.  
  24939.    ■   By placing it on the command line.
  24940.  
  24941.    ■   By responding to prompts.
  24942.  
  24943.    ■   By specifying a file containing responses to prompts. This type of
  24944.        file is known as a "response file."
  24945.  
  24946.  
  24947.  This section describes how to invoke link from the command line. For
  24948.  information about responding to prompts or using a response file, see
  24949.  the sections "LINK Prompts" and "LINK Response File" later in this chapter.
  24950.  
  24951.  
  24952.  The command-line syntax for the LINK command is as follows:
  24953.  
  24954.  LINK
  24955.    [options%]@AE@%  objfiles[, [exefile] [, [mapfile%]@AE@%[, [libraries]
  24956.    [, [deffile]] ] ] ] ]    [;]
  24957.  
  24958.  A module-definition file is needed only for OS/2 protected mode and
  24959.  Microsoft Windows programs (not compatible with BASIC programs); however,
  24960.  this prompt is still issued when you are linking DOS programs.
  24961.  
  24962.  The command line fields are described fully in the sections that follow.
  24963.  
  24964.  A comma must separate each command-line field from the next. You may omit
  24965.  the text from any field (except the required  objfiles), but you must
  24966.  include the comma. A semicolon may end the command line after any field,
  24967.  causing LINK to use defaults for the remaining fields.
  24968.  
  24969.  The following example causes LINK to load and link the object modules
  24970.  SPELL.OBJ, TEXT.OBJ, DICT.OBJ, and THES.OBJ, and to search for unresolved
  24971.  references in the library XLIB.LIB as well as in the default library created
  24972.  during setup.
  24973.  
  24974.  LINK SPELL+TEXT+DICT+THES, ,SPELLIST, XLIB.LIB;
  24975.  By default, the executable file produced by LINK is named SPELL.EXE. LINK
  24976.  also produces a map file, SPELLIST.MAP. The semicolon at the end of the line
  24977.  tells NMAKE to accept the default module-definition file (NUL.DEF).
  24978.  
  24979.  The following example produces a map file named SPELL.MAP because a comma
  24980.  appears as a placeholder for the map file specified on the command line:
  24981.  
  24982.  LINK SPELL,,;
  24983.  
  24984.  The following example does not produce a map file because commas do not
  24985.  appear as placeholders for the map file specified:
  24986.  
  24987.  LINK SPELL,;
  24988.  LINK SPELL;
  24989.  
  24990.  The following example causes LINK to link the three files MAIN.OBJ,
  24991.  GETDATA.OBJ, and PRINTIT.OBJ into an executable file. A map file named
  24992.  MAIN.MAP is also produced:
  24993.  
  24994.  LINK MAIN+GETDATA+PRINTIT, , MAIN;
  24995.  
  24996.  
  24997.  Default Filename Extensions
  24998.  
  24999.  You can use any combination of uppercase and lowercase letters for the
  25000.  filenames you specify on the LINK command line or give in response to the
  25001.  LINK command prompts.
  25002.  
  25003.  You can override the default extension for a particular command-line
  25004.  field or prompt by specifying a different extension. To enter a filename
  25005.  that has no extension, type the name followed by a period.
  25006.  
  25007.  
  25008.  
  25009.  Choosing Defaults
  25010.  
  25011.  If you include a comma (to indicate where a field would be) but do not put a
  25012.  filename before the comma, then LINK selects the default for that field.
  25013.  However, if you use a comma to include the  mapfile field (but do not
  25014.  include a name), then LINK creates a map file. This file has the same base
  25015.  name as the executable file. Use NUL for the map filename if you do not want
  25016.  to produce a map file.
  25017.  
  25018.  You can also select default responses by using a semicolon (;). The
  25019.  semicolon tells LINK to use the defaults for all remaining fields. Anything
  25020.  after the semicolon is ignored. If you do not give all filenames on the
  25021.  command line or if you do not end the command line with a semicolon, LINK
  25022.  prompts you for the files you omitted. Descriptions of these prompts are
  25023.  given in the following section.
  25024.  
  25025.  If you do not specify a drive or directory for a file, LINK assumes that the
  25026.  file is on the current drive and directory. If you want LINK to create files
  25027.  in a location other than the current drive and directory, you must specify
  25028.  the new drive and directory for each such file on the command line.
  25029.  
  25030.  
  25031.  LINK Options
  25032.  
  25033.  The  linkoptions field contains command-line options to LINK. You may
  25034.  specify command-line options after any field, but before the comma that
  25035.  terminates the field. You do not have to give any options when you run LINK.
  25036.  See the sections that follow for individual descriptions of command-line
  25037.  link options.
  25038.  
  25039.  This section explains how to use LINK options to specify and control the
  25040.  tasks performed by LINK. When you use the LINK command line to invoke
  25041.  LINK, you may put options at the end of the line or after individual
  25042.  fields on the line. Options, however, must immediately precede the comma
  25043.  that separates each field from the next.
  25044.  
  25045.  If you respond to the individual prompts for the LINK command, you may
  25046.  specify LINK options at the end of any response. When you use more than one
  25047.  option, you can either group the options at the end of a single response or
  25048.  distribute the options among several responses. Every option must begin with
  25049.  the slash character (/) or a dash (-), even if other options precede it on
  25050.  the same line.
  25051.  
  25052.  In a response file, options may appear on a line by themselves or after
  25053.  individual response lines.
  25054.  
  25055.  
  25056.  Abbreviations
  25057.  
  25058.  Because LINK options are named according to their functions, some of their
  25059.  names are quite long. You can abbreviate the options to save space and
  25060.  effort. Be sure that your abbreviation is unique so that LINK can determine
  25061.  which option you want. The minimum legal abbreviation for each option is
  25062.  indicated in the description of that option listed in the sections that
  25063.  follow.
  25064.  
  25065.  Abbreviations must begin with the first letter of the name and must be
  25066.  continuous through the last letter typed. No spaces or transpositions are
  25067.  allowed. Options may be entered in uppercase or lowercase letters.
  25068.  
  25069.  
  25070.  Numeric Arguments
  25071.  
  25072.  Some LINK options take numeric arguments. A numeric argument can be any of
  25073.  the following:
  25074.  
  25075.    ■   A decimal number from 0 to 65,535.
  25076.  
  25077.    ■   An octal number from 00 to 0177777. A number is interpreted as octal
  25078.        if it starts with 0. For example, the number "10" is interpreted as a
  25079.        decimal number, but the number "010" is interpreted as an octal
  25080.        number, equivalent to 8 in decimal.
  25081.  
  25082.    ■   A hexadecimal number from 0X0 to 0XFFFF. A number is interpreted as
  25083.        hexadecimal if it starts with 0X. For example, "0X10" is a hexadecimal
  25084.        number, equivalent to 16 in decimal.
  25085.  
  25086.  
  25087.  
  25088.  LINK Environment Variable
  25089.  
  25090.  You can use the LINK environment variable to cause certain options to be
  25091.  used each time you link. LINK checks the environment variable for options if
  25092.  the variable exists.
  25093.  
  25094.  LINK expects to find options listed in the variable exactly as you would
  25095.  type them on the command line. It does not accept any other arguments; for
  25096.  instance, including filenames in the environment variable causes the error
  25097.  message Unrecognized option name.
  25098.  
  25099.  Each time you link, you can specify other options in addition to those
  25100.  in the LINK environment variable. If you enter the same option on the
  25101.  command line and in the environment variable, LINK ignores the redundant
  25102.  option. If the options conflict, however, the command-line option overrides
  25103.  the effect of the environment-variable option. For example, the
  25104.  command-line option /SE:512 cancels the effect of the environment-variable
  25105.  option /SE:256.
  25106.  
  25107.  
  25108.  Note
  25109.  
  25110.  Unless you override it on the command line, the only way to prevent an
  25111.  option in the environment variable from being used is to reset the
  25112.  environment variable itself.
  25113.  
  25114.  
  25115.  Examples
  25116.  
  25117.  In the following example, the file TEST.OBJ is linked with the options /NOI,
  25118.  /SE:256, and /CO:
  25119.  
  25120.  SET LINK=/NOI /SE:256 /CO
  25121.  LINK TEST;
  25122.  
  25123.  In the next example, the file PROG.OBJ is then linked with the option /NOD,
  25124.  in addition to /NOI, /SE:256, and /CO (note that the second /CO option is
  25125.  ignored):
  25126.  
  25127.  LINK /NOD /CO PROG;
  25128.  
  25129.  
  25130.  Valid LINK Options
  25131.  
  25132.  LINK provides many options that can be used to link programs written in
  25133.  several Microsoft languages. While most are valid for BASIC programs,
  25134.  several should not be used. Table 18.1 lists those options that are valid
  25135.  for BASIC programs.
  25136.  
  25137.  
  25138.  
  25139.  Invalid LINK Options
  25140.  
  25141.  Not all options of the LINK command are suitable for use with BASIC
  25142.  programs. Table 18.2 lists options that do not have an effect or that should
  25143.  not be used with BASIC programs.
  25144.  
  25145.  
  25146.  Aligning Segment Data (/A:size)
  25147.  
  25148.  This option directs LINK to align segment data in the executable file along
  25149.  with the boundaries specified by  size. The  size argument must be a power
  25150.  of two. For example, /A:16 indicates an alignment boundary of 16 bytes. The
  25151.  default alignment for OS/2 application and dynamic-link segments is 512.
  25152.  This option is used for linking Windows applications or protected-mode
  25153.  programs.
  25154.  
  25155.  
  25156.  Running in Batch Mode (/BA)
  25157.  
  25158.  By default, LINK prompts you for a new path whenever it cannot find a
  25159.  library that it has been directed to use. It also prompts you if it cannot
  25160.  find an object file that it expects to find on a removable disk. If you use
  25161.  the /BA option, however, LINK does not prompt you for any libraries or
  25162.  object files that it cannot find. Instead, LINK generates an error or
  25163.  warning message, if appropriate. In addition, when you use /BA, LINK does
  25164.  not display its copyright banner, nor does it echo commands from response
  25165.  files. This option does not prevent LINK from prompting for command-line
  25166.  arguments. You can prevent such prompting only by using a semicolon on the
  25167.  command line or in a response file.
  25168.  
  25169.  Using this option may result in unresolved external references. It is
  25170.  intended primarily for use with batch or NMAKE files that link many
  25171.  executable files with a single command and to prevent LINK operation from
  25172.  halting.
  25173.  
  25174.  
  25175.  Note
  25176.  
  25177.  In earlier versions of LINK, the /BATCH option was abbreviated to /B.
  25178.  
  25179.  
  25180.  Preparing for Debugging (/CO)
  25181.  
  25182.  The /CO option is used to prepare for debugging with the Microsoft CodeView
  25183.  window-oriented debugger. This option tells LINK to prepare a special
  25184.  executable file containing symbolic data and line-number information.
  25185.  
  25186.  Object files linked with the /CO option must first be compiled with the /Zi
  25187.  option, which is described in Chapter 16, "Compiling With BC."
  25188.  
  25189.  You can run this executable file outside the CodeView debugger; the extra
  25190.  data in the file is ignored. To keep file size to a minimum, however, use
  25191.  the special-format-executable file only for debugging; then you can link a
  25192.  separate version without the /CO option after the program is debugged.
  25193.  
  25194.  
  25195.  Ordering Segments (/DO)
  25196.  
  25197.  The /DO option forces a special ordering on segments. This option is
  25198.  automatically enabled by a special object-module record in Microsoft BASIC
  25199.  libraries. If you are linking to one of these libraries, then you do not
  25200.  need to specify this option.This option is also enabled by assembly modules
  25201.  that use the MASM directive .DOSSEG.
  25202.  
  25203.  The /DO option forces
  25204.  segments to be ordered as follows:
  25205.  
  25206.  
  25207.     1. All segments with a class name ending in CODE
  25208.  
  25209.     2. All other segments outside DGROUP
  25210.  
  25211.     3. DGROUP segments, in the following order:
  25212.  
  25213.           a. Any segments of class BEGDATA (this class name
  25214.             reserved for Microsoft use)
  25215.  
  25216.         b. Any segments not of class BEGDATA, BSS, or
  25217.             STACK
  25218.  
  25219.         c. Segments of class BSS
  25220.  
  25221.         d. Segments of class STACK
  25222.  
  25223.  
  25224.  When the /DO option is in effect LINK initializes two special variables as
  25225.  follows:
  25226.  
  25227.  _edata = DGROUP : BSS
  25228.  _end = DGROUP : STACK
  25229.  
  25230.  The variables _edata and _end have special meanings for the Microsoft C and
  25231.  FORTRAN compilers, so it is not wise to give these names to your own program
  25232.  variables. Assembly modules can reference these variables but should not
  25233.  change them.
  25234.  
  25235.  
  25236.  Packing Executable Files (/E)
  25237.  
  25238.  The /E option directs LINK to remove sequences of repeated bytes (typically
  25239.  null characters) and to optimize the load-time-relocation table before
  25240.  creating the executable file. (The load-time-relocation table is a table of
  25241.  references, relative to the start of the program. Each reference changes
  25242.  when the executable image is loaded into memory and an actual address for
  25243.  the entry point is assigned.)
  25244.  
  25245.  Executable files linked with this option may be smaller, and thus load
  25246.  faster, than files linked without this option. Programs with many load-time
  25247.  relocations (about 500 or more) and long streams of repeated characters are
  25248.  usually shorter if packed. The /E option, however, does not always save a
  25249.  significant amount of disk space and sometimes may increase file size. LINK
  25250.  notifies you if the packed file is larger than the unpacked file.
  25251.  
  25252.  
  25253.  Optimizing Far Calls (/F)
  25254.  
  25255.  The /F option directs LINK to optimize far calls to procedures that lie in
  25256.  the same segment as the caller. Using the /F option may result in slightly
  25257.  faster code and smaller executable-file size. It should be used with the
  25258.  /PAC option for significant results. By default, the /F option is off.
  25259.  
  25260.  For example, a medium- or large-model program may include a machine
  25261.  instruction that makes a far call to a procedure in the same segment.
  25262.  Because the instruction and the procedure it calls have the same segment
  25263.  address, only a near call is truly necessary. A near-call instruction does
  25264.  not require an entry in the relocation table as does a far-call instruction.
  25265.  In this situation, use of /F (together with /PAC) would result in a smaller
  25266.  executable file because the relocation table is smaller. Such files load
  25267.  faster.
  25268.  
  25269.  When /F has been specified, LINK optimizes code by removing the
  25270.  following instruction:
  25271.  
  25272.  call FAR label
  25273.  
  25274.  LINK then substitutes the sequence:
  25275.  
  25276.  nop
  25277.  push cs
  25278.  call NEAR label
  25279.  
  25280.  Upon execution, the called procedure still returns with a far-return
  25281.  instruction. Because the code segment and the near address are on the stack,
  25282.  however, the far return is executed correctly. The nop (no-op) instruction
  25283.  appears so that exactly 5 bytes replace the 5-byte far-call instruction;
  25284.  LINK may in some cases place nop at the beginning of the sequence.
  25285.  
  25286.  The /F option has no effect on programs that make only near calls. Of the
  25287.  high-level Microsoft languages, only small- and compact-model C programs use
  25288.  near calls.
  25289.  
  25290.  
  25291.  Note
  25292.  
  25293.  There is a small risk involved with the /F option: LINK may mistakenly
  25294.  translate a byte in a code segment that happens to have the far-call opcode
  25295.  (9A hexadecimal). If a program linked with /F inexplicably fails, then you
  25296.  may want to try linking with this option off. Object modules produced by
  25297.  Microsoft high-level languages, however, should be safe from this problem
  25298.  because relatively little immediate data is stored in code segments.
  25299.  
  25300.  In general, assembly language programs are also relatively safe for use with
  25301.  the /F option, as long as they do not involve advanced system-level code,
  25302.  such as might be found in operating systems or interrupt handlers.
  25303.  
  25304.  
  25305.  Viewing the Options List (/HE)
  25306.  
  25307.  The /HE option causes LINK to display a list of its options on the screen.
  25308.  This gives you a convenient reminder of the options.
  25309.  
  25310.  When you use this option, LINK ignores any other input you give and does not
  25311.  create an executable file.
  25312.  
  25313.  
  25314.  Preparing for Incremental Linking (/INC)
  25315.  
  25316.  The /INC option prepares a file for subsequent linking with ILINK. The use
  25317.  of this option produces a .SYM file and an .ILK file, each containing extra
  25318.  information needed by ILINK. Note that this option is not compatible with
  25319.  the /E option.
  25320.  
  25321.  
  25322.  Displaying LINK Process Information (/INF)
  25323.  
  25324.  The /INF option tells LINK to display information about the linking process,
  25325.  including the phase of linking and the names of the object files being
  25326.  linked. This option is useful if you want to determine the locations of the
  25327.  object files being linked and the order in which they are linked.
  25328.  
  25329.  Output from this option is sent to the standard error output.
  25330.  
  25331.  Example
  25332.  
  25333.  The following is a sample of LINK output when the /INF option is specified
  25334.  on the LINK command line:
  25335.  
  25336.  **** PARSE DEFINITIONS FILE ****
  25337.  **** PASS ONE ****
  25338.  HELLO.OBJ(HELLO.OBJ)
  25339.  **** LIBRARY SEARCH ****
  25340.  BRT70ENR.LIB(..\rt\rtmdata.asm)
  25341.  BRT70ENR.LIB(..\rt\rtmload.asm)
  25342.  BRT70ENR.LIB(..\rt\rmessage.asm)
  25343.  BRT70ENR.LIB(fixups.ASM)
  25344.  BRT70ENR.LIB(..\rt\rtmint1.asm)
  25345.  BRT70ENR.LIB(C:\TEMP\B6.)
  25346.  **** ASSIGN ADDRESSES ****
  25347.  **** PASS TWO ****
  25348.  BRT70ENR.LIB(..\rt\rtmdata.asm)
  25349.  BRT70ENR.LIB(..\rt\rtmload.asm)
  25350.  BRT70ENR.LIB(..\rt\rmessage.asm)
  25351.  BRT70ENR.LIB(fixups.ASM)
  25352.  BRT70ENR.LIB(..\rt\rtmint1.asm)
  25353.  BRT70ENR.LIB(C:\TEMP\B6.)
  25354.  **** WRITING EXECUTABLE ****
  25355.  Segments 36
  25356.  Groups 1
  25357.  Bytes in symbol table 10546
  25358.  
  25359.  
  25360.  Including Line Numbers in the Map File (/LI)
  25361.  
  25362.  You can include the line numbers and associated addresses of your source
  25363.  program in the map file by using the /LI option. This option is primarily
  25364.  useful if you will be debugging with the SYMDEB debugger included with
  25365.  earlier releases of Microsoft language products.
  25366.  
  25367.  Ordinarily the map file does not contain line numbers. To produce a map file
  25368.  with line numbers, you must give LINK an object file (or files) with
  25369.  line-number information. (The /Zd and /Zi options of the compiler direct the
  25370.  compiler to include line numbers in the object file.) If you give LINK an
  25371.  object file without line-number information, the /LI option has no effect
  25372.  
  25373.  The /LI option forces LINK to create a map file even if you did not
  25374.  explicitly tell LINK to create a map file. By default, the file is
  25375.  given the same base name as the executable file plus the extension .MAP.
  25376.  You can override the default name by specifying a new map file on the
  25377.  LINK command line or in response to the "List File" prompt.
  25378.  
  25379.  
  25380.  Listing Public Symbols (/M)
  25381.  
  25382.  You can list all public (global) symbols defined in the object file(s) by
  25383.  using the /M option. When you invoke LINK with the /M option, the map file
  25384.  contains a list of all the symbols sorted by name and a list of all the
  25385.  symbols sorted by address. LINK sorts the maximum number of symbols that can
  25386.  be sorted in available memory. If you do not use this option, the map file
  25387.  contains only a list of segments.
  25388.  
  25389.  When you use this option, the default for the  mapfile field or "List File"
  25390.  prompt response is no longer NUL. Instead, the default is a name that
  25391.  combines the base name of the executable file with a .MAP extension. You may
  25392.  still specify NUL in the  mapfile field (which indicates that no map file is
  25393.  to be generated); if you do, the /M option has no effect.
  25394.  
  25395.  
  25396.  Ignoring Default Libraries (/NOD:filename)
  25397.  
  25398.  The /NOD option tells LINK  not to search any library specified in the
  25399.  object file to resolve external references. If you specify  filename, then
  25400.  LINK searches all libraries specified in the object file except for
  25401.  filename.
  25402.  
  25403.  In general, higher-level language programs do not work correctly without a
  25404.  standard library. Therefore, if you use the /NOD option, you should
  25405.  explicitly specify the name of a standard library in the  libraries field.
  25406.  
  25407.  
  25408.  Ignoring Extended Dictionary (/NOE)
  25409.  
  25410.  The /NOE option prevents LINK from searching the extended dictionary, which
  25411.  is an internal list of symbol locations that LINK maintains. Normally, LINK
  25412.  consults this list to speed up library searches. The effect of the /NOE
  25413.  option is to slow down LINK. You often need this option when a library
  25414.  symbol is redefined. Use /NOE if LINK issues the following error message:
  25415.  
  25416.  symbol  name multiply defined
  25417.  
  25418.  
  25419.  Disabling Far-Call Optimization (/NOF)
  25420.  
  25421.  This option is normally not necessary because far-call optimization
  25422.  (translation) is turned off by default. However, if an environment variable
  25423.  such as LINK turns on far-call translation automatically, you can use /NOF
  25424.  to turn far-call translation off again.
  25425.  
  25426.  
  25427.  Preserving Case Sensitivity (/NOI)
  25428.  
  25429.  By default, LINK treats uppercase letters and lowercase letters as
  25430.  equivalent. Thus, ABC, abc, and Abc are considered the same name. When you
  25431.  use the /NOI option, LINK distinguishes between uppercase letters and
  25432.  lowercase letters, and considers ABC, abc, and Abc to be three separate
  25433.  names. Because names in Microsoft BASIC are not case sensitive, this option
  25434.  can have minimal importance. You should not use the /NOI option when linking
  25435.  a protected-mode custom run-time module, or protected-mode program without
  25436.  the /O option.
  25437.  
  25438.  
  25439.  Suppressing the Sign-On Logo (/NOL)
  25440.  
  25441.  This option prevents the LINK sign-on banner from being displayed.
  25442.  
  25443.  
  25444.  Ordering Segments Without Inserting Null Bytes (/NON)
  25445.  
  25446.  This option directs LINK to arrange segments in the same order as they are
  25447.  arranged by the /DO option. The only difference is that the /DO option
  25448.  inserts 16 null bytes at the beginning of the _TEXT segment (if it is
  25449.  defined), whereas /NON does not insert these extra bytes.
  25450.  
  25451.  
  25452.  Disabling Segment Packing (/NOP)
  25453.  
  25454.  This option is normally not necessary because code-segment packing is turned
  25455.  off by default. However, if an environment variable such as LINK turns on
  25456.  code-segment packing automatically, you can use /NOP to turn segment packing
  25457.  off again.
  25458.  
  25459.  
  25460.  Setting the Overlay Interrupt (/O:number)
  25461.  
  25462.  By default, the interrupt number used for passing control to overlays is 63
  25463.  (3F hexadecimal). The /O option allows you to select a different interrupt
  25464.  number.
  25465.  
  25466.  The  number can be a decimal number from 0 to 255, an octal number from
  25467.  octal 0 to octal 0377, or a hexadecimal number from hexadecimal 0 to
  25468.  hexadecimal FF. Numbers that conflict with DOS interrupts can be used;
  25469.  however, their use is not advised.
  25470.  
  25471.  In general, you should not use /O with programs. The exception to this
  25472.  guideline would be a program that uses overlays and spawns another program
  25473.  that also uses overlays. In this case, each program should use a separate
  25474.  overlay-interrupt number, meaning that at least one of the programs should
  25475.  be compiled with /O.
  25476.  
  25477.  
  25478.  Packing Contiguous Segments (/PAC:number)
  25479.  
  25480.  The /PAC option affects code segments only in medium- and large-model
  25481.  programs. It is intended to be used with the /F option. It is not necessary
  25482.  to understand the details of the /PAC option in order to use it. You only
  25483.  need to know that this option, used in conjunction with /F, produces
  25484.  slightly faster and more compact code. The packing of code segments provides
  25485.  more opportunities for far-call optimization, which is enabled with /F. The
  25486.  /PAC option is off by default and can always be turned off with the /NOP
  25487.  option.
  25488.  
  25489.  The /PAC option directs LINK to group neighboring code segments. Segments
  25490.  in the same group are assigned the same segment address; offset addresses
  25491.  are adjusted upward accordingly. In other words, all items have the
  25492.  correct physical address whether the /PAC option is used or not. However,
  25493.  /PAC changes segment and offset addresses so that all items in a group
  25494.  share the same segment address.
  25495.  
  25496.  The  number field specifies the maximum size of groups formed by /PAC. LINK
  25497.  stops adding segments to a group as soon as it cannot add another segment
  25498.  without exceeding  number. At that point, LINK starts forming a new group.
  25499.  The default for  number is 65,530.
  25500.  
  25501.  Do not use this option if you have overlays. In addition, the /PAC option
  25502.  should not be used with assembly programs that make assumptions about the
  25503.  relative order of code segments. For example, the following assembly code
  25504.  attempts to calculate the distance between CSEG1 and CSEG2. This code would
  25505.  produce incorrect results when used with /PAC because /PAC causes the two
  25506.  segments to share the same segment address. Therefore, the procedure would
  25507.  always return 0.
  25508.  
  25509.  CSEG1 SEGMENT PARA PUBLIC 'CODE'
  25510.    .
  25511.    .
  25512.    .
  25513.    CSEG1 ENDS
  25514.  
  25515.    CSEG2 SEGMENT PARA PUBLIC 'CODE'
  25516.    ASSUME cs:CSEG2
  25517.  
  25518.    ; Return the length of CSEG1 in AX.
  25519.  
  25520.    codsize PROC NEAR
  25521.    mov ax,CSEG2 ; Load para address of CSEG2
  25522.    sub ax,CSEG1 ; Load para address of CSEG1
  25523.   mov cx,4     ; Load count, and convert
  25524.    shl ax,cl    ; distance from paragraphs
  25525.                 ; to bytes
  25526.    codsize ENDP
  25527.  
  25528.    CSEG2 ENDS
  25529.  
  25530.  
  25531.  Packing Contiguous Data Segments (/PACKD)
  25532.  
  25533.  This option only affects code segments in medium- and large-model programs
  25534.  and is safe with all Microsoft high-level language compilers. It behaves
  25535.  exactly like the /PAC option except that is applies to data segments, not
  25536.  code segments. LINK recognizes data segments as any segment definition with
  25537.  a class name that does not end in CODE. The adjacent data segment
  25538.  definitions are combined into the same physical segment up to the given
  25539.  limit. The default limit is 65,536.
  25540.  
  25541.  
  25542.  Padding Code Segments (/PADC:padsize)
  25543.  
  25544.  The /PADC option causes LINK to add filler bytes to the end of each code
  25545.  module for subsequent linking with ILINK. The option is followed by a colon
  25546.  and the number of bytes to add (a decimal radix is assumed, but you can
  25547.  specify octal or hexadecimal numbers by using a C-language prefix). Thus,
  25548.  the following adds an additional 256 bytes to each module:
  25549.  
  25550.  /PADCODE:256
  25551.  
  25552.  The default size for code-module padding is 0 bytes. To use this option, you
  25553.  must also specify the /INC option.
  25554.  
  25555.  
  25556.  Note
  25557.  
  25558.  Code padding is usually not necessary for large- and medium-model programs,
  25559.  but is recommended for small, compact, and mixed-memory model programs, and
  25560.  for assembly language programs in which code segments are grouped.
  25561.  
  25562.  
  25563.  Padding Data Segments (/PADD:padsize)
  25564.  
  25565.  The /PADD option performs a function similar to the /PADC option, except
  25566.  that it specifies padding for data segments (or data modules, if the program
  25567.  uses the small- or medium-memory model). The default size for data-segment
  25568.  padding is 16 bytes. To use the /PADD option, you must also specify the /INC
  25569.  option.
  25570.  
  25571.  
  25572.  Note
  25573.  
  25574.  If you specify too large a value for  padsize, you may exceed the 64K
  25575.  limitation on the size of the default data segment.
  25576.  
  25577.  
  25578.  Pausing During Linking (/PAU)
  25579.  
  25580.  The /PAU option tells LINK to pause before it writes the executable file to
  25581.  disk. This option is useful on machines without hard disks, where you might
  25582.  want to create the executable file on a new disk. Without the /PAU option,
  25583.  LINK performs the linking session from beginning to end without stopping.
  25584.  
  25585.  If you specify the /PAU option, LINK displays the following message before
  25586.  it creates the file:
  25587.  
  25588.   About to generate .EXE file
  25589.   Change diskette in drive  letter and press <ENTER>
  25590.  
  25591.  The  letter corresponds to the current drive. LINK resumes
  25592.  processing when you press Enter.
  25593.  
  25594.  
  25595.  Note
  25596.  
  25597.  Do not remove the disk that will receive the listing file or the disk used
  25598.  for the temporary file.
  25599.  
  25600.  Depending on how much memory is available, LINK may create a temporary disk
  25601.  file during processing, as described in the section "LINK Memory
  25602.  Requirements," later in this chapter and display the following message:
  25603.  
  25604.  Temporary file  tempfile has been created.
  25605.  Do not change diskette in drive,  letter
  25606.  
  25607.  If the file is created on the disk you plan to swap, press Ctrl+C to
  25608.  terminate the LINK session. Rearrange your files so that the temporary file
  25609.  and the executable file can be written to the same disk, then try linking
  25610.  again.
  25611.  
  25612.  
  25613.  Specifying OS/2 Window Type (/PM:type)
  25614.  
  25615.  The /PM option specifies the type of Presentation Manager window that the
  25616.  application can be run in. The argument  type can be one of
  25617.  the following:
  25618.  
  25619.  PM         Presentation Manager application.  The application uses the
  25620.             Presentation Manager API and must be executed in the Presen-
  25621.             tation Manager environment.  Not valid for BASIC programs.
  25622.  
  25623.  VIO        Presentation Manager-compatible application. This application
  25624.             can run in the Presentation Manager environment from a VIO
  25625.             window, or it can be run in a separate screen group. An
  25626.             application can be of this type if it uses the proper subset
  25627.             of OS/2 video, keyboard, and mouse functions supported in the
  25628.             Presentation Manager API.
  25629.  
  25630.             VIO applications written in BASIC are valid with the following
  25631.             restrictions: the application cannot support event handling or
  25632.             graphics. You can link your program with the NOEVENT.OBJ and
  25633.             NOGRAPH.OBJ stub files to remove these features.
  25634.  
  25635.  NOVIO      Application is not compatible with the Presentation Manager
  25636.             and must operate in a separate screen group (default). Valid
  25637.             for BASIC programs.
  25638.  
  25639.             This option can be used in place of the WINDOAPI, WINDOWCOMPAT,
  25640.             and NOTWINDOWCOMPAT keywords in the module-definition file.
  25641.  
  25642.  
  25643.  Creating a Quick Library (/Q)
  25644.  
  25645.  When you use this option, LINK will create a Quick library that can be used
  25646.  from within the QBX environment. For instructions on how to create a Quick
  25647.  library, see Chapter 19, "Creating and Using Quick Libraries."
  25648.  
  25649.  
  25650.  Setting Maximum Number of Segments (/SE:number)
  25651.  
  25652.  The /SE option controls the number of segments that LINK allows a program to
  25653.  have. The default is 128, but you can set  number to any value (decimal,
  25654.  octal, or hexadecimal) in the range 1-3072 (decimal).
  25655.  
  25656.  For each segment, LINK must allocate some space to keep track of segment
  25657.  information. By using a relatively low segment limit as a default (128),
  25658.  LINK is able to link faster and allocate less storage space.
  25659.  
  25660.  When you set the segment limit higher than 128, LINK allocates additional
  25661.  space for segment information. This option allows you to raise the segment
  25662.  limit for programs with a large number of segments. For programs with fewer
  25663.  than 128 segments, you can keep the storage requirements of LINK at the
  25664.  lowest level possible by setting  number to reflect the actual number of
  25665.  segments in the program. If the number of segments allocated is too high for
  25666.  the amount of memory available to LINK, LINK issues the following error
  25667.  message:
  25668.  
  25669.  segment limit set too high
  25670.  
  25671.  If this occurs, link the object files again, specifying a lower segment
  25672.  limit.
  25673.  
  25674.  
  25675.  Issuing Fixup Warnings (/W)
  25676.  
  25677.  This option directs LINK to issue a warning for each segment-relative fixup
  25678.  of location type  offset, such that the segment is contained within a group
  25679.  but is not at the beginning of the group. LINK will include the displacement
  25680.  of the segment from the group in determining the final value of the fixup,
  25681.  unlike DOS executable files. Use this option when linking protected-mode
  25682.  programs or Windows applications.
  25683.  
  25684.  To use this option with BASIC programs, you must also specify the /NOP
  25685.  option. This option should not be used to link custom run-time modules.
  25686.  
  25687.  
  25688.  Object Files
  25689.  
  25690.  The  objfiles field allows you to specify the names of the object files you
  25691.  are linking. At least one object filename is required. A space or plus sign
  25692.  (+) must separate each pair of object filenames. LINK automatically supplies
  25693.  the .OBJ extension when you give a filename without an extension. If your
  25694.  object file has a different extension or if it appears in another directory
  25695.  or on another disk, you must give the full name--including the extension and
  25696.  path--for the file to be found. If LINK cannot find a given object file, and
  25697.  the drive associated with the object file is a removable-disk drive, then
  25698.  LINK displays a message and waits for you to change disks.
  25699.  
  25700.  You may also specify one or more libraries in the  objfiles
  25701.  field. To enter a library in this field, make sure that you include the
  25702.  .LIB extension; otherwise, LINK assumes the .OBJ extension. Libraries
  25703.  entered in this field are called "load libraries" as opposed to regular
  25704.  libraries. LINK automatically links every object module in a load library;
  25705.  it does not search for unresolved external references first. The effect
  25706.  of entering a load library is exactly the same as if you had entered the
  25707.  names of all the library's object modules in the objfiles field.
  25708.  This feature is useful if you are developing software using many modules
  25709.  and wish to avoid typing the name of each module on the LINK command line.
  25710.  
  25711.  
  25712.  Executable File
  25713.  
  25714.  The  exefile field allows you to specify the name of the executable file. If
  25715.  the filename you give does not have an extension, LINK automatically adds
  25716.  .EXE as the extension (or .QLB if /Q is specified). You can give any
  25717.  filename you like; however, if you are specifying an extension, you should
  25718.  always use .EXE because the operating system expects executable files to
  25719.  have either this extension or the .COM extension.
  25720.  
  25721.  
  25722.  Map File
  25723.  
  25724.  The  mapfile field allows you to specify the name of the map file if you are
  25725.  creating one. To include public symbols and their addresses in the map file,
  25726.  specify the /M option on the LINK command line.
  25727.  
  25728.  If you specify a map filename without an extension, LINK automatically adds
  25729.  a .MAP extension. LINK creates the map file in the current working directory
  25730.  unless you specify a path for the map file.
  25731.  
  25732.  
  25733.  Libraries
  25734.  
  25735.  The  libraries field allows you to specify the name of one or more libraries
  25736.  that you want linked with the object file(s). When LINK finds the name of a
  25737.  library in this field, it treats the library as a "regular library" and
  25738.  links only those object modules needed to resolve external references.
  25739.  
  25740.  Each time you compile a source file for a high-level language, the compiler
  25741.  places the name of one or more libraries in the object file that it creates;
  25742.  LINK automatically searches for a library with this name (see the next
  25743.  section, "How LINK Searches for Libraries"). Because of this, you need not
  25744.  supply library names on the LINK command line unless you want to search
  25745.  libraries other than the default libraries or search for libraries in
  25746.  different locations.
  25747.  
  25748.  When you link your program with a library, LINK pulls any library modules
  25749.  that your program references into your executable file. If the library
  25750.  modules have external references to other library modules, your program is
  25751.  linked with those other library modules as well.
  25752.  
  25753.  
  25754.  
  25755.  How LINK Searches for Libraries
  25756.  
  25757.  LINK searches for libraries that are specified in either of the following
  25758.  ways:
  25759.  
  25760.    ■   In the  libraries field on the command line or in response to the
  25761.        "Libraries" prompt.
  25762.  
  25763.    ■   By an object module. BC writes the name of a default library in each
  25764.        object module it creates.
  25765.  
  25766.  
  25767.  Note
  25768.  
  25769.  The material in the following sections does not apply to libraries that LINK
  25770.  finds in the  objfiles field, on the command line or in response to the
  25771.  "Object Modules" prompt. Those libraries are treated simply as a series of
  25772.  object files, and LINK does not conduct extensive searches in such cases.
  25773.  
  25774.  
  25775.  Library Name with Path
  25776.  
  25777.  If the library name includes a path, LINK searches only that directory for
  25778.  the library. Libraries specified by object modules (that is, default
  25779.  libraries) normally do not include a path.
  25780.  
  25781.  
  25782.  Library Name Without Path
  25783.  
  25784.  If the library name does not include a path, LINK searches the following
  25785.  locations, in the order shown, to find the library file:
  25786.  
  25787.     1. The current directory.
  25788.  
  25789.     2. Any paths or drive names that you give on the command line or type in
  25790.        response to the "Libraries" prompt, in the order in which they appear.
  25791.  
  25792.     3. The locations given by the LIB environment variable.
  25793.  
  25794.  
  25795.  Because object files created by BC contain the names of all the standard
  25796.  libraries you need, you are not required to specify a library on the LINK
  25797.  command line or in response to the LINK "Libraries" prompt unless you want
  25798.  to do one of the following:
  25799.  
  25800.    ■   Add the names of additional libraries to be searched.
  25801.  
  25802.    ■   Search for libraries in different locations.
  25803.  
  25804.    ■   Override the use of one or more default libraries.
  25805.  
  25806.  
  25807.  For example, if you have developed your own libraries, you might want to
  25808.  include one or more of them as additional libraries when you link them.
  25809.  
  25810.  
  25811.  Searching Additional Libraries
  25812.  
  25813.  You can tell LINK to search additional libraries by specifying one or more
  25814.  library files on the command line or in response to the "Libraries" prompt.
  25815.  LINK searches these libraries in the order you specify  before it searches
  25816.  default libraries.
  25817.  
  25818.  LINK automatically supplies the .LIB extension if you omit it from a
  25819.  library filename. If you want to link a library file that has a different
  25820.  extension, be sure to specify the extension.
  25821.  
  25822.  For example, suppose that you want LINK to search the NEWLIBV3.LIB library
  25823.  before it searches the default library BCL70AFR.LIB. You would type LINK to
  25824.  start LINK, and then respond to the prompts as follows:
  25825.  
  25826.  Object Modules .OBJ:  SPELL TEXT DICT THES
  25827.   Run File SPELL.EXE:
  25828.   List File NUL.MAP:
  25829.   Libraries .LIB:  C:\TESTLIB\NEWLIBV3
  25830.  
  25831.  This example links four object modules to create an executable filename
  25832.  SPELL.EXE. LINK searches NEWLIBV3.LIB before searching BCL70AFR.LIB to
  25833.  resolve references. To locate NEWLIBV3.LIB and the default libraries,
  25834.  LINK searches the current working directory, then the C:\TESTLIB\
  25835.  directory, and finally the locations given by the LIB environment variable.
  25836.  
  25837.  
  25838.  Searching Different Locations for Libraries
  25839.  
  25840.  You can tell LINK to search additional locations for libraries by giving a
  25841.  drive name or path in the  libraries field on the command line or in
  25842.  response to the "Libraries" prompt. You can specify up to 32 additional
  25843.  paths. If you give more than 32 paths, LINK ignores the additional paths
  25844.  without displaying an error message.
  25845.  
  25846.  
  25847.  Overriding Libraries Named in Object Files
  25848.  
  25849.  If you do not want to link with the library whose name is included in the
  25850.  object file, you can give the name of a different library instead. You might
  25851.  need to specify a different library name in the following cases:
  25852.  
  25853.    ■   You assigned a "custom" name to a standard library when you set up
  25854.        your libraries.
  25855.  
  25856.    ■   You want to link with a library that supports a different math package
  25857.        than the math package you gave on the compiler command line (or the
  25858.        default).
  25859.  
  25860.  
  25861.  If you specify a new library name on the LINK command line, LINK searches
  25862.  the new library to resolve external references before it searches the
  25863.  library specified in the object file.
  25864.  
  25865.  If you want LINK to ignore the library whose name is included in the object
  25866.  file, you must use the /NOD option. This option tells LINK to ignore the
  25867.  default-library information that is encoded in the object files created by
  25868.  high-level language compilers. Use this option with caution; for more
  25869.  information, see the section "Ignoring Default Libraries (/NOD:filename)"
  25870.  earlier in this chapter.
  25871.  
  25872.  
  25873.  
  25874.  Module-Definition File
  25875.  
  25876.  The  deffile field allows you to specify the name of a module-definition
  25877.  file (OS/2 protected-mode or Microsoft Windows programs only). Leave this
  25878.  field blank if you are linking a real mode program. The use of a
  25879.  module-definition file is optional for applications, but is required for
  25880.  dynamic-link libraries.
  25881.  
  25882.  
  25883.  LINK Prompts
  25884.  
  25885.  If you want LINK to prompt you for input, start LINK by typing the following
  25886.  at the system prompt:
  25887.  
  25888.   LINK
  25889.  
  25890.  LINK also displays prompts if you type an incomplete command line that
  25891.  does not end with a semicolon or if a response file (described in the
  25892.  section "LINK Response File" later in this chapter) is missing any required
  25893.  responses.
  25894.  
  25895.  
  25896.  LINK prompts you for the input it needs by displaying the following lines,
  25897.  one at a time. The items in square brackets are the defaults LINK applies if
  25898.  you press Enter in response to the prompt. (You must supply at least one
  25899.  object filename for the "Object Modules" prompt.) LINK waits for you to
  25900.  respond to each prompt before it displays the next one.
  25901.  
  25902.  Object Modules [.OBJ]:
  25903.   Run File basename.[EXE]:
  25904.   List File [NUL.MAP]:
  25905.   Libraries [.LIB]:
  25906.   Definitions File [NUL.DEF]:
  25907.  
  25908.  Note that the default for the "Run File" prompt is the base name of the
  25909.  first object file with the .EXE extension. To select the default response
  25910.  to the current prompt, press the Enter key without giving a filename.
  25911.  The next prompt appears.
  25912.  
  25913.  To select default responses to the current prompt and all remaining prompts,
  25914.  type a semicolon (;) and press Enter. After you type a semicolon, you cannot
  25915.  respond to any of the remaining prompts for that link session. This saves
  25916.  time when you want the default responses. Note, however, that you cannot
  25917.  enter only a semicolon in response to the "Object Modules" prompt because
  25918.  there is no default response for that prompt; LINK requires the name of at
  25919.  least one object file.
  25920.  
  25921.  
  25922.  LINK Response File
  25923.  
  25924.  A response file contains responses to the LINK prompts. The responses must
  25925.  be in the same order as the LINK prompts discussed in the previous section.
  25926.  Each new response must appear on a new line or must begin with a comma;
  25927.  however, you can extend long responses across more than one line by typing a
  25928.  plus sign (+) as the last character of each incomplete line. You may give
  25929.  options at the end of any response or place them on one or more separate
  25930.  lines.
  25931.  
  25932.  LINK treats the input from the response file just as if you had entered it
  25933.  in response to prompts or on a command line. It treats any new-line
  25934.  character in the response file as if you had pressed Enter in response to a
  25935.  prompt or included a comma in a command line. For compatibility with OS/2
  25936.  versions of LINK, it is recommended that all LINK response files end with a
  25937.  semicolon after the last line.
  25938.  
  25939.  To use LINK with a response file, create the response file, then type the
  25940.  following command:
  25941.  
  25942.   LINK @ responsefile
  25943.  
  25944.  Here  responsefile specifies the name or path of the
  25945.  response file for LINK. You can also enter the name of a response file,
  25946.  preceded by an "at" sign (@), after any LINK command prompt or at any
  25947.  position in the LINK command line; in this case, the response file completes
  25948.  the remaining input.
  25949.  
  25950.  
  25951.  Options and Command Characters
  25952.  
  25953.  You can use options and command characters in the response file in the same
  25954.  way as you would use them in responses you type at the keyboard. For
  25955.  example, if you type a semicolon on the line of the response file
  25956.  corresponding to the "Run File" prompt, LINK uses the default responses for
  25957.  the executable file and for the remaining prompts.
  25958.  
  25959.  
  25960.  Prompts
  25961.  
  25962.  When you enter the LINK command with a response file, each LINK prompt is
  25963.  displayed on your screen with the corresponding response from your response
  25964.  file. If the response file does not include a line with a filename,
  25965.  semicolon, or carriage return for each prompt, LINK displays the appropriate
  25966.  prompt and waits for you to enter a response. When you type an acceptable
  25967.  response, LINK continues.
  25968.  
  25969.  
  25970.  Example
  25971.  
  25972.  Assume that the following response file is named SPELL.LNK:
  25973.  
  25974.  SPELL+TEXT+DICT+THES /PAUSE /MAP
  25975.  SPELL
  25976.  SPELLIST
  25977.  XLIB.LIB;
  25978.  
  25979.  You can type the following command to run LINK and tell it to use the
  25980.  responses in SPELL.LNK:
  25981.  
  25982.  LINK @SPELL.LNK
  25983.  
  25984.  The response file tells LINK to load the four object files SPELL, TEXT,
  25985.  DICT, and THES. LINK produces an executable file named SPELL.EXE and a map
  25986.  file named SPELLIST.MAP. The /PAU option tells LINK to pause before it
  25987.  produces the executable file so that you can swap disks, if necessary. The
  25988.  /M option tells LINK to include public symbols and addresses in the map
  25989.  file. LINK also links any needed routines from the library file XLIB.LIB.
  25990.  The semicolon is included after the library name for compatibility with the
  25991.  OS/2 version of LINK.
  25992.  
  25993.  
  25994.  LINK Operation
  25995.  
  25996.  LINK performs the following steps to combine object modules and produce an
  25997.  executable file:
  25998.  
  25999.     1. Reads the object modules submitted.
  26000.  
  26001.     2. Searches the given libraries, if necessary, to resolve external
  26002.        references.
  26003.  
  26004.     3. Assigns addresses to segments.
  26005.  
  26006.     4. Assigns addresses to public symbols.
  26007.  
  26008.     5. Reads code and data in the segments.
  26009.  
  26010.     6. Reads all relocation references in object modules.
  26011.  
  26012.     7. Performs fixups.
  26013.  
  26014.     8. Produces an executable file (executable image and relocation
  26015.        information).
  26016.  
  26017.  
  26018.  Steps 5, 6, and 7 are performed concurrently: in other words, LINK moves
  26019.  back and forth between these steps before it progresses to step 8.
  26020.  
  26021.  The "executable image" contains the code and data that constitute the
  26022.  executable file. The "relocation information"  is a list of references,
  26023.  relative to the start of the program. The references change when the
  26024.  executable image is loaded into memory and an actual address for the entry
  26025.  point is assigned.
  26026.  
  26027.  The following sections explain the process LINK uses to concatenate segments
  26028.  and resolve references to items in memory.
  26029.  
  26030.  
  26031.  Alignment of Segments
  26032.  
  26033.  LINK uses a segment's alignment type to set the starting address for the
  26034.  segment. The alignment types are BYTE, WORD, PARA, and PAGE. These
  26035.  correspond to starting addresses at byte, word, paragraph, and page
  26036.  boundaries, representing addresses that are multiples of 1, 2, 16, and 256,
  26037.  respectively. The default alignment is PARA.
  26038.  
  26039.  When LINK encounters a segment, it checks the alignment type before copying
  26040.  the segment to the executable file. If the alignment is WORD, PARA, or PAGE,
  26041.  LINK checks the executable image to see if the last byte copied ends on the
  26042.  appropriate boundary. If not, LINK pads the image with null bytes.
  26043.  
  26044.  
  26045.  Frame Number
  26046.  
  26047.  LINK computes a starting address for each segment in the program. The
  26048.  starting address is based on the segment's alignment and the sizes of the
  26049.  segments already copied to the executable file (as described in the previous
  26050.  section). The starting address consists of an offset and a canonical frame
  26051.  number. The "canonical frame number" specifies the address of the first
  26052.  paragraph in memory that contains one or more bytes of the segment. (A
  26053.  paragraph is 16 bytes of memory; therefore, to compute a physical location
  26054.  in memory, multiply the frame number by 16 and add the offset.) The offset
  26055.  is the number of bytes from the start of the paragraph to the first byte in
  26056.  the segment. For BYTE and WORD alignments, the offset may be nonzero. The
  26057.  offset is always zero for PARA and PAGE alignments. (An offset of zero means
  26058.  that the physical location is an exact multiple of 16.).
  26059.  
  26060.  You can find the frame number for each segment in the map file created by
  26061.  LINK. The first four digits of the segment's start address give the frame
  26062.  number in hexadecimal. For example, a start address of 0C0A6 indicates the
  26063.  frame number 0C0A.
  26064.  
  26065.  
  26066.  Order of Segments
  26067.  
  26068.  LINK copies segments to the executable file in the same order that it
  26069.  encounters them in the object files. This order is maintained throughout the
  26070.  program unless LINK encounters two or more segments that have the same class
  26071.  name. Segments having identical segment names are copied as a contiguous
  26072.  block to the executable file.
  26073.  
  26074.  The /DO option may change the way in which segments are ordered.
  26075.  
  26076.  
  26077.  Combined Segments
  26078.  
  26079.  LINK uses combine types to determine whether two or more segments that share
  26080.  the same segment name should be combined into one large segment. The valid
  26081.  combine types are PUBLIC, STACK, COMMON, and PRIVATE.
  26082.  
  26083.  If a segment has combine type PUBLIC, LINK automatically combines it with
  26084.  any other segments that have the same name and belong to the same class.
  26085.  When LINK combines segments, it ensures that the segments are contiguous
  26086.  and that all addresses in the segments can be accessed using an offset
  26087.  from the same frame address. The result is the same as if the segment
  26088.  were defined as a whole in the source file.
  26089.  
  26090.  LINK preserves each individual segment's alignment type. This means that
  26091.  even though the segments belong to a single, large segment, the code and
  26092.  data in the segments do not lose their original alignment. If the combined
  26093.  segments exceed 64K, LINK displays an error message.
  26094.  
  26095.  If a segment has combine type STACK, LINK carries out the same combine
  26096.  operation as for PUBLIC segments. The only exception is that STACK segments
  26097.  cause LINK to copy an initial stack-pointer value to the executable file.
  26098.  This stack-pointer value is the offset to the end of the first stack segment
  26099.  (or combined stack segment) encountered.
  26100.  
  26101.  If a segment has combine type COMMON, LINK automatically combines it with
  26102.  any other segments that have the same name and belong to the same class.
  26103.  When LINK combines COMMON segments, however, it places the start of each
  26104.  segment at the same address, creating a series of overlapping segments. The
  26105.  result is a single segment no larger than the largest segment combined.
  26106.  
  26107.  A segment has combine type PRIVATE only if no explicit combine type is
  26108.  defined for it in the source file. LINK does not combine private segments.
  26109.  
  26110.  
  26111.  Groups
  26112.  
  26113.  Groups allow segments to be addressed relative to the same frame address.
  26114.  When LINK encounters a group, it adjusts all memory references to items in
  26115.  the group so that they are relative to the same frame address.
  26116.  
  26117.  Segments in a group do not have to be contiguous, belong to the same class,
  26118.  or have the same combine type. The only requirement is that all segments in
  26119.  the group fit within 64K.
  26120.  
  26121.  Groups do not affect the order in which the segments are loaded. Unless you
  26122.  use class names and enter object files in the right order, there is no
  26123.  guarantee that the segments will be contiguous. In fact, LINK may place
  26124.  segments that do not belong to the group in the same 64K of memory. LINK
  26125.  does not explicitly check whether all the segments in a group fit within 64K
  26126.  of memory; however, LINK is likely to encounter a fixup-overflow error if
  26127.  they do not.
  26128.  
  26129.  
  26130.  Fixups
  26131.  
  26132.  Once LINK knows the starting address of each segment in the program and has
  26133.  established all segment combinations and groups, LINK can "fix up" any
  26134.  unresolved references to labels and variables. To fix up unresolved
  26135.  references, LINK computes the appropriate offset and segment address and
  26136.  replaces the temporary values generated by the assembler with the new
  26137.  values.
  26138.  
  26139.  LINK carries out fixups for the types of references shown in Table 18.3.
  26140.  
  26141.  The size of the value to be computed depends on the type of reference.
  26142.  If LINK discovers an error in the anticipated size of a reference, it
  26143.  displays a fixup-overflow message. This can happen, for example, if a
  26144.  program attempts to use a 16-bit offset to reach an instruction which
  26145.  is more than 64K away. It can also occur if all segments in a group do
  26146.  not fit within a single 64K block of memory.
  26147.  
  26148.  
  26149.  
  26150.  LINK Memory Requirements
  26151.  
  26152.  LINK uses available memory for the link session. If the files to be linked
  26153.  create an output file that exceeds available memory, LINK creates a
  26154.  temporary disk file to serve as memory. This temporary file is handled in
  26155.  one of the following ways, depending on the DOS version
  26156.  
  26157.    ■   For the purpose of creating a temporary file, LINK uses the directory
  26158.        specified by the TMP environment variable. If the TMP variable is set
  26159.        to C:\TEMPDIR, for example, then LINK puts the temporary file in
  26160.        C:\TEMPDIR.
  26161.  
  26162.    ■    If there is no TMP environment variable or if the directory specified
  26163.        by TMP does not exist, then LINK puts the temporary file in the
  26164.        current directory.
  26165.  
  26166.    ■   If LINK is running on DOS version 3.0 or later, it uses a DOS system
  26167.        call to create a temporary file with a unique name in the
  26168.        temporary-file directory.
  26169.  
  26170.    ■   If LINK is running on a version of DOS prior to 3.0, it creates a
  26171.        temporary file named VM.TMP.
  26172.  
  26173.  
  26174.  When LINK creates a temporary disk file, you see the message
  26175.  
  26176.  Temporary file  tempfile has been created. Do not change diskette in drive,
  26177.  
  26178.   letter.In the preceding message, tempfile is ".\" followed by either VM.TMP
  26179.  or a name generated by the system, and letter is the drive containing the
  26180.  temporary file.
  26181.  
  26182.  If you are running on a floppy-disk system, the Do not change diskette
  26183.  message appears. After this message appears, do not remove the disk from the
  26184.  specified drive until the LINK session ends. If you remove the disk, the
  26185.  operation of LINK is unpredictable, and you may see the following message:
  26186.  
  26187.  unexpected end-of-file on scratch file
  26188.  
  26189.  If this happens, rerun the LINK operation. The temporary file created by
  26190.  LINK is a working file only. LINK deletes it at the end of the operation.
  26191.  
  26192.  
  26193.  Note
  26194.  
  26195.  Do not give any of your own files the name VM.TMP. LINK displays an error
  26196.  message if it encounters an existing file with this name.
  26197.  
  26198.  
  26199.  Linking Stub Files
  26200.  
  26201.  Microsoft BASIC provides several special-purpose object files called "stub
  26202.  files" that you can use to minimize the size of your executable file in
  26203.  cases where your program does not use a particular BASIC feature or where
  26204.  special support is needed. By linking these files, you can make LINK exclude
  26205.  (or in some cases, include smaller versions of) code that it would otherwise
  26206.  place in your executable file automatically. Keep in mind that this process
  26207.  is appropriate only for programs compiled with the /O option.
  26208.  
  26209.  You can also link stub files with custom run-time modules. Include the
  26210.  name of the file under the # OBJECTS directive in the export-file list
  26211.  for BUILDRTM.  Table 18.4 lists the stub files included with Microsoft
  26212.  BASIC.
  26213.  
  26214.  
  26215.  
  26216.  Stub files (including the .LIB files listed) are specified in the  objfiles
  26217.  field of LINK. You must supply the /NOE (/NOEXTDICTIONARY) option when
  26218.  linking any of the stub files. It is permissible to link more than one stub
  26219.  file at once. For instance, the following LINK command is appropriate for a
  26220.  program that requires no communications or printer support:
  26221.  
  26222.  LINK /NOE NOLPT+NOCOM+MYPROG.OBJ,MYPROG.EXE;
  26223.  
  26224.  This command links NOLPT.OBJ and NOCOM.OBJ to the user-created object file
  26225.  MYPROG.OBJ, producing the executable file MYPROG.EXE.
  26226.  
  26227.  
  26228.  Linking with Overlays
  26229.  
  26230.  You can direct LINK to create an overlaid version of a program. In an
  26231.  overlaid version of a program, specified parts of the program (known as
  26232.  "overlays") are loaded only if and when they are needed. These parts share
  26233.  the same space in memory. Only code is overlaid; data is never overlaid.
  26234.  Programs that use overlays usually require less memory, but they run more
  26235.  slowly because of the time needed to read and reread the code from disk into
  26236.  memory.
  26237.  
  26238.  You specify overlays by enclosing them in parentheses in the list of object
  26239.  files that you submit to LINK. Each module in parentheses represents one
  26240.  overlay. For example, you could give the following object-file list in the
  26241.  objfiles field of the LINK command line:
  26242.  
  26243.  A + (B+C) + (E+F) + G + (I)
  26244.  
  26245.  In this example, the modules (B+C), (E+F), and (I) are overlays. The
  26246.  remaining modules, and any drawn from the run-time libraries, constitute the
  26247.  resident part (or root) of your program. Overlays are loaded into the same
  26248.  region of memory, so only one can be resident at a time. Duplicate names in
  26249.  different overlays are not supported, so each module can appear only once in
  26250.  a program.
  26251.  
  26252.  LINK replaces calls from the root to an overlay, and calls from an overlay
  26253.  to another overlay, with an interrupt (followed by the module identifier and
  26254.  offset). By default, the interrupt number is 63 (3F hexadecimal). You can
  26255.  use the /O option of the LINK command to change the interrupt number.
  26256.  
  26257.  The CodeView debugger is compatible with overlaid modules. In fact, in
  26258.  the case of large programs, you may need to use overlays to leave
  26259.  sufficient room for the debugger to operate. When you link overlaid code
  26260.  using the /CO option, you will receive an error message Multiple code
  26261.  segments in module of overlaid code. This is normal.
  26262.  
  26263.  Care should be taken to compile each module in the program with compatible
  26264.  options. This means, for example, that all modules must be compiled with the
  26265.  same floating-point options.
  26266.  
  26267.  
  26268.  Using Expanded Memory
  26269.  
  26270.  If expanded memory is present in your computer, overlays are loaded from
  26271.  expanded memory; otherwise, overlays are loaded from disk. You can specify
  26272.  that overlays only be loaded from disk by linking your program with the
  26273.  NOEMS.OBJ stub file.
  26274.  
  26275.  If your program uses overlays from Expanded Memory Specification (EMS), and
  26276.  if it contains a routine that changes the state of EMS (for example, an
  26277.  assembly language routine that shells out to another program), you must
  26278.  restore the state of EMS before returning to the overlaid code. To do this,
  26279.  call the  B_OVREMAP routine. This routine restores EMS to the state that
  26280.  existed before the routine that changed the state was called, and insures
  26281.  that overlays are loaded from EMS correctly.  B_OVREMAP has no effect if
  26282.  overlays are not used or if overlays are not loaded from EMS.
  26283.  
  26284.  
  26285.  Restrictions on Overlays
  26286.  
  26287.  The following restrictions apply to using overlays in Microsoft BASIC:
  26288.  
  26289.    ■   Each Microsoft BASIC overlay cannot be larger than 256K. There is a
  26290.        maximum of 64 overlays per program.
  26291.  
  26292.    ■   Overlays should not be specified as the first object module on the
  26293.        LINK command line (the first object module must be a part of the
  26294.        program that is not overlaid).
  26295.  
  26296.    ■   When you create an overlaid version of a program, make sure that each
  26297.        module contained in the program is compiled with compatible options.
  26298.  
  26299.    ■   You cannot use the /PACKCODE option when linking a program that uses
  26300.        overlays.
  26301.  
  26302.    ■   You can overlay only modules to which control is transferred and
  26303.        returned by a standard 8086 long (32-bit) call/return instruction.
  26304.        Also, LINK does not produce overlay modules that can be called
  26305.        indirectly through function pointers. When a function is called
  26306.        through a pointer, the called function must be in the same overlay or
  26307.        root.
  26308.  
  26309.  
  26310.  
  26311.  Overlay-Manager Prompts
  26312.  
  26313.  The overlay manager is part of the language's run-time library. If you
  26314.  specify overlays during linking, the code for the overlay manager is
  26315.  automatically linked with the other modules of your program. Even with
  26316.  overlays, LINK produces only one .EXE file. At run time, the overlay manager
  26317.  opens the .EXE file each time it needs to extract new overlay modules. The
  26318.  overlay manager first searches for the file in the current directory; then,
  26319.  if it does not find the file, the manager searches the directories listed in
  26320.  the PATH environment variable. When it finds the file, the overlay manager
  26321.  extracts the overlay modules specified by the root program. If the overlay
  26322.  manager cannot find an overlay file when needed, it prompts you for the
  26323.  filename.
  26324.  
  26325.  For example, assume that an executable program named PAYROLL.EXE uses
  26326.  overlays and does not exist in either the current directory or the
  26327.  directories specified by PATH. If your program does not contain expanded
  26328.  memory, when you run PAYROLL.EXE (by entering a complete path), the overlay
  26329.  manager displays the following message when it attempts to load overlay
  26330.  files:
  26331.  
  26332.  Cannot find PAYROLL.EXE
  26333.   Please enter new program spec:
  26334.  
  26335.  You can then enter the drive or directory, or both, where PAYROLL.EXE is
  26336.  located. For example, if the file is located in directory \EMPLOYEE\DATA\ on
  26337.  drive B, you could enter B:\EMPLOYEE\DATA\ or simply \EMPLOYEE\DATA\ if the
  26338.  current drive is B.
  26339.  
  26340.  If you later remove the disk in drive B and the overlay manager needs to
  26341.  access the overlay again, it does not find PAYROLL.EXE and displays the
  26342.  following message:
  26343.  
  26344.  Please insert diskette containing B:\EMPLOYEE\DATA\PAYROLL.EXE
  26345.  and strike any key when ready.
  26346.  
  26347.  After reading the overlay file from the disk, the overlay manager displays
  26348.  the following message:
  26349.  
  26350.  Please restore the original diskette.
  26351.   Strike any key when ready.
  26352.  
  26353.  Execution of the program then continues.
  26354.  
  26355.   ────────────────────────────────────────────────────────────────────────────
  26356.  
  26357.  Chapter 19:  Creating and Using Quick Libraries
  26358.  
  26359.  This chapter describes how to create and use Quick libraries. You'll
  26360.  learn how to do the following:
  26361.  
  26362.    ■   Make libraries from within the QBX environment and from the command
  26363.        line.
  26364.  
  26365.    ■   Make a Quick library that contains routines from an existing Quick
  26366.        library.
  26367.  
  26368.    ■   Load a Quick library when running a QBX program.
  26369.  
  26370.    ■   View the contents of a Quick library.
  26371.  
  26372.  
  26373.  Also, specific examples of how to create Quick libraries from different
  26374.  types of source code modules are presented and the last section of this
  26375.  chapter provides programming information specific to Quick libraries.
  26376.  
  26377.  For an overview of Quick libraries and reasons why you might want to use
  26378.  them, see Chapter 17, "About Linking and Libraries."
  26379.  
  26380.  
  26381.  The Supplied Library (QBX.QLB)
  26382.  
  26383.  Microsoft BASIC supplies a default Quick library named QBX.QLB. If you
  26384.  invoke QBX with the /L option, but do not supply a Quick library name, QBX
  26385.  automatically loads the library QBX.QLB, included with the QBX package. This
  26386.  file contains three routines,  INTERRUPT,  INT86OLD, and  ABSOLUTE, that
  26387.  provide software-interrupt support for system-service calls and support for
  26388.  CALL  ABSOLUTE. QBX.QLB is also necessary for creating certain other Quick
  26389.  libraries (see the section "Mouse, Menu, and Window Libraries" later in this
  26390.  chapter).
  26391.  
  26392.  You must load QBX.QLB (or another library into which  INTERRUPT,  INT86OLD,
  26393.  and  ABSOLUTE have been incorporated) from the command line when you invoke
  26394.  QBX in order to use the routines from QBX.QLB. If you wish to use these
  26395.  routines along with other routines that you have placed in libraries, make a
  26396.  copy of the QBX.QLB library and use it as a basis for building a library
  26397.  containing all the routines you need.
  26398.  
  26399.  A parallel object-module library, QBX.LIB, is also supplied for use outside
  26400.  of the QBX environment.
  26401.  
  26402.  
  26403.  Files Needed to Create a Quick Library
  26404.  
  26405.  To create a Quick library, make sure that the following files are in
  26406.  the current working directory or accessible to QBX through the
  26407.  appropriate DOS environment variables:
  26408.  
  26409.  QBX.EXE           Directs the process of creating a Quick Library.  If
  26410.                    you are working only with QBX modules, you can do
  26411.                    everything in one step from within the QBX environment.
  26412.  
  26413.  BC.EXE            Creates object files from source code.
  26414.  
  26415.  LINK.EXE          Links object files.
  26416.  
  26417.  LIB.EXE           Manages object-module libraries of object modules.
  26418.  
  26419.