home *** CD-ROM | disk | FTP | other *** search
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.
- Microsoft BASIC Programmer's Reference
-
-
-
-
-
-
-
-
- Chapter 1: Control-Flow Structures
-
- This chapter shows you how to use control-flow structures --
- specifically, loops and decision statements -- to control the flow
- of your program's execution. Loops make a program execute a
- sequence of statements as many times as you want. Decision statements let
- the program decide which of several alternative paths to take.
-
- When you are finished with this chapter, you will know how to do the
- following tasks related to using loops and decision statements in your
- Microsoft BASIC programs:
-
- ■ Compare expressions using relational operators.
-
- ■ Combine string or numeric expressions with logical operators and
- determine whether the resulting expression is true or false.
-
- ■ Create branches in the flow of the program with the statements IF...
- THEN... ELSE and SELECT CASE.
-
- ■ Write loops that repeat statements a specific number of times.
-
- ■ Write loops that repeat statements while or until a certain condition
- is true.
-
-
-
- Changing Statement Execution Order
-
- Left unchecked by control-flow statements, a program's logic flows through
- statements from left to right, top to bottom. While some very simple
- programs can be written with only this unidirectional flow, most of the
- power and utility of any programming language comes from its ability to
- change statement execution order with decision structures and loops.
-
- With a decision structure, a program can evaluate an expression, then branch
- to one of several different groups of related statements (statement blocks)
- depending on the result of the evaluation. With a loop, a program can
- repeatedly execute statement blocks.
-
- If you are coming to Microsoft BASIC after programming in BASICA, you will
- appreciate the added versatility of these additional control-flow
- structures:
-
- ■ The block IF... THEN... ELSE statement
-
- ■ The SELECT CASE statement
-
- ■ The DO... LOOP and EXIT DO statements
-
- ■ The EXIT FOR statement, which provides an alternative way to exit
- FOR... NEXT loops
-
-
-
- Boolean Expressions
-
- A Boolean expression is any expression that returns the value "true" or
- "false." BASIC uses Boolean expressions in certain kinds of decision
- structures and loops. The following IF... THEN... ELSE statement contains a
- Boolean expression, X < Y :
-
- IF X < Y THEN CALL Procedure1 ELSE CALL Procedure2
-
- In the previous example, if the Boolean expression is true (if the value of
- the variable X is in fact less than the value of the variable Y), then
- Procedure1 is executed; otherwise (if X is greater than or equal to Y),
- Procedure2 is executed.
-
- The preceding example also demonstrates a common use of Boolean expressions:
- comparing two other expressions (in this case, X and Y) to determine the
- relationship between them. These comparisons are made with the relational
- operators shown in Table 1.1.
-
-
- = Equal
- <> Not equal
- < Less than
- <= Less than or equal to
- > Greater than
- >= Greater than or equal to
-
-
- You can use these relational operators to compare string expressions. In
- this case "greater than," "less than," and so on refer to alphabetical
- order. For example, the following expression is true, since the word deduce
- comes alphabetically before the word deduct:
-
- "deduce" < "deduct"
-
- Boolean expressions also frequently use the "logical operators" AND, OR,
- NOT, XOR, IMP, and EQV. These operators allow you to construct compound
- tests from one or more Boolean expressions. For example:
-
- expression1 AND expression2
-
- The preceding example is true only if expression1 and expression2 are both
- true. Thus, in the following example, the message All sorted is printed only
- if both the Boolean expressions X <= Y and Y <= Z are true:
-
- IF (X <= Y) AND (Y <= Z) THEN PRINT "All sorted"
-
- The parentheses around the Boolean expressions in the last example are not
- really necessary, since relational operators such as <= are evaluated
- before logical operators such as AND.
-
-
- However, parentheses make a complex Boolean expression more readable and
- ensure that its components are evaluated in the order that you intend.
-
- BASIC uses the numeric values -1 and 0 to represent true and false,
- respectively. You can see this by asking BASIC to print a true expression
- and a false expression, as in the next example:
-
- X = 5
- Y = 10
- PRINT X < Y ' Evaluate, print a "true" Boolean expression.
- PRINT X > Y ' Evaluate, print a "false" Boolean expression.
-
- Output
- -1
- 0
-
- The value -1 for true makes more sense when you consider how BASIC's
- NOT operator works: NOT inverts each bit in the binary representation of
- its operand, changing one bits to zero bits, and zero bits to one bits.
- Therefore, since the integer value 0 (false) is stored internally as a
- sequence of 16 zero bits, NOT 0 (true) is stored internally as 16 one bits,
- as follows:
-
- 0000000000000000
-
- TRUE = NOT FALSE = 1111111111111111
-
- In the two's-complement method that BASIC uses to store integers, 16 one
- bits represent the value -1.
-
- Note that BASIC returns -1 when it evaluates a Boolean expression as
- true; however, BASIC considers any nonzero value to be true, as shown by the
- output from the following example:
-
- INPUT "Enter a value: ", X
-
- IF X THEN PRINT X;"is true."
-
- Output
- Enter a value: 2
- 2 is true.
-
- The NOT operator in BASIC is a "bitwise" operator. Some programming
- languages, such as C and Pascal, have both a bitwise NOT operator and a
- "logical" NOT operator. The distinction is as follows:
-
- ■ A bitwise NOT returns false (0) only for the value -1.
-
- ■ A logical NOT returns false (0) for any true (nonzero) value.
-
-
-
- In BASIC, for any true expression not equal to -1, NOT expression
- returns another true value, as shown in the following table:
-
- ╓┌───────────────────────────┌───────────────────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 1 -2
- 2 -3
- -2 1
- -1 0
-
-
-
-
- So beware: NOT expression is false only if expression evaluates to a
- value of -1. If you define Boolean constants or variables for use in
- your programs, use -1 for true.
-
- You can use the values 0 and -1 to define helpful mnemonic Boolean
- constants for use in loops or decisions. This technique is used in many of
- the examples in this manual, as shown in the following program fragment,
- which sorts the elements of an array named Amount in ascending order:
-
- ' Define symbolic constants to use in program:
- CONST FALSE = 0, TRUE = NOT FALSE
- .
- .
- .
- DO
- Swaps% = FALSE
- FOR I% = 1 TO TransacNum - 1
- IF Amount(I%) < Amount(I%+1) THEN
- SWAP Amount(I%), Amount(I%+1)
- Swaps% = TRUE
- END IF
- NEXT I%
- LOOP WHILE Swaps% ' Keep looping while Swaps is TRUE.
- .
- .
- .
-
- Decision Structures
-
- Based on the value of an expression, decision structures cause a program to
- take one of the following two actions:
-
- * Execute one of several alternative statements within the decision
- structure itself.
-
- * Branch to another part of the program outside the decision structure.
-
-
- In BASICA, decision-making is handled solely by the single-line IF...
- THEN... ELSE statement. In its simplest form ( IF... THEN), the expression
- following the IF keyword is evaluated. If the expression is true, the
- program executes the statements following the THEN keyword; if the
- expression is false, the program continues with the next line after the
-
-
- IF... THEN statement. Lines 50 and 70 from the following BASICA program
- fragment show examples of IF... THEN:
-
- 30 INPUT A
- 40 ' If A is greater than 100, print a message and branch
- 45 ' back to line 30; otherwise, go on to line 60:
- 50 IF A > 100 THEN PRINT "Too big": GOTO 30
- 60 ' If A is equal to 100, branch to line 300;
- 65 ' otherwise, go on to line 80:
- 70 IF A = 100 THEN GOTO 300
- 80 PRINT A/100: GOTO 30
- .
- .
- .
-
- By adding the ELSE clause to an IF... THEN statement, you can have your
- program take one set of actions (those following the THEN keyword) if an
- expression is true, and another set of actions (those following the ELSE
- keyword) if it is false. The next program fragment shows how ELSE works in
- an IF... THEN... ELSE statement:
-
- 10 INPUT "What is your password"; Pass$
- 15 ' If user enters "sword," branch to line 50;
- 20 ' otherwise, print a message and branch back to line 10:
- 30 IF Pass$="sword" THEN 50 ELSE PRINT "Try again": GOTO 10
-
- While BASICA's single-line IF... THEN... ELSE is adequate for simple
- decisions, it can lead to virtually unreadable code in cases of more
- complicated decisions. This is especially true if you write your programs so
- all alternative actions take place within the IF... THEN... ELSE statement
- itself or if you nest IF... THEN... ELSE statements (that is, if you put
- one IF... THEN... ELSE inside another, a perfectly legal construction). As
- an example of how difficult it is to follow even a simple test, consider the
- next fragment from a BASICA program:
-
- 10 ' The following nested IF...THEN...ELSE statements print
- 15 ' different output for each of the following four cases:
- 20 ' 1) A <= 50, B <= 50 3) A > 50, B <= 50
- 25 ' 2) A <= 50, B > 50 4) A > 50, B > 50
- 30
- 35 INPUT A, B
- 40
- 45 IF A <= 50 THEN IF B <= 50 THEN PRINT "A <= 50, B <= 50" ELSE PRINT
- "A <= 50, B > 50" ELSE IF B <= 50 THEN PRINT "A > 50, B <= 50;" ELSE
- PRINT "A > 50, B > 50"
-
- Even though line 45 extends over several physical lines on the screen, it is
- just one logical line (everything typed before the Enter key was pressed).
- BASICA wraps long lines on the screen. To avoid the kind of complicated
- statement shown by the preceding example, BASIC now includes the block form
- of the IF... THEN... ELSE statement, so that a decision is no longer
- restricted to one logical line. The following example shows the same BASICA
- program rewritten to use block IF...THEN...ELSE:
-
-
-
- INPUT A, B
- IF A <= 50 THEN
- IF B <= 50 THEN
- PRINT "A <= 50, B <= 50"
- ELSE
- PRINT "A <= 50, B > 50"
- END IF
- ELSE
- IF B <= 50 THEN
- PRINT "A > 50, B <= 50"
- ELSE
- PRINT "A > 50, B > 50"
- END IF
- END IF
-
- Microsoft BASIC also provides the SELECT CASE... END SELECT (referred to as
- SELECT CASE) statement for structured decisions.
-
- The block IF... THEN... ELSE statement and the SELECT CASE statement allow
- the appearance of your code to be based on program logic, rather than
- requiring many statements to be crowded onto one line. This gives you
- increased flexibility while you are programming, as well as improved program
- readability and ease of maintenance when you are done.
-
-
- Block IF...THEN...ELSE
-
- Table 1.2 shows the syntax of the block IF... THEN... ELSE statement and
- gives an example of its use.
-
- The arguments condition1, condition2, and so on are expressions. They can
- be any numeric expression (in which case true becomes any nonzero value, and
- false is 0), or they can be Boolean expressions (in which case true is
- -1 and false is 0). As explained previously, Boolean expressions
- typically compare two numeric or string expressions using one of the
- relational operators, such as < or >=.
-
- Each IF, ELSEIF, and ELSE clause is followed by a block of statements.
- None of the statements in the block can be on the same line as the IF,
- ELSEIF, or ELSE clause; otherwise, BASIC considers it a single-line IF...
- THEN statement.
-
- BASIC evaluates each of the expressions in the IF and ELSEIF clauses from
- top to bottom, skipping over statement blocks until it finds the first true
- expression. When it finds a true expression, it executes the statements
- corresponding to the expression, then branches out of the block to the
- statement following the END IF clause.
-
- If none of the expressions in the IF or ELSEIF clauses is true, BASIC
- skips to the ELSE clause, if there is one, and executes its statements.
- Otherwise, if there is no ELSE clause, the program continues with the next
- statement after the END IF clause.
-
- The ELSE and ELSEIF clauses are optional, as shown in the following
- example:
-
- ' If the value of X is less than 100, execute the two statements
- ' before END IF; otherwise, go to the INPUT statement
- ' following END IF:
-
- IF X < 100 THEN
- PRINT X
- Number = Number + 1
- END IF
- INPUT "New value"; Response$
- .
- .
- .
-
- A single block IF... THEN... ELSE can contain multiple ELSEIF statements,
- as follows:
-
- IF C$ >= "A" AND C$ <= "Z" THEN
- PRINT "Capital letter"
- ELSEIF C$ >= "a" AND C$ <= "z" THEN
- PRINT "Lowercase letter"
- ELSEIF C$ >= "0" AND C$ <= "9" THEN
- PRINT "Number"
- ELSE
- PRINT "Not alphanumeric"
- END IF
-
-
- At most, only one block of statements is executed, even if more than one
- condition is true. For example, if you enter the word ace as input to the
- next example, it prints the message Input too short but does not print the
- message Can't start with an a.
-
- INPUT Check$
- IF LEN(Check$) > 6 THEN
- PRINT "Input too long"
- ELSEIF LEN(Check$) < 6 THEN
- PRINT "Input too short"
- ELSEIF LEFT$(Check$, 1) = "a" THEN
- PRINT "Can't start with an a"
- END IF
-
- IF... THEN... ELSE statements can be nested; in other words, you can put an
- IF... THEN... ELSE statement inside another IF... THEN... ELSE statement,
- as shown here:
-
- IF X > 0 THEN
- IF Y > 0 THEN
- IF Z > 0 THEN
- PRINT "All are greater than zero."
- ELSE
- PRINT "Only X and Y greater than zero."
- END IF
- END IF
- ELSEIF X = 0 THEN
- IF Y = 0 THEN
- IF Z = 0 THEN
- PRINT "All equal zero."
- ELSE
- PRINT "Only X and Y equal zero."
- END IF
- END IF
- ELSE
- PRINT "X is less than zero."
- END IF
-
- SELECT CASE
-
- The SELECT CASE statement is a multiple-choice decision structure similar
- to the block IF... THEN... ELSE statement. SELECT CASE can be used
- anywhere block IF... THEN... ELSE can be used.
-
- The difference between the two is that SELECT CASE evaluates a single
- expression, then executes different statements or branches to different
- parts of the program based on the result. In contrast, a block IF...
- THEN... ELSE can evaluate completely different expressions.
-
-
- Examples
-
- The following examples illustrate the similarities and differences between
- the SELECT CASE and IF... THEN... ELSE statements. Here is an example of
- using block IF... THEN... ELSE for a multiple-choice decision:
-
- INPUT X
- IF X = 1 THEN
- PRINT "One"
- ELSEIF X = 2 THEN
- PRINT "Two"
- ELSEIF X = 3 THEN
- PRINT "Three"
- ELSE
- PRINT "Must be integer from 1 to 3."
- END IF
-
- The previous decision is rewritten using SELECT CASE as follows:
-
- INPUT X
- SELECT CASE X
- CASE 1
- PRINT "One"
- CASE 2
- PRINT "Two"
- CASE 3
- PRINT "Three"
- CASE ELSE
- PRINT "Must be integer from 1 to 3."
- END SELECT
-
- The following decision can be made either with the SELECT CASE or the block
- IF... THEN... ELSE statement. The comparison is more efficient with the
- IF... THEN... ELSE statement because different expressions are being
- evaluated in the IF and ELSEIF clauses.
-
- INPUT X, Y
- IF X = 0 AND Y = 0 THEN
- PRINT "Both are zero."
- ELSEIF X = 0 THEN
- PRINT "Only X is zero."
- ELSEIF Y = 0 THEN
- PRINT "Only Y is zero."
- ELSE
- PRINT "Neither is zero."
- END IF
-
- Using the SELECT CASE Statement
-
- Table 1.3 shows the syntax of a SELECT CASE statement and an example.
-
- The expressionlist arguments following a CASE clause can be one or more of
- the following, separated by commas:
-
- ■ A numeric expression or a range of numeric expressions
-
- ■ A string expression or a range of string expressions
-
-
- To specify a range of expressions, use the following syntax for the CASE
- statement:
-
- CASE expression TO expression
- CASE IS relational-operator expression
-
- The relational-operator is any of the operators shown in Table 1.1. For
- example, if you use CASE 1 TO 4, the statements associated with this case
- are executed when the testexpression in the SELECT CASE statement is
- greater than or equal to 1 and less than or equal to 4. If you use CASE IS
- < 5, the associated statements are executed only if testexpression is less
- than 5.
-
- If you are expressing a range with the TO keyword, be sure to put the
- lesser value first. For example, if you want to test for a value from
- -5 to -1, write the CASE statement as follows:
-
- CASE -5 TO -1
-
-
- However, the following statement is not a valid way to specify the range
- from -5 to -1, so the statements associated with this case are
- never executed:
-
- CASE -1 TO -5
-
- Similarly, a range of string constants should list the string that comes
- first alphabetically:
-
- CASE "aardvark" TO "bear"
-
- Multiple expressions or ranges of expressions can be listed for each CASE
- clause, as in the following lines:
-
- CASE 1 TO 4, 7 TO 9, WildCard1%, WildCard2%
- CASE IS = Test$, IS = "end of data"
- CASE IS < LowerBound, 5, 6, 12, IS > UpperBound
- CASE IS < "HAN", "MAO" TO "TAO"
-
- If the value of the SELECT CASE expression appears in the list following a
- CASE clause, only the statements associated with that CASE clause are
- executed. Control then jumps to the first executable statement following
- END SELECT, not the next block of statements inside the SELECT CASE
- structure, as shown by the output from the next example:
-
- INPUT X
- SELECT CASE X
- CASE 1
- PRINT "One, ";
- CASE 2
- PRINT "Two, ";
- CASE 3
- PRINT "Three, ";
- END SELECT
- PRINT "that's all"
-
- Output
- ? 1
- One, that's all
-
- If the same value or range of values appears in more than one CASE clause,
- only the statements associated with the first occurrence are executed, as
- shown by the next example's output:
-
- INPUT Test$
- SELECT CASE Test$
- CASE "A" TO "AZZZZZZZZZZZZZZZZZ"
- PRINT "An uppercase word beginning with A"
- CASE IS < "A"
- PRINT "Some sequence of nonalphabetic characters"
-
- CASE "ABORIGINE"
- ' This case is never executed since ABORIGINE
- ' falls within the range in the first CASE clause:
- PRINT "A special case"
- END SELECT
-
- Output
- ? ABORIGINE
- An uppercase word beginning with A
-
- If you use a CASE ELSE clause, it must be the last CASE clause listed in
- the SELECT CASE statement. The statements between a CASE ELSE clause and
- an END SELECT clause are executed only if the testexpression argument does
- not match any of the other CASE selections in the SELECT CASE statement.
- In fact, it is a good idea to have a CASE ELSE statement in your SELECT
- CASE block to handle unforeseen values for testexpression. However, if
- there is no CASE ELSE statement and no match is found in any CASE
- statement for testexpression, the program continues execution.
-
- Example
-
- The following program fragment demonstrates a common use of the SELECT CASE
- statement. It prints a menu on the screen, then branches to different
- subprograms based on the number typed by the user.
-
- DO' Start menu loop.
-
- CLS' Clear screen.
-
- ' Print five menu choices on the screen:
- PRINT "MAIN MENU" : PRINT
- PRINT "1) Add New Names"
- PRINT "2) Delete Names"
- PRINT "3) Change Information"
- PRINT "4) List Names"
- PRINT
- PRINT "5) EXIT"
-
- ' Print input prompt:
- PRINT : PRINT "Type your selection (1 to 5):"
-
- ' Wait for the user to press a key. INPUT$(1)
- ' reads one character input from the keyboard:
- Ch$ = INPUT$(1)
-
- ' Use SELECT CASE to process the response:
- SELECT CASE Ch$
-
- CASE "1"
- CALL AddData
- CASE "2"
- CALL DeleteData
- CASE "3"
- CALL ChangeData
- CASE "4"
- CALL ListData
- CASE "5"
- EXIT DO' The only way to exit the menu loop.
- CASE ELSE
- BEEP
- END SELECT
-
- LOOP' End menu loop.
-
- END
-
- ' Subprograms AddData, DeleteData, ChangeData, and ListData:
- .
- .
- .
-
- SELECT CASE Vs. ON...GOSUB
-
- You can use the more versatile SELECT CASE statement as a replacement for
- the old ON... GOSUB statement. The SELECT CASE statement has many
- advantages over the ON... GOSUB statement, summarized as follows:
-
- ■ The testexpression in SELECT CASE can evaluate to either a string or
- numeric value. The expression given in the statement ON GOSUB must
- evaluate to a number within the range 0 to 255.
-
- ■ The SELECT CASE statement branches to a statement block immediately
- following the matching CASE clause. In contrast, ON GOSUB branches
- to a subroutine in another part of the program.
-
- ■ CASE clauses can be used to test expression against a range of
- values. When the range is quite large, this is not easily done with
- ON GOSUB, especially in cases such as those shown in the code
- fragments in the rest of this section.
-
-
- In the following fragment, the ON... GOSUB statement branches to one of the
- subroutines 50, 100, or 150, depending on the value entered by the user:
-
- X% = -1
- WHILE X%
- INPUT "Enter choice (0 to quit): ", X%
- IF X% = 0 THEN END
-
-
- WHILE X% < 1 OR X% > 12
- PRINT "Must be value from 1 to 12"
- INPUT "Enter choice (0 to quit): ", X%
- WEND
- ON X% GOSUB 50,50,50,50,50,50,50,50,100,100,100,150
- WEND
- .
- .
- .
-
- Contrast the preceding example with the next one, which uses a SELECT CASE
- statement with ranges of values in each CASE clause:
-
- DO
- INPUT "Enter choice (0 to quit): ", X%
- SELECT CASE X%
- CASE 0
- END
- CASE 1 TO 8' Replaces "subroutine 50"
- ' in preceding example
- CASE 9 TO 11 ' Replaces "subroutine 100"
- ' in preceding example
- CASE 12' Replaces "subroutine 150"
- ' in preceding example
- CASE ELSE' Input was out of range.
- PRINT "Must be value from 1 to 12"
- END SELECT
- LOOP
-
- Looping Structures
-
- Looping structures repeat a block of statements (the loop), either for a
- specified number of times or until a certain expression (the loop condition)
- is true or false.
-
- Users of BASICA are familiar with two looping structures, FOR... NEXT and
- WHILE... WEND, which are discussed in the following sections "FOR...NEXT
- Loops" and "WHILE...WEND Loops." Microsoft BASIC has extended the available
- loop structures with DO... LOOP.
-
-
- FOR...NEXT Loops
-
- A FOR... NEXT loop repeats the statements enclosed in the loop a specified
- number of times, counting from a starting value to an ending value by
- increasing or decreasing a loop counter. As long as the loop counter has not
- reached the ending value, the loop continues to execute. Table 1.4 shows the
- syntax of the FOR... NEXT statement and gives an example of its use.
-
- In a FOR... NEXT loop, the counter variable initially has the value
- of the expression start. After each repetition of the loop, the value of
- counter is adjusted. If you leave off the optional STEP keyword, the
- default value for this adjustment is 1; that is, 1 is added to counter each
- time the loop executes. If you use STEP, then counter is adjusted by the
- amount increment. The increment argument can be any numeric value; if it
- is negative, the loop counts down from start to end. After the counter
- variable is increased or decreased, its value is compared with end. At this
- point, if either of the following is true, the loop is completed:
-
- ■ The loop is counting up ( increment is positive) and counter is
- greater than end.
-
- ■ The loop is counting down ( increment is negative) and counter is
- less than end.
-
-
- Figure 1.1 shows the logic of a FOR... NEXT loop when the value of
- increment is positive.
-
- Figure 1.2 shows the logic of a FOR... NEXT loop when the value of
- increment is negative.
-
- A FOR... NEXT statement always "tests at the top," so if one of the
- following conditions is true, the loop is never executed:
-
- ■ The increment is positive, and the initial value of start is greater
- than the value of end:
-
-
- ' Loop never executes, because I% starts out greater
- ' than 9:
- FOR I% = 10 TO 9
- .
- .
- .
- NEXT I%
-
- ■ The increment is negative, and the initial value of start is less
- than the value of end:
-
- ' Loop never executes, because I% starts out less than 9:
- FOR I% = -10 TO -9 STEP -1
- NEXT I%
-
-
-
- You don't have to use the counter argument in the NEXT clause; however, if
- you have several nested FOR... NEXT loops (one loop inside another),
- listing the counter arguments can be a helpful way to keep track of what
- loop you are in.
-
- Here are some general guidelines for nesting FOR... NEXT loops:
-
- ■ If you use a loop counter variable in a NEXT clause, the counter for
- a nested loop must appear before the counter for any enclosing loop.
- In other words, the following is a legal nesting:
-
-
- FOR I = 1 TO 10
- FOR J = -5 TO 0
- .
- .
- .
- NEXT J
- NEXT I
-
- However, the following is not a legal nesting:
-
- FOR I = 1 TO 10
- FOR J = -5 TO 0
- .
- .
- .
- NEXT I
- NEXT J
-
- * For faster loops that generate smaller code, use integer variables
- for counters in the loops whenever possible.
-
- * If you use a separate NEXT clause to end each loop, then the
- number of NEXT clauses must always be the same as the number of
- FOR clauses.
-
- * If you use a single NEXT clause to terminate several levels of
- FOR... NEXT loops, then the loop-counter variables must appear
- after the NEXT clause in "inside-out" order:
-
- NEXT innermost-loopcounter, ... , outermost-loopcounter
-
-
- In this case, the number of loop-counter variables in the NEXT
- clause must be the same as the number of FOR clauses.
-
-
- Examples
-
- The following three program fragments illustrate different ways of nesting
- FOR... NEXT loops to produce the identical output. The first example shows
- nested FOR... NEXT loops with loop counters and separate NEXT clauses for
- each loop:
-
- FOR I = 1 TO 2
- FOR J = 4 TO 5
- FOR K = 7 TO 8
- PRINT I, J, K
- NEXT K
- NEXT J
- NEXT I
-
-
- The following example also uses loop counters but only one NEXT clause for
- all three loops:
-
- FOR I = 1 TO 2
- FOR J = 4 TO 5
- FOR K = 7 TO 8
- PRINT I, J, K
- NEXT K, J, I
-
- The final example shows nested FOR... NEXT loops without loop counters:
-
- FOR I = 1 TO 2
- FOR J = 4 TO 5
- FOR K = 7 TO 8
- PRINT I, J, K
- NEXT
- NEXT
- NEXT
-
- Output
- 1 4 7
- 1 4 8
- 1 5 7
- 1 5 8
- 2 4 7
- 2 4 8
- 2 5 7
- 2 5 8
-
-
- Exiting a FOR...NEXT Loop with EXIT FOR
- Sometimes you may want to exit a FOR... NEXT loop before the counter
- variable reaches the ending value of the loop. You can do this with the
- EXIT FOR statement. A single FOR... NEXT loop can have any number of EXIT
- FOR statements, and the EXIT FOR statements can appear anywhere within the
- loop. The following fragment shows one use for an EXIT FOR statement:
-
- ' Print the square roots of the numbers from 1 to 30,000.
- ' If the user presses any key while this loop is executing,
- ' control exits from the loop:
- FOR I% = 1 TO 30000
- PRINT SQR(I%)
- IF INKEY$ <> "" THEN EXIT FOR
- NEXT
- .
- .
- .
-
-
- EXIT FOR exits only the smallest enclosing FOR... NEXT loop in which it
- appears. For example, if the user presses a key while the following nested
- loops are executing, the program would simply exit the innermost loop. If
- the outermost loop is still active (that is, if the value of I% is less than
- or equal to 100), control passes right back to the innermost loop:
-
- FOR I% = 1 TO 100
- FOR J% = 1 TO 100
- PRINT I% / J%
- IF INKEY$ <> "" THEN EXIT FOR
- NEXT J%
- NEXT I%
- Suspending Program Execution with FOR...NEXT
- Many BASICA programs use an empty FOR... NEXT loop such as the following to
- insert a pause in a program:
-
- .
- .
- .
- ' There are no statements in the body of this loop;
- ' all it does is count from 1 to 10,000
- ' using integers (whole numbers).
- FOR I% = 1 TO 10000: NEXT
- .
- .
- .
-
- For very short pauses or pauses that do not have to be of some exact
- interval, using FOR... NEXT is fine. The problem with using an empty
- FOR... NEXT loop in this way is that different computers, different versions
- of BASIC, or different compile-time options can all affect how quickly the
- arithmetic in a FOR... NEXT loop is performed. So the length of a pause can
- vary widely. BASIC's SLEEP statement now provides a better alternative.
-
-
- WHILE...WEND Loops
-
- The FOR... NEXT statement is useful when you know ahead of time exactly how
- many times a loop should be executed. When you cannot predict the precise
- number of times a loop should be executed, but do know the condition that
- will end the loop, the WHILE... WEND statement is useful. Instead of
- counting to determine if it should keep executing a loop, WHILE... WEND
- repeats the loop as long as the loop condition is true.
-
-
- Table 1.5 shows the syntax of the WHILE... WEND statement and an example.
-
- Example
-
- The following example assigns an initial value of ten to the variable X,
- then successively halves that value and keeps halving it until the value of
- X is less than .00001:
-
- X = 10
-
- WHILE X > .00001
- PRINT X
- X = X/2
- WEND
-
-
- Figure 1.3 illustrates the logic of a WHILE... WEND loop.
-
-
- DO...LOOP Loops
-
- Like the WHILE... WEND statement, the DO... LOOP statement executes a
- block of statements an indeterminate number of times; that is, exiting from
- the loop depends on the truth or falsehood of the loop condition. Unlike
- WHILE... WEND, DO... LOOP allows you to test for either a true or false
- condition. You can also put the test at either the beginning or the end of
- the loop.
-
-
- Table 1.6 shows the syntax of a loop that tests at the loop's beginning.
-
- Figures 1.4 and 1.5 illustrate the two kinds of DO... LOOP statements that
- test at the beginning of the loop.
-
- Table 1.7 shows the syntax of a loop that tests for true or false
- at the end of the loop.
-
- Figures 1.6 and 1.7 illustrate the two kinds of DO... LOOP statements that
- test at the end of the loop.
-
- Loop Tests: One Way to Exit DO...LOOP
- You can use a loop test at the end of a DO... LOOP
- statement to create a loop in which the statements always execute
- at least once. With the WHILE... WEND statement, you sometimes have to
- resort to the trick of presetting the loop variable to some value in order
- to force the first pass through the loop. With DO... LOOP, such tricks are
- not necessary.
-
-
- The following examples illustrate both approaches:
-
- ' WHILE...WEND loop tests at the top, so assigning "Y"
- ' to Response$ is necessary to force execution of the
- ' loop at least once:
- Response$ = "Y"
- WHILE UCASE$(Response$) = "Y"
- .
- .
- .
- INPUT "Do again"; Response$
- WEND
- ' The same loop using DO...LOOP to test after the
- ' body of the loop:
- DO
- .
- .
- .
- INPUT "Do again"; Response$
- LOOP WHILE UCASE$(Response$) = "Y"
-
- You can also rewrite a condition expressed with WHILE using UNTIL instead,
- as in the following:
-
- ' =======================================================
- ' Using DO WHILE NOT...LOOP
- ' =======================================================
-
- ' While the end of file 1 has not been reached, read
- ' a line from the file and print it on the screen:
- DO WHILE NOT EOF(1)
- LINE INPUT #1, LineBuffer$
- PRINT LineBuffer$
- LOOP
-
- ' =======================================================
- ' Using DO UNTIL...LOOP
- ' =======================================================
-
- ' Until the end of file 1 has been reached, read
- ' a line from the file and print it on the screen:
- DO UNTIL EOF(1)
- LINE INPUT #1, LineBuffer$
- PRINT LineBuffer$
- LOOP
-
- EXIT DO: An Alternative Way to Exit DO...LOOP
- Inside a DO... LOOP statement, other statements are executed that
- eventually change the loop-test condition from true to false or false to
- true, ending the loop. In the DO... LOOP examples presented so far, the
- test has occurred either at the beginning or the end of the loop. However,
- by using the EXIT DO statement to exit from the loop, you can move the test
- elsewhere inside the loop. A single DO... LOOP can contain any number of
- EXIT DO statements, and the EXIT DO statements can appear anywhere within
- the loop.
-
- Example
-
- The following example opens a file and reads it, one line at a time, until
- the end of the file is reached or until it finds the pattern entered by the
- user. If it finds the pattern before getting to the end of the file, an
- EXIT DO statement exits the loop.
-
- INPUT "File to search: ", File$
- IF File$ = "" THEN END
-
- INPUT "Pattern to search for: ", Pattern$
- OPEN File$ FOR INPUT AS #1
-
- DO UNTIL EOF(1) ' EOF(1) returns a true value if the
- ' end of the file has been reached.
- LINE INPUT #1, TempLine$
- IF INSTR(TempLine$, Pattern$) > 0 THEN
-
- ' Print the first line containing the pattern and
- ' exit the loop:
- PRINT TempLine$
- EXIT DO
- END IF
- LOOP
-
- Sample Applications
-
- The sample applications for this chapter are a checkbook balancing program
- and a program that ensures that every line in a text file ends with a
- carriage-return line-feed sequence.
-
-
- Checkbook Balancing Program (CHECK.BAS)
-
- This program prompts the user for the starting checking account balance and
- all transactions --withdrawals or deposits -- that have occurred.
- It then prints a sorted list of the transactions and the final balance in
- the account.
-
- Statements UsedThe
- program demonstrates the following statements discussed in this chapter:
-
- ■ DO... LOOP WHILE
-
- ■ FOR... NEXT
-
- ■ EXIT FOR
-
- ■ Block IF... THEN... ELSE
-
- Program Listing
- DIM Amount(1 TO 100) AS CURRENCY, Balance AS CURRENCY
- CONST FALSE = 0, TRUE = NOT FALSE
- CLS
- ' Get account's starting balance:
- INPUT "Type starting balance, then press <ENTER>: ", Balance
- ' Get transactions. Continue accepting input
- ' until the input is zero for a transaction,
- ' or until 100 transactions have been entered:
- FOR TransacNum% = 1 TO 100
- PRINT TransacNum%;
- PRINT ") Enter transaction amount (0 to end): ";
- INPUT "", Amount(TransacNum%)
- IF Amount(TransacNum%) = 0 THEN
- TransacNum% = TransacNum% - 1
- EXIT FOR
- END IF
- NEXT
-
- ' Sort transactions in ascending order,
- ' using a "bubble sort":
- Limit% = TransacNum%
- DO
- Swaps% = FALSE
- FOR I% = 1 TO (Limit% - 1)
- ' If two adjacent elements are out of order,
- ' switch those elements:
- IF Amount(I%) > Amount(I% + 1) THEN
- SWAP Amount(I%), Amount(I% + 1)
- Swaps% = I%
- END IF
- NEXT I%
- ' Sort on next pass only to where last switch was made:
- Limit% = Swaps%
- ' Sort until no elements are exchanged:
- LOOP WHILE Swaps%
-
-
- ' Print the sorted transaction array. If a transaction
- ' is greater than zero, print it as a "CREDIT"; if a
- ' transaction is less than zero, print it as a "DEBIT":
- FOR I% = 1 TO TransacNum%
- IF Amount(I%) > 0 THEN
- PRINT USING "CREDIT: $$#####.##"; Amount(I%)
- ELSEIF Amount(I%) < 0 THEN
- PRINT USING "DEBIT: $$#####.##"; Amount(I%)
- END IF
- ' Update balance:
- Balance = Balance + Amount(I%)
- NEXT I%
- ' Print the final balance:
- PRINT
- PRINT "--------------------------"
- PRINT USING "Final Balance: $$######.##"; Balance
- END
-
- Carriage-Return/Line-Feed Filter (CRLF.BAS)
-
- Some text files are saved in a format that uses only a carriage return
- (return to the beginning of the line) or a line feed (advance to the next
- line) to signify the end of a line. Many text editors expand this single
- carriage return (CR) or line feed (LF) to a carriage-return/line-feed
- (CR-LF) sequence whenever you load the file for editing. However, if you use
- a text editor that does not expand a single CR or LF to CR-LF, you may have
- to modify the file so it has the correct sequence at the end of each line.
-
- The following program is a filter that opens a file, expands a single CR or
- LF to a CR-LF combination, then writes the adjusted lines to a new file. The
- original contents of the file are saved in a file with a .BAK extension.
-
- Statements UsedThis program demonstrates the following statements discussed
- in this chapter:
-
- ■ DO... LOOP WHILE
-
- ■ DO UNTIL... LOOP
-
- ■ Block IF... THEN... ELSE
-
- ■ SELECT CASE... END SELECT
-
-
- To make this program more useful, it contains the following constructions
- not discussed in this chapter:
-
- ■ A FUNCTION procedure named Backup$ that creates the file with the
- .BAK extension.
-
- See Chapter 2, "SUB and FUNCTION Procedures," for more information on
- defining and using procedures.
-
-
-
- ■ An error-handling routine named ErrorHandler to deal with errors that
- could occur when the user enters a filename. For instance, if the user
- enters the name of a nonexistent file, this routine prompts for a new
- name. Without this routine, such an error would end the program.
-
- See Chapter 8, "Error Handling," for more information on trapping
- errors.
-
- Program Listing
- DEFINT A-Z ' Default variable type is integer.
-
- ' The Backup$ function makes a backup file with
- ' the same base as FileName$ plus a .BAK extension:
- DECLARE FUNCTION Backup$ (FileName$)
-
- ' Initialize symbolic constants and variables:
- CONST FALSE = 0, TRUE = NOT FALSE
-
- CarReturn$ = CHR$(13)
- LineFeed$ = CHR$(10)
-
- DO
- CLS
-
- ' Input the name of the file to change:
- INPUT "Which file do you want to convert"; OutFile$
-
- InFile$ = Backup$(OutFile$) ' Get backup file's name.
-
- ON ERROR GOTO ErrorHandler ' Turn on error trapping.
-
- NAME OutFile$ AS InFile$ ' Rename input file as
- ' backup file.
-
- ON ERROR GOTO 0 ' Turn off error trapping.
-
- ' Open backup file for input and old file for output:
- OPEN InFile$ FOR INPUT AS #1
- OPEN OutFile$ FOR OUTPUT AS #2
-
- ' The PrevCarReturn variable is a flag set to TRUE
- ' whenever the program reads a carriage-return character:
- PrevCarReturn = FALSE
- ' Read from input file until reaching end of file:
- DO UNTIL EOF(1)
- ' This is not end of file, so read a character:
- FileChar$ = INPUT$(1, #1)
- CASE CarReturn$ ' The character is a CR.
-
- ' If the previous character was also a
- ' CR, put a LF before the character:
- IF PrevCarReturn THEN
- FileChar$ = LineFeed$ + FileChar$
- END IF
-
- ' In any case, set the PrevCarReturn
- ' variable to TRUE:
- PrevCarReturn = TRUE
-
- CASE LineFeed$ ' The character is a LF.
-
- ' If the previous character was not a
- ' CR, put a CR before the character:
- IF NOT PrevCarReturn THEN
- FileChar$ = CarReturn$ + FileChar$
- END IF
-
- ' Set the PrevCarReturn variable to FALSE:
- PrevCarReturn = FALSE
-
- CASE ELSE ' Neither a CR nor a LF.
-
- ' If the previous character was a CR,
- ' set the PrevCarReturn variable to FALSE
- ' and put a LF before the current character:
- IF PrevCarReturn THEN
- PrevCarReturn = FALSE
- FileChar$ = LineFeed$ + FileChar$
- END IF
-
- END SELECT
-
- ' Write the character(s) to the new file:
- PRINT #2, FileChar$;
- LOOP
-
- ' Write a LF if the last character in the file was a CR:
- IF PrevCarReturn THEN PRINT #2, LineFeed$;
- CLOSE ' Close both files.
- PRINT "Another file (Y/N)?" ' Prompt to continue.
-
- ' Change the input to uppercase (capital) letter:
- More$ = UCASE$(INPUT$(1))
-
- ' Continue the program if the user entered a "Y" or a "y":
- LOOP WHILE More$ = "Y"
- END
-
- ErrorHandler: ' Error-handling routine
- CONST NOFILE = 53, FILEEXISTS = 58
-
- ' The ERR function returns the error code for last error:
- SELECT CASE ERR
- CASE NOFILE ' Program couldn't find file
- ' with input name.
- PRINT "No such file in current directory."
- INPUT "Enter new name: ", OutFile$
- InFile$ = Backup$(OutFile$)
- RESUME
- CASE FILEEXISTS ' There is already a file named
- ' <filename>.BAK in this directory:
- ' remove it, then continue.
- KILL InFile$
- RESUME
- CASE ELSE ' An unanticipated error occurred:
- ' stop the program.
- ON ERROR GOTO 0
- END SELECT
-
- ' ======================== BACKUP$ =========================
- ' This procedure returns a filename that consists of the
- ' base name of the input file (everything before the ".")
- ' plus the extension ".BAK"
- ' ==========================================================
-
- FUNCTION Backup$ (FileName$) STATIC
-
- ' Look for a period:
- Extension = INSTR(FileName$, ".")
-
- ' If there is a period, add .BAK to the base:
- IF Extension > 0 THEN
- Backup$ = LEFT$(FileName$, Extension - 1) + ".BAK"
- ' Otherwise, add .BAK to the whole name:
- ELSE
- Backup$ = FileName$ + ".BAK"
- END IF
- END FUNCTION
-
- ───────────────────────────────────────────────────────────────────────────
-
-
- Chapter 2: SUB and FUNCTION Procedures
-
-
- This chapter explains how to simplify your
- programming by breaking programs into smaller logical components. These
- components [mdash] known as "procedures" [mdash] can then become building
- blocks that let you enhance and extend the BASIC language itself.
-
- When you are finished with this chapter, you will know how to perform the
- following tasks with procedures:
-
- ■ Define and call BASIC procedures.
-
- ■ Use local and global variables in procedures.
-
- ■ Use procedures instead of GOSUB subroutines and DEF FN functions.
-
- ■ Pass arguments to procedures and return values from procedures.
-
- ■ Write recursive procedures (procedures that can call themselves).
-
-
- Although you can create a BASIC program with any text editor, the QuickBASIC
- Extended (QBX) development environment makes it especially easy to write
- programs that contain procedures. Also, in most cases QBX automatically
- generates a DECLARE statement when you save your program. (The DECLARE
- statement ensures that only the correct number and type of arguments are
- passed to a procedure and allows your program to call procedures defined in
- separate modules.)
-
-
- Procedures: Building Blocks for Programming
-
- As used in this chapter, the term "procedure" covers SUB... END SUB and
- FUNCTION... END FUNCTION constructions. Procedures are useful for condensing
- repeated tasks. For example, suppose you are writing a program that you
- intend eventually to compile as a stand-alone application and you want the
- user of this application to be able to pass several arguments to the
- application from the command line. It then makes sense to turn this task
- [mdash] breaking the string returned by the COMMAND$ function into two or
- more arguments [mdash] into a separate procedure. Once you have this
- procedure up and running, you can use it in other programs. In essence, you
- are extending BASIC to fit your individual needs when you use procedures.
-
- These are the two major benefits of programming with procedures:
-
- ■ Procedures allow you to break your programs into discrete logical
- units, each of which can be more easily debugged than can an entire
- program without procedures.
-
- ■ Procedures used in one program can be used as building blocks in other
- programs, usually with little or no modification.
-
-
- You can also put procedures in your own Quick library, which is a special
- file that you can load into memory when you start QBX. Once the contents of
- a Quick library are in memory with QBX, any program that you write has
- access to the procedures in the library. This makes it easier for all of
- your programs to share and save code. (See Chapter 19, "Creating and Using
- Quick Libraries," for more information on how to build Quick libraries.)
-
-
- Comparing Procedures with Subroutines
-
- If you are familiar with earlier versions of BASIC, you might think of a
- SUB... END SUB procedure as being roughly similar to a GOSUB... RETURN
- subroutine. You will also notice some similarities between a FUNCTION...
- END FUNCTION procedure and a DEF FN... END DEF function. However,
- procedures have many advantages over these older constructions, as shown in
- the following sections.
-
-
- Note
-
- To avoid confusion between a SUB procedure and the target of a GOSUB
- statement, SUB procedures are referred to in this manual as "subprograms,"
- while statement blocks accessed by GOSUB... RETURN statements are referred
- to as "subroutines."
-
-
- Comparing SUB with GOSUB
-
- Although use of the GOSUB subroutine does help break programs into
- manageable units, SUB procedures have a number of advantages over
- subroutines, as discussed in the following sections.
-
- Local and Global Variables
- In SUB procedures, all variables are local by default; that is,
- they exist only within the scope of the SUB procedure's definition.
- To illustrate, the variable named I% in the following subprogram
- is local to the procedure, and has no connection with the variable named I%
- in the module-level code:
-
- I% = 1
- CALL Test
- PRINT I% ' I% still equals 1.
- END
-
- SUB Test STATIC
- I% = 50
- END SUB
-
-
- A GOSUB has a major drawback as a building block in programs: it contains
- only "global variables." With global variables, if you have a variable named
- I% inside your subroutine, and another variable named I% outside the
- subroutine but in the same module, they are one and the same. Any changes to
- the value of I% in the subroutine affect I% everywhere it appears in the
- module. As a result, if you try to patch a subroutine from one module into
- another module, you may have to rename subroutine variables to avoid
- conflict with variable names in the new module.
-
- Use in Multiple-Module Programs
- A SUB can be defined in one module and called from another. This
- significantly reduces the amount of code required for a program and
- increases the ease with which code can be shared among a number of
- programs.
-
- A GOSUB subroutine, however, must be defined and used in the same module.
-
- Operating on Different Sets of Variables
- A SUB procedure can be called any number of times within a
- program, with a different set of variables being passed to it each time.
- This is done by calling the SUB procedure with an argument
- list. (See the section "Passing Arguments to Procedures" later in
- this chapter for more information on how to do this.) In the following
- example, the subprogram Compare is called twice, with different pairs of
- variables passed to it each time:
-
- X = 4: Y = 5
-
- CALL Compare (X, Y)
-
- Z = 7: W = 2
- CALL Compare (Z, W)
- END
- SUB Compare (A, B)
- IF A < B THEN SWAP A, B
- END SUB
-
-
- Calling a GOSUB subroutine more than once in the same program and having it
- operate on a different set of variables each time is difficult. The process
- involves copying values to and from global variables, as shown in the next
- example:
-
- X = 4: Y = 5
- A = X: B = Y
- GOSUB Compare
- X = A: Y = B
-
- Z = 7 : W = 2
- A = Z : B = W
- GOSUB Compare
- Z = A : W = B
- END
-
- Compare:
- IF A < B THEN SWAP A, B
- RETURN
-
-
- Comparing FUNCTION with DEF FN
-
- While the multiline DEF FN function definition answers the need for
- functions more complex than can be squeezed onto a single line, FUNCTION
- procedures give you this capability plus the additional advantages discussed
- in the following sections.
-
- Local and Global Variables
- By default, all variables within a FUNCTION procedure are
- local to it, although you do have the option of using global variables.
- (See the section "Sharing Variables with SHARED" later in this
- chapter for more information on procedures and global variables.)
-
- In a DEF FN function, variables used within the function's body are global
- to the current module by default (this is also true for GOSUB subroutines).
- However, you can make a variable in a DEF FN function local by putting it
- in a STATIC statement.
-
- Changing Variables Passed to the Procedure
- Variables are passed to FUNCTION procedures by reference or
- by value. When you pass a variable by reference, you can change the
- variable by changing its corresponding parameter in the procedure.
- For example, after the call to GetRemainder in the next program,
- the value of X is 2, since the value of M is 2 at the end of the procedure:
-
- X = 89
- Y = 40
- PRINT GetRemainder(X, Y)
- PRINT X, Y ' X is now 2.
- END
-
-
- FUNCTION GetRemainder (M, N)
- GetRemainder = M MOD N
- M = M \ N
- END FUNCTION
-
- Variables are passed to a DEF FN function only by value, so in the next
- example, FNRemainder changes M without affecting X:
-
- DEF FNRemainder (M, N)
- FNRemainder = M MOD N
- M = M \ N
- END DEF
-
- X = 89
- Y = 40
- PRINT FNRemainder(X, Y)
-
- PRINT X,Y ' X is still 89.
-
- See the sections "Passing Arguments by Reference" and "Passing Arguments by
- Value" later in this chapter for more information on the distinction between
- passing by reference and by value.
-
- Calling the Procedure Within Its Definition
- A FUNCTION procedure can be "recursive"; in other words,
- it can call itself within its own definition. (See the section
- "Recursive Procedures" later in this chapter for more information on
- how procedures can be recursive.) A DEF FN function cannot
- be recursive.
-
- Use in Multiple-Module Programs
- You can define a FUNCTION procedure in one module and use
- it in another module. You need to put a DECLARE statement in
- the module in which the procedure is used; otherwise, your program thinks
- the procedure name refers to a variable. (See the section "Checking
- Arguments with DECLARE" later in this chapter for more information on using
- DECLARE this way.)
-
- A DEF FN function can only be used in the module in which it is defined.
- Unlike SUB or FUNCTION procedures, which can be called before they appear
- in the program, a DEF FN function must always be defined before it is used
- in a module.
-
-
- Note
-
- The name of a FUNCTION procedure can be any valid BASIC variable name,
- except one beginning with the letters "FN." The name of a DEF FN function
- must always be preceded
-
- by "FN."
-
-
- Defining Procedures
-
- BASIC procedure definitions have the following general syntax:
-
- { SUB | FUNCTION} procedurename ( parameterlist) STATIC
- statementblock-1
- EXIT { SUB | FUNCTION}
- statementblock-2
- END { SUB | FUNCTION}
-
- The following table describes the parts of a procedure definition:
-
- ╓┌──────────────────┌──────────────────┌──────────────────┌──────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- { SUB | FUNCTION} Marks the
- beginning of a
- SUB or FUNCTION
- procedure,
- respectively.
- ────────────────────────────────────────────────────────────────────────────
- respectively.
-
- procedurename Any valid
- variable name up
- to 40 characters
- long. The same
- name cannot be
- used for a SUB
- and a FUNCTION
- procedure.
-
- parameterlist A list of
- variables,
- separated by
- commas, that
- shows the number
- and type of
- arguments to be
- passed to the
- procedure. (The
- ────────────────────────────────────────────────────────────────────────────
- procedure. (The
- section
- "Parameters and
- Arguments" later
- in this chapter
- explains the
- difference
- between
- parameters and
- arguments.)
-
- STATIC If you use the If you omit the See the section
- STATIC attribute, STATIC attribute, "Automatic and
- local variables local variables Static Variables"
- are static; that are "automatic" later in this
- is, they retain by default; that chapter for more
- their values is, they are information.
- between calls to initialized to
- the procedure. zeros or null
- strings at the
- ────────────────────────────────────────────────────────────────────────────
- strings at the
- start of each
- procedure call.
-
-
-
-
-
-
- ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- END { SUB | FUNCTION} Ends a SUB or When your program
- FUNCTION definition. To encounters an END SUB
- run correctly, every or END FUNCTION, it
- procedure must have exits the procedure and
- exactly one END { SUB returns to the statement
- | FUNCTION} statement. immediately following
- the one that called the
- procedure. You can also
- use one or more optional
- ────────────────────────────────────────────────────────────────────────────
- use one or more optional
- EXIT { SUB | FUNCTION}
- statements within the
- body of a procedure
- definition to exit from
- the procedure.
-
-
-
-
-
- All valid BASIC expressions and statements except the following are allowed
- within a procedure definition.
-
- ■ DEF FN... END DEF, FUNCTION... END FUNCTION, or SUB... END SUB
-
- ■ COMMON.
-
- ■ DECLARE.
-
- ■ DIM SHARED.
-
- ■ OPTION BASE.
-
- ■ TYPE... END TYPE.
-
-
- Example
-
- The following example is a FUNCTION procedure named IntegerPower:
-
- FUNCTION IntegerPower& (X&, Y&) STATIC
- PowerVal& = 1
- FOR I& = 1 TO Y&
- PowerVal& = PowerVal& * X&
- NEXT I&
- IntegerPower& = PowerVal&
- END FUNCTION
-
-
- Calling Procedures
-
- Calling a FUNCTION procedure is different from calling a SUB procedure, as
- shown in the next two sections.
-
-
- Calling a FUNCTION Procedure
-
- You call a FUNCTION procedure the same way you use an intrinsic BASIC
- function such as ABS, that is, by using its name in an expression. For
- example, any of the following statements would call a FUNCTION named ToDec
- :
-
- PRINT 10 * ToDec
- X = ToDec
- IF ToDec = 10 THEN PRINT "Out of range."
-
- A FUNCTION procedure can return values by changing variables passed to it
- as arguments. (See the section "Passing Arguments by Reference" later in
- this chapter for an explanation of how this is done.) Additionally, a
- FUNCTION procedure returns a single value in its name, so the name of the
- function must agree with the type it returns. For example, if the function
- returns a string value, either its name must have the string
- type-declaration character ( $) appended to it, or it must be declared as
- having the string type in a preceding DEFSTR statement.
-
- Example
-
- The following program shows a FUNCTION procedure that returns a string
- value. Note that the type-declaration suffix for strings ($) is part of the
- procedure name.
-
- DECLARE FUNCTION GetInput$ ()
- Banner$ = GetInput$ ' Call the function and assign the
- ' return value to a string variable.
- PRINT Banner$ ' Print the string.
- END
-
- ' ======================= GetInput$ ========================
- ' The $ type-declaration character at the end of this
- ' function name means that it returns a string value.
- ' ==========================================================
-
- FUNCTION GetInput$ STATIC
-
- ' Return a string of 10 characters read from the
- ' keyboard, echoing each character as it is typed:
- FOR I% = 1 TO 10
- Char$ = INPUT$(1) ' Get the character.
- PRINT Char$; ' Echo the character on the
- ' screen.
- Temp$ = Temp$ + Char$ ' Add the character to the
- ' string.
- NEXT
- PRINT
- GetInput$ = Temp$ ' Assign the string to the procedure.
- END FUNCTION
-
-
- Calling a SUB Procedure
-
- A SUB procedure differs from a FUNCTION procedure in that a sub procedure
- cannot be called by using its name within an expression. A call to a sub
- procedure is a stand-alone statement, like BASIC's CIRCLE statement. Also,
- a sub procedure does not return a value in its name as does a function.
- However, like a function, a sub procedure can modify the values of any
- variables passed to it. (The section "Passing Arguments by Reference" later
- in this chapter explains how this is done.)
-
- There are two ways to call a SUB procedure:
-
- ■ Put its name in a CALL statement:
- CALL PrintMessage ■ n
-
- Use its name as a statement by itself:
- PrintMessage
-
- If you omit the CALL keyword, don't put parentheses around arguments passed
- to the sub procedure:
-
- ' Call the ProcessInput subprogram with CALL and pass the
- ' three arguments First$, Second$, and NumArgs% to it:
- CALL ProcessInput (First$, Second$, NumArgs%)
-
- ' Call the ProcessInput subprogram without CALL and pass
- ' it the same arguments (note no parentheses around the
- ' argument list):
- ProcessInput First$, Second$, NumArgs%
-
- See the next section for more information on passing arguments to
- procedures.
-
- If your program calls SUB procedures without using CALL, and if you are
- not using QBX to write the program, you must put the name of the subprogram
- in a DECLARE statement before it is called:
-
- DECLARE SUB CheckForKey
- .
- .
- .
- CheckForKey
-
-
- You need to be concerned about this only if you are developing programs
- outside QBX, since QBX automatically inserts DECLARE statements wherever
- they are needed when it saves a program.
-
-
- Passing Arguments to Procedures
-
- The following sections explain how to tell the difference between parameters
- and arguments, how to pass arguments to procedures, and how to check
- arguments to make sure they are of the correct type and quantity.
-
-
- Parameters and Arguments
-
- The first step in learning about passing arguments to procedures is
- understanding the difference between the terms "parameter" and "argument":
-
- ╓┌─────────────────────────────────────┌─────────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- A variable name that appears in a A constant, variable, or expression
- SUB, FUNCTION, or DECLARE passed to a SUB or FUNCTION when
- statement the procedure is called
-
-
-
-
-
- In a procedure definition, parameters are placeholders for arguments. As
- shown in Figure 2.1, when a procedure is called, arguments are plugged into
- the variables in the parameter list, with the first parameter receiving the
- first argument, the second parameter receiving the second argument, and so
- on.
-
- Figure 2.1 also demonstrates another important rule: although the
- names of variables do not have to be the same in an argument list and a
- parameter list, the number of parameters and the number of arguments do.
- Furthermore, the type (string, integer numeric, single-precision numeric,
- and so on) should be the same for corresponding arguments and parameters.
- (See the section "Checking Arguments with DECLARE" later in this chapter for
- more information on how to ensure that arguments and parameters agree in
- number and type.)
-
-
- A parameter list consists of any of the following, all separated by commas:
-
- ■ Valid variable names, except for fixed-length strings.
-
- For example, X$ and X AS STRING are both legal in a parameter list,
- since they refer to variable-length strings. However, X AS STRING * 10
- refers to a fixed-length string 10 characters long and cannot appear
- in a parameter list. (Fixed-length strings are perfectly all right as
- arguments passed to procedures. See Chapter 4, "String Processing,"
- for more information on fixed-length and variable-length strings.)
-
- ■ Array names followed by a pair of left and right parentheses.
-
- An argument list consists of any of the following, all separated by
- commas:
-
- ■ Constants.
-
- ■ Expressions.
-
- ■ Valid variable names.
-
- ■ Array names followed by left and right parentheses.
-
-
- Examples
-
- The following example shows the first line of a subprogram definition with a
- parameter list:
-
- SUB TestSub (A%, Array(), RecVar AS RecType, Cs$)
-
- The first parameter, A%, is an integer; the second parameter, Array(), is a
- single-precision array, since untyped numeric variables are single precision
- by default; the third parameter, RecVar, is a record of type RecType; and
- the fourth parameter, Cs$, is a string.
-
- The CALL TestSub line in the next example calls the TestSub subprogram and
- passes it four arguments of the appropriate type:
-
- TYPE RecType
- Rank AS STRING * 12
- SerialNum AS LONG
- END TYPE
-
- DIM RecVar AS RecType
-
- CALL TestSub (X%, A(), RecVar, "Daphne")
-
-
- Passing Constants and Expressions
-
- Constants [mdash] whether string or numeric [mdash] can appear in the list
- of arguments passed to a procedure. Naturally, a string constant must be
- passed to a string parameter and a numeric constant to a numeric parameter,
- as shown in the next example:
-
-
- CONST SCREENWIDTH = 80
- CALL PrintBanner (SCREENWIDTH, "Monthly Status Report")
- .
- .
- .
- SUB PrintBanner (SW%, Title$)
- .
- .
- .
- END SUB
-
- If a numeric constant in an argument list does not have the same type as the
- corresponding parameter in the SUB or FUNCTION statement, then the
- constant is coerced to the type of the parameter, as you can see by the
- output from the next example:
-
- CALL test(4.6, 4.1)
- END
-
- SUB test (x%, y%)
- PRINT x%, y%
- END SUB
- Output5 4
-
- Expressions resulting from operations on variables and constants can also be
- passed to a procedure. As is the case with constants, numeric expressions
- that disagree in type with their parameters are coerced into agreement, as
- shown here:
-
- Checker A! + 25!, NOT BooleanVal%
-
- ' In the next call, putting parentheses around the
- ' long-integer variable Bval& makes it an expression.
- ' The (Bval&) expression is coerced to a short integer
- ' in the Checker procedure:
- Checker A! / 3.1, (Bval&)
- .
- .
- .
- END
-
- SUB Checker (Param1!, Param2%)
- .
- .
- .
- END SUB
-
-
- Passing Variables
-
- This section discusses how to pass simple variables, complete arrays,
- elements of arrays, records, and elements of records to procedures.
-
- Passing Simple Variables
- In argument and parameter lists, you can declare the type for a simple
- variable in one of the following three ways:
-
- ■ Append one of the type-declaration suffixes ( %, &, !, #, @ or $)
- to the variable name.
-
- ■ Declare the variable in a DIM, COMMON, REDIM, SHARED, or STATIC
- statement . For example:
-
-
- DIM A AS LONG ■ n
-
- Use a DEF type statement to set the default type.
-
-
- No matter which method you choose, corresponding variables must have the
- same type in the argument and parameter lists, as shown in the example
- below.
-
-
- Example
-
- In this example, two arguments are passed to the FUNCTION procedure. The
- first is an integer giving the length of the string returned by CharString$,
- while the second is a character that is repeated to make the string.
-
- FUNCTION CharString$(A AS INTEGER, B$) STATIC
- CharString$ = STRING$(A%, B$)
- END FUNCTION
-
- DIM X AS INTEGER
- INPUT "Enter a number (1 to 80): ", X
- INPUT "Enter a character: ", Y$
-
- ' Print a string consisting of the Y$ character, repeated
- ' X number of times:
- PRINT CharString$(X, Y$)
- END
- OutputEnter a number (1 to 80): 21
- Enter a character: #
- #####################
- Passing an Entire Array
- To pass all the elements of an array to a procedure, put the array's name,
- followed by left and right parentheses, in the argument and parameter lists.
-
-
- Example
-
- This example shows how to pass all the elements of an array to a procedure:
-
- DIM Values(1 TO 5) AS INTEGER
-
- ' Note empty parentheses after array name when calling
- ' procedure and passing array:
- CALL ChangeArray (1, 5, Values())
- CALL PrintArray (1, 5, Values())
- END
-
- ' Note empty parentheses after P parameter:
-
- SUB ChangeArray (Min%, Max%, P() AS INTEGER) STATIC
- FOR I% = Min% TO Max%
- P(I%) = I% ^ 3
- NEXT I%
- END SUB
-
- SUB PrintArray (Min%, Max%, P() AS INTEGER) STATIC
- FOR I% = Min% TO Max%
- PRINT P(I%)
- NEXT I%
- PRINT
- END SUB
-
-
- Passing Individual Array Elements
-
- If a procedure does not require an entire array, you can pass individual
- elements of the array instead. To pass an element of an array, use the array
- name followed by the appropriate subscripts inside parentheses.
-
- Example
-
- The SqrVal Array(4,2) statement in the following example passes the element
- in row 4, column 2 of the array to the SqrVal subprogram. (Note how the
- subprogram actually changes the value of this array element.)
-
- DIM Array(1 TO 5,1 TO 3)
-
- Array(4,2) = -36
- PRINT Array(4,2)
- SqrVal Array(4,2)
- PRINT Array(4,2)' The call to SqrVal has changed
- ' the value of Array(4,2).
- END
- SUB SqrVal(A) STATIC
- A = SQR(ABS(A))
- END SUB
- Output-36
- 6
-
-
- Using Array-Bound Functions
-
- The LBOUND and UBOUND functions provide a useful way to determine the size
- of an array passed to a procedure. The LBOUND function finds the smallest
- index value of an array subscript, while the UBOUND function finds the
- largest one. These functions save you the trouble of having to pass the
- upper and lower bounds of each array dimension to a procedure.
-
- Example
-
- The subprogram in the following example uses the LBOUND function to
- initialize the variables Row and Col to the lowest subscript values in each
- dimension of A. It also uses the UBOUND function to limit the number of
- times the FOR loop executes to the number of elements in the array.
-
- SUB PrintOut(A()) STATIC
- FOR Row = LBOUND(A,1) TO UBOUND(A,1)
- FOR Col = LBOUND(A,2) TO UBOUND(A,2)
- PRINT A(Row,Col)
- NEXT Col
- NEXT Row
- END SUB
-
-
- Passing an Entire Record
-
- To pass a complete record (a variable declared as having a user-defined
- type) to a procedure, complete the following steps:
-
- 1. Define the type (StockItem in this example).
- TYPE StockItem
- PartNumber AS STRING * 6
- Description AS STRING * 20
- UnitPrice AS SINGLE
- Quantity AS INTEGER
- END TYPE
-
- 2. Declare a variable (StockRecord) with that type.
- DIM StockRecord AS StockItem 3.
-
- 3. Call a procedure (FindRecord) and pass it the variable
- you have declared.
- CALL FindRecord(StockRecord) 4.
-
- 4. In the procedure definition, give the parameter the same
- type as the variable.
-
- SUB FindRecord (RecordVar AS StockItem) STATIC
- .
- .
- .
- END SUB
-
-
- Passing Individual Elements of a Record
- To pass an individual element in a record to a procedure, put the
- name of the element ( recordname . elementname)
- in the argument list. Be sure, as always, that the corresponding
- parameter in the procedure definition agrees with the type of that
- element.
-
- Example
-
- The following example shows how to pass the two elements in the record
- variable StockItem to the PrintTag SUB procedure. Note how each parameter
- in the SUB procedure agrees with the type of the individual record
- elements.
-
- TYPE StockType
- PartNumber AS STRING * 6
- Descrip AS STRING * 20
- UnitPrice AS CURRENCY
- Quantity AS INTEGER
- END TYPE
-
- DIM StockItem AS StockType
-
- CALL PrintTag (StockItem.Descrip, StockItem.UnitPrice)
- .
- .
- .
- END
-
- SUB PrintTag (Desc$, Price AS CURRENCY)
- .
- .
- .
- END SUB
-
- Checking Arguments with DECLARE
-
- If you are using QBX to write your program, you will notice that QBX
- automatically inserts a DECLARE statement for each procedure whenever you
- save the program. Each DECLARE statement consists of the word DECLARE,
- followed by the words SUB or FUNCTION, the name of the procedure, and a
- set of parentheses. If the procedure has no parameters, then the parentheses
- are empty. If the procedure has parameters, then the parentheses enclose a
- parameter list that specifies the number and type of the arguments to be
- passed to the procedure. This parameter list has the same format as the list
- in the definition line found in the SUB or FUNCTION procedure.
-
- The purpose of the parameter list in a DECLARE statement is to turn on
- "type checking" of arguments passed to the procedure. That is, every time
- the procedure is called with variable arguments, those variables are checked
- to be sure they agree with the number and type of the parameters in the
- DECLARE statement.
-
-
- QBX puts all procedure definitions at the end of a module when it saves a
- program. Therefore, if there are no DECLARE statements, when you try to
- compile a program with the BC command you would run into a problem known as
- "forward reference" (calling a procedure before it is defined). By
- generating a prototype of the procedure definition, DECLARE statements
- allow your program to call procedures that are defined later in a module, or
- in another module altogether.
-
- Examples
-
- The next example shows an empty parameter list in the DECLARE statement,
- since no arguments are passed to GetInput$:
-
- DECLARE FUNCTION GetInput$ ()
- X$ = GetInput$
-
- FUNCTION GetInput$ STATIC
- GetInput$ = INPUT$(10)
- END FUNCTION
-
- The next example shows a parameter list in the DECLARE statement, since an
- integer argument is passed to this version of GetInput$:
-
- DECLARE FUNCTION GetInput$ (X%)
- X$ = GetInput$ (5)
-
- FUNCTION GetInput$ (X%) STATIC
- GetInput$ = INPUT$(X%)
- END FUNCTION
- When QBX Does Not Generate a DECLARE Statement
- In certain instances, QBX does not generate DECLARE statements in the
- module that calls a procedure.
-
- QBX cannot generate a DECLARE statement in one module for a SUB procedure
- defined in another module if the module containing the definition is not
- loaded. However, the DECLARE statement is not needed unless you want to
- call the SUB procedure without using the keyword CALL.
-
- QBX does not generate a DECLARE statement for a FUNCTION procedure,
- whether that module is loaded or not. In such a case, you must type the
- DECLARE statement yourself at the beginning of the module where the
- FUNCTION procedure is called; otherwise, QBX considers the call to the
- procedure to be a variable name.
-
- QBX also cannot generate a DECLARE statement for any procedure in a Quick
- library. You must add one to the program yourself.
-
- Developing Programs Outside the QBX Environment
- If you are writing your programs with your own text editor and then
- compiling them outside the QBX environment with the BC and LINK
- commands, be sure to put DECLARE statements in the
- following three locations:
-
- ■ At the beginning of any module that calls a FUNCTION procedure before
- it is defined:
-
- ■ At the beginning of any module that calls a SUB procedure before it
- is defined and does not use CALL when calling the sub procedure:
-
- When you call a SUB procedure with CALL, you
- don't have to declare the procedure first:
-
- ■ At the beginning of any module that calls a SUB or FUNCTION
- procedure defined in another module (an "external procedure").
-
- If your procedure has no parameters, remember to put empty parentheses after
- the name of the procedure in the DECLARE statement, as in the next example:
-
- DECLARE FUNCTION GetHour$ ()
- PRINT GetHour$
- END
-
- FUNCTION GetHour$ STATIC
- GetHour$ = LEFT$(TIME$,2)
- END FUNCTION
-
- Remember, a DECLARE statement can appear only at the module level, not the
- procedure level. A DECLARE statement affects the entire module in which it
- appears.
-
- Using Include Files for DeclarationsIf you have created a separate
- procedure-definition module that defines one or more SUB or FUNCTION
- procedures, it is a good idea to make an include file to go along with this
- module. This include file should contain the following:
-
- ■ DECLARE statements for all the module's procedures.
-
- ■ TYPE... END TYPE record definitions for any record parameters in this
- module's SUB or FUNCTION procedures.
-
- ■ COMMON statements listing variables shared between this module and
- other modules in the program. (See the section "Sharing Variables with
- Other Modules" later in this chapter for more information on using
- COMMON for this purpose.)
-
-
- Every time you use the definition module in one of your programs, insert a
- $INCLUDE metacommand at the beginning of any module that invokes procedures
- in the definition module. When your program is compiled, the actual contents
- of the include file are substituted for the $INCLUDE metacommand.
-
- A simple rule of thumb is to make an include file for every module and then
- use the module and the include file together as outlined previously. The
- following list itemizes some of the benefits of this technique:
-
- ■ A module containing procedure definitions remains truly modular
- [mdash] that is, you don't have to copy all the DECLARE statements
- for its procedures every time you call them from another module;
- instead, you can just substitute one $INCLUDE metacommand.
-
- ■ In QBX, using an include file for procedure declarations suppresses
- automatic generation of DECLARE statements when you save a program.
-
- ■ Using an include file for declarations avoids problems with getting
- one module to recognize a FUNCTION procedure in another module. (See
- the section "When QBX Does Not Generate a DECLARE Statement" earlier
- in this chapter for more information.)
-
-
-
- You can take advantage of QBX's facility for generating DECLARE statements
- when creating your include file. The following steps show you how to do
- this:
-
- 1. Create your module.
-
- 2. Within that module, call any SUB or FUNCTION procedures you have
- defined.
-
- 3. Save the module to get automatic DECLARE statements for all the
- procedures.
-
- 4. Re-edit the module, removing the procedure calls and moving the
- DECLARE statements to a separate include file.
-
-
- See the BASIC Language Reference for more information on the syntax and
- usage of the $INCLUDE metacommand.
-
- Example
-
- The following fragments illustrate how to use a definition module and an
- include file together:
-
- ' =========================================================
- ' MODDEF.BAS
- ' This module contains definitions for the Prompter and
- ' Max! procedures.
- ' =========================================================
- ' $INCLUDE: 'MODDEF.BI'
- FUNCTION Max! (X!, Y!) STATIC
- IF X! > Y! THEN Max! = X! ELSE Max! = Y!
- END FUNCTION
- SUB Prompter (Row%, Column%, RecVar AS RecType) STATIC
- LOCATE Row%, Column%
- INPUT "Description: ", RecVar.Description
- INPUT "Quantity: ", RecVar.Quantity
- END SUB
-
- ' =========================================================
- ' MODDEF.BI
- ' This is an include file that contains DECLARE statements
- ' for the Prompter and Max! procedures (as well as a TYPE
- ' statement defining the RecType user type). Use this file
- ' whenever you use the MODDEF.BAS module.
- ' =========================================================
- '
- TYPE RecType
- Description AS STRING * 15
- Quantity AS INTEGER
- END TYPE
-
- DECLARE FUNCTION Max! (X!, Y!)
- DECLARE SUB Prompter (Row%, Column%, RecVar AS RecType)
-
-
- ' ============================================================
- 'SAMPLE.BAS
- ' This module is linked with the MODDEF.BAS module, and
- ' calls the Prompter and Max! procedures in MODDEF.BAS.
- ' ============================================================
- '
- ' The next line makes the contents of the MODDEF.BI include
- ' file part of this module as well:
- ' $INCLUDE: 'MODDEF.BI'
- .
- .
- .
- INPUT A, B
- PRINT Max!(A, B)' Call the Max! FUNCTION procedure in MODDEF.BAS.
- .
- .
- .
- Prompter 5, 5, RecVar' Call the Prompter SUB procedure in MODDEF.BAS
- .
- .
- .
-
- Important
-
- While it is good programming practice to put procedure declarations in an
- include file, do not put the procedures themselves ( SUB... END SUB or
- FUNCTION... END FUNCTION blocks) in an include file. Procedure definitions
- are not allowed inside include files in QBX. If you have used include files
- to define SUB procedures in programs written with QuickBASIC versions 2.0
- or 3.0, either put these definitions in a separate module or incorporate
- them into the module where they are called.
-
- Declaring Procedures in Quick Libraries
-
- A convenient programming practice is to put all the declarations for
- procedures in a Quick library into one include file. With the $INCLUDE
- metacommand you can then incorporate this include file into programs
- using the library. This saves you the trouble of copying all the
- relevant DECLARE statements every time you use the library.
-
-
- Passing Arguments by Reference
-
- By default, variables [mdash] whether simple scalar variables, arrays and
- array elements, or records [mdash]are passed "by reference" to FUNCTION and
- SUB procedures. Here is what is meant by passing variables by reference:
-
- ■ Each program variable has an address or a location in memory where its
- value is stored.
-
- ■ The process of calling a procedure and passing variables to it by
- reference calls the procedure and passes it the address of each
- variable. This means that the address of the variable and the address
- of its corresponding parameter in the procedure are one and the same.
-
- ■ Therefore, if the procedure modifies the value of the parameter, it
- also modifies the value of the variable that is passed.
-
-
- If you do not want a procedure to change the value of a variable, pass the
- procedure the value contained in the variable, not the address. This way,
- changes are made only to a copy of the variable, not the variable itself.
- See the next section for a discussion of this alternative way of passing
- variables.
-
- Example
-
- In the following program, changes made to the parameter A$ in the Replace
- procedure also change the argument Test$:
-
- Test$ = "a string with all lowercase letters."
- PRINT "Before subprogram call: "; Test$
- CALL Replace (Test$, "a")
- PRINT "After subprogram call: "; Test$
- END
-
- SUB Replace (A$, B$) STATIC
- Start = 1
- DO
-
- ' Look for B$ in A$, starting at the character
- ' with position "Start" in A$:
- Found = INSTR(Start, A$, B$)
- ' Make every occurrence of B$ in A$
- ' an uppercase letter:
- IF Found > 0 THEN
- MID$(A$,Found) = UCASE$(B$)
- Start = Start + 1
- END IF
- LOOP WHILE Found > 0
- END SUB
-
- Output
- Before subprogram call: a string with all lowercase letters.
- After subprogram call: A string with All lowercAse letters.
-
-
- Passing Arguments by Value
-
- Passing an argument "by value" means the value of the argument is passed,
- rather than its address. This prevents the original variable from being
- changed by the procedure that is called. In BASIC procedures, an actual
- value cannot be passed, but the same result is achieved by putting
- parentheses around the variable name. This causes BASIC to treat the
- variable as an expression. In this case, as with all expressions, the
- variable is copied to a temporary location, and the address of this
- temporary location is passed. Since the procedure does not have access to
- the address of the original variable, it cannot change the original
- variable; it makes all changes to the copy instead.
-
-
- Expressions are passed to procedures as in the following:
-
- ' A + B is an expression; the values of A and B
- ' are not affected by this procedure call:
- CALL Mult(A + B, C)
-
- Any variable enclosed in parentheses is treated by BASIC as an expression,
- as shown in the next example.
-
- Example
-
- In this example, a variable is enclosed in parentheses and passed to a
- procedure. This simulates actual passing by value because the variable data
- is copied to a temporary location whose address is passed. As you can see
- from the output that follows, changes to the SUB procedure's local variable
- Y are passed back to the module-level code as changes to the variable B.
- However, changes to X in the procedure do not affect the value of A, since A
- is passed by value.
-
- A = 1
- B = 1
- PRINT "Before subprogram call, A ="; A; ", B ="; B
-
- ' A is passed by value, and B is passed by reference:
- CALL Mult((A), B)
- PRINT "After subprogram call, A ="; A; ", B ="; B
- END
- SUB Mult (X, Y) STATIC
- X = 2 * X
- Y = 3 * Y
- PRINT "In subprogram, X ="; X; ", Y ="; Y
- END SUB
-
- Output
- Before subprogram call, A = 1 , B = 1
- In subprogram, X = 2 , Y = 3
- After subprogram call, A = 1 , B = 3
-
- Sharing Variables with SHARED
-
- In addition to passing variables through argument and parameter lists,
- procedures can also share variables with other procedures and with code at
- the module level (that is, code within a module but outside of any
- procedure) in one of the following two ways:
-
- ■ Variables listed in a SHARED statement within a procedure are shared
- only between that procedure and the module-level code. Use this method
- when different procedures in the same module need different
- combinations of module-level variables.
-
- ■ Variables listed in a module-level COMMON SHARED, DIM SHARED, or
- REDIM SHARED statement are shared between the module-level code and
- all procedures within that module. This method is most useful when all
- procedures in a module use a common set of variables.
-
-
- You can also use the COMMON or COMMON SHARED statement to share variables
- among two or more modules. The next three sections discuss these three ways
- to share variables.
-
-
- Sharing Variables with Specific Procedures
-
- in a Module
-
- If different procedures within a module need to share different variables
- with the module-level code, use the SHARED statement within each procedure.
-
- Arrays in SHARED statements consist of the array name followed by a set of
- empty parentheses:
-
- SUB JustAnotherSub STATIC
- SHARED ArrayName ()
- .
- .
- .
-
- If you give a variable its type in an AS type clause, then the variable
- must also be typed with the AS type clause in a SHARED statement:
-
- DIM Buffer AS STRING * 10
- .
- .
- .
- END
-
- SUB ReadRecords STATIC
- SHARED Buffer AS STRING * 10
- .
- .
- .
- END SUB
-
-
- Example
-
- In this example, the SHARED statements in the GetRecords and InventoryTotal
- procedures show the format of a shared variable list:
-
- DECLARE SUB GetRecords ()
- DECLARE FUNCTION InventoryTotal! ()
- ' =========================================================
- ' MODULE-LEVEL CODE
- ' =========================================================
- TYPE RecType
- Price AS SINGLE
- Desc AS STRING * 35
- END TYPE
-
- DIM RecVar(1 TO 100) AS RecType ' Array of records
-
- INPUT "File name: ", FileSpec$
- CALL GetRecords
- PRINT InventoryTotal
- END
-
- ' =========================================================
- ' PROCEDURE-LEVEL CODE
- ' =========================================================
- SUB GetRecords STATIC
-
- ' Both FileSpec$ and the RecVar array of records
- ' are shared with the module-level code above:
- SHARED FileSpec$, RecVar() AS RecType
- OPEN FileSpec$ FOR RANDOM AS #1
- .
- .
- .
- END SUB
-
- FUNCTION InventoryTotal STATIC
-
- ' Only the RecVar array is shared with the module-level
- ' code:
- SHARED RecVar() AS RecType
- .
- .
- .
- END FUNCTION
-
-
- Sharing Variables with All Procedures in a Module
-
- If variables are declared at the module level with the SHARED attribute in
- a COMMON, DIM, or REDIM statement (for example, by using a statement of
- the form COMMON SHARED variablelist), then all procedures within that
- module have access to those variables; in other words, the SHARED attribute
- makes variables global throughout a module.
-
- The SHARED attribute is convenient when you need to share large numbers of
- variables among all procedures in a module.
-
- Examples
-
- These statements declare variables shared among all procedures in one
- module:
-
- COMMON SHARED A, B, C
- DIM SHARED Array(1 TO 10, 1 TO 10) AS UserType
- REDIM SHARED Alpha(N%)
-
- In the following example, the module-level code shares the string array
- StrArray and the integer variables Min and Max with the two SUB procedures
- FillArray and PrintArray:
-
- ' =========================================================
- ' MODULE-LEVEL CODE
- ' =========================================================
- '
- DECLARE SUB FillArray ()
- DECLARE SUB PrintArray ()
-
- ' The following DIM statements share the Min and Max
- ' integer variables and the StrArray string array
- ' with any SUB or FUNCTION procedure in this module:
- DIM SHARED StrArray (33 TO 126) AS STRING * 5
- DIM SHARED Min AS INTEGER, Max AS INTEGER
-
- Min = LBOUND(StrArray)
- Max = UBOUND(StrArray)
-
- FillArray' Note the absence of argument lists.
- PrintArray
- END
-
- ' =========================================================
- ' PROCEDURE-LEVEL CODE
- ' =========================================================
- '
- SUB FillArray STATIC
-
- ' Load each element of the array from 33 to 126
- ' with a 5-character string, each character of which
- ' has the ASCII code I%:
- FOR I% = Min TO Max
- StrArray(I%) = STRING$(5, I%)
- NEXT
-
- END SUB
-
- SUB PrintArray STATIC
- FOR I% = Min TO Max
- PRINT StrArray(I%)
- NEXT
- END SUB
- Partial Output!!!!!
- """""
- #####
- $$$$$
- %%%%%
- &&&&&
- '''''
- .
- .
- .
-
- If you are using your own text editor to write your programs and directly
- compiling those programs outside the QBX development environment, note that
- variable declarations with the SHARED attribute must precede the procedure
- definition. Otherwise, the value of any variable declared with SHARED is
- not available to the procedure, as shown by the output from the next
- example. (If you are using QBX to create your programs, this sequence is not
- required, since QBX automatically saves programs in the correct order.)
-
- DEFINT A-Z
-
- FUNCTION Adder (X, Y) STATIC
- Adder = X + Y + Z
- END FUNCTION
-
- DIM SHARED Z
- Z = 2
- PRINT Adder (1, 3)
- END
- Output4
-
-
- The next example shows how you should save the module shown previously, with
- the definition of Adder following the DIM SHARED Z statement:
-
- DEFINT A-Z
-
- DECLARE FUNCTION Adder (X, Y)
-
- ' The variable Z is now shared with Adder:
- DIM SHARED Z
- Z = 2
- PRINT Adder (1, 3)
- END
-
- FUNCTION Adder (X, Y) STATIC
- Adder = X + Y + Z
- END FUNCTION
- Output6
-
-
- Sharing Variables with Other Modules
-
- If you want to share variables across modules in your program, list the
- variables in COMMON or COMMON SHARED statements at the module level in
- each module.
-
- Examples
-
- The following example shows how to share variables between modules by using
- a COMMON statement in the module that calls the SUB procedures, as well as
- a COMMON SHARED statement in the module that defines the procedures. With
- COMMON SHARED, all procedures in the second module have access to the common
- variables.
-
- ' =========================================================
- ' MAIN MODULE
- ' =========================================================
-
- COMMON A, B
- A = 2.5
- B = 1.2
- CALL Square
- CALL Cube
- END
-
- ' =========================================================
- ' Module with Cube and Square Procedures
- ' =========================================================
-
- ' NOTE: The names of the variables (X, Y) do not have to be
- ' the same as in the other module (A, B). Only the types
- ' have to be the same.
-
- COMMON SHARED X, Y ' This statement is at the module level.
- ' Both X and Y are shared with the CUBE
- ' and SQUARE procedures below.
- SUB Cube STATIC
- PRINT "A cubed ="; X ^ 3
- PRINT "B cubed ="; Y ^ 3
- END SUB
-
- SUB Square STATIC
- PRINT "A squared ="; X ^ 2
- PRINT "B squared ="; Y ^ 2
- END SUB
-
- The following example uses named COMMON blocks at the module levels and
- SHARED statements within procedures to share different sets of variables
- with each procedure:
-
- DECLARE SUB VolumeCalc ()
- DECLARE SUB DensityCalc ()
- ' =========================================================
- ' MAIN MODULE
- ' Prints the volume and density of a filled cylinder given
- ' the input values.
- ' =========================================================
-
- COMMON /VolumeValues/ Height, Radius, Volume
- COMMON /DensityValues/ Weight, Density
-
- INPUT "Height of cylinder in centimeters: ", Height
- INPUT "Radius of cylinder in centimeters: ", Radius
- INPUT "Weight of filled cylinder in grams: ", Weight
-
- CALL VolumeCalc
- CALL DensityCalc
-
- PRINT "Volume is"; Volume; "cubic centimeters."
- PRINT "Density is"; Density; "grams/cubic centimeter."
- END
-
-
- ' =========================================================
- ' Module with DensityCalc and VolumeCalc Procedures
- ' =========================================================
-
- COMMON /VolumeValues/ H, R, V
- COMMON /DensityValues/ W, D
-
- SUB DensityCalc STATIC
-
- ' Share the Weight, Volume, and Density variables
- ' with this procedure:
- SHARED W, V, D
- D = W / V
- END SUB
-
- SUB VolumeCalc STATIC
-
- ' Share the Height, Radius, and Volume variables
- ' with this procedure:
- SHARED H, R, V
- CONST PI = 3.141592653589#
- V = PI * H * (R ^ 2)
- END SUB
- OutputHeight of cylinder in centimeters: 100
- Radius of cylinder in centimeters: 10
- Weight of filled cylinder in grams: 10000
- Volume is 31415.93 cubic centimeters.
- Density is .3183099 grams/cubic centimeter.
-
- The Problem of Variable Aliases
-
- "Variable aliases" can become a problem in long programs containing many
- variables and procedures. Variable aliases occur when two or more names
- refer to the same location in memory. Situations where it arises are:
-
- ■ When the same variable appears more than once in the list of arguments
- passed to a procedure.
-
- ■ When a variable passed in an argument list is also accessed by the
- procedure by means of the SHARED statement or the SHARED attribute.
-
-
-
- To avoid alias problems, double-check variables shared with a procedure to
- make sure they don't also appear in a procedure call's argument list. Also,
- don't pass the same variable twice, as in the next statement:
-
- ' X is passed twice; this will lead to alias problems
- ' in the Test procedure:
- CALL Test(X, X, Y)
-
- Example
-
- The following example illustrates how variable aliases can occur. Here the
- variable A is shared between the module-level code and the SUB procedure
- with the DIM SHARED statement. However, A is also passed by reference to the
- subprogram as an argument. Therefore, in the subprogram, A and X both refer
- to the same location in memory. Thus, when the subprogram modifies X, it is
- also modifying A, and vice versa.
-
- DIM SHARED A
- A = 4
- CALL PrintHalf(A)
- END
-
- SUB PrintHalf (X) STATIC
- PRINT "Half of"; X; "plus half of"; A; "equals";
- X = X / 2 ' X and A now equal 2.
- A = A / 2 ' X and A now equal 1.
- PRINT A + X
- END SUB
- OutputHalf of 4 plus half of 4 equals 2
-
- Automatic and Static Variables
-
- When the STATIC attribute appears on a procedure-definition line, it means
- that local variables within the procedure are "static"; that is, their
- values are preserved between calls to the procedure.
-
- Leaving off the STATIC attribute makes local variables within the procedure
- "automatic" by default; that is, you get a fresh set of local variables each
- time the procedure is called.
-
- You can override the effect of leaving off the STATIC attribute by using
- the STATIC statement within the procedure, thus making some variables
- automatic and others static (see the next section for more information).
-
- Note
-
- The SHARED statement also overrides the default for variables in a
- procedure (local static or local automatic), since any variable appearing in
- a SHARED statement is known at the module level and thus is not local to
- the procedure.
-
-
- Preserving Values of Local Variables with STATIC
-
- Sometimes you may want to make some local variables in a procedure static
- while keeping the rest automatic. List those variables in a STATIC
- statement within the procedure.
-
- Also, putting a variable name in a STATIC statement is a way of making
- absolutely sure that the variable is local, since a STATIC statement
- overrides the effect of a module-level SHARED statement.
-
- Note
-
- If you give a variable its type in an AS type clause, then the AS type
- clause must appear along with the variable's name in the STATIC and DIM
- statements.
-
- A STATIC statement can appear only within a procedure. An array name in a
- STATIC statement must be followed by a set of empty parentheses. Also, you
- must dimension any array that appears in a STATIC statement before using
- the array, as shown in the next example:
-
- SUB SubProg2
- STATIC Array() AS INTEGER
- DIM Array(-5 TO 5, 1 TO 25) AS INTEGER
- .
- .
- .
- END SUB
-
- Example
-
- The following example shows how a STATIC statement preserves the value of
- the string variable Y$ throughout successive calls to TestSub:
-
- DECLARE SUB TestSub ()
- FOR I% = 1 TO 5
- TestSub ' Call TestSub five times.
- NEXT I%
- END
-
- SUB TestSub' Note: no STATIC attribute.
-
- ' Both X$ and Y$ are local variables in TestSub (that is,
- ' their values are not shared with the module-level code).
- ' However since X$ is an automatic variable, it is
- ' reinitialized to a null string every time TestSub is
- ' called. In contrast, Y$ is static, so it retains the
- ' value it had from the last call:
- STATIC Y$
- X$ = X$ + "*"
- Y$ = Y$ + "*"
- PRINT X$, Y$
- END SUB
-
- Output
- * *
- * **
- * ***
- * ****
- * *****
-
- Recursive Procedures
-
- Procedures in BASIC can be recursive. A recursive procedure is one that can
- call itself or call other procedures that in turn call the first procedure.
-
-
- The Factorial Function
-
- A good way to illustrate recursive procedures is to consider the factorial
- function from mathematics. One way to define n! ("n factorial") is with the
- following formula:
-
- n! = n * (n[ndash]1) * (n[ndash]2) * ... * 2 * 1
-
-
- For example, 5 factorial is evaluated as follows:
-
- 5! = 5 * 4 * 3 * 2 * 1 = 120
-
-
- Note
-
- Do not confuse the mathematical factorial symbol (!) used in this discussion
- with the single-precision type-declaration suffix used by BASIC.
-
- Factorials lend themselves to a recursive definition as well:
-
- n! = n * (n[ndash]1)!
-
-
- This leads to the following progression:
-
- 5! = 5 * 4!
-
-
-
- 4! = 4 * 3!
-
-
-
- 3! = 3 * 2!
-
-
-
- 2! = 2 * 1!
-
-
-
- 1! = 1 * 0!
-
-
- Recursion must always have a terminating condition. With factorials, this
- terminating condition occurs when 0! is evaluated [mdash] by definition, 0!
- is equal to 1.
-
- Note
-
- Although a recursive procedure can have static variables by default (as in
- the next example), it is often preferable to let automatic variables be the
- default instead. In this way, recursive calls will not overwrite variable
- values from a preceding call.
-
-
- Example
-
- The following example uses a recursive FUNCTION procedure to calculate
- factorials:
-
- DECLARE FUNCTION Factorial# (N%)
- DO
- INPUT "Enter number from 0 [ndash] 20 (or -1 to end): ", Num%
- IF Num% >= 0 AND Num% <= 20 THEN
- PRINT Num%; Factorial#(Num%)
- END IF
- LOOP WHILE Num% >= 0
- END
-
- FUNCTION Factorial# (N%) STATIC
-
- IF N% > 0 THEN' Call Factorial# again
- ' if N is greater than zero.
- Factorial# = N% * Factorial#(N% - 1)
-
- ELSE ' Reached the end of recursive calls
- ' (N% = 0), so "climb back up the ladder."
- Factorial# = 1
- END IF
- END FUNCTION
-
- Adjusting the Size of the Stack
-
- Recursion can eat up a lot of memory, since each set of automatic variables
- in a SUB or FUNCTION procedure is saved on the stack. (Saving variables
- this way allows a procedure to continue with the correct variable values
- after control returns from a recursive call.)
-
- If you have a recursive procedure with many automatic variables, or a deeply
- nested recursive procedure, you may need to adjust the size of the stack
- before starting the procedure. Otherwise, you may get an Out of stack space
- error message.
-
- To make this adjustment you use the FRE and STACK functions, plus the
- STACK statement as explained in the following.
-
- Before actually adjusting the size of the stack, there are several facts
- that need to be determined. First, you must estimate the amount of memory
- your recursive procedure needs. Do this by following these steps:
-
- 1. Make a test module consisting of the DECLARE statement for the
- procedure, a single call to the procedure (using CALL), and the
- procedure itself.
-
- 2. Add a FRE([ndash]2) function (which returns the total unused stack
- space) just before you call the recursive procedure. Add a second
- FRE([ndash]2) function right at the end of the recursive procedure.
- Save the returned values in two long integers.
-
- 3. Run the test module. The difference in values is the amount of stack
- space (in bytes) used by one call to the procedure.
-
- 4. Estimate the maximum number of times the procedure is likely to be
- invoked, then multiply this value by the stack space consumed by one
- call to the procedure. The result is the amount of memory your
- recursive procedure needs.
-
-
- Once you know how many bytes of stack space the procedure needs, you then
- determine the currently allocated size of the stack. This is 3K for DOS and
- 3.5K for OS/2 unless you have previously changed it with the STACK
- statement. Assuming that you are running under DOS and using the default
- stack size, the following code adjusts the size of the stack (if space is
- available):
-
- ' Initialize a variable that contains the currently allocated stack size.
- CurrentSize = 3072
- ' Initialize a variable with the calculated recursion stack space
- ' requirements as explained above.
- RecursiveBytes = 6000
- ' Find out how many bytes are used up on the stack right now.
- BytesOnStack = CurrentSize - FRE(-2)
- ' Calculate the total required stack space.
- RequiredSpace = RecursiveBytes + BytesOnStack
- ' Request the space if there's room.
- IF RequiredSpace <= STACK THEN
- STACK RequiredSpace
- ELSE GOTO ReportError
- END IF
-
- Notice that in the preceding example, the STACK statement and STACK
- function were used. The STACK function returns the maximum space that can
- be allocated. The STACK statement allocates the space. See the BASIC
- Language Reference for further information.
-
-
- Transferring Control to Another Program
-
- with CHAIN
-
- Unlike procedure calls, which occur within the same program, the CHAIN
- statement simply starts a new program. When a program chains to another
- program, the following sequence occurs:
-
- 1. The first program stops running.
-
- 2. The second program is loaded into memory.
-
- 3. The second program starts running.
-
-
- The advantage of using CHAIN is that it enables you to split a program with
- large memory requirements into several smaller programs.
-
-
- The COMMON statement allows you to pass variables from one program to
- another program in a chain. A prevalent programming practice is to put these
- COMMON statements in an include file, and then use the $INCLUDE
- metacommand at the beginning of each program in the chain.
-
- Note
-
- Don't use a COMMON /blockname/ variablelist statement (a "named COMMON
- block") to pass variables to a chained program, since variables listed in
- named COMMON blocks are not preserved when chaining. Use a blank COMMON
- block ( COMMON variablelist) instead.
-
- Example
-
- This example, which shows a chain connecting three separate programs, uses
- an include file to declare variables passed in common among the programs:
-
- ' ============ CONTENTS OF INCLUDE FILE COMMONS.BI ========
- DIM Values(10)
- COMMON Values(), NumValues
-
- ' ======================= MAIN.BAS ========================
- '
- ' Read in the contents of the COMMONS.BI file:
- ' $INCLUDE: 'COMMONS.BI'
-
- ' Input the data:
- INPUT "Enter number of data values (<=10): ", NumValues
- FOR I = 1 TO NumValues
- Prompt$ = "Value ("+LTRIM$(STR$(I))+")? "
- PRINT Prompt$;
- INPUT "", Values(I)
- NEXT I
-
- ' Have the user specify the calculation to do:
- INPUT "Calculation (1=st. dev., 2=mean)? ", Choice
-
- ' Now, chain to the correct program:
- SELECT CASE Choice
-
- CASE 1: ' Standard deviation
- CHAIN "STDEV"
-
- CASE 2: ' Mean
- CHAIN "MEAN"
- END SELECT
- END
-
-
- ' ======================= STDEV.BAS =======================
- ' Calculates the standard deviation of a set of data
- ' =========================================================
- '
- ' $INCLUDE: 'COMMONS.BI'
-
- Sum = 0 ' Normal sum
- SumSq = 0 ' Sum of values squared
-
- FOR I = 1 TO NumValues
- Sum = Sum + Values(I)
- SumSq = SumSq + Values(I) ^ 2
- NEXT I
-
- Stdev = SQR(SumSq / NumValues - (Sum / NumValues) ^ 2)
- PRINT "The Standard Deviation of the samples is: " Stdev
- END
-
- ' ======================== MEAN.BAS =======================
- ' Calculates the mean (average) of a set of data
- ' =========================================================
- '
- ' $INCLUDE: 'COMMONS.BI'
-
- Sum = 0
-
- FOR I = 1 TO NumValues
- Sum = Sum + Values(I)
- NEXT
-
- Mean = Sum / NumValues
- PRINT "The mean of the samples is: " Mean
- END
-
- Sample Application: Recursive Directory Search
-
- (WHEREIS.BAS)
-
- The following program uses a recursive SUB procedure, ScanDir, to scan a
- disk for the filename entered by the user. Each time this program finds the
- given file, it prints the complete directory path to the file.
-
-
- Statements Used
-
- This program demonstrates the following statements discussed in this
- chapter:
-
- ■ DECLARE
-
- ■ FUNCTION... END FUNCTION
-
- ■ STATIC
-
- ■ SUB... END SUB
-
-
-
- Program Listing
-
- DEFINT A-Z
-
- ' Declare symbolic constants used in program:
- CONST EOFTYPE = 0, FILETYPE = 1, DIRTYPE = 2, ROOT = "TWH"
-
- DECLARE SUB ScanDir (PathSpec$, Level, FileSpec$, Row)
-
- DECLARE FUNCTION MakeFileName$ (Num)
- DECLARE FUNCTION GetEntry$ (FileNum, EntryType)
- CLS
- INPUT "File to look for"; FileSpec$
- PRINT
- PRINT "Enter the directory where the search should start"
- PRINT "(optional drive + directories). Press <ENTER> to "
- PRINT "begin search in root directory of current drive."
- PRINT
- INPUT "Starting directory"; PathSpec$
- CLS
-
- RightCh$ = RIGHT$(PathSpec$, 1)
-
- IF PathSpec$ = "" OR RightCh$ = ":" OR RightCh$ <> "\" THEN
- PathSpec$ = PathSpec$ + "\"
- END IF
-
-
- FileSpec$ = UCASE$(FileSpec$)
- PathSpec$ = UCASE$(PathSpec$)
- Level = 1
- Row = 3
-
- ' Make the top level call (level 1) to begin the search:
- ScanDir PathSpec$, Level, FileSpec$, Row
-
- KILL ROOT + ".*" ' Delete all temporary files created
- ' by the program.
-
- LOCATE Row + 1, 1: PRINT "Search complete."
- END
-
- ' ======================= GetEntry ========================
- ' This procedure processes entry lines in a DIR listing
- ' saved to a file.
-
- ' This procedure returns the following values:
-
- 'GetEntry$A valid file or directory name
- 'EntryTypeIf equal to 1, then GetEntry$
- 'is a file.
- 'If equal to 2, then GetEntry$
- 'is a directory.
- ' =========================================================
- '
- FUNCTION GetEntry$ (FileNum, EntryType) STATIC
-
- ' Loop until a valid entry or end-of-file (EOF) is read:
- DO UNTIL EOF(FileNum)
- LINE INPUT #FileNum, EntryLine$
- IF EntryLine$ <> "" THEN
-
- ' Get first character from the line for test:
- TestCh$ = LEFT$(EntryLine$, 1)
- IF TestCh$ <> " " AND TestCh$ <> "." THEN EXIT DO
- END IF
- LOOP
-
- ' Entry or EOF found, decide which:
- IF EOF(FileNum) THEN' EOF, so return EOFTYPE
- EntryType = EOFTYPE' in EntryType.
- GetEntry$ = ""
-
- ELSE ' Not EOF, so it must be a
- ' file or a directory.
-
- ' Build and return the entry name:
- EntryName$ = RTRIM$(LEFT$(EntryLine$, 8))
-
- ' Test for extension and add to name if there is one:
- EntryExt$ = RTRIM$(MID$(EntryLine$, 10, 3))
- IF EntryExt$ <> "" THEN
- GetEntry$ = EntryName$ + "." + EntryExt$
- ELSE
- GetEntry$ = EntryName$
- END IF
-
- ' Determine the entry type, and return that value
- ' to the point where GetEntry$ was called:
- IF MID$(EntryLine$, 15, 3) = "DIR" THEN
- EntryType = DIRTYPE ' Directory
- ELSE
- EntryType = FILETYPE ' File
- END IF
-
- END IF
-
- END FUNCTION
-
- ' ===================== MakeFileName$ =====================
- ' This procedure makes a filename from a root string
- ' ("TWH," defined as a symbolic constant at the module
- ' level) and a number passed to it as an argument (Num).
- ' =========================================================
- '
- FUNCTION MakeFileName$ (Num) STATIC
-
- MakeFileName$ = ROOT + "." + LTRIM$(STR$(Num))
-
- END FUNCTION
-
- ' ======================= ScanDir =========================
- ' This procedure recursively scans a directory for the
- ' filename entered by the user.
-
- ' NOTE: The SUB header doesn't use the STATIC keyword
- ' since this procedure needs a new set of variables
- ' each time it is invoked.
- ' =========================================================
- '
- SUB ScanDir (PathSpec$, Level, FileSpec$, Row)
-
- LOCATE 1, 1: PRINT "Now searching"; SPACE$(50);
- LOCATE 1, 15: PRINT PathSpec$;
-
- ' Make a file specification for the temporary file:
- TempSpec$ = MakeFileName$(Level)
-
-
- ' Get a directory listing of the current directory,
- ' and save it in the temporary file:
- SHELL "DIR " + PathSpec$ + " > " + TempSpec$
-
- ' Get the next available file number:
- FileNum = FREEFILE
-
- ' Open the DIR listing file and scan it:
- OPEN TempSpec$ FOR INPUT AS #FileNum
- ' Process the file, one line at a time:
- DO
-
- ' Input an entry from the DIR listing file:
- DirEntry$ = GetEntry$(FileNum, EntryType)
-
- ' If entry is a file:
- IF EntryType = FILETYPE THEN
-
- ' If the FileSpec$ string matches,
- ' print entry and exit this loop:
- IF DirEntry$ = FileSpec$ THEN
- LOCATE Row, 1: PRINT PathSpec$; DirEntry$;
- Row = Row + 1
- EntryType = EOFTYPE
- END IF
-
- ' If the entry is a directory, then make a recursive
- ' call to ScanDir with the new directory:
- ELSEIF EntryType = DIRTYPE THEN
- NewPath$ = PathSpec$ + DirEntry$ + "\"
- ScanDir NewPath$, Level + 1, FileSpec$, Row
- LOCATE 1, 1: PRINT "Now searching"; SPACE$(50);
- LOCATE 1, 15: PRINT PathSpec$;
- END IF
-
- LOOP UNTIL EntryType = EOFTYPE
-
- ' Scan on this DIR listing file is finished, so close it:
- CLOSE FileNum
- END SUB
-
- ──────────────────────────────────────────────────────────────────────────
-
- Chapter 3: File and Device I/O
-
- This chapter shows you how to use Microsoft BASIC input and output
- (I/O) functions and statements. These functions and statements
- permit your programs to access data stored in files and to communicate with
- devices attached to your system.
-
- The chapter includes material on a variety of programming tasks related to
- retrieving, storing, and formatting information. The relationship between
- data files and physical devices such as screens and keyboards is also
- covered.
-
- When you are finished with this chapter, you will know how to perform the
- following programming tasks:
-
- ■ Print text on the screen.
- ■ Get input from the keyboard for use in a program.
- ■ Create data files on disk.
- ■ Store records in data files.
- ■ Read records from data files.
- ■ Read or modify data in files that are not in ASCII format.
- ■ Communicate with other computers through the serial port.
-
-
- Note
-
- Creating and using ISAM files are discussed in Chapter 10, "Database
- Programming with ISAM."
-
-
- Printing Text on the Screen
-
- This section explains how to accomplish the following tasks:
-
- ■ Display text on the screen using PRINT.
- ■ Display formatted text on the screen using PRINT USING.
- ■ Skip spaces in a row of printed text using SPC.
- ■ Skip to a given column in a row of printed text using TAB.
- ■ Change the number of rows or columns appearing on the screen using
- WIDTH.
- ■ Open a text viewport using VIEW PRINT.
-
-
- Note
-
- Output that appears on the screen is sometimes referred to as "standard
- output." You can redirect standard output by using the DOS command-line
- symbols > or >>, thus sending output that would have gone to the screen to a
- different output device (such as a printer) or to a disk file. (See your
- operating system documentation for more information on redirecting output.)
-
-
- Screen Rows and Columns
-
- To understand how text is printed on the screen, it helps to think of the
- screen as a grid of "rows" and "columns." The height of one row slightly
- exceeds the height of a line of printed output; the width of one column is
- just wider than the width of one character. A standard screen configuration
- in text mode (nongraphics) is 80 columns wide by 25 rows high. Figure 3.1
- shows how each character printed on the screen occupies a unique cell in the
- grid, a cell that can be identified by pairing a row argument with a column
- argument.
-
- The bottom row of the screen is not usually used for output, unless
- you use a LOCATE statement to display text there. (See the section
- "Controlling the Text Cursor" later in the chapter for more information on
- LOCATE.)
-
-
- Displaying Text and Numbers with PRINT
-
- By far the most commonly used statement for output to the screen is the
- PRINT statement. With PRINT, you can display numeric or string values, or a
- mixture of the two. In addition, PRINT with no arguments prints a blank
- line.
-
-
- The following are some general comments about PRINT:
-
- ■ PRINT always prints numbers with a trailing blank space. If the
- number is positive, the number is also preceded by a space; if the
- number is negative, the number is preceded by a minus sign (-).
-
- ■ The PRINT statement can be used to print lists of expressions.
- Expressions in the list can be separated from other expressions by
- commas, semicolons, one or more blank spaces, or one or more tab
- characters. A comma causes PRINT to skip to the beginning of the next
- "print zone," or block of 14 columns, on the screen. A semicolon (or
- any combination of spaces and/or tabs) between two expressions prints
- the expressions on the screen next to each other, with no spaces in
- between (except for the built-in spaces for numbers).
-
- ■ Ordinarily, PRINT ends each line of output with a new-line sequence
- (a carriage return and line feed). However, a comma or semicolon at
- the end of the list of expressions suppresses this; the next printed
- output from the program appears on the same line unless it is too long
- to fit on that line.
-
- ■ PRINT wraps an output line that exceeds the width of the screen onto
- the next line. For example, if you try to print a line that is 100
- characters long on an 80-column screen, the first 80 characters of the
- line show up on one row, followed by the next 20 characters on the
- next row. If the 100-character line didn't start at the left edge of
- the screen (for example, if it followed a PRINT statement ending in a
- comma or semicolon), then the line would print until it reached the
- 80th column of one row and continue in the first column of the next
- row.
-
-
- Example
-
- The output from the following program shows some of the different ways you
- can use PRINT:
-
- A = 2
- B = -1
- C = 3
- X$ = "over"
- Y$ = "there"
-
- PRINT A, B, C
- PRINT B, A, C
- PRINT A; B; C
- PRINT X$; Y$
- PRINT X$, Y$;
- PRINT A, B
- PRINT
- FOR I = 1 TO 8
- PRINT X$,
- NEXT
-
- Output
-
- 2 -1 3
- -1 2 3
- 2 -1 3
- overthere
- over there 2 -1
-
- over over over over over
- over over over
-
-
- Displaying Formatted Output with PRINT USING
-
- The PRINT USING statement gives greater control than PRINT over the
- appearance of printed data, especially numeric data. Through the use of
- special characters embedded in a format string, PRINT USING allows you to
- specify information such as how many digits from a number (or how many
- characters from a string) are displayed, whether or not a plus sign ( +) or
- a dollar sign ( $) appears in front of a number, and so forth.
-
- Example
-
- The example that follows shows what can be done with PRINT USING. You can
- list more than one expression after the PRINT USING format string. As is
- the case with PRINT, the expressions in the list can be separated from one
- another by commas, semicolons, spaces, or tab characters.
-
- X = 441.2318
-
- PRINT USING "The number with 3 decimal places ###.###";X
- PRINT USING "The number with a dollar sign $$##.##";X
- PRINT USING "The number in exponential format #.###^^^^";X
- PRINT USING "Numbers with plus signs +### "; X; 99.9
-
- Output
-
- The number with 3 decimal places 441.232
- The number with a dollar sign $441.23
- The number in exponential format 0.441E+03
- Numbers with plus signs +441 Numbers with plus signs +100
-
- Consult online Help for more on PRINT USING.
-
-
- Skipping Spaces and Advancing to a Specific Column
-
- By using the SPC( n) statement in a PRINT statement, you can skip n
- spaces in a row of printed output, as shown in the next example:
-
- PRINT " 1 2 3"
- PRINT "123456789012345678901234567890"
- PRINT "First Name"; SPC(10); "Last Name"
-
- Output
-
- 1 2 3
- 123456789012345678901234567890
- First Name Last Name
-
- By using the TAB(n) statement in a PRINT statement, you can skip to the nth c
- output. In the following example, TAB produces the same output shown in the
- preceding example:
-
- PRINT " 1 2 3"
- PRINT "123456789012345678901234567890"
- PRINT "First Name"; TAB(21); "Last Name"
-
- Neither SPC nor TAB can be used by itself to position printed output on
- the screen; they can only appear in PRINT statements.
-
-
- Changing the Number of Columns or Rows
-
- You can control the maximum number of characters that appear in a single row
- of output by using the WIDTH columns statement. The WIDTH columns
- statement actually changes the size of characters that are printed on the
- screen, so that more or fewer characters can fit on a row. For example,
- WIDTH 40 makes characters wider, so the maximum row length is 40 characters.
- WIDTH 80 makes characters narrower, so the maximum row length is 80
- characters. The numbers 40 and 80 are the only valid values for the columns
- argument.
-
- On machines equipped with an Enhanced Graphics Adapter (EGA) or Video
- Graphics Array (VGA), the WIDTH statement can also control the number of
- rows that appear on the screen by using this syntax:
-
- WIDTH [ screenwidth%],[ screenheight%]
-
- The value for screenheight% may be 25, 30, 43, 50, or 60,
- depending on the type of display adapter you use and the screen mode
- set in a preceding SCREEN statement.
-
-
- Creating a Text Viewport
-
- So far, the entire screen has been used for text output. However, with the
- VIEW PRINT statement, you can restrict printed output to a "text viewport,"
- a horizontal slice of the screen. The syntax of the VIEW PRINT statement
- is:
-
- VIEW PRINT [topline% TO bottomline%]
-
- The values for topline% and bottomline% specify the
- locations where the viewport will begin and end, respectively.
-
-
- A text viewport also gives you control over on-screen scrolling. Without
- a viewport, when printed output reaches the bottom of the screen, text
- or graphics output that was at the top of the screen scrolls off and
- is lost. However, after a VIEW PRINT statement, scrolling
- takes place only between the top and bottom lines of the viewport. This
- means you can label the displayed output at the top and/or bottom of
- the screen without having to worry that the labeling will scroll it
- off if too many lines of data appear. You can also use CLS 2 to
- clear just the text viewport, leaving the contents of the rest of
- the screen intact. See the section "Defining a Graphics Viewport" in
- Chapter 5, "Graphics," to learn how to create a viewport for graphics
- output on the screen.
-
-
- Example
-
- You can see the effects of a VIEW PRINT statement by examining the output
- from the next example:
-
- CLS
- LOCATE 3, 1
- PRINT "This is above the text viewport; it doesn't scroll."
-
- LOCATE 4, 1
- PRINT STRING$(60, "_") ' Print horizontal lines above
- LOCATE 11, 1
- PRINT STRING$(60, "_") ' and below the text viewport.
-
- PRINT "This is below the text viewport."
-
- VIEW PRINT 5 TO 10 ' Text viewport extends from
- ' lines 5 to 10.
-
- FOR I = 1 TO 20 ' Print numbers and text in
- PRINT I; "a line of text" ' the viewport.
- NEXT
-
- DO: LOOP WHILE INKEY$ = "" ' Wait for a key press.
- CLS 2 ' Clear just the viewport.
- END
-
-
- Getting Input from the Keyboard
-
- This section shows you how to use the following statements and functions to
- enable your BASIC programs to accept input entered from the keyboard:
-
- ■ INPUT
- ■ LINE INPUT
- ■ INPUT$
- ■ INKEY$
-
-
- Note
-
- Input typed at the keyboard is often referred to as "standard input." You
- can use the DOS redirection symbol (<) to direct standard input to your
- program from a file or other input device instead of from the keyboard. (See
- your operating system documentation for more information on redirecting
- input.)
-
-
- The INPUT Statement
-
- The INPUT statement takes information typed by the user and stores it in a
- list of variables, as shown in the following example:
-
- INPUT A%, B, C$
- INPUT D$
- PRINT A%, B, C$, D$
-
- Output
-
- 6.6,45,a string ?
- "two, three"
- 7 45 a string two, three
-
- Here are some general comments about INPUT:
-
- ■ An INPUT statement by itself prompts the user with a question mark
- (?) followed by a blinking cursor.
-
- ■ The INPUT statement is followed by one or more variable names. When
- there are two or more variables, they are separated by commas.
-
- ■ The number of constants entered by the user after the INPUT prompt
- must be the same as the number of variables in the INPUT statement
- itself.
-
- ■ The values the user enters must agree in type with the variables in
- the list following INPUT. In other words, enter a number if the
- variable is designated as having the type integer, long integer,
- single precision, or double precision. Enter a string if the variable
- is designated as having the type string.
-
- ■ Since constants in an input list must be separated by commas, an input
- string constant containing one or more commas should be enclosed in
- double quotation marks. The double quotation marks ensure that the
- string is treated as a unit and not broken into two or more parts.
-
- If the user breaks any of the last three rules, BASIC prints the error
- message Redo from start. This message reappears until the input agrees in
- number and type with the variable list.
-
- If you want your input prompt to be more informative than a simple question
- mark, you can make a prompt appear, as in the following example:
-
- INPUT "What is the correct time (hour, min)"; Hr$, Min$
-
- This prints the following prompt:
- What is the correct time (hour, min)?
-
- Note the semicolon between the prompt and the input variables. This
- semicolon causes a question mark to appear as part of the prompt. Sometimes
- you may want to eliminate the question mark altogether; in this case, put a
- comma between the prompt and the variable list:
- INPUT "Enter the time (hour, min): ", Hr$, Min$
-
- This prints the following prompt:
- Enter the time (hour, min):
-
- The LINE INPUT Statement
-
- If you want your program to accept lines of text with embedded commas,
- leading blanks, or trailing blanks, but you do not want to have to remind
- the user to enclose the input in double quotation marks, use the LINE INPUT
- statement. The LINE INPUT statement, as its name implies, accepts a line of
- input (terminated by pressing Enter) from the keyboard and stores it in a
- single string variable. Unlike INPUT, the LINE INPUT statement does not
- print a question mark by default to prompt for input; it does, however,
- allow you to display a prompt string.
-
- Example
-
- The following example shows the difference between INPUT and LINE INPUT:
-
- ' Assign the input to three separate variables:
- INPUT "Enter three values separated by commas: ", A$, B$, C$
-
- ' Assign the input to one variable (commas not treated
- ' as delimiters between input):
- LINE INPUT "Enter the same three values: ", D$
- PRINT "A$ = "; A$
- PRINT "B$ = "; B$
- PRINT "C$ = "; C$
- PRINT "D$ = "; D$
-
- Output
-
- Enter 3 values separated by commas:
- by land, air, and sea Enter the same three values:
- by land, air, and sea A$ = by land
-
- B$ = air
- C$ = and sea
- D$ = by land, air, and sea
-
- With INPUT and LINE INPUT, input is terminated when the user presses
- Enter, which also advances the cursor to the next line. As the next example
- shows, a semicolon between the INPUT keyword and the prompt string keeps
- the cursor on the same line:
-
- INPUT "First value: ", A
- INPUT; "Second value: ", B
- INPUT " Third value: ", C
-
- The following shows some sample input to the preceding program and the
- positions of the prompts:
-
- First value:
- 5 Second value:
- 4 Third value: 3
- The INPUT$ Function
-
- INPUT and LINE INPUT wait for the user to press Enter before they store
- what is typed; that is, they read a line of input, then assign it to
- program variables. In contrast, the INPUT$( number )
- function doesn't wait for Enter to be pressed; it just reads a specified
- number of characters. For example, the following line in a program reads
- three characters typed by the user, then stores the three-character
- string in the variable Test$:
-
- Test$ = INPUT$(3)
-
- Unlike the INPUT statement, the INPUT$ function does not prompt the user
- for data, nor does it echo input characters on the screen. Also, since
- INPUT$ is a function, it cannot stand by itself as a complete statement.
- INPUT$ must appear in an expression, as in the following:
-
- INPUT X ' INPUT is a statement.
-
- PRINT INPUT$(1) ' INPUT$ is a function, so it must
- Y$ = INPUT$(1) ' appear in an expression.
-
- The INPUT$ function reads input from the keyboard as an unformatted stream
- of characters. Unlike INPUT or LINE INPUT, INPUT$ accepts any key
- pressed, including control keys like Esc or Backspace. For example, pressing
- Enter five times assigns five carriage-return characters to the Test$
- variable in the next line:
-
- Test$ = INPUT$(5)
-
- The INKEY$ Function
-
- The INKEY$ function completes the list of BASIC's keyboard-input functions
- and statements. When BASIC encounters an expression containing the INKEY$
- function, it checks to see if the user has pressed a key since one of the
- following:
-
- ■ The last time it found an expression with INKEY$
-
- ■ The beginning of the program, if this is the first time INKEY$
- appears
-
- If no key has been pressed since the last time the program checked, INKEY$
- returns a null string (""). If a key has been pressed, INKEY$ returns the
- character corresponding to that key.
-
- Example
-
- The most important difference between INKEY$ and the other statements and
- functions discussed in this section is that INKEY$ lets your program
- continue doing other things while it checks for input. In contrast, LINE
- INPUT, INPUT$, and INPUT suspend program execution until there is input,
- as shown in this example:
-
- PRINT "Press any key to start. Press any key to end."
-
- ' Don't do anything else until the user presses a key:
- Begin$ = INPUT$(1)
-
- I& = 1
-
- ' Print the numbers from one to one million.
- ' Check for a key press while the loop is executing:
- DO
- PRINT I&
- I& = I& + 1
-
- ' Continue looping until the value of the variable I& is
- ' greater than one million or until a key is pressed:
- LOOP UNTIL I& > 1000000 OR INKEY$ <> ""
-
- Controlling the Text Cursor
-
- When you display printed text on the screen, the text cursor marks the place
- on the screen where output from the program--or input typed by the
- user--will appear next. In the following example, after the INPUT statement
- displays its 12-character prompt, "First name: ", the cursor waits for input
- in row 1 at column 13:
-
- ' Clear the screen; start printing in row one, column one:
- CLS
- INPUT "First name: ", FirstName$
-
- In the next example, the semicolon at the end of the second PRINT
- statement leaves the cursor in row 2 at column 27:
-
- CLS
- PRINT
-
- ' Twenty-six characters are in the next line:
- PRINT "Press any key to continue.";
- PRINT INPUT$(1)
- The following sections show how to control the location of the text cursor,
- change its shape, and get information about its location.
-
- Positioning the Cursor
-
- The input and output statements and functions discussed so far do not allow
- much control over where output is displayed or where the cursor is located
- after the output is displayed. Input prompts or output always start in the
- far left column of the screen and descend one row at a time from top to
- bottom unless a semicolon is used in the PRINT or INPUT statements to
- suppress the carriage-return-and-line-feed sequence.
-
- The SPC and TAB statements, discussed in the section "Skipping Spaces and
- Advancing to a Specific Column" later in this chapter give some control over
- the location of the cursor by allowing you to move it to any column within a
- given row.
-
- The LOCATE statement extends this control one step further. The syntax for
- LOCATE is:
-
- LOCATE [row%],[ column%],[ cursor%],[ start%],[ stop%]
-
-
- Example
-
- Using the LOCATE statement allows you to position the cursor in any row or
- column on the screen, as shown by the output in the next example:
-
- CLS
- FOR Row = 9 TO 1 STEP -2
- Column = 2 * Row
- LOCATE Row, Column
- PRINT "12345678901234567890";
- NEXT
-
- Output
-
- 12345678901234567890
-
- 12345678901234567890
-
- 12345678901234567890
-
- 12345678901234567890
-
- 12345678901234567890
-
-
- Changing the Cursor's Shape
-
- The optional cursor%, start%, and stop% arguments shown in the syntax for
- the LOCATE statement also allow you to change the shape of the cursor and
- make it visible or invisible. A value of 1 for cursor% makes the cursor
- visible, while a value of 0 makes the cursor invisible. The start% and
- stop% arguments control the height of the cursor, if it is on, by specifying
- the top and bottom "pixel" lines, respectively, for the cursor. (Any
- character on the screen is composed of lines of pixels, which are dots of
- light on the screen.) If a cursor spans the height of one row of text, then
- the line of pixels at the top of the cursor has the value 0, while the line
- of pixels at the bottom has a value of 7 or 13, depending whether your
- display adapter is monochrome (13) or color (7).
-
- You can turn the cursor on and change its shape without specifying a new
- location for it. For example, the following statement keeps the cursor
- wherever it is at the completion of the next PRINT or INPUT statement,
- then makes it half a character high:
-
- LOCATE , , 1, 2, 5 ' Row and column arguments both optional.
-
- The following examples show different cursor shapes produced using different
- start and stop values on a color display. Each LOCATE statement shown in
- the left column is followed by the statement:
-
- INPUT "PROMPT:", X$
-
- In the preceding examples, note that making the start% argument bigger than
- the stop% argument results in a two-piece cursor.
-
-
- Getting Information About the Cursor's Location
-
- You can think of the functions CSRLIN and POS( numeric-expression ) as the
- complements of the LOCATE statement: whereas LOCATE tells the cursor where
- to go, CSRLIN and POS( numeric-expression ) tell your program where the
- cursor is. The CSRLIN function returns the current row and the POS(
- numeric-expression ) function returns the current column of the cursor's
- position.
-
- The argument n for POS( numeric-expression ) is what is known as a "dummy"
- argument; that is, numeric-expression is a placeholder that can be any
- numeric expression. For example, POS(0) and POS(1) return the same value.
-
-
- Example
-
- The following example uses the POS( numeric-expression ) function to print
- 50 asterisks in rows of 13 asterisks:
-
- FOR I% = 1 TO 50
- PRINT "*";' Print an asterisk and keep
- ' the cursor on the same line.
- IF POS(1) > 13 THEN PRINT ' If the cursor's position
- ' is past column 13, advance
- ' to the next line.
- NEXT
-
- Output
- *************
- *************
- *************
- ***********
-
- Working with Data Files
-
- Data files are physical locations on your disk where information is
- permanently stored. The following tasks are greatly simplified by using data
- files in your BASIC programs:
-
- ■ Creating, manipulating, and storing large amounts of data
-
- ■ Accessing several sets of data with one program
-
- ■ Using the same set of data in several different programs
-
-
- The sections that follow introduce the concepts of records and fields and
- contrast different ways to access data files from BASIC. When you have
- completed those sections, you should know how to do the following:
-
- ■ Create new data files
-
- ■ Open existing files and read their contents
-
- ■ Add new information to an existing data file
-
- ■ Change the contents of an existing data file
-
-
-
- How Data Files Are Organized
-
- A data file is a collection of related blocks of information, or "records."
- Each record in a data file is further subdivided into "fields" or regularly
- recurring items of information within each record. If you compare a data
- file with a more old-fashioned way of storing information--for example, a
- folder containing application forms filled out by job applicants at a
- particular company--then a record is analogous to one application form in
- that folder. To carry the comparison one step further, a field is analogous
- to an item of information included on every application form, such as a
- Social Security number.
-
- Note
-
- If you do not want to access a file using records but instead want to treat
- it as an unformatted sequence of bytes, then read the section "Binary File
- I/O" later in this chapter.
-
-
- Sequential and Random-Access Files
-
- The terms "sequential file" and "random-access file" refer to two different
- ways to store and access data on disk from your BASIC programs. A simplified
- way to think of these two kinds of files is with the following analogy: a
- sequential file is like a cassette tape, while a random-access file is like
- an LP record. To find a song on a cassette tape, you have to start at the
- beginning and fast-forward through the tape sequentially until you find the
- song you are looking for--there is no way to jump right to the song you
- want. This is similar to the way you have to find information in a
- sequential file: to get to the 500th record, you first have to read records
- 1 through 499.
-
- In contrast, if you want to play a certain song on an LP, all you have to do
- is lift the tone arm of the record player and put the needle down right on
- the song: you can randomly access anything on the LP without having to play
- all the songs before the one you want. Similarly, you can call up any record
- in a random-access file just by specifying its number, greatly reducing
- access time.
-
-
- Note
-
- Although there is no way to jump directly to a specific record in a
- sequential file, the SEEK statement lets you jump directly to a specific
- byte in the file. See the section "Binary File I/O" later in this chapter
- for more information on how to do this.
-
-
- Opening a Data File
-
- Before your program can read, modify, or add to a data file, it must first
- open the file. BASIC does this with the OPEN statement. The OPEN statement
- can be used to create a new file. The following list describes the various
- uses of the OPEN statement:
-
- ■ Create a new data file and open it so records can be added to it. For
- example:
-
-
- ' No file named PRICE.DAT is in the current directory:
- OPEN "PRICE.DAT" FOR OUTPUT AS #1
-
- ■ Open an existing data file so new records overwrite any data already
- in the file. For example:
-
- ' A file named PRICE.DAT is already in the current
- ' directory; new records can be written to it, but all
- ' old records are lost:
- OPEN "PRICE.DAT" FOR OUTPUT AS #1
-
- ■ Open an existing data file so new records are added to the end of the
- file, preserving data already in the file. For example:
-
- OPEN "PRICE.DAT" FOR APPEND AS #1
-
- The APPEND mode will also create a new file if a file
- with the given name does not already appear in the current directory.
-
- ■ Open an existing data file so old records can be read from it. For
- example:
-
- OPEN "PRICE.DAT" FOR INPUT AS #1
-
- See the section "Using Sequential Files" for more information about the INPU
- OUTPUT, and APPEND modes.
-
- ■ Open an existing data file (or create a new one if a file with
- that name doesn't exist), then read or write fixed-length records
- to and from the file. For example:
-
- OPEN "PRICE.DAT" FOR RANDOM AS #1
-
- See the section "Using Random-Access Files" for more information about
- this mode.
-
- ■ Open an existing data file (or create a new one if a file with
- that name doesn't exist), then read data from the file or add new
- data to the file, starting at any byte position in the file. For
- example:
-
- OPEN "PRICE.DAT" FOR BINARY AS #1
-
- See the section "Binary File I/O" for more information about this mode.
-
-
- File Numbers in BASIC
-
- The OPEN statement does more than just specify a mode for data I/O for a
- particular file ( OUTPUT, INPUT, APPEND, RANDOM, or BINARY); it also
- associates a unique file number with that file. This file number, which can
- be any integer from 1 to 255, is then used by subsequent file I/O statements
- in the program as a shorthand way to refer to the file. As long as the file
- is open, this number remains associated with the file. When the file is
- closed, the file number is freed for use with another file. Your BASIC
- programs can open more than one file at a time.
-
- The FREEFILE function can help you find an unused file number. This
- function returns the next available number that can be associated with a
- file in an OPEN statement. For example, FREEFILE might return the value 3
- after the following OPEN statements:
-
- OPEN "Test1.Dat" FOR RANDOM AS #1
- OPEN "Test2.Dat" FOR RANDOM AS #2
- FileNum = FREEFILE
- OPEN "Test3.Dat" FOR RANDOM AS #FileNum
- The FREEFILE function
- is particularly useful when you create your own library procedures that open
- files. With FREEFILE, you don't have to pass information about the number
- of open files to these procedures.
-
-
- Filenames in BASIC
-
- Filenames in OPEN statements can be any string expression, composed of any
- combination of the following characters:
-
- ■ The letters a-z and A-Z
-
- ■ The numbers 0-9
-
- ■ The following special characters:
-
- ( ) @ # $ % ^ & ! - _ ' ~
-
-
- The string expression can also contain an optional drive, as well as a
- complete or partial path specification. This means your BASIC program can
- work with data files on another drive or in a directory other than the one
- where the program is running. For example, the following OPEN statements
- are all valid:
-
- OPEN "..\Grades.Qtr" FOR INPUT AS #1
-
- OPEN "A:\SALARIES\1990.MAN" FOR INPUT AS #2
-
- FileName$ = "TempFile"
- OPEN FileName$ FOR OUTPUT AS #3
-
- BaseName$ = "Invent"
- OPEN BaseName$ + ".DAT" FOR OUTPUT AS #4
-
- DOS also imposes its own restrictions on filenames: you can use no more than
- eight characters for the filename (everything to the left of an optional
- period) and no more than three characters for the extension (everything to
- the right of an optional period).
-
-
- Long filenames in BASIC
- programs are truncated in the following fashion:
-
-
- ╓┌──────────────────┌───────────────────────────┌────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- Prog@Data@File PROG@DAT.A@F The BASIC name is more than
- 11 characters long, so
- BASIC takes the first eight
- characters for the base
- name, inserts a period (.),
- and uses the next three
- characters as the extension.
- Everything else is
- discarded.
-
- Mail#.Version1 MAIL#.VER The filename (Mail#) is
- shorter than eight
- characters, but the
- extension (Version1) is
- longer than three, so the
- extension is shortened to
- ────────────────────────────────────────────────────────────────────────────
- extension is shortened to
- three characters.
-
- RELEASE_NoteS.BAK Gives the run-time error
- message Bad file name. The
- base name must be shorter
- than eight characters if
- you are going to include
- an explicit extension
- (.BAK in this case).
-
-
-
-
-
- DOS is not case sensitive, so lowercase letters in filenames are converted
- to all uppercase (capital) letters. Therefore, you should not rely on the
- mixing of lowercase and uppercase to distinguish between files. For example,
- if you already had a file on the disk named INVESTOR.DAT, the following
- OPEN statement would overwrite that file, destroying any information already
- stored in it:
-
- OPEN "Investor.Dat" FOR OUTPUT AS #1
-
- Closing a Data File
-
- Closing a data file has two important results: first, it writes any data
- currently in the file's buffer (a temporary holding area in memory) to the
- file; second, it frees the file number associated with that file for use by
- another OPEN statement.
-
- Use the CLOSE statement following a program to close a file. For example,
- consider a file PRICE.DAT that is opened with this statement:
-
- OPEN "PRICE.DAT" FOR OUTPUT AS #1
-
- The statement CLOSE #1 then ends output to PRICE.DAT. Next, PRICE.DAT is
- opened with the following:
-
- OPEN "PRICE.DAT" FOR OUTPUT AS #2
-
- Then the appropriate statement for ending output is CLOSE #2. A CLOSE
- statement with no file number arguments closes all open files.
-
- A data file is also
- closed when either of the following occurs:
-
-
- ■ The BASIC program performing I/O ends. (Program termination always
- closes all open data files.)
-
- ■ The program performing I/O transfers control to another program with
- the RUN statement (or with the CHAIN statements if compiled with the
- /O option).
-
-
- Using Sequential Files
-
- This section discusses how records are organized in sequential data files
- and then shows how to read data from, or write data to, an open sequential
- file.
-
-
- Records in Sequential Files
-
- Sequential files are ASCII (text) files. This means you can use any word
- processor to view or modify a sequential file. Records are stored in
- sequential files as a single line of text, terminated by a
- carriage-return-and-line-feed (CR-LF) sequence. Each record is divided into
- fields, or repeated chunks of data that occur in the same order in every
- record. Figure 3.2 shows how three records might appear in a sequential
- file.
-
- Note that each record in a sequential file can be a different
- length; moreover, fields can be different lengths in different records.
-
-
- The kind of variable in which a field is stored determines where that
- field begins and ends. (See the following sections for examples of
- reading and storing fields from records.) For example, if your program
- reads a field into a string variable, then any of the following can
- signal the end of that field:
-
- ■ Double quotation marks (") if the string begins with double quotation
- marks
-
- ■ Comma (,) if the string does not begin with double quotation marks
-
- ■ CR-LF if the field is at the end of the record
-
-
- On the other hand, if your program reads a field into a numeric variable,
- then any of the following can signal the end of that field:
-
- ■ Comma
-
- ■ One or more spaces
-
- ■ CR-LF
-
-
- Putting Data in a New Sequential File
-
- You can add data to a new sequential file after first opening it to receive
- records with an OPEN filename FOR OUTPUT statement. Use the WRITE #
- statement to write records to the file.
-
- You can open sequential files for reading or for writing but not for both at
- the same time. If you are writing to a sequential file and want to read back
- the data you stored, you must first close the file, then reopen it for
- input.
-
-
- Example
-
- The following short program creates a sequential file named PRICE.DAT, then
- adds data entered at the keyboard to the file. The OPEN statement in this
- program creates the file and readies it to receive records. The WRITE #
- statement then writes each record to the file. Note that the number used in
- the WRITE # statement is the same number given to the filename PRICE.DAT in
- the OPEN statement.
-
- ' Create a file named Price.Dat
- ' and open it to receive new data:
-
- OPEN "Price.Dat" FOR OUTPUT AS #1
-
- DO
- ' Continue putting new records in Price.Dat until the
- ' user presses Enter without entering a company name:
- INPUT "Company (press <ENTER> to quit): ", Company$
-
-
- IF Company$ <> "" THEN
-
-
- ' Enter the other fields of the record:
- INPUT "Style: ", Style$
- INPUT "Size: ", Size$
- INPUT "Color: ", Clr$
- INPUT "Quantity: ", Qty
-
- ' Put the new record in the file
- ' with the WRITE # statement:
- WRITE #1, Company$, Style$, Size$, Clr$, Qty
- END IF
- LOOP UNTIL Company$ = ""
-
- ' Close Price.Dat (this ends output to the file):
- CLOSE #1
- END
-
-
- Warning
-
- If, in the case of the preceding example, you already had a file named
- PRICE.DAT on the disk, the OUTPUT mode given in the OPEN statement would
- erase the existing contents of PRICE.DAT before writing any new data to it.
- If you want to add new data to the end of an existing file without erasing
- what is already in it, use the APPEND mode of OPEN. See the section
- "Adding Data to a Sequential File" later in this chapter for more
- information on this mode.
-
-
- Reading Data from a Sequential File
-
- You can read data from a sequential file after first opening it with the
- statement OPEN filename FOR INPUT. Use the INPUT# statement to read
- records from the file one field at a time. (See the section "Other Ways to
- Read Data from a Sequential File" later in this chapter for information on
- other file-input statements and functions you can use with a sequential
- file.)
-
-
- Example
-
- The following program opens the PRICE.DAT data file created in the previous
- example and reads the records from the file, displaying the complete record
- on the screen if the quantity for the item is less than the input amount.
-
- The INPUT #1 statement reads one record at a time from PRICE.DAT, assigning
- the fields in the record to the variables Company$, Style$, Size$, Clr$, and
- Qty. Since this is a sequential file, the records are read in order from the
- first to the last entered.
-
- The EOF (end-of-file) function tests whether the last record has been read
- by INPUT#. If the last record has been read, EOF returns the value -1
- (true), and the loop for getting data ends; if the last record has not been
- read, EOF returns the value 0 (false), and the next record is read from the
- file.
-
- OPEN "PRICE.DAT" FOR INPUT AS #1
-
-
- INPUT "Display all items below what level"; Reorder
-
- DO UNTIL EOF(1)
- INPUT #1, Company$, Style$, Size$, Clr$, Qty
- IF Qty < Reorder THEN
- PRINT Company$, Style$, Size$, Clr$, Qty
- END IF
- LOOP
- CLOSE #1
- END
-
- Adding Data to a Sequential File
-
- As mentioned earlier, if you have a sequential file on disk and want to add
- more data to the end of it, you cannot simply open the file in output mode
- and start writing data. As soon as you open a sequential file in output
- mode, you destroy its current contents. You must use the APPEND mode
- instead, as shown in the next example:
-
- OPEN "PRICE.DAT" FOR APPEND AS #1
-
- APPEND is always a safe alternative to OUTPUT, since APPEND creates a new
- file if one with the name specified doesn't already exist. For example, if a
- file named PRICE.DAT did not reside on disk, the example statement would
- make a new file with that name.
-
-
- Other Ways to Write Data to a Sequential File
-
- The preceding examples all use the WRITE # statement to write records to a
- sequential file. There is, however, another statement you can use to write
- sequential file records: PRINT #.
-
- The best way to show the difference between these two data-storage
- statements is to examine the contents of a file created with both. The
- following short fragment opens a file named TEST.DAT, then places the same
- record in it twice, once with WRITE # and once with PRINT #. After running
- this program you can examine the contents of TEST.DAT with the DOS TYPE
- command.
-
- OPEN "TEST.DAT" FOR OUTPUT AS #1
- Nm$ = "Penn, Will"
- Dept$ = "User Education"
- Level = 4
- Age = 25
- WRITE #1, Nm$, Dept$, Level, Age
- PRINT #1, Nm$, Dept$, Level, Age
- CLOSE #1
-
- Output
-
- "Penn, Will","User Education",4,25
- Penn, Will User Education 4 25
-
- The record stored with WRITE # has commas that explicitly
- separate each field of the record, as well as double quotation marks
- enclosing each string expression. On the other hand, PRINT # has
- written an image of the record to the file exactly as it would appear on
- screen with a simple PRINT statement. The commas in the PRINT #
- statement are interpreted as meaning "advance to the next print zone"
- (a new print zone occurs every 14 spaces, starting at the beginning of
- a line), and quotation marks are not placed around the string expressions.
-
-
- At this point, you may be wondering what difference these output statements
- make, except in the appearance of the data within the file. The answer lies
- in what happens when your program reads the data back from the file with an
- INPUT # statement. In the following example, the program reads the record
- stored with WRITE # and prints the values of its fields without any
- problem:
-
- OPEN "TEST.DAT" FOR INPUT AS #1
-
- ' Input the first record,
- ' and display the contents of each field:
- INPUT #1, Nm$, Dept$, Level, Age
- PRINT Nm$, Dept$, Level, Age
-
- ' Input the second record,
- ' and display the contents of each field:
- INPUT #1, Nm$, Dept$, Level, Age
- PRINT Nm$, Dept$, Level, Age
-
- CLOSE #1
-
-
- Output
-
- Penn, Will User Education 4 25
-
- However, when the program tries to input the next record stored with PRINT
- #, the attempt produces the error message Input past end of file. Without
- double quotation marks enclosing the first field, the INPUT # statement
- sees the comma between Penn and Will as a field delimiter, so it assigns
- only the last name Penn to the variable Nm$. INPUT # then reads the rest of
- the line into the variable Dept$. Since all of the record has now been read,
- there is nothing left to put in the variables Level and Age. The result is
- the error message Input past end of file.
-
- If you are storing records that have string expressions and you want to read
- these records later with the INPUT # statement, follow one of these two
- rules of thumb:
-
- ■ Use the WRITE # statement to store the records.
-
- ■ If you want to use the PRINT # statement, remember it does not put
- commas in the record to separate fields, nor does it put double
- quotation marks around strings. You have to put these field separators
- in the PRINT # statement yourself.
-
-
- Example
-
- For example, you can avoid the problems shown in the preceding example by
- using PRINT # with double quotation marks surrounding each field containing
- a string expression, as in the following example:
-
- ' 34 is ASCII value for double-quotation-mark character:
-
- Q$ = CHR$(34)
-
- ' The next four statements all write the record to the
- ' file with double quotation marks around each string field:
-
- PRINT #1, Q$ Nm$ Q$ Q$ Dept$ Q$ Level Age
- PRINT #1, Q$ Nm$ Q$;Q$ Dept$ Q$;Level;Age
- PRINT #1, Q$ Nm$ Q$,Q$ Dept$ Q$,Level,Age
- WRITE #1, Nm$, Dept$, Level, Age
-
- Output to File
-
- "Penn, Will""User Education" 4 25
- "Penn, Will""User Education" 4 25
- "Penn, Will" "User Education" 4 25
- "Penn, Will","User Education",4,25
-
- Other Ways to Read Data from a Sequential File
-
- In the preceding sections, INPUT # is used to read a record (one line of
- data from a file), assigning different fields in the record to the variables
- listed after INPUT #. This section explores alternative ways to read data
- from sequential files, as records ( LINE INPUT #) and as unformatted
- sequences of bytes ( INPUT$).
-
-
- The LINE INPUT # Statement
-
- With the LINE INPUT# statement, your program can read a line of text
- exactly as it appears in a file without interpreting commas or double
- quotation marks as field delimiters. This is particularly useful in programs
- that work with ASCII text files.
-
- The LINE INPUT # statement reads an entire line from a sequential file (up
- to a carriage-return-and-line-feed sequence) into a single string variable.
-
- Examples
-
- The following short program reads each line from the file CHAP1.TXT and then
- echoes that line on the screen:
-
- ' Open CHAP1.TXT for sequential input:
- OPEN "CHAP1.TXT" FOR INPUT AS #1
-
- ' Keep reading lines sequentially from the file until
- ' there are none left in the file:
- DO UNTIL EOF(1)
-
-
- ' Read a line from the
- file and store it
-
- ' in the variable LineBuffer$:
- LINE INPUT #1, LineBuffer$
-
- ' Print the line on the screen:
- PRINT LineBuffer$
- LOOP
-
- The preceding program is easily modified to a file-copying utility that
- prints each line read from the specified input file to another file, instead
- of to the screen:
-
- ' Input names of input and output files:
-
- INPUT "File to copy: ", FileName1$
- IF FileName1$ = "" THEN END
- INPUT "Name of new file: ", FileName2$
- IF FileName2$ = "" THEN END
-
- ' Open first file for sequential input:
- OPEN FileName1$ FOR INPUT AS #1
-
- ' Open second file for sequential output:
- OPEN FileName2$ FOR OUTPUT AS #2
-
- ' Keep reading lines sequentially from first file
- ' until there are none left in the file:
- DO UNTIL EOF(1)
-
- ' Read a line from first file and store it in the
- ' variable LineBuffer$:
- LINE INPUT #1, LineBuffer$
-
- ' Write LineBuffer$ to the second file:
- PRINT #2, LineBuffer$
-
- LOOP
-
- The INPUT$ Function
-
- Another way to read data from sequential files (and, in fact, from any file)
- is to use the INPUT$ function. Whereas INPUT # and LINE INPUT # read one
- line at a time from a sequential file, INPUT$ reads a specified number of
- characters from a file, as shown in the following examples:
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- X$ = INPUT$(100, #1) Reads 100 characters from file
- number 1 and assigns all of them
- to the string variable X$.
-
- Test$ = INPUT$(1, #2) Reads one character from file
- number 2 and assigns it to the
- string variable Test$.
-
- ────────────────────────────────────────────────────────────────────────────
-
-
-
-
- The INPUT$ function
- without a file number always reads input from standard input (usually the
- keyboard).
-
-
- The INPUT$ function does what is known as "binary input"; that is, it reads
- a file as an unformatted stream of characters. For example, it does not see
- a carriage-return-and-line-feed sequence as signalling the end of an input
- operation. Therefore, INPUT$ is the best choice when you want your program
- to read every single character from a file or when you want it to read a
- binary, or non-ASCII, file.
-
- Example
-
- The following program copies the named binary file to the screen, printing
- only alphanumeric and punctuation characters in the ASCII range 32 to 126,
- as well as tabs, carriage returns, and line feeds:
-
- ' 9 is ASCII value for horizontal tab, 10 is ASCII value
- ' for line feed, and 13 is ASCII value for carriage return:
- CONST LINEFEED = 10, CARRETURN = 13, TABCHAR = 9
-
- INPUT "Print which file: ", FileName$
- IF FileName$ = "" THEN END
-
- OPEN FileName$ FOR INPUT AS #1
-
- DO UNTIL EOF(1)
- Character$ = INPUT$(1, #1)
- CharVal = ASC(Character$)
- SELECT CASE CharVal
- CASE 32 TO 126
- PRINT Character$;
- CASE TABCHAR, CARRETURN
- PRINT Character$;
- CASE LINEFEED
- IF OldCharVal <> CARRETURN THEN PRINT Character$;
- CASE ELSE
- ' This is not one of the characters this program
- ' is interested in, so don't print anything.
- END SELECT
- OldCharVal = CharVal
- LOOP
-
- Using Random-Access Files
-
- This section discusses how records are organized in random-access data
- files, then shows you how to read data from, and write data to, a file
- opened for random access.
-
-
- Records in Random-Access Files
-
- Random-access records are stored quite differently from sequential records.
- Each random-access record is defined with a fixed length, as is each field
- within the record. These fixed lengths determine where a record or field
- begins and ends, as there are no commas separating fields, and no
- carriage-return-and-line-feed sequences between records. Figure 3.3 shows
- how three records might appear in a random-access file.
-
- If you are storing records containing numbers, using random-access
- files saves disk space when compared with using sequential files. This is
- because sequential files save numbers as a sequence of ASCII characters
- representing each digit, whereas random-access files save numbers in binary
- format.
-
-
- For example, the number 17,000 is represented in a sequential file using 5
- bytes, one for each digit. However, if 17,000 is stored in an integer field
- of a random-access record, it takes only 2 bytes of disk space.
-
- Integers in random-access files take 2 bytes, long integers and
- single-precision numbers take 4 bytes, and double-precision numbers and
- currency data types take 8 bytes.
-
-
- Adding Data to a Random-Access File
-
- To write a program that adds data to a random-access file, follow these
- steps:
-
- 1. Define the fields of each record.
-
- 2. Open the file in random-access mode and specify the length of each
- record.
-
- 3. Get input for a new record and store the record in the file.
-
- Each of these steps is now considerably easier than it was in BASICA, as you
- can see from the examples that follow.
-
-
- Defining Records
-
- You can define your own record with a TYPE... END TYPE statement, which
- allows you to create a composite data type that mixes string and numeric
- elements. This is a big advantage over the earlier method of setting up
- records with a FIELD statement, which required that each field be defined
- as a string. By defining a record with TYPE... END TYPE, you eliminate the
- need to use the functions that convert numeric data to strings ( MK
- type $, MK$, MKSMBF$, and
- MKDMBF$) and strings to numeric data ( CV type, CVSMBF, andCVDMBF).
-
-
- The following examples contrast these two methods of defining records.
-
- ■ Record defined with TYPE... END TYPE:
-
-
- ' Define the RecordType structure:
-
- TYPE RecordType
- Name AS STRING * 30
- Age AS INTEGER
- Salary AS SINGLE
- END TYPE
-
- ' Declare the variable RecordVar
- ' as having the type RecordType:
- DIM RecordVar AS RecordType
-
-
- ■ Record defined with FIELD:
-
- ' Define the lengths of the fields
- ' in the temporary storage buffer:
- FIELD #1,30 AS Name$,2 AS Age$,4 AS Salary$
-
-
- Opening the File and Specifying Record Length
-
- Since the length of a random-access record is fixed, you should let your
- program know how long you want each record to be; otherwise, record length
- defaults to 128 bytes.
-
- To specify record length, use the LEN = clause in the OPEN statement. The
- next two fragments, which continue the contrasting examples started in the
- preceding section, show how to use LEN =.
-
- ■ Specify the record length for a record that is defined with the
- statement TYPE... END TYPE:
-
- ' Open the random-access file and specify the length
- ' of one record as being equal to the length of the
- ' RecordVar variable:
- OPEN "EMPLOYEE.DAT" FOR RANDOM AS #1 LEN = LEN(RecordVar)
-
-
-
- ■ Specify record length for a record defined with FIELD:
- ' Open the random-access file and specify the length
- ' of a record:
- OPEN "EMPLOYEE.DAT" FOR RANDOM AS #1 LEN = 36
-
- As you can see, when you use FIELD, you have to add the lengths of each
- field yourself (Name$ is 30 bytes, Age$ is 2 bytes, Salary$ is 4 bytes, so
- the record is 30+2+4 or 36 bytes). With TYPE... END TYPE, you no longer
- have to do these calculations. Instead, just use the LEN function to
- calculate the length of the variable you have created to hold your records
- (RecordVar, in this case).
-
-
- Entering Data and Storing the Record
-
- You can enter data directly into the elements of a user-defined record
- without having to worry about left or right justification of input within a
- field with LSET or RSET. Compare the following two fragments, which
- continue the examples started in the preceding section, to see the amount of
- code this approach saves you.
-
- ■ Enter data for a random-access record and storing the record using
- TYPE... END TYPE:
- ' Enter the data:
- INPUT "Name"; RecordVar.Name
- INPUT "Age"; RecordVar.Age
- INPUT "Salary"; RecordVar.Salary
- ' Store the record:
- PUT #1, , RecordVar
-
- ■ Enter data for a random-access record and store the record using
- FIELD:
- ' Enter the data:
- INPUT "Name"; Nm$
- INPUT "Age"; AgeVal%
- INPUT "Salary"; SalaryVal!
-
- ' Left justify the data in the storage-buffer fields,
- ' using the MKI$ and MKS$ functions to convert numbers
- ' to file strings:
- LSET Name$ = Nm$
- LSET Age$ = MKI$(AgeVal%)
- LSET Salary$ = MKS$(SalaryVal!)
-
- ' Store the record:
- PUT #1
-
-
-
-
- Putting It All Together
-
- The following program puts together all the steps outlined in the preceding
- section -- defining fields, specifying record length, entering data, and
- storing the input data -- to open a random-access data file named STOCK.DAT
- and add records to it:
-
- DEFINT A-Z
-
- ' Define structure of a single record in the random-access
- ' file. Each record will consist of four fixed-length fields
- ' ("PartNumber", "Description", "UnitPrice", "Quantity"):
- TYPE StockItem
- PartNumber AS STRING * 6
- Description AS STRING * 20
- UnitPrice AS SINGLE
- Quantity AS INTEGER
- END TYPE
- ' Declare a variable (StockRecord) using the above type:
- DIM StockRecord AS StockItem
-
- ' Open the random-access file, specifying the length of one
- ' record as the length of the StockRecord variable:
- OPEN "STOCK.DAT" FOR RANDOM AS #1 LEN=LEN(StockRecord)
- ' Use LOF() to
- calculate the number of records already in
-
- ' the file, so new records will be added after them:
- RecordNumber = LOF(1) \ LEN(StockRecord)
-
- ' Now, add new records:
- DO
-
- ' Input data for a stock record from keyboard and store
- ' in the different elements of the StockRecord variable:
- INPUT "Part Number? ", StockRecord.PartNumber
- INPUT "Description? ", StockRecord.Description
- INPUT "Unit Price ? ", StockRecord.UnitPrice
- INPUT "Quantity ? ", StockRecord.Quantity
-
- RecordNumber = RecordNumber + 1
-
- ' Write data in StockRecord to a new record in the file:
- PUT #1, RecordNumber, StockRecord
-
- ' Check to see if more data are to be read:
- INPUT "More (Y/N)? ", Resp$
- LOOP UNTIL UCASE$(Resp$) = "N"
-
- ' All done; close the file and end:
- CLOSE #1
- END
- If the STOCK.DAT file already existed, this program would add more records
- to the file without overwriting any that were already in the file. The
- following key statement makes this work:
-
- RecordNumber = LOF(1) \ LEN(StockRecord)
- Here is what happens:
-
- 1. The LOF(1) function calculates the total number of bytes in the file
- STOCK.DAT. If STOCK.DAT is new or has no records in it, LOF(1) returns
- the value 0.
-
- 2. The LEN(StockRecord) function calculates the number of bytes in one
- record. (StockRecord is defined as having the same structure as the
- user-defined type StockItem.)
-
- 3. Therefore, the number of records is equal to the total bytes in the
- file divided by the bytes in one record. This is another advantage of
- having a fixed-length record. Since each record is the same size, you
- can always use a formula to calculate the number of records in the
- file. Obviously, this would not work with a sequential file, since
- sequential records can have different lengths.
-
-
-
- Reading Data Sequentially
-
- Using the technique outlined in the preceding section for calculating the
- number of records in a random-access file, you can write a program that
- reads all the records in that file.
-
- Example
-
- The following program reads records sequentially (from the first record
- stored to the last) from the STOCK.DAT file created in the previous section:
-
- ' Define a record structure (type) for random-access
- ' file records:
- TYPE StockItem
- PartNumber AS STRING * 6
- Description AS STRING * 20
- UnitPrice AS SINGLE
- Quantity AS INTEGER
- END TYPE
-
- ' Declare a variable (StockRecord) using the above type:
- DIM StockRecord AS StockItem
-
- ' Open the random-access file:
- OPEN "STOCK.DAT" FOR RANDOM AS #1 LEN = LEN(StockRecord)
-
- ' Calculate number of records in the file:
- NumberOfRecords = LOF(1) \ LEN(StockRecord)
-
- ' Read the records and write the data to the screen:
- FOR RecordNumber = 1 TO NumberOfRecords
-
- ' Read the data from a new record in the file:
- GET #1, RecordNumber, StockRecord
-
- ' Print the data to the screen:
- PRINT "Part Number: ", StockRecord.PartNumber
- PRINT "Description: ", StockRecord.Description
- PRINT "Unit Price : ", StockRecord.UnitPrice
- PRINT "Quantity : ", StockRecord.Quantity
-
- NEXT
-
- ' All done; close the file and end:
- CLOSE #1
- END
-
- It is not necessary to close STOCK.DAT before reading from it. Opening a
- file for random access lets you write to or read from the file with a single
- OPEN statement.
-
-
- Using Record Numbers to Retrieve Records
-
- You can read any record from a random-access file by specifying the record's
- number in a GET statement. You can write to any record in a random-access
- file by specifying the record's number in a PUT statement. This is one of
- the major advantages that random-access files have over sequential files,
- since sequential files do not permit direct access to a specific record.
-
- The sample application program, INDEX.BAS, listed in the section "Indexing a
- Random-Access File" later in this chapter shows a technique that quickly
- finds a particular record by searching an index of record numbers.
-
- Example
-
- The following fragment shows how to use GET with a record number:
-
- DEFINT A-Z' Default variable type is integer.
- CONST FALSE = 0, TRUE = NOT FALSE
-
- TYPE StockItem
- PartNumber AS STRING * 6
- Description AS STRING * 20
- UnitPrice AS SINGLE
- Quantity AS INTEGER
- END TYPE
-
- DIM StockRecord AS StockItem
-
- OPEN "STOCK.DAT" FOR RANDOM AS #1 LEN=LEN(StockRecord)
-
- NumberOfRecords = LOF(1) \ LEN(StockRecord)
- GetMoreRecords = TRUE
-
- DO
- PRINT "Enter record number for part you want to see ";
- PRINT "(0 to end): ";
- INPUT "", RecordNumber
-
- IF RecordNumber>0 AND RecordNumber<NumberOfRecords THEN
-
- ' Get the record whose number was entered and store
- ' it in StockRecord:
- GET #1, RecordNumber, StockRecord
-
- ' Display the record:
- .
- .
- .
- ELSEIF RecordNumber = 0
- THEN
-
- GetMoreRecords = FALSE
- ELSE
- PRINT "Input value out of range."
- END IF
- LOOP WHILE GetMoreRecords
- END
-
- Binary File I/O
-
- Binary access is a third way -- in addition to random access and sequential
- access -- to read or write a file's data. Use the following statement to
- open a file for binary I/O:
-
- OPEN file$ FOR BINARY AS # filenumber%Binary access
- is a way to get at the raw bytes of any file, not just an ASCII file.
- This makes it very useful for reading or modifying files saved in a
- non-ASCII format, such as Microsoft Word files or executable files.
-
-
- Files opened for binary access are treated as an unformatted sequence of
- bytes. Although you can read or write a record (a variable declared as
- having a user-defined type) to a file opened in the binary mode, the file
- itself does not have to be organized into fixed-length records. In fact,
- binary I/O does not have to deal with records at all, unless you consider
- each byte in a file as a separate record.
-
-
- Comparing Binary Access and Random Access
-
- The BINARY mode is similar to the RANDOM mode in that you can both read
- from and write to a file after a single OPEN statement. (Binary thus
- differs from sequential access, where you must first close a file and then
- reopen it if you want to switch between reading and writing.) Also, like
- random access, binary access lets you move backward and forward within an
- open file. Binary access even supports the same statements used for reading
- and writing random-access files using this syntax:
-
- GET | PUT} [#] filenumber% [, [position%]],[ variable]]
- Here, variable can have any type, including a variable-length
- string or a user-defined type, and position% points to the
- place in the file where the next GET or PUT
- operation will take place. (The position% value is relative to the
- beginning of the file; that is, the first byte in the file has position one,
- the second byte has position two, and so on.) If you leave off the
- position% argument, successive GET and PUT operations move the file
- pointer sequentially through the file from the first byte to the last.
-
-
- The GET statement reads a number of bytes from the file equal to the length
- variable. Similarly, the PUT statement writes a number of bytes to the
- file equal to the length variable.
-
- For example, if
- variable has integer type, then GET reads 2 bytes into variable; if
- variable has single-precision type, GET reads 4 bytes. Therefore, if you
- don't specify a position argument in a GET or PUT statement, the file
- pointer is moved a distance equal to the length variable.
-
-
- The GET statement and INPUT$ function are the only ways to read data from
- a file opened in binary mode. The PUT statement is the only way to write
- data to a file opened in binary mode.
-
- Binary access, unlike random access, enables you to move to any byte
- position in a file and then read or write any number of bytes you want. In
- contrast, random access can only move to a record boundary and read a fixed
- number of bytes (the length of a record) each time.
-
-
- Positioning the File Pointer with SEEK
-
- If you want to move the file pointer to a certain place in a file without
- actually performing any I/O, use the SEEK statement. Its syntax is:
-
- SEEK [#] filenumber%, position&After a SEEK statement, the next read or
- write operation in the file opened with filenumber% begins at the byte
- noted in position&.
-
-
- The counterpart to the SEEK statement is the SEEK function, with this
- syntax:
-
- SEEK( filenumber%)
-
- The SEEK function tells you the byte position where the
- very next read or write operation begins. (If you are using binary I/O to
- access a file, the LOC and SEEK functions give similar results, but LOC
- returns the position of the last byte read or written, while SEEK returns
- the position of the next byte to be read or written.)
-
-
- The SEEK statement and function also work on files opened for sequential or
- random access. With sequential access, the statement and the function behave
- in the same way they do with binary access; that is, the SEEK statement
- moves the file pointer to a specific byte position, and the SEEK function
- returns information about the next byte to read or write.
-
- However, if a file is opened for random access, the SEEK statement can move
- the file pointer only to the beginning of a record, not to a byte within a
- record. Also, when used with random-access files, the SEEK function returns
- the number of the next record rather than the position of the next byte.
-
- Example
-
- The following program opens a BASIC Quick library, then reads and prints the
- names of BASIC procedures and other external symbols contained in the
- library. This program is in the file named QLBDUMP.BAS on the Microsoft
- BASIC distribution disks.
-
- ' This program prints the names of Quick library procedures.
-
- DECLARE SUB DumpSym (SymStart AS INTEGER, QHdrPos AS LONG)
-
- TYPE ExeHdr' Part of DOS .EXE header.
- other1 AS STRING* 8' Other header information.
- CParHdr AS INTEGER ' Size of header in paragraphs.
- other2 AS STRING* 10' Other header information.
- IP AS INTEGER ' Initial IP value.
- CS AS INTEGER ' Initial (relative) CS value.
- END TYPE
- TYPE QBHdr' QLB header.
- QBHead AS STRING* 6' QBX specific heading.
- Magic AS INTEGER ' Magic word: identifies file as a Quick '
- library.
- SymStart AS INTEGER ' Offset from header to first code symbol.
- DatStart AS INTEGER ' Offset from header to first data symbol.
- END TYPE
-
- TYPE QbSym' QuickLib symbol entry.
- Flags AS INTEGER ' Symbol flags.
- NameStart AS INTEGER ' Offset into name table.
- Other AS STRING* 4' Other header information.
- END TYPE
-
- DIM EHdr AS ExeHdr, Qhdr AS QBHdr, QHdrPos AS LONG
-
- INPUT "Enter Quick library filename: ", FileName$
- FileName$ = UCASE$(FileName$)
- IF INSTR(FileName$,".QLB") = 0 THEN FileName$ = FileName$ + ".QLB"
- INPUT "Enter output filename or press Enter for screen: ", OutFile$
- OutFile$ = UCASE$(OutFile$)
- IF OutFile$ = "" THEN OutFile$ = "CONS:"
- OPEN FileName$ FOR BINARY AS #1
- OPEN OutFile$ FOR OUTPUT AS #2
-
- GET #1,, EHdr' Read the EXE format header.
- QHdrPos= (EHdr.CParHdr+ EHdr.CS) * 16 + EHdr.IP + 1
-
- GET #1,QHdrPos, Qhdr' Read the QuickLib format header.
- IF Qhdr.Magic <> &H6C75THEN PRINT "Not a QBX UserLibrary": END
-
- PRINT #2, "Code Symbols:": PRINT #2,
- DumpSym Qhdr.SymStart, QHdrPos' dump code symbols
- PRINT #2,
- PRINT #2, "Data Symbols:": PRINT #2, ""
- DumpSym Qhdr.DatStart, QHdrPos' dump data symbols
- PRINT #2,
-
- END
- SUB DumpSym (SymStart
- AS INTEGER, QHdrPos AS LONG)
-
- DIM QlbSym AS QbSym
- DIM NextSym AS LONG, CurrentSym AS LONG
-
- ' Calculate the location of the first symbol entry,
- ' then read that entry:
- NextSym = QHdrPos + SymStart
- GET #1, NextSym, QlbSym
- DO
- NextSym = SEEK(1)' Save the location of the next symbol.
- CurrentSym = QHdrPos + QlbSym.NameStart
- SEEK #1, CurrentSym' Use SEEK to move to the name
- ' for the current symbol entry.
- Prospect$ = INPUT$(40, 1)' Read the longest legal string,
- ' plus one additional byte for
- ' the final null character (CHR$(0)).
-
- ' Extract the null-terminated name:
- SName$ = LEFT$(Prospect$, INSTR(Prospect$, CHR$(0)))
-
- ' Print only those names that do not begin with "__", "$", or
- ' "b$" as these names are usually considered reserved:
- T$ = LEFT$(SName$, 2)
- IF T$ <> "__" AND LEFT$(SName$, 1) <> "$" AND UCASE$(T$) <> "B$" THEN
- PRINT #2, " " + SName$
- END IF
-
- GET #1, NextSym, QlbSym ' Read a symbol entry.
- LOOP WHILE QlbSym.Flags' Flags=0 (false) means end of table.
-
- END SUB
-
- Working with Devices
-
- Microsoft BASIC supports device I/O. This means certain computer peripherals
- can be opened for I/O just like data files on disk. Exceptions to the
- statements and functions that can be used with these devices are noted in
- the following section,"Differences Between Device I/O and File I/O." Table
- 3.1 lists the devices supported by BASIC.
-
-
- Differences Between Device I/O and File I/O
-
- Certain functions and statements used for file I/O are not allowed for
- device I/O, while other statements and functions have the same name but
- behave differently. These are some of the differences:
-
- ■ The CONS, SCRN, and LPT n devices cannot be opened in the input,
- append , or random-access modes.
-
- ■ The KYBD device cannot be opened in the output, append , or
- random-access modes.
-
- ■ The EOF, LOC, and LOF functions cannot be used with the CONS, KYBD,
- LPT n, or SCRN devices.
-
-
- ■ The EOF, LOC, and LOF functions can be used with the COM n serial
- device; however, the values these functions return have a different
- meaning than the values they return when used with data files. (See
- the next section for an explanation of what these functions do when
- used with COM n.)
-
-
- Example
-
- The following program shows how the devices LPT1 or SCRN can be opened for
- output using the same syntax as that for data files. This program reads all
- the lines from the file chosen by the user and then prints the lines on the
- screen or the printer according to the user's choice.
-
- CLS
- ' Input the name of the file to look at:
- INPUT "Name of file to display: ", FileNam$
-
- ' If no name is entered, end the program;
- ' otherwise, open the given file for reading (INPUT):
- IF FileNam$ = "" THEN END ELSE OPEN FileNam$ FOR INPUT AS #1
-
- ' Input choice for displaying file (Screen or Printer);
- ' continue prompting for input until either the "S" or "P"
- ' key is pressed:
- DO
- ' Go to row 2, column 1 on the screen and print prompt:
- LOCATE 2, 1, 1
- PRINT "Send output to screen (S), or to printer (P): ";
-
-
- ' Print over anything
- after the prompt:
-
- PRINT SPACE$(2);
-
- ' Relocate cursor after the prompt, and make it visible:
- LOCATE 2, 47, 1
- Choice$ = UCASE$(INPUT$(1)) ' Get input.
- PRINT Choice$
- LOOP WHILE Choice$ <> "S" AND Choice$ <> "P"
-
- ' Depending on the key pressed, open either the printer
- ' or the screen for output:
- SELECT CASE Choice$
- CASE "P"
- OPEN "LPT1:" FOR OUTPUT AS #2
- PRINT "Printing file on printer."
- CASE "S"
- CLS
- OPEN "SCRN:" FOR OUTPUT AS #2
- END SELECT
-
- ' Set the width of the chosen output device to 80 columns:
- WIDTH #2, 80
-
- END COLUMN BREAK' As
- long as there are lines in the file, read a line
-
- ' from the file and print it on the chosen device:
- DO UNTIL EOF(1)
- LINE INPUT #1, LineBuffer$
- PRINT #2, LineBuffer$
- LOOP
-
- CLOSE' End input from the file and output to the device.
- END
-
- Communications Through the Serial Port
-
- The OPEN "COM n :" statement (where n can be 1 or, if you have two serial
- ports, 2) allows you to open your computer's serial port(s) for serial
- (bit-by-bit) communication with other computers or with peripheral devices
- such as modems or serial printers. The following are some of the parameters
- you can specify:
-
- ■ Rate of data transmission, measured in "baud" (bits per second)
-
- ■ Whether or not to detect transmission errors and how those errors will
- be detected
-
- ■ How many stop bits (1, 1.5, or 2) are to be used to signal the end of
- a transmitted byte
-
- ■ How many bits in each byte of data transmitted or received constitute
- actual data
-
-
- When the serial port is opened for communication, an input buffer is
- set aside to hold the bytes being read from another device. This is
- because, at high baud rates, characters arrive faster than they can
- be processed. The default size for this buffer is 512 bytes, and it
- can be modified with the LEN = reclen%
- option of the OPEN "COM n :" statement. The values returned by the EOF,
-
- LOC, and LOF functions when used with a communications device return
- information about the size of this buffer, as shown in the following table:
-
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Function Information returned
- ────────────────────────────────────────────────────────────────────────────
- EOF Whether any characters are waiting
- to be read from the input buffer
-
- LOC The number of characters waiting
- in the input buffer
-
- LOF The amount of space remaining (in
- bytes) in the output buffer
-
-
-
-
-
- Since every character is potentially significant data, INPUT # and LINE
- INPUT # have serious drawbacks for getting input from another device. This
- is because INPUT # stops reading data into a variable when it encounters a
- comma or new-line character (and, sometimes, a space or double quotation
- mark), and LINE INPUT # stops reading data when it encounters a new-line
- character. This makes INPUT$ the best function to use for input from a
- communications device, since it reads all characters.
-
- The following line uses the LOC function to check the input buffer for the
- number of characters waiting there from the communications device opened as
- file #1; it then uses the INPUT$ function to read those characters,
- assigning them to a string variable named ModemInput$:
-
- ModemInput$ = INPUT$(LOC(1), #1)
-
- Sample Applications
-
- The sample applications listed in this section include a screen-handling
- program that prints a calendar for any month in any year from 1899 to 2099,
- a file I/O program that builds and searches an index of record numbers from
- a random-access file, and a communications program that makes your PC behave
- like a terminal when connected to a modem.
-
-
- Perpetual Calendar (CAL.BAS)
-
- After prompting the user to input a month from 1 to 12 and a year from 1899
- to 2099, the following program prints the calendar for the given month and
- year. The IsLeapYear procedure makes appropriate adjustments to the calendar
- for months in a leap year.
-
-
- Statements and Functions Used
-
- This program demonstrates the following screen-handling statements and
- functions:
-
- ■ INPUT
-
- ■ INPUT$
-
- ■ LOCATE
-
- ■ POS(0)
-
- ■ PRINT
-
- ■ PRINT USING
-
- ■ TAB
-
-
-
- Program Listing
-
- DEFINT A-Z ' Default variable type is integer.
-
-
- ' Define a data type for the names of the months and the
- ' number of days in each:
- TYPE MonthType
- Number AS INTEGER ' Number of days in the month.
- MName AS STRING * 9 ' Name of the month.
- END TYPE
-
- ' Declare procedures used:
- DECLARE FUNCTION IsLeapYear% (N%)
- DECLARE FUNCTION GetInput% (Prompt$, Row%, LowVal%, HighVal%)
-
- DECLARE SUB PrintCalendar (Year%, Month%)
- DECLARE SUB ComputeMonth (Year%, Month%, StartDay%, TotalDays%)
-
- DIM MonthData(1 TO 12) AS MonthType
-
- ' Initialize month definitions from DATA statements below:
- FOR I = 1 TO 12
- READ MonthData(I).MName, MonthData(I).Number
- NEXT
-
- ' Main loop, repeat for as many months as desired:
- DO
- CLS
-
- ' Get year and month as
- input:
-
- Year = GetInput("Year (1899 to 2099): ", 1, 1899, 2099)
- Month = GetInput("Month (1 to 12): ", 2, 1, 12)
-
- ' Print the calendar:
- PrintCalendar Year, Month
- ' Another Date?
- LOCATE 13, 1 ' Locate in 13th row, 1st column.
- PRINT "New Date? "; ' Keep cursor on same line.
- LOCATE , , 1, 0, 13 ' Turn cursor on and make it one
- ' character high.
- Resp$ = INPUT$(1) ' Wait for a key press.
- PRINT Resp$ ' Print the key pressed.
-
- LOOP WHILE UCASE$(Resp$) = "Y"
- END
-
- ' Data for the months of a year:
- DATA January, 31, February, 28, March, 31
- DATA April, 30, May, 31, June, 30, July, 31, August, 31
- DATA September, 30, October, 31, November, 30, December, 31
-
- ' ====================== ComputeMonth =====================
- ' Computes the first day and the total days in a month
- ' =========================================================
- '
- SUB ComputeMonth (Year, Month, StartDay, TotalDays) STATIC
- SHARED MonthData() AS MonthType
-
- CONST LEAP = 366 MOD 7
- CONST NORMAL = 365 MOD 7
-
- ' Calculate total number of days (NumDays) since 1/1/1899:
-
- ' Start with whole years:
- NumDays = 0
- FOR I = 1899 TO Year - 1
- IF IsLeapYear(I) THEN ' If leap year,
- NumDays = NumDays + LEAP ' add 366 MOD 7.
- ELSE ' If normal year,
- NumDays = NumDays + NORMAL ' add 365 MOD 7.
- END IF
- NEXT
-
- ' Next, add in days
- from whole months:
-
- FOR I = 1 TO Month - 1
- NumDays = NumDays + MonthData(I).Number
- NEXT
-
- ' Set the number of days in the requested month:
- TotalDays = MonthData(Month).Number
-
- ' Compensate if requested year is a leap year:
- IF IsLeapYear(Year) THEN
-
- ' If after February, add one to total days:
- IF Month > 2 THEN
- NumDays = NumDays + 1
-
- ' If February, add one to the month's days:
- ELSEIF Month = 2 THEN
- TotalDays = TotalDays + 1
- END IF
- END IF
-
- ' 1/1/1899 was a Sunday, so calculating "NumDays MOD 7"
- ' gives the day of week (Sunday = 0, Monday = 1, Tuesday
- ' = 2, and so on) for the first day of the input month:
- StartDay = NumDays MOD 7
- END SUB
-
- ' ======================== GetInput =======================
- ' Prompts for input, then tests for a valid range
- ' =========================================================
- '
- FUNCTION GetInput (Prompt$, Row, LowVal, HighVal) STATIC
-
- ' Locate prompt at specified row, turn cursor on and
- ' make it one character high:
- LOCATE Row, 1, 1, 0, 13
- PRINT Prompt$;
-
- ' Save column position:
- Column = POS(0)
-
- ' Input value until it's within range:
- DO
-
- LOCATE Row, Column ' Locate cursor at end of prompt.
- PRINT SPACE$(10) ' Erase anything already there.
- LOCATE Row, Column ' Relocate cursor at end of prompt.
- INPUT "", Value ' Input value with no prompt.
- LOOP WHILE (Value < LowVal OR Value > HighVal)
-
- ' Return valid input as value of function:
- GetInput = Value
-
- END FUNCTION
-
- ' ====================== IsLeapYear =======================
- ' Determines if a year is a leap year or not
- ' =========================================================
- '
- FUNCTION IsLeapYear (N) STATIC
-
- ' If the year is evenly divisible by 4 and not divisible
- ' by 100, or if the year is evenly divisible by 400,
- ' then it's a leap year:
- IsLeapYear = (N MOD 4 = 0 AND N MOD 100 <> 0) OR (N MOD 400 = 0)
- END FUNCTION
-
- ' ===================== PrintCalendar =====================
- ' Prints a formatted calendar given the year and month
- ' =========================================================
- '
- SUB PrintCalendar (Year, Month) STATIC
- SHARED MonthData() AS MonthType
-
- ' Compute starting day (Su M Tu ...)
- ' and total days for the month:
- ComputeMonth Year, Month, StartDay, TotalDays
- CLS
- Header$ = RTRIM$(MonthData(Month).MName) + "," + STR$(Year)
-
- ' Calculate location for centering month and year:
- LeftMargin = (35 - LEN(Header$)) \ 2
- ' Print header:
- PRINT TAB(LeftMargin); Header$
- PRINT
- PRINT "Su M Tu W Th F Sa"
- PRINT
-
- ' Recalculate and print
- tab
-
- ' to the first day of the month (Su M Tu ...):
- LeftMargin = 5 * StartDay + 1
- PRINT TAB(LeftMargin);
-
- ' Print out the days of the month:
- FOR I = 1 TO TotalDays
- PRINT USING "##_ "; I;
-
- ' Advance to the next line
- ' when the cursor is past column 32:
- IF POS(0) > 32 THEN PRINT
- NEXT
-
- END SUB
-
- Output
-
- 6787bfff
- Indexing a Random-Access File (INDEX.BAS)
-
- The following program uses an indexing technique to store and retrieve
- records in a random-access file. Each element of the Index() array has two
- parts: a string field (PartNumber) and an integer field (RecordNumber). This
- array is sorted alphabetically on the PartNumber field, which allows the
- array to be rapidly searched for a specific part number using a binary
- search.
-
- The Index array
- functions much like the index to a book. When you want to find the pages in
- a book that deal with a particular topic, you look up an entry for that
- topic in the index. The entry then points to a page number in the book.
- Similarly, this program looks up a part number in the alphabetically sorted
- Index() array. Once it finds the part number, the associated record number
- in the RecordNumber field points to the record containing all the
- information for that part.
-
-
- Statements and Functions Used
-
- This program demonstrates the following statements and functions used in
- accessing random-access files:
-
- ■ TYPE... END TYPE
-
- ■ OPEN... FOR RANDOM
-
- ■ GET #
-
- ■ PUT #
-
- ■ LOF
-
-
-
- Program Listing
-
-
- DEFINT A-Z
-
-
- ' Define the symbolic constants used globally in the program:
- CONST FALSE = 0, TRUE = NOT FALSE
-
- ' Define a record structure for random-access records:
- TYPE StockItem
- PartNumber AS STRING * 6
- Description AS STRING * 20
- UnitPrice AS SINGLE
- Quantity AS INTEGER
- END TYPE
-
- ' Define a record structure for each element of the index:
- TYPE IndexType
- RecordNumber AS INTEGER
- PartNumber AS STRING * 6
- END TYPE
-
- ' Declare procedures that will be called:
- DECLARE FUNCTION Filter$ (Prompt$)
- DECLARE FUNCTION FindRecord% (PartNumber$, RecordVar AS StockItem)
-
- DECLARE SUB AddRecord
- (RecordVar AS StockItem)
-
- DECLARE SUB InputRecord (RecordVar AS StockItem)
- DECLARE SUB PrintRecord (RecordVar AS StockItem)
- DECLARE SUB SortIndex ()
- DECLARE SUB ShowPartNumbers ()
- ' Define a buffer (using the StockItem type)
- ' and define and dimension the index array:
- DIM StockRecord AS StockItem, Index(1 TO 100) AS IndexType
-
- ' Open the random-access file:
- OPEN "STOCK.DAT" FOR RANDOM AS #1 LEN = LEN(StockRecord)
-
- ' Calculate number of records in the file:
- NumberOfRecords = LOF(1) \ LEN(StockRecord)
-
- ' If there are records, read them and build the index:
- IF NumberOfRecords <> 0 THEN
- FOR RecordNumber = 1 TO NumberOfRecords
-
- ' Read the data from a new record in the file:
- GET #1, RecordNumber, StockRecord
-
- ' Place part number and record number in index:
- Index(RecordNumber).RecordNumber = RecordNumber
- Index(RecordNumber).PartNumber = StockRecord.PartNumber
- NEXT
-
- SortIndex ' Sort index in part-number order.
- END IF
-
- DO ' Main-menu loop.
- CLS
- PRINT "(A)dd records."
- PRINT "(L)ook up records."
- PRINT "(Q)uit program."
- PRINT
- LOCATE , , 1
- PRINT "Type your choice (A, L, or Q) here: ";
-
- ' Loop until user presses, A, L, or Q:
- DO
- Choice$ = UCASE$(INPUT$(1))
- LOOP WHILE INSTR("ALQ", Choice$) = 0
-
- ' Branch according to
- choice:
-
- SELECT CASE Choice$
- CASE "A"
- AddRecord StockRecord
- CASE "L"
- IF NumberOfRecords = 0 THEN
- PRINT : PRINT "No records in file yet. ";
- PRINT "Press any key to continue.";
- Pause$ = INPUT$(1)
- ELSE
- InputRecord StockRecord
- END IF
- CASE "Q" ' End program.
- END SELECT
- LOOP UNTIL Choice$ = "Q"
-
- CLOSE #1 ' All done, close file and end.
- END
-
- ' ======================== AddRecords ======================
- ' Adds records to the file from input typed at the keyboard
- ' =========================================================
- '
- SUB AddRecord (RecordVar AS StockItem) STATIC
- SHARED Index() AS IndexType, NumberOfRecords
- DO
- CLS
- INPUT "Part Number: ", RecordVar.PartNumber
- INPUT "Description: ", RecordVar.Description
-
- ' Call the Filter$ function to input price & quantity:
- RecordVar.UnitPrice = VAL(Filter$("Unit Price : "))
- RecordVar.Quantity = VAL(Filter$("Quantity : "))
-
- NumberOfRecords = NumberOfRecords + 1
-
- PUT #1, NumberOfRecords, RecordVar
-
- Index(NumberOfRecords).RcrNme
- = NumberOfRecords
-
- Index(NumberOfRecords).PartNumber = RecordVar.PartNumber
- PRINT : PRINT "Add another? ";
- OK$ = UCASE$(INPUT$(1))
- LOOP WHILE OK$ = "Y"
-
- SortIndex ' Sort index file again.
- END SUB
-
- ' ========================= Filter$ ========================
- ' Filters all non-numeric characters from a string
- ' and returns the filtered string
- ' =========================================================
- '
- FUNCTION Filter$ (Prompt$) STATIC
- ValTemp2$ = ""
- PRINT Prompt$; ' Print the prompt passed.
- INPUT "", ValTemp1$ ' Input a number as
- ' a string.
- StringLength = LEN(ValTemp1$) ' Get the string's length.
- FOR I% = 1 TO StringLength ' Go through the string,
- Char$ = MID$(ValTemp1$, I%, 1) ' one character at a time.
-
- ' Is the character a valid part of a number (i.e.,
- ' a digit or a decimal point)? If yes, add it to
- ' the end of a new string:
- IF INSTR(".0123456789", Char$) > 0 THEN
- ValTemp2$ = ValTemp2$ + Char$
-
- ' Otherwise, check to see if it's a lowercase "l",
- ' since typewriter users may enter a one that way:
- ELSEIF Char$ = "l" THEN
- ValTemp2$ = ValTemp2$ + "1" ' Change the "l" to a "1."
- END IF
- NEXT I%
-
- Filter$ = ValTemp2$ ' Return filtered string.
-
- END FUNCTION
-
- '
- ======================= FindRecord% ===================
-
- ' Uses a binary search to locate a record in the index
- ' ======================================================
- '
- FUNCTION FindRecord% (Part$, RecordVar AS StockItem) STATIC
- SHARED Index() AS IndexType, NumberOfRecords
-
- ' Set top and bottom bounds of search:
- TopRecord = NumberOfRecords
- BottomRecord = 1
-
- ' Search until top of range is less than bottom:
- DO UNTIL (TopRecord < BottomRecord)
-
- ' Choose midpoint:
- Midpoint = (TopRecord + BottomRecord) \ 2
-
- ' Test to see if it's the one wanted (RTRIM$()
- ' trims trailing blanks from a fixed string):
- Test$ = RTRIM$(Index(Midpoint).PartNumber)
-
- ' If it is, exit loop:
- IF Test$ = Part$ THEN
- EXIT DO
-
- ' Otherwise, if what you're looking for is greater,
- ' move bottom up:
- ELSEIF Part$ > Test$ THEN
- BottomRecord = Midpoint + 1
-
- ' Otherwise, move the top down:
- ELSE
- TopRecord = Midpoint - 1
- END IF
- LOOP
-
- ' If part was found, input record from file using
- ' pointer in index and set FindRecord% to TRUE:
- IF Test$ = Part$ THEN
- GET #1, Index(Midpoint).RecordNumber, RecordVar
- FindRecord% = TRUE
-
- ' Otherwise, if part
- was not found, set FindRecord%
-
- ' to FALSE:
- ELSE
- FindRecord% = FALSE
- END IF
- END FUNCTION
-
- ' ======================= InputRecord =====================
- ' First, InputRecord calls ShowPartNumbers, which prints
- ' a menu of part numbers on the top of the screen. Next,
- ' InputRecord prompts the user to enter a part number.
- ' Finally, it calls the FindRecord and PrintRecord
- ' procedures to find and print the given record.
- ' =========================================================
- '
- SUB InputRecord (RecordVar AS StockItem) STATIC
- CLS
- ShowPartNumbers ' Call the ShowPartNumbers SUB procedure.
-
- ' Print data from specified records
- ' on the bottom part of the screen:
- DO
- PRINT "Type a part number listed above ";
- INPUT "(or Q to quit) and press <ENTER>: ", Part$
- IF UCASE$(Part$) <> "Q" THEN
- IF FindRecord(Part$, RecordVar) THEN
- PrintRecord RecordVar
- ELSE
- PRINT "Part not found."
- END IF
- END IF
- PRINT STRING$(40, "_")
- LOOP WHILE UCASE$(Part$) <> "Q"
-
- VIEW PRINT ' Restore the text viewport to entire screen.
- END SUB
-
- '
- ======================= PrintRecord =====================
-
- ' Prints a record on the screen
- ' =========================================================
- '
- SUB PrintRecord (RecordVar AS StockItem) STATIC
- PRINT "Part Number: "; RecordVar.PartNumber
- PRINT "Description: "; RecordVar.Description
- PRINT USING "Unit Price :$$###.##"; RecordVar.UnitPrice
- PRINT "Quantity :"; RecordVar.Quantity
- END SUB
-
- ' ===================== ShowPartNumbers ===================
- ' Prints an index of all the part numbers in the upper part
- ' of the screen
- ' =========================================================
- '
- SUB ShowPartNumbers STATIC
- SHARED index() AS IndexType, NumberOfRecords
-
- CONST NUMCOLS = 8, COLWIDTH = 80 \ NUMCOLS
-
- ' At the top of the screen, print a menu indexing all
- ' the part numbers for records in the file. This menu is
- ' printed in columns of equal length (except possibly the
- ' last column, which may be shorter than the others):
- ColumnLength = NumberOfRecords
- DO WHILE ColumnLength MOD NUMCOLS
- ColumnLength = ColumnLength + 1
- LOOP
- ColumnLength = ColumnLength \ NUMCOLS
- Column = 1
- RecordNumber = 1
- DO UNTIL RecordNumber > NumberOfRecords
- FOR Row = 1 TO ColumnLength
- LOCATE Row, Column
- PRINT index(RecordNumber).PartNumber
- RecordNumber = RecordNumber + 1
- IF RecordNumber > NumberOfRecords THEN EXIT FOR
- NEXT Row
- Column = Column + COLWIDTH
- LOOP
-
- LOCATE ColumnLength +
- 1, 1
-
- PRINT STRING$(80, "_") ' Print separator line.
-
- ' Scroll information about records below the part-number
- ' menu (this way, the part numbers are not erased):
- VIEW PRINT ColumnLength + 2 TO 24
- END SUB
-
- ' ========================= SortIndex =====================
- ' Sorts the index by part number
- ' =========================================================
- '
- SUB SortIndex STATIC
- SHARED Index() AS IndexType, NumberOfRecords
-
- ' Set comparison offset to half the number of records
- ' in Index:
- Offset = NumberOfRecords \ 2
-
- ' Loop until offset gets to zero:
- DO WHILE Offset > 0
- Limit = NumberOfRecords - Offset
- DO
-
- ' Assume no switches at this offset:
- Switch = FALSE
-
- ' Compare elements and switch ones out of order:
- FOR I = 1 TO Limit
- IF Index(I).PartNumber > Index(I + Offset).PartNumber THEN
- SWAP Index(I), Index(I + Offset)
- Switch = I
- END IF
- NEXT I
-
- ' Sort on next pass only to where
- ' last switch was made:
- Limit = Switch
- LOOP WHILE Switch
-
- ' No switches at last offset, try one half as big:
- Offset = Offset \ 2
- LOOP
- END SUB
-
- Output
-
- b315bfff
- Terminal Emulator (TERMINAL.BAS)
-
- The following simple program turns your computer into a "dumb" terminal;
- that is, it makes your computer function solely as an I/O device in tandem
- with a modem. This program uses the OPEN "COM1:" statement and associated
- device I/O functions to do the following things:
-
- ■ Send the characters you type to the modem
-
- ■ Print characters returned by the modem on the screen
-
-
- Note that typed characters displayed on the screen are first sent to the
- modem and then returned to your computer through the open communications
- channel. You can verify this if you have a modem with Receive Detect (RD)
- and Send Detect (SD) lights -- they will flash in sequence as you type.
-
- To dial a number, you would have to enter the Attention Dial Touch-Tone
- (ATDT) or Attention Dial Pulse (ATDP) commands at the keyboard (assuming you
- have a Hayes-compatible modem).
-
- Any other commands sent to the modem would also have to be entered at the
- keyboard.
-
-
- Statements and Functions Used
-
- This program demonstrates the following statements and functions used in
- communicating with a modem through your computer's serial port:
-
- ■ OPEN "COM1:"
-
- ■ EOF
-
- ■ INPUT$
-
- ■ LOC
-
-
-
- Program Listing
-
- DEFINT A-Z
-
-
- DECLARESUB Filter (InString$)
-
- COLOR 7, 1' Set screen color.
- CLS
- Quit$ = CHR$(0) + CHR$(16)' Value returned by INKEY$
- ' when Alt+Q is pressed.
-
- ' Set up prompt on bottom line of screen and turn cursor on:
- LOCATE 24, 1, 1
- PRINT STRING$(80, "_");
- LOCATE 25, 1
- PRINT TAB(30); "Press Alt+Q to quit";
-
- VIEW PRINT 1 TO23' Print between lines 1 & 23.
-
- ' Open communications (1200 baud, no parity, 8-bit data,
- ' 1 stop bit, 256-byte input buffer):
- OPEN "COM1:1200,N,8,1" FOR RANDOM AS #1LEN = 256
-
- DO ' Main communications loop.
-
- KeyInput$ = INKEY$ ' Check the keyboard.
-
- IF KeyInput$ = Quit$ THEN' Exit the loop if the user
- EXIT DO' pressed Alt+Q.
-
- ELSEIF KeyInput$ <> ""
- THEN' Otherwise, if the user has
-
- PRINT #1,KeyInput$; ' pressed a key, send the
- END IF ' character typed to modem.
- ' Check the modem. If characters are waiting (EOF(1) is
- ' true), get them and print them to the screen:
- IF NOT EOF(1) THEN
-
- ' LOC(1) gives the number of characters waiting:
- ModemInput$ = INPUT$(LOC(1), #1)
-
- Filter ModemInput$ ' Filter out line feeds and
- PRINT ModemInput$; ' backspaces, then print.
- END IF
- LOOP
-
- CLOSE ' End communications.
- CLS
- END
- '
- ' ========================= Filter ========================
- ' Filters characters in an input string
- ' =========================================================
- '
- SUB Filter (InString$) STATIC
-
- ' Look for Backspace characters and recode
- ' them to CHR$(29) (the Left direction key):
- DO
- BackSpace = INSTR(InString$, CHR$(8))
- IF BackSpace THEN
- MID$(InString$, BackSpace) = CHR$(29)
- END IF
- LOOP WHILE BackSpace
-
- ' Look for line-feed characters and
- ' remove any found:
- DO
- LnFd = INSTR(InString$, CHR$(10))
- IF LnFd THEN
- InString$=LEFT$(InString$,LnFd-1)+MID$(InString$,LnFd+1)
- END IF
- LOOP WHILE LnFd
-
- END SUB
-
- ────────────────────────────────────────────────────────────────────────────
-
- Chapter 4: String Processing
-
-
- This chapter shows you how to manipulate sequences of ASCII characters,
- known as strings. String manipulation is indispensable when you are
- processing text files, sorting data, or modifying string-data input.
-
- When you are finished with this chapter, you will know how to perform the
- following string-processing tasks:
-
- ■ Declare fixed-length strings.
-
- ■ Compare strings and sort them alphabetically.
-
- ■ Search for one string inside another.
-
- ■ Get part of a string.
-
- ■ Trim spaces from the beginning or end of a string.
-
- ■ Generate a string by repeating a single character.
-
- ■ Change uppercase letters in a string to lowercase and vice versa.
-
- ■ Convert numeric expressions to string representations and vice versa.
-
-
-
- Strings Defined
-
- A string is a sequence of contiguous characters. Examples of characters are
- the letters of the alphabet (a - z and A - Z), punctuation symbols such as
- commas (,) or question marks (?), and other symbols from the fields of math
- and finance such as plus (+) or percent (%) signs.
-
- In this chapter, the term "string" can refer to any of the following:
-
- ■ A string constant ■ A string variable
-
- ■ A string expression
- String constants are
- declared in one of two ways:
-
-
- * By enclosing a sequence of characters between double quotation marks,
- as in the following PRINT statement:
-
- PRINT "Processing the file. Please wait."
-
- This is known as a "literal string constant."
-
- * By setting a name equal to a literal string in a CONST statement,
- as in the following:
-
- ' Define the string constant MESSAGE:
- CONST MESSAGE = "Drive door open."
-
- This is known as a "symbolic string constant."
-
-
- String variables can be declared in one of three ways:
-
- ■ By appending the string-type suffix ( $) to the variable name:
- LINE INPUT "Enter your name: "; Buffer$ ■ n
-
- By using the DEFSTR statement:
-
-
- ' All variables beginning with the letter "B"
- ' are strings, unless they end with one of the
- ' numeric-type suffixes (%, &, !, @, or #):
- DEFSTR B
- . . .
- Buffer = "Your name here" ' Buffer is a string variable.
-
- ■ By declaring the string name in an AS STRING statement:
-
- DIM Buffer1 AS STRING ' Buffer1 is a variable-length string
- DIM Buffer2 AS STRING*10 ' Buffer2 is a fixed-length
- ' string 10 bytes long.
-
-
- A string expression is a combination of string variables, constants, and/or
- string functions.
-
- Of course, the character components of strings are not stored in your
- computer's memory in a form generally recognizable. Instead, each character
- is translated to a number known as the American Standard Code for
- Information Interchange code for that character. For example, uppercase "A"
- is stored as decimal 65 (or hexadecimal 41H), while lowercase "a" is stored
- as decimal 97 (or hexadecimal 61H).
-
- You can also use the BASIC ASC function to determine the ASCII code for a
- character; for example, ASC("A") returns the value 65. The inverse of the
- ASC function is the CHR$ function. CHR$ takes an ASCII code as its
- argument and returns the character with that code; for example, the
- statement PRINT CHR$(64) displays the character @.
-
-
- Variable- and Fixed-Length Strings
-
- In previous versions of BASIC, strings were always variable length. BASIC
- now supports variable-length strings and fixed-length strings.
-
-
- Variable-Length Strings
-
- Variable-length strings are "elastic"; that is, they expand and contract to
- store different numbers of characters (from none to a maximum of 32,767).
-
- Example
-
- The length of the variable Temp$ in the following example varies according
- to the size of what is stored in Temp$:
-
- Temp$ = "1234567"
-
- ' LEN function returns length of string
- ' (number of characters it contains):
- PRINT LEN(Temp$)
-
- Temp$ = "1234"
- PRINT LEN(Temp$)
-
- Output
-
- 7
- 4
-
- Fixed-Length Strings
-
- Fixed-length strings are commonly used as record elements in a TYPE... END
- TYPE user-defined data type. However, they can also be declared by
- themselves in COMMON, DIM, REDIM, SHARED, or STATIC statements, as in
- the following statement:
-
- DIM Buffer AS STRING * 10
-
- Examples
-
- As their name implies, fixed-length strings have a constant length,
- regardless of the length of the string stored in them. This is shown by the
- output from the following example:
-
- DIM LastName AS STRING * 12
- DIM FirstName AS STRING * 10
-
- LastName = "Huntington-Ashley"
- FirstName = "Claire"
-
- PRINT "123456789012345678901234567890"
- PRINT FirstName; LastName
- PRINT LEN(FirstName)
- PRINT LEN(LastName)
-
- Output
-
- 123456789012345678901234567890
- Claire Huntington-A
- 10
- 12
-
- Note that the lengths of the string variables FirstName and LastName did not
- change, as demonstrated by the values returned by the LEN function (as well
- as the four spaces following the six-letter name, Claire).
-
- The output from the preceding example also illustrates how values assigned
- to fixed-length variables are left-justified and, if necessary, truncated on
- the right. It is not necessary to use the LSET function to left-justify
- values in fixed-length strings; this is done automatically. If you want to
- right-justify a string inside a fixed-length string, use RSET, as shown
- here:
-
- DIM NameBuffer AS STRING * 10
- RSET NameBuffer = "Claire"
- PRINT "1234567890"
- PRINT NameBuffer
-
- Output
- 1234567890
- Claire
-
- Combining Strings
-
- Two strings can be combined with the addition operator ( +). The string
- following the plus operator is appended to the string preceding the plus
- operator.
-
- Examples
-
- The following example combines two strings:
-
- A$ = "first string"
- B$ = "second string"
- C$ = A$ + B$
- PRINT C$
-
- Output
-
- first stringsecond string
-
- The process of combining strings in this way is called "concatenation,"
- which means linking together.
-
- Note that in the previous example, the two strings are combined without any
- intervening spaces. If you want a space in the combined string, you could
- pad one of the strings A$ or B$, like this:
-
- B$ = " second string" ' Leading blank in B$
- Because values are
- left-justified in fixed-length strings, you may find extra spaces when you
- concatenate them, as in this example:
-
-
- TYPE NameType
- First AS STRING * 10
- Last AS STRING * 12
- END TYPE
-
- DIM NameRecord AS NameType
-
- ' The constant "Ed" is left-justified in the variable
- ' NameRecord.First, so there are eight trailing blanks:
- NameRecord.First = "Ed"
- NameRecord.Last = "Feldstein"
-
- ' Print a line of numbers for reference:
- PRINT "123456789012345678901234567890"
-
- WholeName$ = NameRecord.First + NameRecord.Last
- PRINT WholeName$
-
- Output
-
- 123456789012345678901234567890
- Ed Feldstein
-
- The LTRIM$ function returns a string with its leading spaces stripped away,
- while the RTRIM$ function returns a string with its trailing spaces
- stripped away. The original string is unaltered. (See the section
- "Retrieving Parts of Strings" later in this chapter for more information on
- these functions.)
-
-
- Comparing Strings
-
- Strings are compared with the following relational operators:
-
- ╓┌──────────────────┌────────────────────────────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- <> Not equal
- = Equal
- < Less than
- > Greater than
- <= Less than or equal to
- >= Greater than or equal to
- ────────────────────────────────────────────────────────────────────────────
- >= Greater than or equal to
-
-
-
-
- A single character is greater than another character if its ASCII value is
- greater. For example, the ASCII value of the letter "B" is greater than the
- ASCII value of the letter "A," so the expression "B" > "A" is true.
-
- When comparing two strings, BASIC looks at the ASCII values of
- corresponding characters. The first character where the two strings
- differ determines the alphabetical order of the strings.
-
- For instance, the strings "doorman" and "doormats" are
- the same up to the seventh character in each ("n" and "t"). Since the ASCII
- value of "n" is less than the ASCII value of "t," the expression "doorman" <
- "doormats" is true. Note that the ASCII values for letters follow
- alphabetical order from A to Z and from a to z, so you can use the
- relational operators listed in the preceding table to alphabetize strings.
- Moreover, uppercase letters have smaller ASCII values than lowercase
- letters, so in a sorted list of strings, "ASCII" would come before "ascii."
-
- If there is no difference between corresponding characters of two strings
- and they are the same length, then the two strings are equal. If there is no
- difference between corresponding characters of two strings, but one of the
- strings is longer, then the longer string is greater than the shorter
- string. For example abc =abc and aaaaaaa >aaa are true expressions.
-
- Leading and trailing blank spaces are significant in string comparisons. For
- example, the string " test" is less than the string "test," since a blank
- space (" ") is less than a "t"; on the other hand, the string "test " is
- greater than the string "test." Keep this in mind when comparing
- fixed-length and variable-length strings, since fixed-length strings may
- contain trailing spaces.
-
-
- Searching for Strings
-
- One of the most common string-processing tasks is searching for a string
- inside another string. The INSTR( stringexpression1$, stringexpression2$)
- function tells you whether or not string2 is contained in
- stringexpression1$ by returning the position of the first character in
- stringexpression1$ (if any) where the match begins.
-
- Examples
-
- The following example finds the starting position of one string inside
- another string:
-
- String1$ = "A line of text with 37 letters in it."
- String2$ = "letters"
-
- PRINT " 1 2 3 4"
- PRINT "1234567890123456789012345678901234567890"
- PRINT String1$
- PRINT String2$
- PRINT INSTR(String1$, String2$)
-
- Output
-
- 1 2 3 4
- 1234567890123456789012345678901234567890
- A line of text with 37 letters in it.
- letters
- 24
-
- If no match is found (that is, stringexpression2$ is not a substring of
- stringexpression1$), INSTR returns the value 0.
-
- A variation of the preceding syntax, INSTR( start%,
- stringexpression1$, stringexpression2$),
- allows you to specify where you want the search to start in stringexpression
- To illustrate, making the following modification to the preceding example
- changes the output:
-
-
- ' Start looking for a match at the 30th
- ' character of String1$:
- PRINT INSTR(30, String1$, String2$)
-
- Output
-
- 1 2 3 4
- 1234567890123456789012345678901234567890
- A line of text with 37 letters in it.
- letters
- 0
-
- In other words, the string letters does not appear after the 30th character
- of String1$.
-
- The INSTR( position, string1, string2) variation is useful for finding
- every occurrence of stringexpression2$ in stringexpression1$ instead of
- just the first occurrence, as shown in the next example:
-
- String1$ = "the dog jumped over the broken saxophone."
- String2$ = "the"
- PRINT String1$
-
- Start = 1
- NumMatches = 0
-
- DO
- Match = INSTR(Start, String1$, String2$)
- IF Match > 0 THEN
- PRINT TAB(Match); String2$
- Start = Match + 1
- NumMatches = NumMatches + 1
- END IF
- LOOP WHILE MATCH
-
- PRINT "Number of matches ="; NumMatches
-
- Output
-
- the dog jumped over the broken saxophone.
- the
- the
- Number of matches = 2
-
- Retrieving Parts of Strings
-
- The section "Combining Strings" shows how to put strings together
- (concatenate them) by using the addition operator ( +). Several functions
- are available in BASIC that take strings apart, returning pieces of a
- string, either from the left side, the right side, or the middle of a target
- string.
-
-
- Retrieving Characters from the Left Side of a String
-
- The LEFT$ and RTRIM$ functions return characters from the left side of a
- string. The LEFT$( stringexpression$, n% ) function returns number
- characters from the left side of string.
-
- Examples
-
- The following example retrieves four characters from the left side of a
- string:
-
- Test$ = "Overtly"
-
- ' Print the leftmost 4 characters from Test$:
- PRINT LEFT$(Test$, 4)
-
- Output
-
- Over
-
- Note that LEFT$, like the other functions described in this chapter, does
- not change the original string Test$; it merely returns a different string,
- a copy of part of Test$.
-
- The RTRIM$ function returns the left part of a string after removing any
- blank spaces at the end. For example, contrast the output from the two
- PRINT statements in the following example:
-
- PRINT "a left-justified string "; "next"
- PRINT RTRIM$("a left-justified string "); "next"
-
- Output
-
- a left-justified string next
- a left-justified stringnext
-
- The RTRIM$ function is useful for comparing fixed-length and
- variable-length strings, as in the next example:
-
- DIM NameRecord AS STRING * 10
- NameRecord = "Ed"
-
- NameTest$ = "Ed"
-
-
- ' Use RTRIM$ to trim
- all the blank spaces from the right
-
- ' side of the fixed-length string NameRecord, then compare
- ' it with the variable-length string NameTest$:
- IF RTRIM$(NameRecord) = NameTest$ THEN
- PRINT "They're equal"
- ELSE
- PRINT "They're not equal"
- END IF
-
- Output
-
- They're equal
-
-
- Retrieving Characters from the Right Side of a String
-
- The RIGHT$ and LTRIM$ functions return characters from the right side of a
- string. The RIGHT$( stringexpression$, n%) function returns n% characters
- from the right side of stringexpression$.
-
- Examples
-
- The following example retrieves five characters from the right side of a
- string:
-
- Test$ = "1234567890"
-
- ' Print the rightmost 5 characters from Test$:
- PRINT RIGHT$(Test$,5)
-
- Output
-
- 67890
-
- The LTRIM$ function returns the right part of a string after removing any
- blank spaces at the beginning. For example, contrast the output from the
- next two PRINT statements:
-
- PRINT "first"; " a right-justified string"
- PRINT "first"; LTRIM$(" a right-justified string")
-
- Output
-
- first a right-justified string
- firsta right-justified string
-
-
- Retrieving Characters from Anywhere in a String
-
- Use the MID$ function to retrieve any number of characters from any point
- in a string. The MID$( stringexpression$, start%, length% ) function
- returns n% characters from stringexpression$, starting at the character
- with position start%. For example, the following statement starts at the
- 12th character of the string ("h") and returns the next three characters
- ("hill"):
-
- MID$("**over the hill**", 12, 4)
-
- Example
-
- The following example shows how to use the MID$ function to step through a
- line of text character by character:
-
- ' Get the number of characters in the string of text:
- Length = LEN(TextString$)
-
- FOR I = 1 TO Length
-
- ' Get the first character, then the second, third,
- ' and so forth, up to the end of the string:
- Char$ = MID$(TextString$, I, 1)
-
- ' Evaluate that character:
- .
- .
- .
- NEXT
-
-
- Generating Strings
-
- The easiest way to create a string of one character repeated over and over
- is to use the intrinsic function STRING$. The STRING$( m%,
- stringexpression$) function produces a new string with m% characters, each
- character of which is the first character of stringexpression$. For
- example, the following statement generates a string of 27 asterisks:
-
- Filler$ = STRING$(27, "*")
-
-
- Example
-
- For characters that cannot be produced by typing, such as characters whose
- ASCII values are greater than 127, use the alternative form of this
- function, STRING$( m%, n%). This form creates a string with m%
- characters, each character of which has the ASCII code specified by m%, as
- in the next example:
-
- ' Print a string of 10 "@" characters
- ' (64 is ASCII code for @):
- PRINT STRING$(10, 64)
-
- Output
-
- @@@@@@@@@@
- The SPACE$( n)
- function generates a string consisting of n blank spaces.
-
-
- Changing the Case of Letters
-
- You may want to convert uppercase (capital) letters in a string to lowercase
- or vice versa. This conversion is useful in a non-case-sensitive search for
- a particular string pattern in a large file (in other words, "help," "HELP,"
- and "Help" are all be considered the same). These functions are also handy
- when you are not sure whether a user will input text in uppercase or
- lowercase letters.
-
- The UCASE$ and LCASE$ functions make the following conversions to a
- string:
-
- ■ UCASE$ returns a copy of the string passed to it, with all the
- lowercase letters converted to uppercase.
-
- ■ LCASE$ returns a copy of the string passed to it, with all the
- uppercase letters converted to lowercase.
-
-
- Example
-
- The following example prints a string unchanged, in all uppercase letters,
- and in all lowercase letters:
-
- Sample$ = "* The ASCII Appendix: a table of useful codes *"
- PRINT Sample$
- PRINT UCASE$(Sample$)
- PRINT LCASE$(Sample$)
-
- Output
-
- * The ASCII Appendix: a table of useful codes *
- * THE ASCII APPENDIX: A TABLE OF USEFUL CODES *
- * the ascii appendix: a table of useful codes *
-
- Letters that are already uppercase, as well as characters that are not
- letters, remain unchanged by UCASE$; similarly, lowercase letters and
- characters that are not letters are unaffected by LCASE$.
-
-
- Strings and Numbers
-
- BASIC does not allow a string to be assigned to a numeric variable, nor does
- it allow a numeric expression to be assigned to a string variable. For
- example, if you use either of these statements, BASIC will generate the
- error message Type mismatch.
-
- TempBuffer$ = 45
- Counter% = "45"
-
- Instead, use the STR$ function to return the string
- representation of a number or the VAL function to return
- the numeric representation of a string:
-
- ' The following statements are both valid:
- TempBuffer$ = STR$(45)
- Counter% = VAL("45")
- Example
-
- Note that STR$ includes the leading blank space that BASIC prints for
- positive numbers, as this short example shows:
-
- FOR I = 0 TO 9
- PRINT STR$(I);
- NEXT
-
- Output
-
- 0 1 2 3 4 5 6 7 8 9
-
- If you want to eliminate this space, you can use the LTRIM$ function, as
- shown in the following example:
-
- FOR I = 0 TO 9
- PRINT LTRIM$(STR$(I));
- NEXT
-
- Output
-
- 0123456789
-
- Another way to format numeric output is with the PRINT USING statement (see
- the section "Printing Text on the Screen" in Chapter 3, "File and Device
- I/O").
-
-
- Changing Strings
-
- The functions mentioned in each of the preceding sections all leave their
- string arguments unchanged. Changes are made to a copy of the argument.
-
- In contrast, the MID$ statement (unlike the MID$ function discussed in the
- section "Retrieving Characters from Anywhere in a String" earlier in this
- chapter) changes its argument by embedding another string in it. The
- embedded string replaces part or all of the original string.
-
- Example
-
- The following example replaces parts of a string:
-
- Temp$ = "In the sun."
- PRINT Temp$
-
- ' Replace the "I" with an "O":
- MID$(Temp$,1) = "O"
-
- ' Replace "sun." with "road":
- MID$(Temp$,8) = "road"
- PRINT Temp$
-
- Output
-
- In the sun.
- On the road
-
-
- Sample Application: Converting a String to a Number (STRTONUM.BAS)
-
- The following program takes a number that is input as a string, filters any
- numeric characters (such as commas) out of the string, then converts the
- string to a number with the VAL function.
-
-
- Functions Used
-
- This program demonstrates the following string-handling functions discussed
- in this chapter:
-
- ■ INSTR ■ LEN
-
- ■ MID$ ■ VAL
-
-
-
- Program Listing
-
- ' Input a line:
- LINE INPUT "Enter a number with commas: "; A$
-
- ' Look only for valid numeric characters (0123456789.-)
- ' in the input string:
- CleanNum$ = Filter$(A$, "0123456789.-")
-
- ' Convert the string to a number:
- PRINT "The number's value = "; VAL(CleanNum$)
- END
-
- ' ========================== Filter$ =======================
- ' Takes unwanted characters out of a string by
- ' comparing them with a filter string containing
- ' only acceptable numeric characters
- ' =========================================================
-
- FUNCTION Filter$ (Txt$, FilterString$) STATIC
- Temp$ = ""
- TxtLength = LEN(Txt$)
-
- FOR I = 1 TO TxtLength ' Isolate each character in
- C$ = MID$(Txt$, I, 1) ' the string.
-
- ' If the character is in the filter string, save it:
- IF INSTR(FilterString$, C$) <> 0 THEN
- Temp$ = Temp$ + C$
- END IF
- NEXT I
-
- Filter$ = Temp$
- END FUNCTION
-
-
-
- ────────────────────────────────────────────────────────────────────────────
-
- Chapter 5: Graphics
-
- This chapter shows you how to use the graphics statements and functions
- of Microsoft BASIC to create a wide variety of shapes, colors, and
- patterns on your screen. With graphics, you can add a visual dimension to
- almost any program, whether it's a game, an educational tool, a scientific
- application, or a financial package.
-
- When you have finished studying this chapter, you will know how to perform
- the following graphics tasks:
-
- ■ Use the physical-coordinate system of your personal computer's screen
- to locate individual pixels, turn those pixels on or off, and change
- their colors.
-
- ■ Draw lines.
-
- ■ Draw and fill simple shapes, such as circles, ovals, and boxes.
-
- ■ Restrict the area of the screen showing graphics output by using
- viewports.
-
- ■ Adjust the coordinates used to locate a pixel by redefining screen
- coordinates.
-
- ■ Use color in graphics output.
-
- ■ Create patterns and use them to fill enclosed figures.
-
- ■ Copy images and reproduce them in another location on the screen.
-
- ■ Animate graphics output.
-
-
- The next section contains important information on what you'll need to run
- the graphics examples shown in this chapter. Read it first.
-
-
- Note
-
- To learn how to use BASIC's new presentation graphics, see Chapter 6,
- "Presentation Graphics."
-
-
- What You Need for Graphics Programs
-
- To run the graphics examples shown in this chapter, your computer must have
- graphics capability, either built in or in the form of a graphics card such
- as the Color Graphics Adapter (CGA), Enhanced Graphics Adapter (EGA), or
- Video Graphics Array (VGA). You also need a video display (either monochrome
- or color) that supports pixel-based graphics.
-
- Also, these programs all require that your screen be in one of the
- "screen modes" supporting graphics output. (The screen mode controls
- the clarity of graphics images, the number of colors available, and the
- part of the video memory to display.) To select a graphics output mode,
- use the following statement in your program before using any of the
- graphics statements or functions described in this chapter:
-
-
- SCREEN mode%
- Here, mode% can be either 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, or 13, depending
- on the monitor/adapter combination installed on your computer.
-
- If you are not sure whether or not the users of your programs have hardware
- that supports graphics, you can use the following simple test:
-
- CONST FALSE = 0, TRUE = NOT FALSE
-
- ' Test to make sure user has hardware
- ' with color/graphics capability:
- ON ERROR GOTO Message ' Turn on error trapping.
- SCREEN 1 ' Try graphics mode one.
- ON ERROR GOTO 0 ' Turn off error trapping.
- IF NoGraphics THEN END ' End if no graphics hardware.
- .
- .
- .
- END
-
- ' Error-handling routine:
- Message:
- PRINT "This program requires graphics capability."
- NoGraphics = TRUE
- RESUME NEXT
-
- If the user has only a monochrome display with no graphics adapter, the
- SCREEN statement produces an error that in turn triggers a branch to the
- error-handling-routine message. (See Chapter 8, "Error Handling," for more
- information on error handling.)
-
-
- Pixels and Screen Coordinates
-
- Shapes and figures on a video display are composed of individual dots of
- light known as picture elements or "pixels" (or sometimes as "pels") for
- short. BASIC draws and paints on the screen by turning these pixels on or
- off and by changing their colors.
-
- A typical screen is composed of a grid of pixels. The exact number of
- pixels in this grid depends on the hardware you have installed and the
- screen mode you have selected in the SCREEN statement. The larger the
- number of pixels, the higher the clarity of graphics output. For example,
- SCREEN 1 gives a resolution of 320 pixels horizontally by 200 pixels
- vertically (320 x 200 pixels), while SCREEN 2 gives a resolution of
- 640 x 200 pixels. The higher horizontal density in screen mode 2 -- 640
- pixels per row versus 320 pixels per row -- gives images a sharper,
- less ragged appearance than they have in screen mode 1.
-
- Depending on the graphics capability of your system, you can use other
- screen modes that support even higher resolutions (as well as adjust other
- screen characteristics). Consult online Help for more information.
-
- When your screen is in one of the graphics modes, you can locate each pixel
- by means of pairs of coordinates. The first number in each coordinate pair
- tells the number of pixels from the left side of the screen, while the
- second number in each pair tells the number of pixels from the top of the
- screen. For example, in screen mode 2 the point in the extreme upper-left
- corner of the screen has coordinates (0, 0), the point in the center of the
- screen has coordinates (320, 100), and the point in the extreme lower-right
- corner of the screen has coordinates (639, 199), as shown in Figure 5.1.
-
- BASIC uses these screen coordinates to determine where to display
- graphics (for example, to locate the end points of a line or the center of a
- circle), as shown in the next section "Drawing Basic Shapes: Points, Lines,
- Boxes, and Circles."
-
-
- Graphics coordinates differ from text-mode coordinates specified in a
- LOCATE statement. First, LOCATE is not as
- precise: graphics coordinates pinpoint individual pixels on the screen,
- whereas coordinates used by LOCATE are character positions.
- Second, text-mode coordinates are given in the form "row, column," as in the
- following:
-
- ' Move to the 13th row, 15th column,
- ' then print the message shown:
- LOCATE 13, 15
- PRINT "This should appear in the middle of the screen."
- This is the reverse of graphics coordinates, which are given in the form
- "column, row." A LOCATE statement has no effect on the positioning of
- graphics output on the screen.
-
-
- Drawing Basic Shapes: Points, Lines, Boxes, and Circles
-
- You can pass coordinate values to BASIC graphics statements to produce a
- variety of simple figures, as shown in the following sections.
-
-
- Plotting Points with PSET and PRESET
-
- The most fundamental level of control over graphics output is simply turning
- individual pixels on and off. You do this in BASIC with the PSET (for pixel
- set) and PRESET (for pixel reset) statements. The statement PSET ( x%, y%
- ) gives the pixel at location ( x%, y%) the current foreground color. The
- PRESET ( x%, y% ) statement gives the pixel at location ( x%, y%) the curre
-
- On monochrome monitors, the foreground color is the color that is used for
- printed text and is typically white, amber, or light green; the background
- color on monochrome monitors is typically black or dark green. You can
- choose another color for PSET and PRESET to use by adding an optional
- color argument. The syntax is then:
-
- {
- PSET|PRESET} ( x%, y%), color%
-
- See the section "Using Colors" later in this chapter for more information
- on choosing colors.
-
- Because PSET uses the current foreground color by default and PRESET uses
- the current background color by default, PRESET without a color argument
- erases a point drawn with PSET, as shown in the next example:
-
- SCREEN 2' 640 x 200 resolution
- PRINT "Press any key to end."
-
- DO
-
- ' Draw a horizontal line from the left to the right:
- FOR X = 0 TO 640
- PSET (X, 100)
- NEXT
- ' Erase the line from
- the right to the left:
-
- FOR X = 640 TO 0 STEP -1
- PRESET (X, 100)
- NEXT
-
- LOOP UNTIL INKEY$ <> ""
- END
-
- While it is possible to draw any figure using only PSET statements to
- manipulate individual pixels, the output tends to be rather slow, since most
- pictures consist of many pixels. BASIC has several statements that
- dramatically increase the speed with which simple figures -- such as lines,
- boxes, and ellipses -- are drawn, as shown in the following sections
- "Drawing Lines and Boxes with LINE" and "Drawing Circles and Ellipses with
- CIRCLE."
-
-
- Drawing Lines and Boxes with LINE
-
- When using PSET or PRESET, you specify only one coordinate pair since you
- are dealing with only one point on the screen. With LINE, you specify two
- pairs, one for each end of a line segment. The simplest form of the LINE
- statement is as follows:
-
- LINE( x1%, y1%) - ( x2%, y2%)
-
- where ( x1%, y1%) are the coordinates of one
- end of a line segment and ( x2%, y2%) are the coordinates of the other. For
- example, the following statement draws a straight line from the pixel with
- coordinates (10, 10) to the pixel with coordinates (150, 200):
-
-
- SCREEN 1
- LINE (10, 10)-(150, 200)
-
- Note that BASIC does not care about the order of the coordinate pairs: a
- line from ( x1%, y1%) to ( x2%, y2%) is the same as a line from ( x2%,
- y2%) to ( x1%, y1%). This means you could also write the preceding
- statement as:
-
- SCREEN 1
- LINE (150, 200)-(10, 10)
-
- However, reversing the order of the coordinates could have an effect on
- graphics statements that follow, as shown in the next section.
-
-
- Using the STEP Keyword
-
- Up to this point, screen coordinates have been presented as absolute values
- measuring the horizontal and vertical distances from the extreme upper-left
- corner of the screen, which has coordinates (0, 0). However, by using the
- STEP keyword in any of the following graphics statements, you can make the
- coordinates that follow STEP relative to the last point referred to on the
- screen:
-
- CIRCLE PRESET
- GET PSET
- LINE PUT
- PAINT
-
-
- If you picture images as being drawn on the screen by a tiny paintbrush
- exactly the size of one pixel, then the last point referenced is the
- location of this paintbrush, or "graphics cursor," when it finishes drawing
- an image. For example, the following statements leave the graphics cursor at
- the pixel with coordinates (100, 150):
-
- SCREEN 2
- LINE (10, 10)-(100, 150)
-
- If the next graphics statement in the program is as follows, then the point
- plotted by PSET is not in the upper-left quadrant of the screen:
-
- PSET STEP(20, 20)
-
- Instead, STEP has made the coordinates (20, 20) relative to the last point
- referenced, which has coordinates (100, 150). This makes the absolute
- coordinates of the point (100 + 20, 150 + 20) or (120, 170).
-
-
- Example
-
- In the preceding example, the last point referenced is determined by a
- preceding graphics statement. You can also establish a reference point
- within the same statement, as shown in this example:
-
- ' Set 640 x 200 pixel resolution, and make the last
- ' point referenced the center of the screen:
- SCREEN 2
-
- ' Draw a line from the lower-left corner of the screen
- ' to the upper-left corner:
- LINE STEP(-310, 100) -STEP(0, -200)
-
- ' Draw the "stair steps" down from the upper-left corner
- ' to the right side of the screen:
- FOR I% = 1 TO 20
- LINE -STEP(30, 0)' Draw the horizontal line.
- LINE -STEP(0, 5)' Draw the vertical line.
- NEXT
-
- ' Draw the unconnected vertical line segments from the
- ' right side to the lower-left corner:
- FOR I% = 1 TO 20
- LINE STEP(-30, 0) -STEP(0, 5)
- NEXT
-
- SLEEP' Wait for a keystroke.
-
- Note the SLEEP statement at the end of the last program. If you are running
- a compiled, stand-alone BASIC program that produces graphics output, your
- program needs a mechanism like this at the end to hold the output on the
- screen. Otherwise, it vanishes from the screen before the user notices it.
-
-
- Drawing Boxes
-
- Using the forms of the LINE statement already presented, it is quite easy
- to write a short program that connects four straight lines to form a box, as
- shown here:
-
- SCREEN 1' 320 x 200 pixel resolution
-
- ' Draw a box measuring
- 120 pixels on a side:
-
- LINE (50, 50)-(170, 50)
- LINE -STEP(0, 120)
- LINE -STEP(-120, 0)
- LINE -STEP(0, -120)
- However, BASIC provides an even simpler way to draw a box, using a single
- LINE statement with the B (for box) option. The B option is shown in the
- next example, which produces the same output as the four LINE statements in
- the preceding program:
-
- SCREEN 1' 320 x 200 pixel resolution
-
- ' Draw a box with coordinates (50, 50) for the upper-left
- ' corner, and (170, 170) for the lower-right corner:
- LINE (50, 50)-(170, 170), , B
-
- When you add the B option, the LINE statement no longer connects the two
- points you specify with a straight line; instead, it draws a rectangle whose
- opposite corners (upper left and lower right) are at the locations
- specified.
-
- Two commas precede the B in the last example. The first comma functions
- here as a placeholder for the unused color& argument, which allows you to
- pick the color for a line or the sides of a box. (See the section "Using
- Colors" later in this chapter for more information on the use of color.)
-
- As before, it does not matter what order the coordinate pairs are given in,
- so the rectangle from the last example could also be drawn with this
- statement:
-
- LINE (170, 170)-(50, 50), , B
-
- Adding the F (for fill) option after B fills the interior of the box with
- the same color used to draw the sides. With a monochrome display, this is
- the same as the foreground color used for printed text. If your hardware has
- color capabilities, you can change this color with the optional color&
- argument (see the section "Selecting a Color for Graphics Output" later in
- this chapter).
-
- The syntax introduced here for drawing a box is the general syntax used in
- BASIC to define a rectangular graphics region, and it also appears in the
- GET and VIEW statements:
-
- GET | LINE | VIEW} ( x1!, y1!) - ( x2!, y2!),...
-
- Here, ( x1!, y1!) and (
- x2!, y2!) are the coordinates of diagonally opposite corners of the
- rectangle (upper left and lower right). (See "Defining a Graphics Viewport"
- later in this chapter for a discussion of VIEW, and "Basic Animation
- Techniques" later in this chapter for information on GET and PUT.)
-
-
-
- Drawing Dotted Lines
-
- The previous sections explain how to use LINE to draw solid lines and use
- them in rectangles; that is, no pixels are skipped. Using yet another option
- with LINE, you can draw dashed or dotted lines instead. This process is
- known as "line styling." The following is the syntax for drawing a single
- dashed line from point ( x1, y1) to point ( x2, y2) using the current for
-
- LINE ( x1!, y1!) - ( x2!, y2!),, B, style%
-
- Here style% is a 16-bit decimal or hexadecimal integer.
- The LINE statement uses the binary representation
- of the line-style argument to create dashes and blank spaces, with a 1 bit
- meaning "turn on the pixel," and a 0 bit meaning "leave the pixel off." For
- example, the hexadecimal integer &HCCCC is equal to the binary integer
- 1100110011001100, and when used as a style% argument it draws a line
- alternating two pixels on, two pixels off.
-
-
- Example
-
- The following example shows different dashed lines produced using different
- values for style%:
-
- SCREEN 2' 640 x 200 pixel resolution
-
- ' Style data:
- DATA &HCCCC, &HFF00, &HF0F0
- DATA &HF000, &H7777, &H8888
-
- Row% = 4
- Column% = 4
- XLeft% = 60
- XRight% = 600
- Y% = 28
-
- FOR I% = 1 TO 6
- READ Style%
- LOCATE Row%, Column%
- PRINT HEX$(Style%)
- LINE (XLeft%, Y%)-(XRight%,Y%), , , Style%
- Row% = Row% + 3
- Y% = Y% + 24
- NEXT
-
-
- Drawing Circles and Ellipses with CIRCLE
-
- The CIRCLE statement draws a variety of circular and elliptical, or oval,
- shapes. In addition, CIRCLE draws arcs (segments of circles) and pie-shaped
- wedges. In graphics mode you can produce just about any kind of curved line
- with some variation of CIRCLE.
-
-
- Drawing Circles
-
- To draw a circle, you need to know only two things: the location of its
- center and the length of its radius (the distance from the center to any
- point on the circle). With this information and a reasonably steady hand (or
- better yet, a compass), you can produce an attractive circle.
-
- Similarly, BASIC needs only the location of a circle's center and the length
- of its radius to draw a circle. The simplest form of the CIRCLE syntax is:
-
- CIRCLE STEP ( x!, y!), radius!
-
- In this statement, x!, y! are the coordinates
- of the center, and radius! is the radius of the circle. The
- next lines of code draw a circle with center (200, 100) and radius 75:
-
-
- SCREEN 2
- CIRCLE (200, 100), 75
-
- You could rewrite the preceding example as follows, making the same circle
- but using STEP to make the coordinates relative to the center
- rather than to the upper-left corner:
-
-
- SCREEN 2 ' Uses center of screen (320,100) as the
- ' reference point for the CIRCLE statement:
- CIRCLE STEP(-120, 0), 75
-
- Drawing Ellipses
-
- The CIRCLE statement automatically adjusts the "aspect ratio" to make sure
- that circles appear round and not flattened on your screen. However, you may
- need to adjust the aspect ratio to make circles come out right on your
- monitor, or you may want to change the aspect ratio to draw the oval figure
- known as an ellipse. In either case, use this syntax:
-
- CIRCLE STEP ( x!, y!), radius!,,,, aspect!
-
- Here, aspect! is a positive real number. (See "Drawing Shapes\
- to Proportion with the Aspect Ratio" later in this chapter for more
- information on the aspect ratio and how to calculate it for different
- screen modes.)
-
- The extra commas between radius! and aspect! are placeholders for other
- options that tell CIRCLE which color to use (if you have a color
- monitor/adapter and are using one of the screen modes that support color),
- or whether to draw an arc or wedge. (See the sections "Drawing Arcs" and
- "Selecting a Color for Graphics Output" later in this chapter for more
- information on these options.)
-
- Since the argument aspect! specifies the ratio of the vertical to
- horizontal dimensions, large values for aspect! produce ellipses stretched
- out along the vertical axis, while small values for aspect! produce
- ellipses stretched out along the horizontal axis. Since an ellipse has two
- radii -- one horizontal x-radius and one vertical y-radius -- BASIC uses the
- single radius! argument in a CIRCLE statement as follows: if aspect! is
- less than one, then radius! is the x-radius; if aspect! is greater than or
- equal to one, then radius! is the y-radius.
-
-
- Example
-
- The following example and its output show how different aspect! values
- affect whether the CIRCLE statement uses the radius! argument as the
- x-radius or the y-radius of an ellipse:
-
- SCREEN 1
-
- ' This draws the ellipse on the left:
- CIRCLE (60, 100), 80, , , , 3
-
- ' This draws the ellipse on the right:
- CIRCLE (180, 100), 80, , , , 3/10
-
-
- Drawing Arcs
-
- An arc is a segment of a ellipse, in other words a short, curved line. To
- understand how the CIRCLE statement draws arcs, you need to know how BASIC
- measures angles.
-
- BASIC uses the radian as its unit of angle measure, not only in the CIRCLE
- statement, but also in the intrinsic trigonometric functions such as COS,
- SIN, or TAN. (The one exception to this use of radians is the DRAW
- statement, which expects angle measurements in degrees. See "DRAW: a
- Graphics Macro Language" later in this chapter for more information about
- DRAW.)
-
- The radian is closely related to the radius of a circle. In fact, the word
- "radian" is derived from the word "radius." The circumference of a circle
- equals 2 * * radius, where is equal to approximately 3.14159265.
- Similarly, the number of radians in one complete angle of revolution (or
- 360) equals 2 * , or a little more than 6.28.
-
- If you are more used to thinking of angles in terms of degrees, here
- are some common equivalences:
-
-
- 360 2(pi) (approximately 6.283)
- 180 (pi) (approximately 3.142)
- 90 (pi)/2 (approximately 1.571)
- 60 (pi)/3 (approximately 1.047)
-
-
- If you picture a clock face on the screen, CIRCLE measures angles by
- starting at the three o'clock position and rotating counterclockwise, as
- shown in Figure 5.2:
-
- The general formula for converting from degrees to radians is to multiply
- degrees by (pi)/180.
-
- To draw an arc, give angle arguments defining the arc's limits:
-
- CIRCLE STEP( x!, y!), radius!, color&, start!, end! , aspect!
-
- The CIRCLE statements in the next example draw seven arcs, with the
- innermost arc starting at the three o'clock position (0 radians) and the
- outermost arc starting at the six o'clock position (3/2 radians), as you can
- see from the output:
-
- SCREEN 2
- CLS
-
- CONST PI = 3.141592653589# ' Double-precision constant
-
- StartAngle = 0
- FOR Radius% = 100 TO 220 STEP 20
- EndAngle = StartAngle + (PI / 2.01)
- CIRCLE (320, 100), Radius%, , StartAngle, EndAngle
- StartAngle = StartAngle + (PI / 4)
- NEXT Radius%
-
- %
- Drawing Pie Shapes
-
- By making either of CIRCLE's start! or end! arguments negative, you can
- connect the arc at its beginning or ending point with the center of the
- circle. By making both arguments negative, you can draw shapes ranging from
- a wedge that resembles a slice of pie to the pie itself with the piece
- missing.
-
- Example
-
- This example code draws a pie shape with a piece missing:
-
- SCREEN 2
-
- CONST RADIUS = 150, PI = 3.141592653589#
-
- StartAngle = 2.5
- EndAngle = PI
-
- ' Draw the wedge:
- CIRCLE (320, 100), RADIUS, , -StartAngle, -EndAngle
-
- ' Swap the values for the start and end angles:
- SWAP StartAngle, EndAngle
-
- ' Move the center 10 pixels down and 70 pixels to the
- ' right, then draw the "pie" with the wedge missing:
- CIRCLE STEP(70, 10), RADIUS, , -StartAngle, -EndAngle
-
-
- Drawing Shapes to Proportion with the Aspect Ratio
-
- As discussed earlier in "Drawing Ellipses," BASIC's CIRCLE statement
- automatically corrects the aspect ratio, which determines how figures are
- scaled on the screen. However, with other graphics statements you need to
- scale horizontal and vertical dimensions yourself to make shapes appear with
- correct proportions. For example, although the following statement draws a
- rectangle that measures 100 pixels on all sides, it does not look like a
- square:
-
- SCREEN 1
- LINE (0, 0)-(100, 100), , B
-
- In fact, this is not an optical illusion; the rectangle really is taller
- than it is wide. This is because in screen mode 1 there is more space
- between pixels vertically than horizontally. To draw a perfect square, you
- have to change the aspect ratio.
-
- The aspect ratio is defined as follows: in a given screen mode consider two
- lines, one vertical and one horizontal, that appear to have the same length.
- The aspect ratio is the number of pixels in the vertical line divided by the
- number of pixels in the horizontal line. This ratio depends on two factors:
-
- * Because of the way pixels are spaced on most screens, a horizontal row
- has more pixels than a vertical column of the exact same physical
- length in all screen modes except modes 11 and 12.
-
- * The standard personal computer's video-display screen is wider than it
- is high. Typically, the ratio of screen width to screen height is 4:3.
-
- To see how these two factors interact to produce the aspect ratio, consider
- a screen after a SCREEN 1 statement, which gives a resolution of 320 x 200
- pixels. If you draw a rectangle from the top of the screen to the bottom,
- and from the left side of the screen three-fourths of the way across, you
- have a square, as shown in Figure 5.3.
-
- As you can see from the diagram, this square has a height of 200 pixels
- and a width of 240 pixels. The ratio of the square's height to its
- width (200 / 240 or, when simplified, 5 / 6) is the aspect ratio for this
- screen resolution. In other words, to draw a square in 320 x 200
- resolution, make its height in pixels equal to 5 / 6 times its width
- in pixels, as shown in the next example:
-
-
- SCREEN 1' 320 x 200 pixel resolution
- ' The height of this box is 100 pixels, and the width is
- ' 120 pixels, which makes the ratio of the height to the
- ' width equal to 100/120, or 5/6. The result is a square:
- LINE (50, 50) -STEP(120, 100), , B
-
- The formula for calculating the aspect ratio for a given screen mode is:
-
- (4 / 3) * ( ypixels / xpixels)
-
- In this formula, xpixels by ypixels is the current
- screen resolution. In screen mode 1, this formula shows the aspect ratio to
- be (4 / 3) * (200 / 320), or 5 / 6; in screen mode 2, the aspect ratio is (4
- / 3) * (200 / 640), or 5 / 12.
-
- If you have a video display with a ratio of width to height that is not
- equal to 4:3, use the more general form of the formula for computing the
- aspect ratio:
-
- ( screenwidth / screenheight) * ( ypixels / xpixels)
-
- The CIRCLE statement can be made to draw an ellipse by
- varying the value of the aspect argument, as shown in
- "Drawing Ellipses" earlier in this chapter.
-
-
- Defining a Graphics Viewport
-
- The graphics examples presented so far have all used the entire
- video-display screen as their drawing board, with absolute coordinate
- distances measured from the extreme upper-left corner of the screen.
-
- However, using the VIEW statement you can also define a kind of miniature
- screen (known as a "graphics viewport") inside the boundaries of the
- physical screen. Once it is defined, all graphics operations take place
- within this viewport. Any graphics output outside the viewport boundaries is
- "clipped"; that is, any attempt to plot a point outside the viewport is
- ignored. There are two main advantages to using a viewport:
-
- * A viewport makes it easy to change the size and position of the screen
- area where graphics appear.
-
- * Using CLS 1, you can clear the screen inside a viewport without
- disturbing the screen outside the viewport.
-
-
- Note
-
- Refer to Chapter 3, "File and Device I/O," to learn how to create a "text
- viewport" for output printed on the screen.
-
- The general syntax for VIEW is as follows:
- VIEW [[SCREEN] ( x1!, y1!) - ( x2!, y2!) , color& , border&
-
- The coordinates (x1 , y1 ) and ( x2 , y2 ) define the corners of the view
- standard BASIC syntax for rectangles (see the section "Drawing Boxes"
- earlier in this chapter). Note that the STEP option is not allowed with
- VIEW. The optional color& and border& arguments allow you to choose a
- color for the interior and edges, respectively, of the viewport rectangle.
- See the section "Using Colors" later in this chapter for more information on
- setting and changing colors.
-
- The VIEW statement without arguments makes the entire screen the viewport.
- Without the SCREEN option, the VIEW statement makes all pixel coordinates
- relative to the viewport, rather than the full screen. In other words, after
- the following VIEW statement, the pixel plotted with the PSET statement is
- visible, since it is 10 pixels below and 10 pixels to the right of the
- upper-left corner of the viewport:
-
- VIEW (50, 60)-(150, 175)
- PSET (10, 10)
-
- Note that this makes the pixel's absolute screen coordinates (50 + 10, 60 +
- 10) or (60, 70).
-
- In contrast, the VIEW statement with the SCREEN
- option keeps all coordinates absolute; that is, coordinates measure
- distances from the side of the screen, not from the sides of the viewport.
-
- Therefore, after the following VIEW SCREEN statement the pixel plotted with
- the PSET is not visible, since it is 10 pixels below and 10 pixels to the
- right of the upper-left corner of the screen -- outside the viewport:
-
- VIEW SCREEN (50, 60)-(150, 175)
- PSET (10, 10)
-
- Examples
-
- The output from the next two examples should further clarify the distinction
- between VIEW and VIEW SCREEN:
-
- SCREEN 2
-
- VIEW (100, 50)-(450, 150), , 1
-
- ' This circle's center point has absolute coordinates
- ' (100 + 100, 50 + 50), or (200, 100):
- CIRCLE (100, 50), 50
-
-
- SCREEN 2
-
- ' This circle's center point has absolute coordinates (100, 50),
- ' so only part of the circle appears within the viewport:
- VIEW SCREEN (100, 50)-(450, 150), , 1
- CIRCLE (100, 50), 50
-
- Note that graphics output located outside the current viewport is
- clipped by the viewport's edges and does not appear on the screen.
-
-
- Redefining Viewport Coordinates with WINDOW
-
- This section shows you how to use the WINDOW statement and your own
- coordinate system to redefine pixel coordinates.
-
- In the preceding sections, the coordinates used to locate pixels on the
- screen represent actual physical distances from the upper-left corner of the
- screen (or the upper-left corner of the current viewport, if it has been
- defined with a VIEW statement). These are known as "physical coordinates."
- The "origin," or reference point, for physical coordinates is always the
- upper-left corner of the screen or viewport, which has coordinates (0, 0).
-
- As you move down the screen and to the right, x values (horizontal
- coordinates) and y values (vertical coordinates) get bigger, as shown in
- the upper diagram of Figure 5.4. While this scheme is standard for video
- displays, it may seem counterintuitive if you have used other coordinate
- systems to draw graphs. For example, on the Cartesian grid used in
- mathematics, y values get bigger toward the top of a graph and smaller
- toward the bottom.
-
- With BASIC's WINDOW statement, you can change the way pixels are addressed
- to use any coordinate system you choose, thus freeing you from the
- limitations of using physical coordinates.
-
- The general syntax for WINDOW is as follows:
-
- WINDOW SCREEN ( x1!, y1!) - ( x2!,y2!)
-
- The coordinates y1!, y2!, x1!, and x2! are real numbers specifying the top
- and right sides of the window, respectively. These numbers are known as
- "window coordinates." For example, the following statement remaps your
- screen so that it is bounded on the top and bottom by the lines y = 10 and y
- = -15 and on the left and right by the lines
-
-
- x = -25 and x = 5:
-
- WINDOW (-25, -15)-(5, 10)
-
- After a WINDOW statement, y values get bigger toward the top of the
- screen. In contrast, after a WINDOW SCREEN statement, y values get bigger
- toward the bottom of the screen. Figure 5.4 shows the effects of a WINDOW
- statement and a WINDOW SCREEN statement on a line drawn in screen mode 2.
- Note also how both of these statements change the coordinates of the screen
- corners. A WINDOW statement with no arguments restores the regular physical
- coordinate system.
-
- The following example uses VIEW and WINDOW to simplify writing a program
- to graph the sine-wave function for angle values from 0 radians to radians
- (or 0 to 180). This program is in the file named SINEWAVE.BAS on the
- Microsoft BASIC distribution disks.
-
- SCREEN 2
-
- ' Viewport sized to proper scale for graph:
- VIEW (20, 2)-(620, 172), , 1
- CONST PI = 3.141592653589#
-
- ' Make window large enough to graph sine wave from
- ' 0 radians to pi radians:
- WINDOW (0, -1.1)-(2 * PI, 1.1)
- Style% = &HFF00 ' Use to make dashed line.
- VIEW PRINT 23 TO 24 ' Scroll printed output in rows 23, 24.
- DO
- PRINT TAB(20);
- INPUT "Number of cycles (0 to end): ", Cycles
- CLS
- LINE (2 * PI, 0)-(0, 0), , , Style% ' Draw the x-axis.
- IF Cycles > 0 THEN
-
- ' Start at (0,0) and plot the graph:
- FOR X = 0 TO 2 * PI STEP .01
- Y = SIN(Cycles * X) ' Calculate the y-coordinate.
- LINE -(X, Y) ' Draw a line to new point.
- NEXT X
- END IF
- LOOP WHILE Cycles > 0
-
-
- The Order of Coordinate Pairs
-
- As with the other BASIC graphics statements that define rectangular areas (
- GET, LINE, and VIEW), the order of coordinate pairs in a WINDOW statement
- is unimportant. In the following example, the first pair of statements has
- the same effect as the second pair of statements:
-
- VIEW (100, 20)-(300, 120)
- WINDOW (-4, -3)-(0, 0)
-
- VIEW (300, 120)-(100, 20)
- WINDOW (0, 0)-(-4, -3)
-
-
- Keeping Track of Window and Physical Coordinates
-
- The PMAP and POINT functions are useful for keeping track of physical and
- view coordinates. POINT( number%) tells you the current location of the
- graphics cursor by returning either the physical or view coordinates
- (depending on the value for number%) of the last point referenced in a
- graphics statement. PMAP allows you to translate physical coordinates to
- view coordinates and vice versa. The physical coordinate values returned by
- PMAP are always relative to the current viewport.
-
-
- Examples
-
- The following example shows the different values that are returned by POINT
- ( number%) for number% values of 0, 1, 2, or 3:
-
- SCREEN 2
- ' Define the window:
- WINDOW (-10, -30)-(-5, -10)
- ' Draw a line from the point with window coordinates (-9,-28)
- ' to the point with window coordinates (-6,-24):
- LINE (-9, -28)-(-6, -24)
-
- PRINT "Physical x-coordinate of the last point = " POINT(0)
- PRINT "Physical y-coordinate of the last point = " POINT(1)
- PRINT
- PRINT "Window x-coordinate of the last point = " POINT(2)
- PRINT "Window y-coordinate of the last point = " POINT(3)
-
- END
-
- Output
- Physical x-coordinate of the last point = 511
- Physical y-coordinate of the last point = 139
-
- Window x-coordinate of the last point = -6
- Window y-coordinate of the last point = -24
-
- Given the WINDOW statement in the preceding example, the
- next four PMAP statements would print the output that
- follows:
-
-
- ' Map the window x-coordinate -6 to physical x and print:
- PhysX% = PMAP(-6, 0)
- PRINT PhysX%
-
- ' Map the window y-coordinate -24 to physical y and print:
- PhysY% = PMAP(-24, 1)
- PRINT PhysY%
-
- ' Map physical x back to view x and print:
- WindowX% = PMAP(PhysX%, 2)
- PRINT WindowX%
-
- ' Map physical y back to view y and print:
- WindowY% = PMAP(PhysY%, 3)
- PRINT WindowY%
-
- Output
-
- 511
- 139
- -6
- -24
-
- Using Colors
-
- If you have a Color Graphics Adapter (CGA), you can choose between the
- following two graphics modes only:
-
- * Screen mode 2 has 640 x 200 high resolution with only one foreground
- and one background color. This is known as "monochrome," since all
- graphics output has the same color.
-
- * Screen mode 1 has 320 x 200 medium resolution with 4 foreground colors
- and 16 background colors.
-
- There is a tradeoff between color and clarity in the two screen modes
- supported by most color-graphics display adapter hardware. Depending on the
- graphics capability of your system, you may not have to sacrifice clarity to
- get a full range of color. However, this section focuses on screen modes 1
- and 2.
-
-
- Selecting a Color for Graphics Output
-
- The following list shows where to put the color argument in the graphics
- statements discussed in previous sections of this chapter. This list also
- shows other options (such as BF with the LINE statement or border with
- the VIEW statement) that can have a different colors. (Please note that
- these do not give the complete syntax for some of these statements. This
- summary is intended to show how to use the color option in those statements
- that accept it.)
-
- PSET ( x%, y%), color&
- PRESET ( x%, y%), color&
- LINE ( x1!, y1!) \~( x2!, y2!), color&, B F
- CIRCLE ( x! , y!), radius!, color&
- VIEW ( x1!, y1!) \~( x2!, y2!), color&, border&
-
- In screen mode 1, the color argument is a numeric expression with the value 0
- these values, known as an "attribute," represents a different color, as
- demonstrated by the following program:
-
- ' Draw an "invisible" line (same color as background):
- LINE (10, 10)-(310, 10), 0
-
- ' Draw a light blue (cyan) line:
- LINE (10, 30)-(310, 30), 1
-
- ' Draw a purple (magenta) line:
- LINE (10, 50)-(310, 50), 2
-
- ' Draw a white line:
- LINE (10, 70)-(310, 70), 3
- END
-
- As noted in the comments for the preceding example, a color& value of 0
- produces no visible output, since it is always equal to the current
- background color. At first glance, this may not seem like such a useful
- color value, but, in fact, it is useful for erasing a figure without having
- to clear the entire screen or viewport, as shown in the next example:
-
- SCREEN 1
-
- CIRCLE (100, 100), 80, 2, , , 3 ' Draw an ellipse.
- Pause$ = INPUT$(1) ' Wait for a key press.
- CIRCLE (100, 100), 80, 0, , , 3 ' Erase the ellipse.
-
-
- Changing the Foreground or Background Color
-
- The preceding section describes how to use four different foreground colors
- for graphics output. You have a wider variety of colors in screen mode 1 for
- the screen's background: 16 in all.
-
- In addition, you can change the foreground color by using a different
- "palette." In screen mode 1, there are two palettes, or groups of four
- colors. Each palette assigns a different color to the same attribute; so,
- for instance, in palette 1 (the default) the color associated with
- attribute 2 is magenta, while in palette 0 the color associated with
- attribute 2 is red. If you have a CGA, these colors are predetermined for
- each palette; that is, the color assigned to number 2 in palette 1 is
- always magenta, while the color assigned to number 2 in palette 0 is
- always red.
-
- If you have an Enhanced Graphics Adapter (EGA) or Video Graphics Array
- (VGA), you can use the PALETTE statement to choose the color displayed by
- any attribute. For example, by changing arguments in a PALETTE statement,
- you could make the color displayed by attribute 1 green one time and brown
- the next. (See "Changing Colors with PALETTE and PALETTE USING" later in
- this chapter for more information on reassigning colors.)
-
- In screen mode 1, the COLOR statement allows you to control the background
- color and the palette for the foreground colors. Here is the syntax for
- COLOR in screen mode 1:
-
- COLOR background& , palette%The background& argument is a numeric
- expression from 0 to 15, and palette% is a numeric expression equal to
- either 0 or 1.
-
- The following program shows all combinations of the two color palettes with
- the 16 different background screen colors. This program is in the file named
- COLORS.BAS on the Microsoft BASIC distribution disks.
-
- SCREEN 1
-
- Esc$ = CHR$(27)
- ' Draw three boxes and paint the interior
- ' of each box with a different color:
- FOR ColorVal = 1 TO 3
- LINE (X, Y) -STEP(60, 50), ColorVal, BF
- X = X + 61
- Y = Y + 51
- NEXT ColorVal
-
- LOCATE 21, 1
- PRINT "Press Esc to end."
- PRINT "Press any other key to continue."
-
- ' Restrict additional printed output to the 23rd line:
- VIEW PRINT 23 TO 23
- DO
- PaletteVal = 1
- DO
- ' PaletteVal is either 1 or 0:
- PaletteVal = 1 - PaletteVal
-
- ' Set the background color and choose the palette:
- COLOR BackGroundVal, PaletteVal
- PRINT "Background ="; BackGroundVal;
- PRINT "Palette ="; PaletteVal;
-
- Pause$ = INPUT$(1) ' Wait for a keystroke.
- PRINT
- ' Exit the loop if both palettes have been shown,
- ' or if the user pressed the Esc key:
- LOOP UNTIL PaletteVal = 1 OR Pause$ = Esc$
-
- BackGroundVal = BackGroundVal + 1
-
- ' Exit this loop if all 16 background colors have
- ' been shown, or if the user pressed the Esc key:
- LOOP UNTIL BackGroundVal > 15 OR Pause$ = Esc$
-
- SCREEN 0 ' Restore text mode and
- WIDTH 80 ' 80-column screen width.
-
-
- Changing Colors with PALETTE and PALETTE USING
-
- The preceding section shows how you can change the color displayed by an
- attribute simply by specifying a different palette in the COLOR statement.
- However, this restricts you to two fixed color palettes, with just four
- colors in each. Furthermore, each attribute can stand for only one of two
- possible colors; for example, attribute 1 can signify only green or cyan.
-
- With an EGA or VGA, your choices are potentially much broader. (If you don't
- have an EGA or VGA, you may want to skip this section.) For instance,
- depending on the amount of video memory available to your computer, with a
- VGA you can choose from a palette with as many as 256,000 colors and assign
- those colors to 256 different attributes. Even an EGA allows you to display
- up to 16 different colors from a palette of 64 colors.
-
- In contrast to the COLOR statement, the PALETTE and PALETTE USING
- statements give you a lot more flexibility in manipulating the available
- color palette. Using these statements, you can assign any color from the
- palette to any attribute. For example, after the following statement, the
- output of all graphics statements using attribute 4 appears in light magenta
- (color 13):
-
- PALETTE 4, 13
-
- This color change is instantaneous and affects not only subsequent
- graphics statements but any output already on the screen. In other words,
- you can draw and paint your screen, then switch the palette to achieve
- an immediate change of color, as shown by the following example:
-
- SCREEN 8
- LINE (50, 50)-(150, 150), 4 ' Draws a line in red.
- SLEEP 1 ' Pauses program.
- PALETTE 4, 13 ' Attribute 4 now means color
- ' 13, so the line drawn in the
- ' last statement is now light
- ' magenta.
-
- With the PALETTE statement's USING option, you can change the colors
- assigned to every attribute all at once.
-
- Example
-
- In the following example, the PALETTE USING statement gives the illusion of
- movement on the screen by constantly rotating the colors displayed by
- attributes 1 through 15. This program is in the file named PALETTE.BAS on
- the Microsoft BASIC distribution disks.
-
- DECLARE SUB InitPalette ()
-
- DECLARESUB ChangePalette ()
- DECLARESUB DrawEllipses ()
-
- DEFINT A-Z
- DIM SHARED PaletteArray(15)
-
- SCREEN 8' 640 x 200 resolution; 16 colors
-
- InitPalette' Initialize PaletteArray.
- DrawEllipses' Draw and paint concentric ellipses.
-
- DO' Shift the palette until a key
- ChangePalette' is pressed.
- LOOP WHILE INKEY$ = ""
-
- END
-
-
- ' ====================== InitPalette ======================
- ' This procedure initializes the integer array used to
- ' change the palette.
- ' =========================================================
-
- SUB InitPaletteSTATIC
- FOR I = 0 TO15
- PaletteArray(I) =I
- NEXTI
- END SUB
- ' =====================DrawEllipses ======================
- ' This procedure draws 15 concentric ellipses and
- ' paints the interior of each with a different color.
- ' =========================================================
-
- SUB DrawEllipses STATIC
- CONST ASPECT= 1 / 3
- FOR ColorVal= 15 TO1 STEP -1
- Radius = 20 * ColorVal
- CIRCLE (320, 100), Radius, ColorVal, , , ASPECT
- PAINT (320, 100),ColorVal
- NEXT
- END SUB
-
-
- ' ===================== ChangePalette =====================
- ' This procedure rotates the palette by one each time it
- ' is called. For example, after the first call to
- ' ChangePalette, PaletteArray(1) = 2, PaletteArray(2) = 3,
- ' . . . , PaletteArray(14) = 15, and PaletteArray(15) = 1
- ' =========================================================
-
- SUB ChangePalette STATIC
- FOR I = 1 TO15
- PaletteArray(I) =(PaletteArray(I) MOD 15) + 1
- NEXTI
- PALETTE USING PaletteArray(0) ' Shift the color displayed
- ' by each of the attributes.
- END SUB
-
-
- Painting Shapes
-
- The section "Drawing Boxes" earlier in this chapter shows how to draw a box
- with the LINE statement's B option, then paint the box by appending the F
- (for fill) option:
-
- SCREEN 1
-
- ' Draw a square, then paint the interior with color 1
- ' (cyan in the default palette):
- LINE (50, 50)-(110, 100), 1, BF
-
- With BASIC's PAINT statement, you can fill any enclosed figure with any
- color you choose. PAINT also allows you to fill enclosed figures with your
- own custom patterns, such as stripes or checks, as shown in "Painting with
- Patterns: Tiling" later in this chapter.
-
-
- Painting with Colors
-
- To paint an enclosed shape with a solid color, use this form of the PAINT
- statement:
-
- PAINT STEP( x!, y!) , paint, bordercolor&
-
- Here, x!, y! are the coordinates of a point in
- the interior of the figure you want to paint, paint is the number
- for the color you want to paint with, and bordercolor& is
- the color number for the outline of the figure.
-
- For example, the following program lines draw a circle in cyan, then paint
- the inside of the circle magenta:
-
- SCREEN 1
- CIRCLE (160, 100), 50, 1
- PAINT (160, 100), 2, 1
-
- The following three rules apply when painting figures:
-
- For example, any one of the following statements would have the same
- effect as the PAINT statements shown in the two preceding examples, since
- all of the coordinates identify points in the interior of the circle:
-
-
- PAINT (150, 90), 2, 1
- PAINT (170, 110), 2, 1
- PAINT (180, 80), 2, 1
-
- In contrast, since (5, 5) identifies a point outside the circle, the next
- statement would paint all of the screen except the inside of the circle,
- leaving it colored with the current background color:
-
- PAINT (5, 5), 2, 1
-
- If the coordinates in a PAINT statement specify a point
- right on the border of the figure, then no painting occurs:
-
- The figure must be completely enclosed; otherwise, the paint color
- will "leak out," filling the entire screen or viewport (or any larger
- figure completely enclosing the first one).
-
- For example, in the following program, the CIRCLE statement draws an
- ellipse that is not quite complete (there is a small gap on the right
- side); the LINE statement then encloses the partial ellipse inside a
- box. Even though painting starts in the interior of the ellipse, the
- paint color flows through the gap and fills the entire box.
-
-
- SCREEN 2
- CONST PI = 3.141592653589#
- CIRCLE (300, 100), 80, , 0, 1.9 * PI, 3
- LINE (200, 10)-(400, 190), , B
- PAINT (300, 100)
-
- If you are painting an object a different color from the one used to
- outline the object, you must use the bordercolor& option to tell
- PAINT where to stop painting.
-
- For example, the following program draws a triangle outlined in green
- (attribute 1 in palette 0) and then tries to paint the interior of the
- triangle red (attribute 2). However, since the PAINT statement
- doesn't indicate where to stop painting, it paints the entire screen
- red.
-
-
- SCREEN 1
- COLOR , 0
- LINE (10, 25)-(310, 25), 1
- LINE -(160, 175), 1
- LINE -(10, 25), 1
- PAINT (160, 100), 2
-
- Making the following change to the PAINT statement (choose red for the
- interior and stop when a border colored green is reached) produces the
- desired effect:
-
-
- PAINT (160, 100), 2, 1
-
- Note that you don't have to specify a border color in the PAINT
- statement if the paint color is the same as the border color.
-
-
- LINE (10, 25)-(310, 25), 1
- LINE -(160, 175), 1
- LINE -(10, 25), 1
- PAINT (160, 100), 1
-
-
-
- Painting with Patterns: Tiling
-
- You can use the PAINT statement to fill any enclosed figure with a pattern;
- this process is known as "tiling." A "tile" is the pattern's basic building
- block. The process is identical to laying down tiles on a floor. When you
- use tiling, the argument paint in the syntax for PAINT is a string
- expression, rather than a number. While paint can be any string expression,
- a convenient way to define tile patterns uses the following form for paint:
-
- CHR$( code1%)+ CHR$( code2%)+ CHR$( code3%)+...+ CHR$( coden%)
-
- Here, code1%, code2%, and so forth are 8-bit integers. See the following sec
- for an explanation of how these 8-bit integers are derived.
-
-
-
- Pattern-Tile Size in Different Screen Modes
-
- Each tile for a pattern is composed of a rectangular grid of pixels. This
- tile grid can have up to 64 rows in all screen modes. However, the number of
- pixels in each row depends on the screen mode.
-
- The reason the length of each tile row varies according to the screen mode
- is because, although the number of bits in each row is fixed at 8 (the
- length of an integer), the number of pixels these 8 bits can represent
- decreases as the number of color attributes in a given screen mode
- increases. For example, in screen mode 2, which has only one color
- attribute, the number of bits per pixel is 1; in screen mode 1, which has
- four different attributes, the number of bits per pixel is 2; and in the EGA
- screen mode 7, which has 16 attributes, the number of bits per pixel is 4.
- The following formula allows you to compute the bits per pixel in any given
- screen mode:
-
- bits-per-pixel = log2( numattributes)Here, numattributes is the number of
- color attributes in that screen mode. (Online Help has this information.)
-
- Thus, the length of a tile row is 8 pixels in screen mode 2 (8 bits divided
- by 1 bit per pixel), but only 4 pixels in screen mode 1 (8 bits divided by 2
- bits per pixel).
-
- The next three sections show the step-by-step process involved in creating a
- pattern tile. The following section shows how to make a monochrome pattern
- in screen mode 2. The section after that shows how to make a multicolored
- pattern in screen mode 1. Finally, if you have an EGA, read the third
- section to see how to make a multicolored pattern in screen mode 8.
-
-
- Creating a Single-Color Pattern in Screen Mode 2
-
- The following steps show how to define and use a pattern tile that resembles
- the letter "M":
-
- 1. Draw the pattern for a tile in a grid with eight columns and however
- many rows you need (up to 64). In this example, the tile has six rows;
- an asterisk (*) in a box means the pixel is on:
-
- 2. Next, translate each row of pixels to an 8-bit number, with a one
- meaning the pixel is on, and a zero meaning the pixel is off:
-
- 3. Convert the binary numbers given in step 2 to hexadecimal integers:
- 10000100 = &H84
- 11001100 = &HCC
- 10110100 = &HB4
- 10000100 = &H84
- 10000100 = &H84
- 00000000 = &H00
-
- These integers do not have to be hexadecimal; they could be decimal or
- octal. However, binary to hexadecimal conversion is easier. To convert
- from binary to hexadecimal, read the binary number from right to left.
- Each group of four digits is then converted to its hexadecimal equivalent,
- as shown here:
-
- Table 5.3 lists 4-bit binary sequences and their hexadecimal equivalents.
-
- 4. Create a string by concatenating the characters with the ASCII values
- from step 3 (use the CHR$ function to get these characters):
-
- Tile$ = CHR$(&H84) + CHR$(&HCC) + CHR$(&HB4)
- Tile$ = Tile$ + CHR$(&H84) + CHR$(&H84) + CHR$(&H00)
-
- 5. Draw a figure and paint its interior using PAINT and the string
- argument from step 4:
-
- PAINT (X, Y), Tile$0001 1 0010 2 0011 3 0100 4 0101 5 0110 6
- 1000 8 10019 1010 A
- 1011 B 1100 C 1101 D 1110 E 1111 F Example
-
- The following example draws a circle and then paints the circle's interior
- with the pattern created in the preceding steps:
-
- SCREEN 2
- CLS
- Tile$ = CHR$(&H84) + CHR$(&HCC) + CHR$(&HB4)
- Tile$ = Tile$ + CHR$(&H84) + CHR$(&H84) + CHR$(&H00)
- CIRCLE STEP(0, 0), 150
- PAINT STEP(0, 0), Tile$
-
-
- Creating a Multicolor Pattern in Screen Mode 1
-
- The following steps show how to create a multicolor pattern consisting of
- alternating diagonal stripes of cyan and magenta (or green and red in
- palette 0):
-
- 1. Draw the pattern for a tile in a grid with four columns (four columns
- because each row of pixels is stored in an 8-bit integer and each
- pixel in screen mode 1 requires 2 bits) and however many rows you need
- (up to 64). In this example, the tile has four rows, as shown in the
- next diagram:
-
- 2. Convert the colors to their respective color numbers in binary
- notation, as shown by the following (be sure to use 2-bit values, so
- that 1 = binary 01 and 2 = binary 10):
-
- 3. Convert the binary numbers from step 2 to hexadecimal integers:
- 01101010 = &H6A
- 10011010 = &H9A
- 10100110 = &HA6
- 10101001 = &HA9
-
- 4. Create a string by concatenating the characters with the ASCII values
- from step 3 (use the CHR$ function to get these characters):
-
- Tile$ = CHR$(&H6A) + CHR$(&H9A) + CHR$(&HA6) + CHR$(&HA9) 5.
-
- Draw a figure and paint its interior using PAINT and the string
- argument from step 4:
-
- PAINT (X, Y), Tile$
-
- The following program draws a triangle and then paints its interior
- with the pattern created in the preceding steps:
-
-
- SCREEN 1
-
- ' Define a pattern:
- Tile$ = CHR$(&H6A) + CHR$(&H9A) + CHR$(&HA6) + CHR$(&HA9)
-
- ' Draw a triangle in
- white (color 3):
- LINE (10, 25)-(310, 25)
- LINE -(160, 175)
- LINE -(10, 25)
-
- ' Paint the interior of the triangle with the pattern:
- PAINT (160, 100), Tile$
-
- Note that if the figure you want to paint is outlined in a color that is
- also contained in the pattern, you must give the bordercolor& argument with
- PAINT as shown by the following example; otherwise, the pattern spills over
- the edges of the figure:
-
- SCREEN 1
-
- ' Define a pattern:
- Tile$ = CHR$(&H6A) + CHR$(&H9A) + CHR$(&HA6) + CHR$(&HA9)
-
- ' Draw a triangle in magenta (color 2):
- LINE (10, 25)-(310, 25), 2
- LINE -(160, 175), 2
- LINE -(10, 25), 2
-
- ' Paint the interior of the triangle with the pattern,
- ' adding the border argument (, 2) to tell PAINT
- ' where to stop:
- PAINT (160, 100), Tile$, 2
-
- Sometimes, after painting a figure with a solid color or pattern, you may
- want to repaint that figure, or some part of it, with a new pattern. If the
- new pattern contains two or more adjacent rows that are the same as the
- figure's current background, you will find that tiling does not work.
- Instead, the pattern starts to spread, finds itself surrounded by pixels
- that are the same as two or more of its rows, then stops.
-
- You can alleviate this problem by using the background argument with PAINT
- if there are at most two adjacent rows in your new pattern that are the same
- as the old background. PAINT with background has the following syntax:
-
-
- PAINT STEP( x!, y!) paint , bordercolor& , background$
-
- The background$ argument is a string character of the form CHR$( code% ) tha
- specifies the rows in the pattern tile that are the same as the figure's
- current background. In essence, background$ tells PAINT to skip over these
- rows when repainting the figure. The next example clarifies how this works:
-
-
- SCREEN 1
-
- ' Define a pattern (two rows each of cyan, magenta, white):
- Tile$ = CHR$(&H55) + CHR$(&H55) + CHR$(&HAA)
- Tile$ = Tile$ + CHR$(&HAA) + CHR$(&HFF) + CHR$(&HFF)
-
- ' Draw a triangle in
- white (color number 3):
- LINE (10, 25)-(310, 25)
- LINE -(160, 175)
- LINE -(10, 25)
-
- ' Paint the interior magenta:
- PAINT (160, 100), 2, 3
-
- ' Wait for a keystroke:
- Pause$ = INPUT$(1)
-
- ' Since the background is already magenta, CHR$(&HAA) tells
- ' PAINT to skip over the magenta rows in the pattern tile:
- PAINT (160, 100), Tile$, , CHR$(&HAA)
-
- Creating a Multicolor Pattern in Screen Mode 8
-
- In the EGA and VGA screen modes, it takes more than one 8-bit integer to
- define one row in a pattern tile. In these screen modes, a row is composed
- of several layers of 8-bit integers. This is because a pixel is represented
- three dimensionally in a stack of "bit planes" rather than sequentially in a
- single plane, as is the case with screen modes 1 and 2. For example, screen
- mode 8 has four of these bit planes. Each of the 4 bits per pixel in this
- screen mode is on a different plane.
-
- The following steps diagram the process for creating a multicolor pattern
- consisting of rows of alternating yellow and magenta. Note how each row in
- the pattern tile is represented by 4 parallel bytes:
-
- 1. Define one row of pixels in the pattern tile. Each pixel in the row
- takes 4 bits, and each bit is in a different plane, as shown in the
- following:
-
- Add the CHR$ values for all four bit planes to get one
- tile byte. This row is repeated in the pattern tile, so:
-
- Row$(1) = Row$(2) = CHR$(&HC3) + CHR$(&H3C) + CHR$(&HFF) + CHR$(&H3C) 2.
-
- Row$(3) = Row$(4) = CHR$(&H3C) + CHR$(&HC3) + CHR$(&HFF) + CHR$(&HC3)
-
-
- Example
-
- The following example draws a box, then paints its interior with the pattern
- created in the preceding steps:
-
- SCREEN 8
- DIM Row$(1 TO 4)
-
- ' Two rows of alternating magenta and yellow:
- Row$(1) = CHR$(&HC3) + CHR$(&H3C) + CHR$(&HFF) + CHR$(&H3C)
- Row$(2) = Row$(1)
-
- ' Invert the pattern (two rows of alternating yellow
- ' and magenta):
- Row$(3) = CHR$(&H3C) + CHR$(&HC3) + CHR$(&HFF) + CHR$(&HC3)
- Row$(4) = Row$(3)
- ' Create a pattern tile from the rows defined above:
- FOR I% = 1 TO 4
- Tile$ = Tile$ + Row$(I%)
- NEXT I%
-
- ' Draw box and fill it with the pattern:
- LINE (50, 50)-(570, 150), , B
- PAINT (320, 100), Tile$
-
-
- DRAW: A Graphics Macro Language
-
- The DRAW statement is a miniature language by itself. It draws and paints
- images on the screen using a set of one- or two-letter commands, known as
- "macros," embedded in a string expression.
-
- DRAW offers the following advantages over the other graphics statements
- discussed so far:
-
- ■ The macro string argument to DRAW is compact: a single, short string
- can produce the same output as several LINE statements.
-
- ■ Images created with DRAW can easily be scaled -- that is, enlarged or
- reduced in size -- by using the S macro in the macro string.
-
- ■ Images created with DRAW can be rotated any number of degrees by
- using the TA macro in the macro string.
-
-
- Consult online Help for more information.
-
- Example
-
- The following program gives a brief introduction to the movement macros U,
- D, L, R, E, F, G, and H; the "plot/don't plot" macro B; and the color
- macro C. This program draws horizontal, vertical, and diagonal lines in
- different colors, depending on which direction key on the numeric keypad (Up
- Arrow, Down Arrow, Left Arrow, PgUp, PgDn, and so on) is pressed.
-
- This program is in the file named PLOTTER.BAS on the Microsoft BASIC
- distribution disks.
-
-
- ' Values for keys on the numeric keypad and the Spacebar:
-
- CONST UP = 72, DOWN = 80, LFT = 75, RGHT = 77
- CONST UPLFT = 71, UPRGHT = 73, DOWNLFT = 79, DOWNRGHT = 81
- CONST SPACEBAR = " "
-
- ' Null$ is the first character of the two-character INKEY$
- ' value returned for direction keys such as Up and Down:
- Null$ = CHR$(0)
- ' Plot$ = "" means draw lines; Plot$ = "B" means
- ' move graphics cursor, but don't draw lines:
- Plot$ = ""
-
- PRINT "Use the cursor movement keys to draw lines."
- PRINT "Press Spacebar to toggle line drawing on and off."
- PRINT "Press <ENTER> to begin. Press q to end the program."
- DO : LOOP WHILE INKEY$ = ""
-
- SCREEN 1
-
- DO
- SELECT CASE KeyVal$
- CASE Null$ + CHR$(UP)
- DRAW Plot$ + "C1 U2"
- CASE Null$ + CHR$(DOWN)
- DRAW Plot$ + "C1 D2"
- CASE Null$ + CHR$(LFT)
- DRAW Plot$ + "C2 L2"
- CASE Null$ + CHR$(RGHT)
- DRAW Plot$ + "C2 R2"
- CASE Null$ + CHR$(UPLFT)
- DRAW Plot$ + "C3 H2"
- CASE Null$ + CHR$(UPRGHT)
- DRAW Plot$ + "C3 E2"
- CASE Null$ + CHR$(DOWNLFT)
- DRAW Plot$ + "C3 G2"
- CASE Null$ + CHR$(DOWNRGHT)
- DRAW Plot$ + "C3 F2"
- CASE SPACEBAR
- IF Plot$ = "" THEN Plot$ = "B " ELSE Plot$ = ""
- CASE ELSE
- ' The user pressed some key other than one of the
- ' direction keys, the Spacebar, or "q," so
- ' don't do anything.
- END SELECT
- KeyVal$ = INKEY$
-
- LOOP UNTIL KeyVal$ =
- "q"
-
- SCREEN 0, 0' Restore the screen to 80-column
- WIDTH 80' text mode and end.
- END
-
-
- Basic Animation Techniques
-
- Using only the graphics statements covered in earlier sections, you can do
- simple animation of objects on the screen. For instance, you can first draw
- a circle with CIRCLE, then redraw it with the background color to erase it,
- and finally recalculate the circle's center point and draw it in a new
- location.
-
- This technique works well enough for simple figures, but its disadvantages
- become apparent when animating more complex images. Even though the graphics
- statements are very fast, you can still notice the lag. Moreover, it is not
- possible to preserve the background with this method: when you erase the
- object, you also erase whatever was already on the screen.
-
- Two statements allow you to do high-speed animation: GET and PUT. You can
- create an image using output from statements such as PSET, LINE, CIRCLE,
- or PAINT, then take a "snapshot" of that image with GET, copying the image
- to memory. With PUT, you can then reproduce the image stored with GET
- anywhere on the screen or viewport.
-
-
- Saving Images with GET
-
- After you have created the original image on the screen, you need to
- calculate the x- and y-coordinates of a rectangle large enough to hold the
- entire image. You then use GET to copy the entire rectangle to memory. The
- syntax for the graphics GET statement is as follows:
-
- GET STEP ( x1!, y1!) - STEP( x2!, y 2!), a r rayname#The arguments
- ( x1! , y1! ) and ( x2! , y2! ) give the coordinates of a rectangle's
- upper-left and lower-right corners. The argument arrayname# refers to any
- numeric array. The size specified in a DIM statement for arrayname#
- depends on the following three factors:
-
-
- The height and width of the rectangle enclosing the image
-
- The screen mode chosen for graphics output
-
- The type of the array (integer, long integer, single precision, or
- double precision)
-
-
- Note
-
- Although the array used to store images can have any numeric type, it is
- strongly recommended that you use only integer arrays. All possible graphics
- patterns on the screen can be represented by integers. This is not the case,
- however, with single-precision or double-precision real numbers. Some
- graphics patterns are not valid real numbers, and it could lead to
- unforeseen results if these patterns were stored in a real-number array.
-
- The formula for calculating the size in bytes of arrayname# is as follows:
-
- size-in-bytes = 4 + height * planes * INT(( width * bits-per-pixel/
- planes + 7)/8)
-
- In the preceding syntax, height and width are the dimensions, in number of
- pixels, of the rectangle to get, and the value for bits-per-pixel depends
- on the number of colors available in the given screen mode. More colors mean
- more bits are required to define each pixel. In screen mode 1, two bits
- define a pixel, while in screen mode 2, one bit is enough. (See
- "Pattern-Tile Size in Different Screen Modes" earlier in this chapter for
- the general formula for bits-per-pixel.) The following list shows the value
- for planes for each of the screen modes:
-
- ╓┌──────────────────────────────────────────┌───────┌────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 1, 2, 11, and 13 1
- 9 (64K of video memory) and 10 2
- 7, 8, 9 (more than 64K of video memory), and 12 4
-
-
-
- To get the number of elements that should be in the array, divide the size-i
- number of bytes for one element in the array. This is where the type of the
- array comes into play. If it is an integer array, each element takes 2 bytes
- of memory (the size of an integer), so size-in-bytes should be divided by
- two to get the actual size of the array. Similarly, if it is a long integer
- array, size-in-bytes should be divided by four (since one long integer
- requires 4 bytes of memory), and so on. If it is single precision, divide by
- four; if it is double precision, divide by eight.
-
- The following steps show how to calculate the size of an integer array large
- enough to hold a rectangle in screen mode 1 with coordinates (10, 40) for
- the upper-left corner and (90, 80) for the lower-right corner:
-
- 1. Calculate the height and width of the rectangle:
-
- RectHeight = ABS( y2 - y1) + 1 = 80 - 40 + 1 = 41
- RectWidth = ABS( x2 - x1) + 1 = 90 - 10 + 1 = 81
- Remember to add one after subtracting y1 from y2 or x1 from x2.
- For example, if x1 = 10 and x2 = 20, then the width of the rectangle
- is 20 - 10 + 1, or 11.
-
- 2. Calculate the size in bytes of the integer array:
-
- ByteSize = 4 + RectHeight * INT((RectWidth * BitsPerPixel + 7) / 8)
- = 4 + 41 * INT((81 * 2 + 7) / 8)
- = 4 + 41 * INT(169 / 8)
- = 4 + 41 * 21
- = 865
-
- 3. Divide the size in bytes by the bytes per element (two for integers)
- and round the result up to the nearest whole number:
-
- 865 / 2 = 433
-
- Therefore, if the name of the array is Image(), the following DIM statement
- ensures that Image is big enough to copy the pixel information in the
- rectangle:
-
- DIM Image (1 TO 433) AS INTEGER
-
-
- Note
-
- Although the GET statement uses view coordinates after a WINDOW statement,
- you must use physical coordinates to calculate the size of the array used in
- GET. (See the section "Redefining Viewport Coordinates with WINDOW" earlier
- in this chapter for more information on WINDOW and how to convert view
- coordinates to physical coordinates.)
-
- Note that the steps outlined previously give the minimum size required for
- the array; however, any larger size will do. For example, the following
- statement also works:
-
- DIM Image (1 TO 500) AS INTEGER
-
- The following program draws an ellipse and paints its interior. A GET
- statement copies the rectangular area containing the ellipse into memory.
- (The following section, "Moving Images with PUT" shows how to use the
- PUT statement to reproduce the ellipse in a different
- location.)
-
-
- SCREEN 1
-
- ' Dimension an integer array large enough
- ' to hold the rectangle:
- DIM Image (1 TO 433) AS INTEGER
-
- ' Draw an ellipse inside the rectangle, using magenta for
- ' the outline and painting the interior white:
- CIRCLE (50, 60), 40, 2, , , .5
- PAINT (50, 60), 3, 2
-
- ' Store the image of the rectangle in the array:
- GET (10, 40)-(90, 80), Image
-
-
- Moving Images with PUT
-
- While the GET statement allows you to take a snapshot of an image, PUT
- allows you to paste that image anywhere you want on the screen. A statement
- of the following form copies the rectangular image stored in arrayname#
- back to the screen and places its upper-left corner at the point with
- coordinates ( x!, y!):
-
- PUT( x!, y!), arrayname# , actionverbNote that only one coordinate pair ap
-
-
- If a WINDOW statement appears in the program before PUT, the coordinates
- x and y refer to the lower-left corner of the rectangle. WINDOW SCREEN,
- however, does not have this effect; that is, after WINDOW SCREEN, x and y
- still refer to the upper-left corner of the rectangle.
-
- For example, adding the next line to the last example in the section "Saving
- Images with GET" causes an exact duplicate of the painted ellipse to appear
- on the right side of the screen much more quickly than redrawing and
- repainting the same figure with CIRCLE and PAINT:
-
- PUT (200, 40), Image
- Take care not to specify coordinates that would put any part of the image
- outside the screen or active viewport, as in the following statements:
-
- SCREEN 2
- .
- .
- .
- ' Rectangle measures 141 pixels x 91 pixels:
- GET (10, 10)-(150, 100), Image
- PUT (510, 120), Image
-
- Unlike other graphics statements such as LINE or CIRCLE, PUT
- does not clip images lying outside the viewport. Instead, it produces
- an error message Illegal function call.
-
- One of the other advantages of the PUT statement is that you can control
- how the stored image interacts with what is already on the screen by using
- the argument actionverb. The actionverb argument can be one of the
- following options: PSET, PRESET, AND, OR, or XOR.
-
- If you do not care what happens to the existing screen background, use the
- PSET option, since it transfers an exact duplicate of the stored image to
- the screen and overwrites anything that was already there.
-
- Table 5.4 shows how other options affect the way the PUT statement causes
- pixels in a stored image to interact with pixels on the screen. In this
- table, 1 means a pixel is on and 0 means a pixel is off.
-
- 1 XOR 1
-
- Example
-
- The output from the following program shows the same image superimposed over
- a filled rectangle using each of the five options discussed previously:
-
- SCREEN 2
-
- DIM CircImage (1 TO 485) AS INTEGER
-
- ' Draw and paint an ellipse then store its image with GET:
- CIRCLE (22, 100), 80, , , , 4
- PAINT (22, 100)
- GET (0, 20)-(44, 180), CircImage
- CLS
-
- ' Draw a box and fill it with a pattern:
- LINE (40, 40)-(600, 160), , B
- Pattern$ = CHR$(126) + CHR$(0) + CHR$(126) + CHR$(126)
- PAINT (50, 50), Pattern$
- ' Put the images of the ellipse over the box
- ' using the different action options:
- PUT (100, 20), CircImage, PSET
- PUT (200, 20), CircImage, PRESET
- PUT (300, 20), CircImage, AND
- PUT (400, 20), CircImage, OR
- PUT (500, 20), CircImage, XOR
-
- PSET PRESET AND OR XOR
- In screen modes supporting color, the options PRESET, AND, OR, and XOR pro
- simply turning a pixel on or off. However, the analogy made between these
- options and logical operators still holds in these modes.
-
- For example, if the current pixel on the screen is color 1
- (cyan in palette 1) and the pixel in the overlaid image is color 2 (magenta
- in palette 1), then the color of the resulting pixel after a PUT statement
- depends on the option, as shown for just 6 of the 16 different combinations
- of image color and background color in Table 5.5.
-
-
- Pixel color inscreen beforescreen after PUT Action optionstored imagePUT
- statementstatement In palette 1, cyan is assigned to attribute 1 (01 binary),
- magenta is assigned to attribute 2 (10 binary), and white is assigned to
- attribute 3 (11 binary). If you have an EGA, you can use the PALETTE
- statement to assign different colors to the attributes 1, 2, and 3.
-
-
- Animation with GET and PUT
-
- One of the most useful things that can be done with the GET and PUT
- statements is animation. The two options best suited for animation are XOR
- and PSET. Animation done with PSET is faster; but as shown by the output
- from the last program, PSET erases the screen background. In contrast, XOR
- is slower but restores the screen background after the image is moved.
- Animation with XOR is done with the following four steps:
-
- 1. Put the object on the screen with XOR. 2. Calculate the new position
-
- 3. Put the object on the screen a second time at the old location, using
- XOR again, this time to remove the old image.
-
- 4. Go to step 1, but this time put the object at the new location.
- Movement done with these four steps leaves the background unchanged after
- step 3. Flicker can be reduced by minimizing the time between steps 4 and 1
- and by making sure that there is enough time delay between steps 1 and 3. If
- more than one object is being animated, every object should be processed at
- once, one step at a time.
-
- If it is not important to preserve the background, use the PSET option for
- animation. The idea is to leave a border around the image when you copy it
- with the GET statement. If this border is as large as or larger than the
- maximum distance the object will move, then each time the image is put in a
- new location, the border erases all traces of the image in the old location.
- This method can be faster than the method using XOR described previously,
- since only one PUT statement is required to move an object (although you
- must move a larger image).
-
- The following example shows how to use PUT with the PSET option to produce
- the effect of a ball bouncing off the bottom and sides of a box. Note in the
- output that follows how the rectangle containing the ball, specified in the
- GET statement, erases the filled box and the printed message.
-
-
- Examples
-
- This program is in the file named BALLPSET.BAS on the Microsoft BASIC
- distribution disks.
-
-
- DECLARE FUNCTION GetArraySize (WLeft, WRight, WTop, WBottom)
-
- SCREEN 2
-
- ' Define a viewport and draw a border around it:
- VIEW (20, 10)-(620, 190),,1
-
- CONST PI = 3.141592653589#
- ' Redefine the coordinates of the viewport with window
- ' coordinates:
- WINDOW (-3.15, -.14)-(3.56, 1.01)
-
- ' Arrays in program are now dynamic:
- ' $DYNAMIC
-
- ' Calculate the window coordinates for the top and bottom of a
- ' rectangle large enough to hold the image that will be
- ' drawn with CIRCLE and PAINT:
- WLeft = -.21
- WRight = .21
- WTop = .07
- WBottom = -.07
-
- ' Call the GetArraySize function,
- ' passing it the rectangle's window coordinates:
- ArraySize% = GetArraySize(WLeft, WRight, WTop, WBottom)
-
- DIM Array (1 TO ArraySize%) AS INTEGER
-
- ' Draw and paint the circle:
- CIRCLE (0, 0), .18
- PAINT (0, 0)
-
- ' Store the rectangle in Array:
- GET (WLeft, WTop)-(WRight, WBottom), Array
- CLS
- ' Draw a box and fill it with a pattern:
- LINE (-3, .8)-(3.4, .2), , B
- Pattern$ = CHR$(126) + CHR$(0) + CHR$(126) + CHR$(126)
- PAINT (0, .5), Pattern$
-
- LOCATE 21, 29
- PRINT "Press any key to end."
-
- ' Initialize loop variables:
- StepSize = .02
- StartLoop = -PI
- Decay = 1
-
- DO
- EndLoop = -StartLoop
- FOR X = StartLoop TO EndLoop STEP StepSize
-
- ' Each time the ball "bounces" (hits the bottom of the
- ' viewport), the Decay variable gets smaller, making
- ' the height of the next bounce smaller:
- Y = ABS(COS(X)) * Decay - .14
- IF Y < -.13 THEN Decay = Decay * .9
- ' Stop if key pressed or Decay less than .01:
- Esc$ = INKEY$
- IF Esc$ <> "" OR Decay < .01 THEN EXIT FOR
-
- ' Put the image on the screen. The StepSize offset is
- ' smaller than the border around the circle. Thus,
- ' each time the image moves, it erases any traces
- ' left from the previous PUT (and also erases anything
- ' else on the screen):
- PUT (X, Y), Array, PSET
- NEXT X
-
- ' Reverse direction:
- StepSize = -StepSize
- StartLoop = -StartLoop
- LOOP UNTIL Esc$ <> "" OR Decay < .01
-
- END
-
- FUNCTION GetArraySize (WLeft, WRight, WTop, WBottom) STATIC
-
- ' Map the window coordinates passed to this function to
- ' their physical coordinate equivalents:
- VLeft = PMAP(WLeft, 0)
- VRight = PMAP(WRight, 0)
- VTop = PMAP(WTop, 1)
- VBottom = PMAP(WBottom, 1)
- ' Calculate the height and width in pixels
- ' of the enclosing rectangle:
- RectHeight = ABS(VBottom - VTop) + 1
- RectWidth = ABS(VRight - VLeft) + 1
-
- ' Calculate size in bytes of array:
- ByteSize = 4 + RectHeight * INT((RectWidth + 7) / 8)
-
- ' Array is integer, so divide bytes by two:
- GetArraySize = ByteSize \ 2 + 1
- END FUNCTION
-
- Contrast the preceding program with the next program, which uses
- PUT with XOR to preserve the screen's background, according to the steps
- outlined earlier. Note how the rectangle containing the ball is smaller than
- in the preceding program, since it is not necessary to leave a border. Also
- note that two PUT statements are required, one to make the image visible
- and another to make it disappear. Finally, observe the empty FOR... NEXT
- delay loop between the PUT statements; this loop reduces the flicker that
- results from the image appearing and disappearing too rapidly.
-
-
- This program is in the file named BALLXOR.BAS on the Microsoft BASIC
- distribution disks.
-
- ' The rectangle is smaller than the one in the previous
- ' program, which means Array is also smaller:
- WLeft = -.18
- WRight = .18
- WTop = .05
- WBottom = -.05
- .
- .
- .
- DO
- EndLoop = -StartLoop
- FOR X = StartLoop TO EndLoop STEP StepSize
- Y = ABS(COS(X)) * Decay - .14
- ' The first PUT statement places the image
- ' on the screen:
- PUT (X,Y), Array, XOR
-
- ' Use an empty FOR...NEXT loop to delay
- ' the program and reduce image flicker:
- FOR I = 1 TO 5: NEXT I
-
- IF Y < -.13 THEN Decay = Decay * .9
- Esc$ = INKEY$
- IF Esc$ <> "" OR Decay < .01 THEN EXIT FOR
-
- ' The second PUT statement erases the image and
- ' restores the background:
- PUT (X, Y), Array, XOR
- NEXT X
-
- StepSize = -StepSize
- StartLoop = -StartLoop
- LOOP UNTIL Esc$ <> "" OR Decay < .01
-
- END
- .
- .
- .
-
-
- Animating with Screen Pages
-
- This section describes an animation technique that utilizes multiple pages
- of your computer's video memory.
-
- Pages in video memory are analogous to pages in a book. Depending on the
- graphics capability of your computer, what you see displayed on the screen
- may only be part of the video memory available -- just as what you see when
- you open a book is only part of the book. However, unlike a book, the unseen
- pages of your computer's video memory can be active; that is, while you are
- looking at one page on the screen, graphics output can be taking place on
- the others. It's as if the author of a book were still writing new pages
- even as you were reading the book.
-
- The area of video memory visible on the screen is called the "visual page,"
- while the area of video memory where graphics statements put their output is
- called the "active page." The SCREEN statement allows you to select visual
- and active screen pages with the following syntax:
-
- SCREEN mode% , activepage%, visiblepage%In this syntax, activepage% is the
- number of the visual page. The active page and the visual page can be
- one and the same (and are by default when the activepage% or
- visiblepage% arguments are not used with SCREEN,
- in which case the value of both arguments is 0).
-
- You can animate objects on the screen by selecting a screen mode with more
- than one video memory page, then alternating the pages, sending output to
- one or more active pages while displaying finished output on the visual
- page. This technique makes an active page visible only when output to that
- page is complete. Since the viewer sees only a finished image, the display
- is instantaneous.
-
-
- Example
-
- The following program demonstrates the technique discussed previously. It
- selects screen mode 7, which has two pages, then draws a cube with the DRAW
- statement. This cube is then rotated through successive 15 angles by
- changing the value of the TA macro in the string used by DRAW. By swapping
- the active and visual pages back and forth, this program always shows a
- completed cube while a new one is being drawn.
-
- This program is in the file named CUBE.BAS on the Microsoft BASIC
- distribution disks.
-
- ' Define the macro string used to draw the cube
- ' and paint its sides:
- One$ ="BR30 BU25 C1 R54 U45 L54 D45 BE20 P1,1 G20 C2 G20"
- Two$ ="R54 E20 L54 BD5 P2,2 U5 C4 G20 U45 E20 D45 BL5 P4,4"
- Plot$ = One$ + Two$
-
- APage% = 1 ' Initialize values for the active and visual
- VPage% = 0 ' pages as well as the angle of rotation.
- Angle% = 0
- DO
- SCREEN 7, , APage%, VPage% ' Draw to the active page
- ' while showing the visual page.
-
- CLS 1' Clear the active page.
-
- ' Rotate thecube "Angle%" degrees:
- DRAW"TA" + STR$(Angle%) + Plot$
-
- ' Angle% is some multiple of 15 degrees:
- Angle% = (Angle% + 15) MOD 360
-
- ' Drawing is complete, so make the cube visible in its
- ' new position by switching the active and visual pages:
- SWAPAPage%,VPage%
-
- LOOP WHILE INKEY$ = ""' A keystroke ends the program.
-
- END
-
-
- Sample Applications
-
- The sample applications in this chapter are a bar-graph generator, a program
- that plots points in the Mandelbrot Set using different colors, and a
- pattern editor.
-
-
- Bar-Graph Generator (BAR.BAS)
-
- This program uses all the forms of the LINE statement presented previously
- to draw a filled bar chart. Each bar is filled with a pattern specified in a
- PAINT statement. The input for the program consists of titles for the
- graph, labels for the x- and y-axes, and a set of up to five labels (with
- associated values) for the bars.
-
-
- Statements Used
-
- This program demonstrates the use of the following graphics statements:
-
- ■ LINE ■ PAINT (with a pattern)
-
- ■ SCREEN
-
- Program Listing
-
- The bar-graph generator program BAR.BAS follows:
-
- ' Define type for the titles:
-
- TYPE TitleType
- MainTitle AS STRING * 40
- XTitle AS STRING * 40
- YTitle AS STRING * 18
- END TYPE
-
- DECLARE SUB InputTitles (T AS TitleType)
- DECLARE FUNCTION DrawGraph$ (T AS TitleType, Label$(), Value!(), N%)
- DECLARE FUNCTION InputData% (Label$(), Value!())
-
- ' Variable declarations for titles and bar data:
- DIM Titles AS TitleType, Label$(1 TO 5), Value(1 TO 5)
-
- CONST FALSE = 0, TRUE = NOT FALSE
-
- DO
- InputTitles Titles
- N% = InputData%(Label$(), Value())
- IF N% <> FALSE THEN
- NewGraph$ = DrawGraph$(Titles, Label$(), Value(), N%)
- END IF
- LOOP WHILE NewGraph$ = "Y"
-
- END
- ' ======================== DRAWGRAPH ======================
- ' Draws a bar graph from the data entered in the
- ' INPUTTITLES and INPUTDATA procedures.
- ' =========================================================
-
- FUNCTION DrawGraph$ (T AS TitleType, Label$(), Value(), N%) STATIC
-
- ' Set size of graph:
- CONST GRAPHTOP = 24, GRAPHBOTTOM = 171
- CONST GRAPHLEFT = 48, GRAPHRIGHT = 624
- CONST YLENGTH = GRAPHBOTTOM - GRAPHTOP
-
- ' Calculate maximum and minimum values:
- YMax = 0
- YMin = 0
- FOR I% = 1 TO N%
- IF Value(I%) < YMin THEN YMin = Value(I%)
- IF Value(I%) > YMax THEN YMax = Value(I%)
- NEXT I%
- ' Calculate width of bars and space between them:
- BarWidth = (GRAPHRIGHT - GRAPHLEFT) / N%
- BarSpace = .2 * BarWidth
- BarWidth = BarWidth - BarSpace
-
- SCREEN 2
- CLS
-
- ' Draw y-axis:
- LINE (GRAPHLEFT, GRAPHTOP)-(GRAPHLEFT, GRAPHBOTTOM), 1
-
- ' Draw main graph title:
- Start% = 44 - (LEN(RTRIM$(T.MainTitle)) / 2)
- LOCATE 2, Start%
- PRINT RTRIM$(T.MainTitle);
-
- ' Annotate y-axis:
- Start% = CINT(13 - LEN(RTRIM$(T.YTitle)) / 2)
- FOR I% = 1 TO LEN(RTRIM$(T.YTitle))
- LOCATE Start% + I% - 1, 1
- PRINT MID$(T.YTitle, I%, 1);
- NEXT I%
-
- ' Calculate scale factor so labels aren't bigger than four digits:
- IF ABS(YMax) > ABS(YMin) THEN
- Power = YMax
- ELSE
- Power = YMin
- END IF
- Power = CINT(LOG(ABS(Power) / 100) / LOG(10))
- IF Power < 0 THEN Power = 0
-
- ' Scale minimum and maximum values down:
- ScaleFactor = 10 ^ Power
- YMax = CINT(YMax / ScaleFactor)
- YMin = CINT(YMin / ScaleFactor)
- ' If power isn't zero then put scale factor on chart:
- IF Power <> 0 THEN
- LOCATE 3, 2
- PRINT "x 10^"; LTRIM$(STR$(Power))
- END IF
-
- ' Put tick mark and number for Max point on y-axis:
- LINE (GRAPHLEFT - 3, GRAPHTOP) -STEP(3, 0)
- LOCATE 4, 2
- PRINT USING "####"; YMax
- ' Put tick mark and number for Min point on y-axis:
- LINE (GRAPHLEFT - 3, GRAPHBOTTOM) -STEP(3, 0)
- LOCATE 22, 2
- PRINT USING "####"; YMin
-
- YMax = YMax * ScaleFactor ' Scale minimum and maximum back
- YMin = YMin * ScaleFactor ' up for charting calculations.
-
- ' Annotate x-axis:
- Start% = 44 - (LEN(RTRIM$(T.XTitle)) / 2)
- LOCATE 25, Start%
- PRINT RTRIM$(T.XTitle);
-
- ' Calculate the pixel range for the y-axis:
- YRange = YMax - YMin
-
- ' Define a diagonally striped pattern:
- Tile$ = CHR$(1)+CHR$(2)+CHR$(4)+CHR$(8)+CHR$(16)+CHR$(32)+_
- CHR$(64)+CHR$(128)
-
- ' Draw a zero line if appropriate:
- IF YMin < 0 THEN
- Bottom = GRAPHBOTTOM - ((-YMin) / YRange * YLENGTH)
- LOCATE INT((Bottom - 1) / 8) + 1, 5
- PRINT "0";
- ELSE
- Bottom = GRAPHBOTTOM
- END IF
-
- ' Draw x-axis:
- LINE (GRAPHLEFT - 3, Bottom)-(GRAPHRIGHT, Bottom)
- ' Draw bars and labels:
- Start% = GRAPHLEFT + (BarSpace / 2)
- FOR I% = 1 TO N%
-
- ' Draw a bar label:
- BarMid = Start% + (BarWidth / 2)
- CharMid = INT((BarMid - 1) / 8) + 1
- LOCATE 23, CharMid - INT(LEN(RTRIM$(Label$(I%))) / 2)
- PRINT Label$(I%);
-
- ' Draw the bar and fill it with the striped pattern:
- BarHeight = (Value(I%) / YRange) * YLENGTH
- LINE (Start%, Bottom) -STEP(BarWidth, -BarHeight), , B
- PAINT (BarMid, Bottom - (BarHeight / 2)), Tile$, 1
-
- Start% = Start% + BarWidth + BarSpace
- NEXT I%
- LOCATE 1, 1
- PRINT "New graph? ";
- DrawGraph$ = UCASE$(INPUT$(1))
-
- END FUNCTION
- '
- ======================== INPUTDATA ======================
- ' Gets input for the bar labels and their values
- ' =========================================================
-
- FUNCTION InputData% (Label$(), Value()) STATIC
-
- ' Initialize the number of data values:
- NumData% = 0
-
- ' Print data-entry instructions:
- CLS
- PRINT "Enter data for up to 5 bars:"
- PRINT " * Enter the label and value for each bar."
- PRINT " * Values can be negative."
- PRINT " * Enter a blank label to stop."
- PRINT
- PRINT "After viewing the graph, press any key ";
- PRINT "to end the program."
-
- ' Accept data until blank label or 5 entries:
- Done% = FALSE
- DO
- NumData% = NumData% + 1
- PRINT
- PRINT "Bar("; LTRIM$(STR$(NumData%)); "):"
- INPUT ; " Label? ", Label$(NumData%)
-
- ' Only input value if label isn't blank:
- IF Label$(NumData%) <> "" THEN
- LOCATE , 35
- INPUT "Value? ", Value(NumData%)
-
- ' If label is blank, decrement data counter
- ' and set Done flag equal to TRUE:
- ELSE
- NumData% = NumData% - 1
- Done% = TRUE
- END IF
- LOOP UNTIL (NumData% = 5) OR Done%
-
- ' Return the number of data values input:
- InputData% = NumData%
-
- END FUNCTION
-
- '
- ====================== INPUTTITLES ======================
- ' Accepts input for the three different graph titles
- ' =========================================================
-
- SUB InputTitles (T AS TitleType) STATIC
- SCREEN 0, 0' Set text screen.
- DO' Input titles.
- CLS
- INPUT "Enter main graph title: ", T.MainTitle
- INPUT "Enter x-axis title : ", T.XTitle
- INPUT "Enter y-axis title : ", T.YTitle
-
- ' Check to see if titles are OK:
- LOCATE 7, 1
- PRINT "OK (Y to continue, N to change)? ";
- LOCATE , , 1
- OK$ = UCASE$(INPUT$(1))
- LOOP UNTIL OK$ = "Y"
- END SUB
-
-
- Color in a Figure Generated Mathematically (MANDEL.BAS)
-
- This program uses BASIC graphics statements to generate a figure known as a
- "fractal." A fractal is a graphic representation of what happens to numbers
- when they are subjected to a repeated sequence of mathematical operations.
- The fractal generated by this program shows a subset of the class of numbers
- known as complex numbers; this subset is called the "Mandelbrot Set," named
- after Benoit B. Mandelbrot of the IBM Thomas J. Watson Research Center.
-
- Briefly, complex numbers have two parts, a real part and a so-called
- imaginary part, which is some multiple of -1. Squaring a complex number,
- then plugging the real and imaginary parts back into a second complex
- number, squaring the new complex number, and repeating the process causes
- some complex numbers to get very large fairly fast. However, others hover
- around a stable value. The stable values are in the Mandelbrot Set and are
- represented in this program by the color black. The unstable values -- that
- is, the ones that are moving away from the Mandelbrot Set -- are represented
- by the other colors in the palette. The smaller the color attribute, the
- more unstable the point.
-
- See A.K. Dewdney's column, "Computer Recreations," in Scientific American,
- August 1985, for more background on the Mandelbrot Set.
-
- This program also tests for the presence of an EGA card, and if one is
- present, it draws the Mandelbrot Set in screen mode 8. After drawing each
- line, the program rotates the 16 colors in the palette with a PALETTE USING
- statement. If there is no EGA card, the program draws a four-color (white,
- magenta, cyan, and black) Mandelbrot Set in screen mode 1.
-
-
- Statements and Functions Used
-
- This program demonstrates the use of the following graphics statements and
- functions:
-
- ■ LINE ■ PALETTE USING
-
- ■ PMAP ■ PSET
-
- ■ SCREEN ■ VIEW
-
- ■ WINDOW
-
- Program Listing
-
- DEFINT A-Z' Default variable type is integer.
-
-
- DECLARESUB ShiftPalette ()
- DECLARESUB WindowVals (WL%, WR%, WT%, WB%)
- DECLARESUB ScreenTest (EM%, CR%, VL%, VR%, VT%, VB%)
-
- CONST FALSE = 0, TRUE = NOT FALSE ' Boolean constants
-
- ' Set maximum number of iterations per point:
- CONST MAXLOOP =30, MAXSIZE = 1000000
-
- DIM PaletteArray(15)
- FOR I =0 TO 15: PaletteArray(I) = I: NEXT I
-
- ' Call WindowVals to get coordinates of window corners:
- WindowVals WLeft, WRight, WTop, WBottom
-
- ' Call ScreenTest to find out if this is an EGA machine
- ' and get coordinates of viewport corners:
- ScreenTest EgaMode, ColorRange, VLeft, VRight, VTop, VBottom
-
- ' Define viewport and corresponding window:
- VIEW (VLeft, VTop)-(VRight, VBottom), 0, ColorRange
- WINDOW (WLeft, WTop)-(WRight, WBottom)
-
- LOCATE 24, 10 : PRINT "Press any key to quit.";
-
- XLength= VRight - VLeft
- YLength= VBottom - VTop
- ColorWidth = MAXLOOP \ ColorRange
-
- ' Loop through each pixel in viewport and calculate
- ' whether or not it is in the Mandelbrot Set:
- FOR Y =0 TO YLength' Loop through every line
- ' in the viewport.
- LogicY = PMAP(Y, 3)' Get the pixel's window
- ' y-coordinate.
- PSET(WLeft,LogicY) ' Plot leftmost pixel in the line.
- OldColor = 0' Start with background color.
-
- FOR X = 0 TOXLength ' Loop through every pixel
- ' in the line.
- LogicX = PMAP(X, 2)' Get the pixel's window
- ' x-coordinate.
- MandelX& = LogicX
- MandelY& = LogicY
- ' Do the calculations to see if this point
- ' is in the Mandelbrot Set:
- FOR I = 1TO MAXLOOP
- RealNum& = MandelX& * MandelX&
- ImagNum& = MandelY& * MandelY&
- IF (RealNum& + ImagNum&) >= MAXSIZE THEN EXIT FOR
- MandelY& = (MandelX& * MandelY&) \ 250 + LogicY
- MandelX& = (RealNum& - ImagNum&) \ 500 + LogicX
- NEXT I
-
- ' Assign a color to the point:
- PColor = I \ ColorWidth
-
- ' If color has changed, draw a line from
- ' the last point referenced to the new point,
- ' using the old color:
- IF PColor<> OldColor THEN
- LINE -(LogicX, LogicY), (ColorRange - OldColor)
- OldColor = PColor
- END IF
-
- IF INKEY$<> "" THEN END
- NEXTX
-
- ' Draw the last line segment to the right edge
- ' of the viewport:
- LINE-(LogicX, LogicY), (ColorRange - OldColor)
-
- ' If this is an EGA machine, shift the palette after
- ' drawing each line:
- IF EgaMode THEN ShiftPalette
- NEXT Y
-
- DO
- ' Continue shifting the palette
- ' until the user presses a key:
- IF EgaMode THEN ShiftPalette
- LOOP WHILE INKEY$ = ""
-
- SCREEN 0, 0 ' Restore the screen to text mode,
- WIDTH 80 ' 80 columns.
- END
-
- BadScreen: ' Error handler that is invoked if
- EgaMode = FALSE ' there is no EGA graphics card.
- RESUME NEXT
-
- '
- ====================== ShiftPalette =====================
- ' Rotates the palette by one each time it is called
- ' =========================================================
-
- SUB ShiftPalette STATIC
- SHARED PaletteArray(), ColorRange
-
- FOR I = 1 TOColorRange
- PaletteArray(I) =(PaletteArray(I) MOD ColorRange) + 1
- NEXTI
- PALETTE USING PaletteArray(0)
-
- END SUB
-
- ' ======================= ScreenTest ======================
- ' Uses a SCREEN 8 statement as a test to see if user has
- ' EGA hardware. If this causes an error, the EM flag is
- ' set to FALSE, and the screen is set with SCREEN 1.
-
- ' Also sets values for corners of viewport (VL = left,
- ' VR = right, VT = top, VB = bottom), scaled with the
- ' correct aspect ratio so viewport is a perfect square.
- ' =========================================================
-
- SUB ScreenTest (EM, CR,VL, VR,VT, VB) STATIC
- EM =TRUE
- ON ERROR GOTO BadScreen
- SCREEN 8, 1
- ON ERROR GOTO 0
-
- IF EM THEN' No error, SCREEN 8 is OK.
- VL = 110: VR = 529
- VT = 5: VB = 179
- CR = 15' 16 colors (0 - 15)
-
- ELSE' Error, so use SCREEN 1.
- SCREEN 1,1
- VL = 55: VR = 264
- VT = 5: VB = 179
- CR = 3' 4 colors (0 - 3)
- END IF
-
- END SUB
-
- '
- ======================= WindowVals ======================
- ' Gets window corners as input from the user, or sets
- ' values for the corners if there is no input.
- ' =========================================================
-
- SUB WindowVals (WL, WR,WT, WB)STATIC
- CLS
- PRINT "This program prints the graphic representation of"
- PRINT "the complete Mandelbrot Set. The default window"
- PRINT "is from (-1000,625) to (250,-625). To zoom in on"
- PRINT "part of the figure, input coordinates inside"
- PRINT "this window."
- PRINT "Press <ENTER> to see the default window or"
- PRINT "any other key to input window coordinates: ";
- LOCATE , , 1
- Resp$ = INPUT$(1)
-
- ' User didn't press ENTER, so input window corners:
- IF Resp$ <> CHR$(13)THEN
- PRINT
- INPUT "x-coordinate of upper-left corner: ", WL
- DO
- INPUT "x-coordinate of lower-right corner: ", WR
- IF WR <= WL THEN
- PRINT "Right corner must be greater than left corner."
- END IF
- LOOP WHILE WR <= WL
- INPUT "y-coordinate of upper-left corner: ", WT
- DO
- INPUT "y-coordinate of lower-right corner: ", WB
- IF WB >= WT THEN
- PRINT "Bottom corner must be less than top corner."
- END IF
- LOOP WHILE WB >= WT
-
- ' User pressed Enter, so set default values:
- ELSE
- WL = -1000
- WR = 250
- WT = 625
- WB = -625
- END IF
- END SUB
-
- Output
-
- The following figure shows the Mandelbrot Set in screen mode 1. This is the
- output you see if you have a CGA and you choose the default window
- coordinates.
-
- The next figure shows the Mandelbrot Set with
- ( x , y ) coordinates of (-500, 250) for the
- upper-left corner and (-300, 50) for the lower-right corner. This figure is
- drawn in screen mode 8, the default for an EGA or VGA.
-
-
- Pattern Editor (EDPAT.BAS)
-
- This program allows you to edit a pattern tile for use with PAINT. While
- you are editing the tile on the left side of the screen, you can check the
- appearance of the finished pattern on the right side of the screen. When you
- have finished editing the pattern tile, the program prints the integer
- arguments used by the CHR$ function to draw each row of the tile.
-
-
- Statements Used
-
- This program demonstrates the use of the following graphics statements:
-
- ■ LINE ■ PAINT (with pattern)
-
- ■ VIEW
-
- Program Listing
-
- DECLARESUB DrawPattern ()
-
- DECLARESUB EditPattern ()
- DECLARESUB Initialize ()
- DECLARESUB ShowPattern (OK$)
-
- DIM Bit%(0 TO 7), Pattern$, Esc$, PatternSize%
-
- DO
- Initialize
- EditPattern
- ShowPattern OK$
- LOOP WHILE OK$ = "Y"
-
- END
- ' ======================= DRAWPATTERN ====================
- ' Draws a patterned rectangle on the right side of screen.
- ' ========================================================
-
- SUB DrawPatternSTATIC
- SHARED Pattern$
- VIEW (320, 24)-(622, 160), 0, 1 ' Set view to rectangle.
- PAINT (1, 1), Pattern$ ' Use PAINT to fill it.
- VIEW ' Set view to full screen.
-
- END SUB
-
- ' ======================= EDITPATTERN =====================
- ' Edits a tile-byte pattern.
- ' =========================================================
-
- SUB EditPattern STATIC
- SHARED Pattern$, Esc$, Bit%(), PatternSize%
-
- ByteNum% = 1' Starting position.
- BitNum% = 7
- Null$ = CHR$(0)' CHR$(0) is the first byte of the
- ' two-byte string returned when a
- ' direction key such as Up or Down is
- ' pressed.
- DO
-
- 'Calculate starting location on screen of this bit:
- X% = ((7 - BitNum%) * 16) + 80
- Y% = (ByteNum% + 2) * 8
- 'Wait for a key press (flash cursor each 3/10 second):
- State% = 0
- RefTime =0
- DO
-
- ' Check timer and switch cursor state if 3/10 second:
- IF ABS(TIMER - RefTime) > .3 THEN
- RefTime = TIMER
- State% = 1 - State%
-
- ' Turn the border of bit on and off:
- LINE (X%-1, Y%-1) -STEP(15, 8), State%, B
- END IF
-
- Check$ = INKEY$' Check for keystroke.
-
- LOOP WHILE Check$= ""' Loop until a key is pressed.
-
- 'Erase cursor:
- LINE (X%-1, Y%-1) -STEP(15, 8), 0, B
-
- SELECT CASE Check$' Respond to keystroke.
-
- CASE CHR$(27)' Esc key pressed:
- EXIT SUB' exit this subprogram.
- CASE CHR$(32)' Spacebar pressed:
- ' reset state of bit.
-
- ' Invert bit in pattern string:
- CurrentByte% = ASC(MID$(Pattern$, ByteNum%, 1))
- CurrentByte% = CurrentByte% XOR Bit%(BitNum%)
- MID$ (Pattern$, ByteNum%) = CHR$(CurrentByte%)
-
- ' Redraw bit on screen:
- IF (CurrentByte% AND Bit%(BitNum%)) <> 0 THEN
- CurrentColor% = 1
- ELSE
- CurrentColor% = 0
- END IF
- LINE (X%+1, Y%+1) -STEP(11, 4), CurrentColor%, BF
-
- CASE CHR$(13)' Enter key pressed: draw
- DrawPattern ' pattern in box on right.
-
- CASE Null$ + CHR$(75)' Left key: move cursor left.
-
- BitNum% = BitNum% + 1
- IFBitNum%> 7 THEN BitNum% = 0
-
- CASE Null$ + CHR$(77)' Right key: move cursor right.
- BitNum% = BitNum% - 1
- IFBitNum%< 0 THEN BitNum% = 7
-
- CASE Null$ + CHR$(72)' Up key: move cursor up.
-
- ByteNum% =ByteNum% - 1
- IFByteNum% < 1 THEN ByteNum% = PatternSize%
-
- CASE Null$ + CHR$(80)' Down key: move cursor down.
-
- ByteNum% =ByteNum% + 1
- IFByteNum% > PatternSize%THEN ByteNum% = 1
-
- CASE ELSE
- ' User pressed a key other than Esc, Spacebar,
- ' Enter, Up, Down, Left, or Right, so don't
- ' do anything.
- END SELECT
- LOOP
- END SUB
-
- ' ======================= INITIALIZE ======================
- ' Sets up starting pattern and screen
- ' =========================================================
-
- SUB Initialize STATIC
- SHARED Pattern$, Esc$, Bit%(), PatternSize%
-
- Esc$= CHR$(27)' Esc character is ASCII 27.
-
- ' Set up an array holding bits in positions 0 to 7:
- FOR I% = 0 TO 7
- Bit%(I%) = 2 ^ I%
- NEXTI%
-
- CLS
-
- ' Input the pattern size (in number of bytes):
- LOCATE 5, 5
- PRINT "Enter pattern size (1-16 rows):";
- DO
- LOCATE 5,38
- PRINT "";
- LOCATE 5,38
- INPUT "",PatternSize%
- LOOPWHILE PatternSize% < 1 OR PatternSize% > 16
- ' Set initial pattern to all bits set:
- Pattern$ = STRING$(PatternSize%, 255)
-
- SCREEN 2' 640 x 200 monochrome graphics mode
-
- ' Draw dividing lines:
- LINE(0, 10)-(635, 10), 1
- LINE(300, 0)-(300, 199)
- LINE(302, 0)-(302, 199)
-
- ' Print titles:
- LOCATE 1, 13: PRINT "Pattern Bytes"
- LOCATE 1, 53: PRINT "Pattern View"
-
- ' Draw editing screen for pattern:
- FOR I% = 1 TO PatternSize%
-
- ' Print label on left of each line:
- LOCATE I%+ 3, 8
- PRINT USING "##:"; I%
-
- ' Draw "bit" boxes:
- X% = 80
- Y% = (I% + 2) * 8
- FOR J% = 1 TO 8
- LINE (X%, Y%) -STEP(13, 6), 1,BF
- X% = X% + 16
- NEXT J%
- NEXTI%
-
- DrawPattern' Draw "Pattern View" box.
-
- LOCATE 21, 1
- PRINT "DIRECTION keys........Move cursor"
- PRINT "SPACEBAR............Changes point"
- PRINT "ENTER............Displays pattern"
- PRINT "ESC.........................Quits";
-
- END SUB
-
- '
- ======================== SHOWPATTERN ====================
- ' Prints the CHR$ values used by PAINT to make pattern
- ' =========================================================
-
- SUB ShowPattern (OK$) STATIC
- SHARED Pattern$, PatternSize%
-
- ' Return screen to 80-column text mode:
- SCREEN 0, 0
- WIDTH 80
-
- PRINT "The following characters make up your pattern:"
- PRINT
-
- ' Print out the value for each pattern byte:
- FOR I% = 1 TO PatternSize%
- PatternByte% = ASC(MID$(Pattern$,I%, 1))
- PRINT "CHR$("; LTRIM$(STR$(PatternByte%)); ")"
- NEXTI%
- PRINT
- LOCATE , , 1
- PRINT "New pattern? ";
- OK$ = UCASE$(INPUT$(1))
- END SUB
-
-
- ────────────────────────────────────────────────────────────────────────────
-
- Chapter 6: Presentation Graphics
-
-
- Microsoft BASIC includes a toolbox of BASIC SUB and function procedures,
- and assembly language routines you can use to add charts and graphs to your
- programs quickly and easily. These procedures are collectively known as the
- Presentation Graphics toolbox and include support for pie charts, bar and
- column charts, line graphs, and scatter diagrams. Each of these types of
- charts can convert masses of numbers to a single expressive picture.
-
- This chapter shows you how to use the Presentation Graphics toolbox in your
- BASIC programs. The first section describes which files you need to use the
- Presentation Graphics toolbox and demonstrates how it simplifies the graphic
- presentation of data. Subsequent sections explain terminology, present more
- elaborate examples, and describe some of the toolbox's many capabilities.
-
- You'll also learn about Presentation Graphics' default data structures and
- how to manipulate them. The final section presents a short reference list of
- all the routines that comprise the Presentation Graphics toolbox and shows
- you how to include custom graphics fonts in your charts.
-
- To use the Presentation Graphics toolbox you need a graphics adapter and a
- monitor capable of bit-mapped display -- the same equipment mentioned in
- Chapter 5, "Graphics." Support is provided for CGA, EGA, VGA, MCGA, Hercules
- monochrome graphics, and the Olivetti Color Board.
-
- The BASIC procedures for Presentation Graphics are contained in the
- source-code module CHRTB.BAS. The assembly language routines are in the
- object file chrtasm.obj. When you ran the Setup program, you had an
- opportunity to have a Quick library (.QLB) and a object-module libraries
- (.LIB) created that contain all necessary BASIC and assembly language
- routines. To write presentation graphics programs within the QBX
- environment, load the Quick library CHRTBEFR.qlb when you start QBX, for
- example:
-
- QBX /L CHRTBEFR
-
- Table 6.1 lists files and libraries that relate to Presentation Graphics
- toolbox. The "???" stands for the combination of characteristics you chose
- for your object files and libraries during Setup. E or A in the first
- position corresponds to your choice of emulator math or alternate math; F or
- N in the second position corresponds to your choice of near or far strings;
- R or P in the third position corresponds to your choice of target
- environments for executable files, either real or protected mode. For
- instance, CHRTBEFR.LIB is a library that uses the emulator math package, far
- strings, and runs only in DOS or the real-mode "compatibility box" of OS/2.
-
-
- Quick libraries can only use the far strings, emulator, and real mode
- options because these are the only possibilities in the QBX environment. You
- can construct object-module libraries with the near-strings and alternate
- math options, but no BASIC graphics programs can run under OS/2. If you want
- your executable files to use near strings and/or alternate math, you must
- compile them from the command line because the Alternate Math, Near Strings
- and OS/2 Protected Mode options are disabled within QBX whenever a Quick
- library is loaded.
-
- The .QLB and .LIB files were created in the \BC7\LIB directory of your root
- directory, unless you specified a different directory during Setup. Check
- the file PACKING.LST on your distribution disks for further information on
- the .BAS and .OBJ files and to find out where they appear on the
- distribution disks.
-
- When you create a stand-alone executable program from within the QBX
- environment from code that uses Presentation Graphics toolbox, the
- appropriate Presentation Graphics toolbox object-module library must be in
- the directory specified in the Option menu's Set Paths dialog box. Using
- libraries is discussed in Chapter 18, "Using LINK and LIB."
-
- The graphic fonts procedures represented by FONTB.BAS, FONTASM.OBJ, and
- FONTB.BI are built into CHRTBEFR.QLB and other CHRTB object-module libraries
- during Setup. You can use the graphic fonts in your Presentation Graphics
- toolbox programs without loading anything but CHRTBEFR.QLB when you start
- QBX. The graphic fonts are also built as a completely separate library
- (FONTBEFR.QLB). You can use the graphic fonts separately and combine them
- with any other libraries. Two font files (TMSRB.FON and helvb.fon) are
- supplied, but you can use any Microsoft Windows compatible bitmap fonts with
- these procedures. These files are designed specifically for the EGA aspect
- ratio.
-
-
- Presentation graphics Program Structure
-
- Typically in programming, you have to have a thorough understanding of a
- tool before you can even begin using it. However, with the Presentation
- Graphics toolbox you have a choice about the level of understanding you wish
- to cultivate. Even if you don't understand the Presentation Graphics toolbox
- any better than you do right now, you can add very adequate charts to your
- programs by following a few simple steps. To create bar, column, and pie
- charts, you can simply collect data, label the data's parts, and then call
- three routines to chart it on the screen. The following example demonstrates
- how simple it is to make the chart shown in Figure 6.1 with Presentation
- Graphics toolbox.
-
- 1 Specify the proper include file:
-
-
- ' $INCLUDE: 'CHRTB.BI' You must specify the file CHRTB.BI to call
- Presentation Graphics toolbox routines.
-
- 2. Declare variables to pass as arguments to the presentation graphics
- routines:
-
-
- DIM Env AS ChartEnvironment
- DIM DataValues(1 TO 4) AS SINGLE
-
-
-
- DIM Labels(1 TO 4) AS STRING
-
-
-
-
-
- You need a variable of the user-defined type ChartEnvironment, plus data
- and the labels for the charted array variables for your charted data. These
- arrays are always dimensioned starting at 1 (rather than 0). Numeric data
- values are always single precision.
-
- 3. Assemble the plot data (the following DATA and READ statements
- simulate data collection):
-
-
- DATA 28, 20, 30, 32
- DATA "Admin", "Acctng", "Advert", "Prod"
-
-
-
- FOR I=1 TO 4
-
-
-
-
-
- READ DataValues(I)
-
-
-
- NEXT I
-
-
-
- FOR I=1 TO 4
-
-
-
-
-
- READ Labels(I)
-
-
-
- NEXT I
-
- Data can come from a variety of sources. It can result from processing
- elsewhere in the program, be read from files, or even entered from the
- keyboard. Wherever it comes from, you must place it in the arrays
- dimensioned in the preceding step.
-
- 4. Use the Presentation Graphics toolbox routine ChartScreen to set the
- video mode. You cannot use the SCREEN statement:
-
-
- ChartScreen 2 You use the ChartScreen routine as you would normally use
- the SCREEN statement. In this case 2 is passed because it gives graphics on
- a common hardware setup (CGA). If you have a Hercules (or compatible) setup,
- pass a 3 to ChartScreen; if you have an Olivetti, pass a 4. Later examples
- illustrate how to choose the best mode for your user's hardware.
-
- 5. Use the Presentation Graphics toolbox routine DefaultChart to set up
- the chart environment to display the type of chart you want. You pass
- the ChartEnvironment type variable declared in step 2, plus constants
- that describe the kind of chart and its features:
-
-
- DefaultChart Env, cColumn, cPlain DefaultChart supplies most of the
- settings you want. After defaults are set with DefaultChart, you can modify
- them with simple assignment statements (as illustrated in the examples later
- in this chapter).
-
- 6. Use the appropriate Presentation Graphics toolbox routine to display
- the chart. You pass the variables declared in step 2, plus an integer
- that specifies the number of values:
-
-
- Chart Env, Labels(), DataValues(), 4 There are separate routines for
- standard charts (i.e. column, bar, and line charts), for pie charts, scatter
- charts, and charts that display multiple data series.
-
-
-
- 7. Pause execution while chart is displayed:
-
-
- SLEEP You can use BASIC's new SLEEP statement to keep the chart on the
- screen for viewing.
-
- 8. Reset the video mode (optional):
-
-
- SCREEN 0
-
- When your program detects the signal to continue, you may want to reset
- the video mode if graphics are not necessary for the rest of the program.
-
-
-
- Terminology
-
- The following explanations of the terms and phrases used when discussing the
- Presentation Graphics toolbox will help you better understand this chapter
- and its contents.
-
-
- Data Point
-
- A data item having a numeric value. In a chart, a data point is usually one
- value among a series of values to be illustrated. Data points are shown
- either as bars, columns, slices of a pie, or as individual plot characters
- (markers). In the "Presentation Graphics Program Structure" section, each of
- the elements of the DataValues() array was a data point.
-
-
- Data Series
-
- Groups or series of data that can be graphed on the same chart, for example,
- as a continuous set of data points on a graph. In the preceding section, the
- collection of elements comprising the DataValues() array represented a data
- series.
-
- Data items related by a common idea or purpose constitute a "series." For
- example, the day-to-day prices of a stock over the course of a year form a
- single data series. The Presentation Graphics toolbox allows you to plot
- multiple series on the same graph. In theory only your system's memory
- capacity restricts the number of data series that can appear on a graph.
- However, there are practical considerations.
-
- Characteristics such as color and pattern help distinguish one series from
- another; you can more readily differentiate series on a color monitor than
- you can on a monochrome monitor. The number of series that can comfortably
- appear on the same chart depends on the chart type and the number of
- available colors. Only experimentation can tell you what is best for the
- system on which your program will run.
-
-
- Categories
-
- "Categories" are non-numeric data. A set of categories forms a frame of
- reference for comparisons of numeric data. For example, the months of the
- year are categories against which numeric data such as rainfall can be
- plotted. In the example in the section "Presentation Graphics Program
- Structure," each element of the Labels() array was a category.
-
- Regional sales provide another example. A chart can show comparisons of a
- company's sales in different parts of the country. Each region forms a
- category. The sales within each region are numeric data that have meaning
- only within the context of a particular category.
-
-
- Values
-
- "Values" are numeric data. Each value could be represented on a chart by a
- data point. Each element (or data point) in a data series has a value.
- Sales, stock prices, air temperatures, populations--all are series of values
- that can be plotted against categories or against other values.
-
- The Presentation Graphics toolbox allows you to represent different series
- of value data on a single graph. For example, average monthly temperatures
- or monthly sales of heating oil during different years -- or a combination
- of temperatures and sales -- can be plotted together on the same graph.
-
-
- Pie Charts
-
- 467fbfffPresentation graphics can display either a standard or an "exploded"
- pie chart. The exploded view shows the pie with one or more pieces separated
- out for emphasis. Presentation graphics optionally labels each slice of a
- pie chart with a percentage figure. You use a legend to associate each slice
- of the pie with a category name.
- Bar and Column Charts
-
- 48b2bfff4821bfff-----------------------------------------------------
- Line Charts
-
- 4f11bfffTraditionally, line charts show a collection of data points
- connected by lines; hence the name. However, Presentation Graphics toolbox
- can also plot points that are not connected by lines.
- Scatter Diagrams
-
- 512dbfffScatter diagrams illustrate the relationship between numeric values
- in different groups of data to show trends and correlations. This is why
- scatter diagrams are a favorite tool of statisticians and forecasters.They
- are most useful with relatively large populations of data. Consider, for
- example, the relationship between personal income and family size. If you
- poll 1,000 wage earners for their income and family size, you have a scatter
- diagram with 1,000 points. If you combine your results so you're left with
- one average income for each family size, you have a line graph.Sometimes the
- related points on scatter charts are connected by lines. For example, if you
- plotted the mathematical relationship f(x)=x2, you would plot the value x
- against the value x2, and connecting the points with lines would illustrate
- the relationship. However, for statistical graphs involving large groups,
- connecting values with lines would make the chart incomprehensible.
- Axes
-
- All charts created with the Presentation Graphics toolbox (except pie
- charts) are displayed with two perpendicular reference lines called "axes."
- These axes are "yardsticks" against which data is charted. Generally, the
- vertical or y-axis runs from top to bottom of the chart and is placed
- against the left side of the screen. The horizontal or x-axis runs from left
- to right across the bottom of the screen.
-
- The chart type determines which axis is used for category data and which
- axis is used for value data. The x-axis is the category axis for column and
- line charts and the value axis for bar charts. The y-axis is the value axis
- for column and line charts and the category axis for bar charts. The x- and
- y-axes are used for value data in scatter charts.
-
-
- Chart Windows
-
- The "chart window" defines that part of the screen on which the chart is
- drawn. Normally the window fills the entire screen, but Presentation
- Graphics toolbox allows you to resize the window for smaller graphs. By
- moving the chart window to different screen locations, you can view separate
- graphs together on the same screen.
-
-
- Data Windows
-
- While the chart window defines the entire graph including axes and labels,
- the "data window" defines only the actual plotting area. This is the portion
- of the graph to the right of the y-axis and above the x-axis. You cannot
- directly specify the size of the data window. Presentation Graphics
- automatically determines its size based on the dimensions of the chart
- window.
-
-
- Chart Styles
-
- Each of the five types of Presentation Graphics toolbox charts can appear in
- two different "chart styles," as described in Table 6.2.
-
- BarSide-by-sideStackedColumnSide-by-sideStackedLinePoints connected by
- linesPoints onlyScatterPoints connected by linesPoints only
- Bar and column charts have only one style when displaying a single series of
- data. The styles "side-by-side" and "stacked" are applicable when more than
- one series appears on the same chart. The first style arranges the bars or
- columns for the different series side by side, showing relative heights or
- lengths. The stacked style, illustrated in Figure 6.2 for a column chart,
- emphasizes relative sizes between bars or columns and shows the totals of
- the series.
-
- 59a9bfff
- Legends
-
- Presentation graphics can display a "legend" to label the different series
- of a chart, in addition to differentiating between series by using colors,
- lines, or patterns. Pie charts, which only represent a single series, can
- use a legend to identify each "slice" of the pie.
-
- The format of a legend is similar to the legends found on printed graphs and
- maps. A sample of the color and pattern used to graph each distinct data
- series appears next to the series label. The section "Palettes" later in
- this chapter, explains how different data series are identified by color and
- pattern.
-
-
- Five Example Chart Programs
-
- The sample programs that follow use only five of the 19 procedures in the
- Presentation Graphics toolbox: DefaultChart, ChartScreen, ChartPie,
- Chart, and ChartScatter. The BASIC Language Reference describes these and
- the remaining Presentation Graphics toolbox. For information on including
- online help for presentation graphics routines in the Microsoft Advisor
- online Help system, see Chapter 22, "Customizing Online Help."The code in
- the example programs is straightforward, and you should be able to follow
- the programs easily without completely understanding all the details. Each
- program is described with comments so that you can recognize the steps
- mentioned earlier in the section "Presentation Graphics Program Structure."
- The examples make use of the same secondary procedure, BestMode. BestMode
- checks the display adapter and returns the best available mode value for
- displaying charts. As in the preceding example, data and read statements are
- used to simulate data
- generation.-----------------------------------------------------
-
-
- A Sample Data Set
-
- Suppose a grocer wants to graph the sales of orange juice over the course of
- a single year. Sales figures are on a monthly basis, so the grocer selects
- as category data the months of the year from January through December. The
- sales figures are shown in Table 6.3.
-
- February27March42April64May106June157July182August217September128October62Nov
- Example: Pie Chart
-
- The example in this section uses Presentation Graphics toolbox to display a
- pie chart for the grocer's data. Interesting elements of the pie-charting
- example include:
-
- ■ Exploded slices A feature unique to pie charts is that any or all
- of the slices can be separated from the rest of the chart for
- emphasis. When pieces are separated, they are sometimes referred to as
- "exploded." You designate which slices should be separated from the
- rest by defining an integer array of "flags," then setting the flags
- by assigning non-zero values to those array elements corresponding to
- the pie slices you want to have separated from the rest. In the
- example in this section, the Exploded() array causes any slice of the
- pie representing a peak sales month (i.e., OJvalues value greater than
- or equal to 100) to be to be separated from the rest.
-
- ■ Setting chart characteristics The wording, alignment, and color of
- the chart's main title and subtitle are set by assigning values to
- elements of the structured variable Env (a variable of user-defined
- type ChartEnvironment). A constant assigned to another element of the
- Env variable specifies that the chart itself is to have no border
- (Env.Chartwindow.Border = cNo). Note that in all these cases, the
- elements themselves are of user-defined type.
-
-
-
- ■ The section "Customizing Presentation Graphics" later in this chapter
- describes the ChartEnvironment type, as well as its constituent types,
- including TitleType (for the elements MainTitle.Title,
- MainTitle.TitleColor, MainTitle.Justify, SubTitle.Title,
- SubTitle.TitleColor, and SubTitle.Justify) and RegionType (for the
- ChartWindow.Border element). Because of the nesting of user-defined
- types, the names of these variables can be lengthy, but the principle
- is a simple one: specifying the appearance of any chart by simple
- assignment of values to variables that are the same for every chart.
-
- ■ Presentation graphics error codes When an error occurs during
- execution of a Presentation Graphics toolbox routine, an error
- condition code is placed in the ChartErr variable (defined in the
- common block /ChartLib/). A ChartErr value of 0 indicates the routine
- has completed its work without error. In the following examples,
- ChartErr is checked after the call to the ChartScreen routine to make
- sure the chart can be displayed. See the include file CHRTB.BI for a
- listing of the error codes.
-
-
- Example
-
- The following example displays a pie chart (PGPIE.BAS) based on the values
- in Table 6.3.
-
- ' PGPIE.BAS: Create sample pie chart
-
- DEFINT A-Z
- ' $INCLUDE: 'FONTB.BI'
- ' $INCLUDE: 'CHRTB.BI'
- DECLARE FUNCTION BestMode ()
- CONST FALSE = 0, TRUE = NOT FALSE, MONTHS = 12
- CONST HIGHESTMODE = 13, TEXTONLY = 0
-
- DIM Env AS ChartEnvironment' See CHRTB.BI for declaration of
- ' the ChartEnvironment type.
- DIM MonthCategories(1 TO MONTHS) AS STRING' Array for categories
- DIM OJvalues(1 TO MONTHS) AS SINGLE' Array for 1st data series
- DIM Exploded(1 TO MONTHS) AS INTEGER' "Explode" flags array
- ' (specifies which pie slices
- ' are separated).
- ' Initialize the data arrays.
- FOR index = 1 TO MONTHS: READ OJvalues(index): NEXT index
- FOR index = 1 TO MONTHS: READ MonthCategories$(index): NEXT index
-
- ' Set elements of the array that determine separation of pie slices
- FOR Flags = 1 TO MONTHS ' If value of OJvalues(Flags)
- Exploded(Flags) = (OJvalues(Flags) >= 100) ' >= 100 the corre-
- ' sponding flag is set
- ' true, separating slices.
- NEXT Flags
-
- ' Pass the value returned by the BestMode function to the Presentation
- ' Graphics routine ChartScreen to set the graphics mode for charting.
-
- ChartScreen (BestMode) ' Even if SCREEN is already set to an acceptable
- ' mode, you still must set it with ChartScreen.
-
- ' Check to make sure ChartScreen succeeded:
- IF ChartErr = cBadScreen THEN
- PRINT "Sorry, there is a screen-mode problem in the chart library"
- END
- END IF
-
- ' Initialize a default pie chart. Pass Env (the environment variable),
- DefaultChart Env, cPie, cPercent' the constant cPie (for Pie Chart)
- ' and cPercent (label slices with
- ' percentage).
-
- ' Add Titles and some chart options. These assignments modify some
- ' default values set in the variable Env (of type ChartEnvironment)
- ' by DefaultChart.
-
- Env.MainTitle.Title = "Good Neighbor Grocery" ' Specifies the title,
- Env.MainTitle.TitleColor = 15' color of title text,
- Env.MainTitle.Justify = cCenter' alignment of title text,
- Env.SubTitle.Title = "Orange Juice Sales"' text of chart subtitle,
- Env.SubTitle.TitleColor = 11' color of subtitle text,
- Env.SubTitle.Justify = cCenter' alignment of subtitle text,
- Env.ChartWindow.Border = cYes' and presence of a border.
-
- ' Call the pie-charting routine --- Arguments for call to ChartPie are:
- ' EnvEnvironment variable
- ' MonthCategories()Array containing Category labels
- ' OJvalues()Array containing Data values to chart
- ' Exploded()Integer array tells which pieces of the pie should
- ' be separated (non-zero=exploded, 0=not exploded)
- ' MONTHSTells number of data values to chart
-
- ChartPie Env, MonthCategories(), OJvalues(), Exploded(), MONTHS
- SLEEP
- ' If the rest of your program isn't graphic, you could reset original
- ' video mode here.
- END
-
- ' Simulate data generation for chart values and category labels.
- DATA 33,27,42,64,106,157,182,217,128,62,43,36
- DATA "Jan","Feb","Mar","Apr","May","Jun","Jly","Aug","Sep","Oct","Nov"
- DATA "Dec"
-
- '=========== Function to determine and set highest resolution ========
- ' The BestMode function uses a local error trap to check available
- ' modes, then assigns the integer representing the best mode for
- ' charting to its name so it is returned to the caller. The function
- ' terminates execution if the hardware doesn't support a mode
- ' appropriate for Presentation Graphics.
- '======================================================================
- FUNCTION BestMode
-
- ' Set a trap for expected local errors -- handled within the function.
- ON LOCAL ERROR GOTO ScreenError
-
- FOR TestValue = HIGHESTMODE TO 0 STEP -1
- DisplayError = FALSE
- SCREEN TestValue
- IF DisplayError = FALSE THEN
- SELECT CASE TestValue
- CASE 0
- PRINT "Sorry, you need graphics to display charts"
- END
- CASE 12, 13
- BestMode = 12
- CASE 2, 7
- BestMode = 2
- CASE ELSE
- BestMode = TestValue
- END SELECT
- EXIT FUNCTION
- END IF
- NEXT TestValue
- ' Note there is no need to turn off the local error handler.
- ' It is turned off automatically when control passes out of
- ' the function.
-
- EXIT FUNCTION
-
- '==================== Local error handler code =====================
- ' The ScreenError label identifies a local error handler referred to
- ' above. Invalid SCREEN values generate Error # 5 (Illegal
- ' function call) --- so if that is not the error reset ERROR to the
- ' ERR value that was generated so the error can be passed to other,
- ' possibly more appropriate, error-handling routine.
- ' =====================================================================
- ScreenError:
- IF ERR = 5 THEN
- DisplayError = TRUE
- RESUME NEXT
- ELSE
- ERROR ERR
- END IF
- END FUNCTION
-
- Output
-
- The pie chart in Figure 6.3 remains on the screen until a key is pressed.
-
- 36b3bfff-----------------------------------------------------
- Bar Chart
-
- The code for PGPIE.BAS needs only the following alterations to produce bar,
- column, and line charts for the same data:
-
- ■ Give new arguments to DefaultChart that specify chart type and style.
-
- ■ Assign Titles for the x-axis and y-axis in the Env structure (this is
- optional).
-
- ■ Replace the call to ChartPie with Chart. This function produces bar,
- column, and line charts depending on the value of the second argument
- to DefaultChart.
-
- ■ Remove references to the Exploded flag array (applicable only to pie
- charts).
-
-
- Example
-
- PGBAR.BAS is the module-level code for a program to display a bar chart. It
- calls the same procedure (BestMode) listed with the preceding PGPIE.BAS
- example. The following example produces the bar chart shown in Figure 6.4.
-
- ' PGBAR.BAS: Creates sample bar chart
-
- DEFINT A-Z
- ' $INCLUDE: 'CHRTB.BI'
- DECLARE FUNCTION BestMode ()
- CONST FALSE = 0, TRUE = NOT FALSE, MONTHS = 12
- CONST HIGHESTMODE = 13, TEXTONLY = 0
-
- DIM Env AS ChartEnvironment' See CHRTB.BI for declaration of
- ' the ChartEnvironment type
- DIM MonthCategories(1 TO MONTHS) AS STRING ' Array for categories
- ' (used for pie, column,
- ' and bar charts).
-
- DIM OJvalues(1 TO MONTHS) AS SINGLE ' Array for data series.
-
- ' Initialize the data arrays
- FOR index = 1 TO MONTHS: READ OJvalues(index): NEXT index
- FOR index = 1 TO MONTHS: READ MonthCategories$(index): NEXT index
-
- ' Pass the value returned by the BestMode function to the Presentation
- ' Graphics routine ChartScreen to set the graphics mode for charting.
-
-
-
- ChartScreen (BestMode)' Even if SCREEN is already set to an acceptable
- ' mode, you still must set it with ChartScreen.
- ' Check to make sure ChartScreen succeeded.IF ChartErr = cBadScreen THEN
- PRINT "Sorry, there is a screen-mode problem in the chart library."
- END
- END IF
- ' Initialize a default pie chart
- DefaultChart Env, cBar, cPlain' Pass Env (the environment variable),
- ' the constant cBar (for Bar Chart)
- ' and cPlain.
-
- ' Add Titles and some chart options. These assignments modify some
- ' default values set in the variable Env (of type ChartEnvironment)
- ' by DefaultChart.
-
- Env.MainTitle.Title = "Good Neighbor Grocery" ' Specifies text of
- ' the chart title,
- Env.MainTitle.TitleColor = 15' color of title text,
- Env.MainTitle.Justify = cRight' alignment of title text,
- Env.SubTitle.Title = "Orange Juice Sales"' text of chart subtitle,
- Env.SubTitle.TitleColor = 15' color of subtitle text,
- Env.SubTitle.Justify = cRight' alignment of subtitle text,
- Env.ChartWindow.Border = cNo' and absence of a border.
-
- ' The next 2 assignments label the x-axis and y-axis
- Env.XAxis.AxisTitle.Title = "Quantity (cases)"
- Env.YAxis.AxisTitle.Title = "Months"
-
- ' Call the bar-charting routine --- Arguments for call to Chart are:
- ' EnvEnvironment variable
- ' MonthCategories()Array containing Category labels
- ' OJvalues()Array containing Data values to chart
- ' MONTHSTells number of data values to chart
-
- Chart Env, MonthCategories(), OJvalues(), MONTHS
- SLEEP
- ' If the rest of your program isn't graphic,
- ' reset original screen mode here.
- END
-
- ' Simulate data generation for chart values and category labels
- DATA 33,27,42,64,106,157,182,217,128,62,43,36
- DATA "Jan","Feb","Mar","Apr","May","Jun","Jly","Aug","Sep","Oct"
- DATA "Nov","Dec"
-
- Output
-
- The PGBAR.BAS example produces the chart shown in Figure 6.4.
-
- 37a6bfff
- Line and Column Charts
-
- You could turn the grocer's bar chart into a line chart in two easy steps.
- Simply specify the new chart type when calling DefaultChart and switch the
- axis Titles. To produce a line chart for the data, replace the call to
- DefaultChart with:
-
- DefaultChart(Env, cLine, cLines)
-
-
- The constant cLine specifies a line chart, and the constant cLines specifies
- that the points are to be joined by lines. If you pass cNoLines as the third
- argument, the points would appear, but would not be connected. To switch the
- labels on the axes, just replace the assignments to the axis labels as
- follows:
-
- Env.XAxis.AxisTitle.Title="Months"
- Env.YAxis.AxisTitle.Title="Quantity (cases)"
-
- Output
-
- Notice that now the x-axis is labelled "Months" and the y -axis is labelled
- "Quantity (cases)." Figure 6.5 shows the resulting line chart.
-
- 332bbfffCreating an equivalent column chart requires only one change. Use
- the same code as for the line chart and replace the call to DefaultChart
- with:
-
- DefaultChart( Env, cColumn, cPlain )
-
- Output
-
- Figure 6.6 shows the column chart for the grocer's data.
-
- 38bdbfff
- Scatter Diagram
-
- Now suppose that the store owner wants to compare the sales of orange juice
- to the sales of another product, say hot chocolate. Table 6.4 shows a
- tabular comparison.
-
- Example
-
- PGSCAT.BAS is the module-level code for a program to display a scatter
- diagram that illustrates the relationship between the sales of orange juice
- and hot chocolate throughout a 12-month period. It calls the same function
- (BestMode) listed with the PIE.BAS example preceding. Note that the data
- array HCvalues replaces the MonthCategories array in this example.
-
- ' PGSCAT.BAS: Create sample scatter diagram.
-
- DEFINT A-Z
- ' $INCLUDE: 'CHRTB.BI'
- DECLARE FUNCTION BestMode ()
- CONST FALSE = 0, TRUE = NOT FALSE, MONTHS = 12
- CONST HIGHESTMODE = 13, TEXTONLY = 0
- DIM Env AS ChartEnvironment' See CHRTB.BI for declaration of the
- ' ChartEnvironment type
- DIM OJvalues(1 TO MONTHS) AS SINGLE' Array for 1st data series
- DIM HCvalues(1 TO MONTHS) AS SINGLE' Array for 2nd data series
-
- ' Initialize the data arrays
- FOR index = 1 TO MONTHS: READ OJvalues(index): NEXT index
- FOR index = 1 TO MONTHS: READ HCvalues(index): NEXT index
-
- ' Pass the value returned by the BestMode function to the Presentation
- ' Graphics routine ChartScreen to set the graphics mode for charting.
-
- ChartScreen (BestMode)' Even if SCREEN is already set to an
- ' acceptable mode, you still have to
- ' set it with ChartScreen.
- IF ChartErr = cBadScreen THEN' Make sure ChartScreen succeeded.
- PRINT "Sorry, there is a screen-mode problem in the chart library"
- END
- END IF
-
- ' Initialize a default pie chart.
- ' Pass Env (the environment
- DefaultChart Env, cScatter, cNoLines' variable), constant cScatter
- ' (for scatter chart),
- ' cNoLines (unjoined points).
-
- ' Add Titles and some chart options. These assignments modify some
- ' default values set in the variable Env (of type ChartEnvironment)
- ' by DefaultChart.
-
- Env.MainTitle.Title = "Good Neighbor Grocery" ' Specify chart title,
- Env.MainTitle.TitleColor = 11' color of title text,
- Env.MainTitle.Justify = cRight' alignment of title text,
- Env.SubTitle.Title = "OJ vs. Hot Chocolate" ' text of chart subtitle,
- Env.SubTitle.TitleColor = 15' color of subtitle text,
- Env.SubTitle.Justify = cRight' alignment of subtitle text,
- Env.ChartWindow.Border = cNo' and absence of a border.
-
- ' The next two assignments label the x and y axes of the chart
- Env.XAxis.AxisTitle.Title = "Orange Juice Sales"
- Env.YAxis.AxisTitle.Title = "Hot Chocolate Sales"
-
- ' Call the pie-charting routine --- Arguments for call to ChartPie are:
- ' EnvEnvironment variable
- 'OJvaluesArray containing orange-juice sales values to chart
- ' HCvaluesArray containing hot-chocolate sales values to chart
- 'MONTHSNumber of data values to chart
-
- ChartScatter Env, OJvalues(), HCvalues(), MONTHS
- SLEEP
- ' If the rest of your program isn't graphic, you could
- ' reset original screen mode here.
- END
-
- ' Simulate data generation for chart values and category labels.
- DATA 33,27,42,64,106,157,182,217,128,62,43,36
- DATA 37,37,30,19,10,5,2,1,7,15,28,39
-
-
- Output
-
- Figure 6.7 shows the results of PGSCAT.BAS. Notice that the scatter points
- form a slightly curved line, indicating a correlation exists between the
- sales of the two products. The store owner can conclude from the scatter
- diagram that the demand for orange juice is roughly the inverse of the
- demand for hot chocolate.
-
- 3497bfff
- Customizing Presentation Graphics
-
- The Presentation Graphics toolbox is built for flexibility. In the preceding
- examples, you saw how easy it was to change subtitles and axis labels with
- simple assignment statements. Other elements of the environment can be
- modified and customized just as easily. You can use its system of default
- values to produce professional-looking charts with a minimum of programming
- effort. Or, you can fine-tune the appearance of your charts by overriding
- default values and initializing variables explicitly in your program. The
- following section describes all the user-defined data types in the
- Presentation Graphics toolbox so you can decide which characteristics to
- accept as supplied and which ones you want to modify. Modification involves
- declaring a variable of the specified type, then assigning values to its
- constituent elements. These modifications are always done between the call
- to DefaultChart and the call to the routine that actually displays the
- chart.
-
-
- Chart Environment
-
- The include file CHRTB.BI declares a user-defined type, ChartEnvironment,
- that declares the constituent elements of a structured variable called the
- "chart environment" variable (Env in the preceding examples). The chart
- environment describes everything about a chart except the actual data to be
- plotted. The environment determines the appearance of text, axes, grid
- lines, and legends.
-
- Calling DefaultChart fills the chart environment with default values.
- Presentation Graphics allows you to modify any variable in the environment
- before displaying a chart. Most initialization of internal Chartlib
- variables is done through the structured variable you define as having the
- ChartEnvironment data type, when it is passed to DefaultChart.
-
- The sample chart programs provided earlier illustrate how to adjust
- variables in the chart environment. These programs define a structured
- variable, Env, having the ChartEnvironment data type . The elements of the
- Env structure are the chart environment variables, initialized by the call
- to DefaultChart. Environment variables such as the chart title are then
- given specific values, as in:
-
- Env.MainTitle.Title = "Good Neighbor Grocery"
-
- Environment variables that determine colors and line styles deserve special
- mention. The chart environment holds several such variables which can be
- recognized by their names. For example, the variable TitleColor specifies
- the color of title text. Similarly, the variable GridStyle specifies the
- line style used to draw the chart grid.
-
- These variables are index numbers, but do not refer directly to the colors
- or line styles. They correspond instead to palette-entry numbers (Palettes
- are described later in the chapter). If you set TitleColor to 2,
- Presentation Graphics toolbox uses the color code in the second palette
- entry to determine the title's color. Thus the title in this case would be
- the same color as the chart's second data series. If you change the color
- code in the palette, you'll also change the title's color. You don't have to
- understand palettes to use the Presentation Graphics toolbox, but
- understanding how they work gives you greater flexibility in specifying the
- appearance of charts.
-
- The user-defined type ChartEnvironment has 10 elements, 7 of which are
- themselves user-defined types. ChartEnvironment is described in detail in
- the section "ChartEnvironment" later in this chapter.
-
-
- The next several sections lead up to that description by describing the
- user-defined types nested within the ChartEnvironment data type. The
- declaration of ChartEnvironment appearing in CHRTB.BI is as follows:
-
- TYPE ChartEnvironment
- ChartType AS INTEGER
- ChartStyle AS INTEGER
- DataFontAS INTEGER
- ChartWindow AS RegionType
- DataWindow AS RegionType
- MainTitle AS TitleType
- SubTitle AS TitleType
- XAxis AS AxisType
- YAxis AS AxisType
- Legend AS LegendType
- END TYPE
-
- The remainder of this section describes the chart environment data structure
- of the Presentation Graphics toolbox. It first examines structures of the
- four secondary types which make up the chart environment structure. The
- section concludes with a description of the ChartEnvironment structure type.
- Each discussion begins with a brief explanation of the structure's purpose,
- followed by a listing of the structure type definition as it appears in the
- CHRTB.BI file. All symbolic constants are defined in the file CHRTB.BI .
-
-
- RegionType
-
- Structures of the type RegionType contain sizes, locations, and color codes
- for the three windows produced by the Presentation Graphics toolbox: the
- chart window, the data window, and the legend. Refer to the "Terminology"
- section earlier in this chapter for definitions of these terms. Placement of
- the chart window is relative to the screen's logical origin. Placement of
- the data and legend windows is relative to the chart window.
-
- The CHRTB.BI file defines RegionType as:
-
- TYPE RegionType
- X1AS INTEGER
- Y1AS INTEGER
- X2AS INTEGER
- Y2AS INTEGER
- Background AS INTEGER
- BorderAS INTEGER
- BorderStyleAS INTEGER
- BorderColorAS INTEGER
- END TYPE
-
-
- The following table describes the RegionType elements:
-
- ╓┌───────────────┌─────────────────────────────┌─────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- X1, Y1, X2, Y2 Window (region) coordinates The reference point for the
- in pixels. The ordered pair coordinates depends on the
- (X1, Y1) specifies the type of window. The chart
- coordinate of the upper left window is located relative
- corner of the window. The to the upper left corner of
- ordered pair (x2, y2) the screen. The data and
- specifies the coordinate of legend windows are located
- the lower right corner. relative to the upper left
- ────────────────────────────────────────────────────────────────────────────
- the lower right corner. relative to the upper left
- corner of the chart window.
- This allows you to change
- the position of the chart
- window without having to
- redefine coordinates for the
- other two windows.
-
- Background An integer between 0 and
- cPalLen representing a
- palette index (described in
- the section "Palettes") that
- specifies the window's
- background color. The
- default value for Background
- is 0.
-
- Border A cYes, cNo (true/false)
- variable that determines
- whether a border frame is
- ────────────────────────────────────────────────────────────────────────────
- whether a border frame is
- drawn around a window.)
-
- BorderStyle An integer between 0 and
- cPalLen representing a
- palette index (described in
- the section "Palettes") that
- specifies the line style of
- the window's border frame.
- The default value is 1..
-
- BorderColor An integer between 0 and
- cPalLen representing a
- palette index (described in
- the section "Palettes") that
- specifies the color of the
- window's border frame. The
- default value is 1.
-
-
-
-
-
-
- TitleType
-
- Structures of the type TitleType determine text, color, font, and alignment
- of titles appearing in the graph. The CHRTB.BI file defines the structure
- type as:
-
- TYPE TitleType
- TitleAs STRING * 70
- TitleFontAS INTEGER
- TitleColor AS INTEGER
- Justify AS INTEGER
- END TYPE
-
- The following list describes TitleType elements:
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- ────────────────────────────────────────────────────────────────────────────
- Title A string containing title text.
- For example, if Env is a
- structured variable of type
- ChartEnvironment, then the
- variable Env.MainTitle.Title holds
- the character string used for the
- main title of the chart. Similarly,
- Env.XAxis.AxisTitle.Title contains
- the x-axis title.
-
- TitleFont An integer between 1 and the
- number of fonts loaded that
- specifies a title's font. The
- default value for TitleFont is 1.
-
- TitleColor An integer between 0 and cPalLen
- that specifies a title's color.
- The default value for TitleColor
- is 1.
-
- ────────────────────────────────────────────────────────────────────────────
- Justify An integer specifying how the
- title is placed on its line within
- the chart window. The symbolic
- constants defined in the CHRTB.BI
- file for this variable are cLeft,
- cCenter, and cRight, meaning flush
- left, centered, and flush right.
-
-
-
-
-
-
- AxisType
-
- Structures of type AxisType contain variables for the axes such as color,
- scale, grid style, and tick marks. The CHRTB.BI file defines the AxisType
- structure as:
-
- TYPE AxisType
- GridAS INTEGER
- GridStyleAS INTEGER
- AxisTitleAS TitleType
- AxisColorAS INTEGER
- Labeled AS INTEGER
- RangeTypeAS INTEGER
- LogBase AS SINGLE
- AutoScaleAS INTEGER
- ScaleMinAS SINGLE
- ScaleMaxAS SINGLE
- ScaleFactorAS SINGLE
- ScaleTitle AS TitleType
- TicFont AS INTEGER
- TicIntervalAS SINGLE
- TicFormatAS INTEGER
- TicDecimalsAS INTEGER
- END TYPE
-
- The following list describes the elements of the AxisType structure:
-
- ╓┌──────────┌───────────────────────────────┌────────────────────────────────╖
- Element Description
- ────────────────────────────────────────────────────────────────────────────
- Grid A cYes, cNo (true/false) value
- that determines whether grid
- lines are drawn for the
- associated axis. Grid lines
- span the data window
- perpendicular to the axis. One
- grid line is drawn for each
- tick mark on the axis.
-
- GridStyle An integer between 0 and Note that the color of the
- cPalLen that specifies the parallel axis determines the
- grid's line style. Lines can color of the grid lines. Thus
- be solid, dashed, dotted, or the x-axis grid is the same
- some combination. Grid styles color as the y-axis, and the
- are drawn from PaletteB%, y-axis grid is the same color
- described in the section as the x-axis.
- "Palettes," later in this
- chapter. The default value for
- Element Description
- ────────────────────────────────────────────────────────────────────────────
- chapter. The default value for
- GridStyle is 1.
-
- AxisTitle A TitleType structure that
- defines the title of the
- associated axis. The title of
- the y-axis displays vertically
- to the left of the y-axis, and
- the title of the x-axis
- displays horizontally below
- the x-axis.
-
-
-
-
-
-
- ╓┌──────────┌────────────────────┌────────────────────┌──────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- ────────────────────────────────────────────────────────────────────────────
- AxisColor An integer between
- 0 and cPalLen that
- specifies the color
- used for the axis
- and parallel grid
- lines. (See the
- preceding
- description for
- GridStyle.) Note
- that this member
- does not determine
- the color of the
- axis title. That
- selection is made
- through the
- structure AxisTitle.
- The default value
- is 1.
-
- Labelled A cYes, cNo
- ────────────────────────────────────────────────────────────────────────────
- Labelled A cYes, cNo
- (true/false) value
- that determines
- whether tick marks
- and labels are
- drawn on the axis.
- Axis labels should
- not be confused
- with axis titles.
- Axis labels are
- numbers or
- descriptions such
- as "23.2" or
- "January" attached
- to each tick mark.
-
- RangeType An integer that Specify a linear Use cLogAxis to
- determines whether scale with the specify a logarithmic
- the scale of the cLinearAxis RangeType.
- axis is linear or constant. A linear Logarithmic scales
- ────────────────────────────────────────────────────────────────────────────
- axis is linear or constant. A linear Logarithmic scales
- logarithmic. The scale is best when are useful when the
- RangeType variable the difference data varies
- applies only to between axis exponentially. Line
- value data (not to minimum and maximum graphs of
- category labels). is relatively small. exponentially varying
- For example, a data can be made
- linear axis range 0 straight with a
- - 10 results in 10 logarithmic RangeType.
- tick marks evenly
- spaced along the
- axis.
-
- LogBase If RangeType is
- logarithmic, the
- LogBase variable
- determines the log
- base used to scale
- the axis. Default
- value is 10.
- ────────────────────────────────────────────────────────────────────────────
- value is 10.
-
- AutoScale A cYes, cNo
- (true/false)
- variable. If
- AutoScale is cYes,
- the Presentation
- Graphics toolbox
- automatically
- determines values
- for ScaleMin,
- ScaleMax,
- ScaleFactor,
- ScaleTitle,
- TicInterval,
- TicFormat, and
- TicDecimals (see
- the following). If
- AutoScale equals
- cNo, these seven
- ────────────────────────────────────────────────────────────────────────────
- cNo, these seven
- variables must be
- specified in your
- program.
-
- ScaleMin Lowest value
- represented by the
- axis.
-
- ScaleMax Highest value
- represented by the
- axis.
-
-
-
-
-
-
- ╓┌──────────────┌─────────────────────────────┌──────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- ────────────────────────────────────────────────────────────────────────────
- ScaleFactor All numeric data is scaled If AutoScale is set to cYes,
- by dividing each value by the Presentation Graphics
- ScaleFactor. For relatively toolbox automatically
- small values, the variable determines a suitable value
- ScaleFactor should be 1, for ScaleFactor based on the
- which is the default. But range of data to be plotted.
- data with large values The Presentation Graphics
- should be scaled by an toolbox selects only values
- appropriate factor. For that are a factor of 1000 --
- example, data in the range 2 that is, values such as one
- million - 20 million should thousand, one million, or one
- be plotted with ScaleMin set billion. It then labels the
- to 2, ScaleMax set to 20, ScaleTitle appropriately (see
- and ScaleFactor set to 1 the following). If you desire
- million. some other value for scaling,
- you must set AutoScale to cNo
- and set ScaleFactor to the
- desired scaling value.
-
- ScaleTitle A TitleType structure Note: The following four
- ────────────────────────────────────────────────────────────────────────────
- ScaleTitle A TitleType structure Note: The following four
- defining the attributes of a variables apply to axes with
- title. If AutoScale is cYes, value data. TicFont also
- Presentation Graphics applies to category labels;
- toolbox automatically writes the remainder are ignored for
- a scale description to the category axis.
- ScaleTitle. If AutoScale
- equals cNo and ScaleFactor
- is 1, ScaleTitle.Title
- should be blank. Otherwise
- your program should copy an
- appropriate scale
- description to
- ScaleTitle.Title, such as
- "(x 1000)," "(in millions of
- units)," "times 10 thousand
- dollars," etc. For the
- y-axis, the Scaletitle text
- displays vertically between
- the axis title and the
- ────────────────────────────────────────────────────────────────────────────
- the axis title and the
- y-axis. For the x-axis the
- scale title appears below
- the x-axis title.
-
- TicFont An integer between 1 and
- the total number of fonts
- loaded specifying which of a
- group of currently loaded
- fonts to use for this axis's
- tick marks. The default
- value is 1.
-
- TicInterval Sets interval between tick
- marks on the axis. The tick
- interval is measured in the
- same units as the numeric
- data associated with the
- axis. For example, if two
- sequential tick marks
- ────────────────────────────────────────────────────────────────────────────
- sequential tick marks
- correspond to the values 20
- and 25, the tick interval
- between them is 5.
-
- TicFormat An integer that determines
- the format of the labels
- assigned to each tick mark.
- Set TicFormat to cExpFormat
- for exponential format or to
- cDecFormat for decimal (the
- default).
-
- TicDecimals Number of digits to display
- after the decimal point in
- tick labels. Maximum value
- is 9.
-
-
-
-
-
-
- LegendType
-
- Structured variables of the LegendType user-defined type contain size,
- location, and colors of the chart legend. The CHRTB.BI file defines the
- elements of LegendType as:
-
- TYPE LegendType
- Legend AS INTEGER
- PlaceAS INTEGER
- TextColorAS INTEGER
- TextFontAS INTEGER
- AutoSizeAS INTEGER
- LegendWindowAS RegionType
-
- The following table describes LegendType elements:
-
- ╓┌──────────┌───────────────────────────────┌────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- ────────────────────────────────────────────────────────────────────────────
- Legend A cYes, cNo (true/false)
- variable that determines
- whether a legend is to appear
- on a multi-series chart. Pie
- charts always have a legend.
- The Legend variable is ignored
- by routines that graph other
- single-series charts.
-
- Place An integer that specifies the These settings influence the
- location of the legend size of the data window. If
- relative to the data window. Place equals cBottom or cRight,
- Setting the variable Place Presentation Graphics toolbox
- equal to the constant cRight automatically sizes the data
- positions the legend to the window to accommodate the
- right of the data window. legend. If Place equals
- Setting Place to cBottom cOverlay the data window is
- positions the legend below the sized without regard to the
- data window. Setting Place to legend.
- cOverLay positions the legend
- ────────────────────────────────────────────────────────────────────────────
- cOverLay positions the legend
- within the data window.
-
- TextColor An integer between 0 and
- cPalLen that specifies the
- color of text within the
- legend window.
-
- TextFont An integer specifying which of
- a group of currently loaded
- fonts to use for the legend
- text.
-
-
-
-
-
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- ────────────────────────────────────────────────────────────────────────────
- AutoSize A cYes, cNo (true/false) variable
- that determines whether the
- Presentation Graphics toolbox will
- automatically calculate the size
- of the legend. If AutoSize equals
- cNo, the legend window must be
- specified in the LegendWindow
- structure (see the following).
-
- LegendWindow A RegionType structure that
- defines coordinates, background
- color, and border frame for the
- legend. Coordinates given in
- LegendWindow are ignored (and
- overwritten) if AutoSize is cYes.
-
-
-
-
-
-
- ChartEnvironment
-
- A structured variable of the ChartEnvironment type defines the chart
- environment. The following listing shows that a ChartEnvironment type
- structure consists almost entirely of structures of the four types discussed
- in the preceding sections.
-
- The CHRTB.BI file defines the ChartEnvironment structure type as:
-
- TYPE ChartEnvironment
- ChartType AS INTEGER
- ChartStyle AS INTEGER
- DataFontAS INTEGER
- ChartWindow AS RegionType
- DataWindow AS RegionType
- MainTitle As TitleType
- SubTitle As TitleType
- XAxis As AxisType
- YAxis As AxisType
- Legend As LegendType
- END TYPE
-
- The following list describes ChartEnvironment elements:
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- ChartType An integer that determines the
- type of chart displayed. The value
- of the variable ChartType is
- either cBarChart, cColumnChart,
- cLineChart, cScatterChart, or
- cPieChart. This variable is set
- from the second argument for the
- DefaultChart routine.
-
-
-
-
-
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- ChartStyle An integer that determines the
- style of the chart. Legal values
- for ChartStyle are cPercent and
- cNoPercent for pie charts;
- cStacked and cPlain for bar and
- column charts; and cLines and
- cPoints for line graphs and
- scatter diagrams. This variable
- corresponds to the third argument
- for the DefaultChart routine.
-
- DataFont An integer that identifies the
- font to use in drawing the
- plotting characters in line charts
- and scatter charts. The range of
- possible values depends on how
- many fonts are loaded. See the
- section "Loading Graphics Fonts"
- later in this chapter, for
- information on loading fonts.
- ────────────────────────────────────────────────────────────────────────────
- information on loading fonts.
-
- ChartWindow A RegionType structure that
- defines the appearance of the
- chart window.
-
- DataWindow A RegionType structure that
- defines the appearance of the data
- window..
-
- MainTitle A TitleType structure that defines
- the appearance of the main title
- of the chart.
-
- SubTitle A TitleType structure that defines
- the appearance of the chart's
- subtitle.
-
- XAxis An AxisType structure that defines
- the appearance of the x-axis.
- ────────────────────────────────────────────────────────────────────────────
- the appearance of the x-axis.
- (This variable is not applicable
- for pie charts.)
-
- YAxis An AxisType structure that defines
- the appearance of the y-axis.
- (This variable is not applicable
- for pie charts.)
-
- Legend A LegendType structure that
- defines the appearance of the
- legend window. Applies to
- multi-series and pie charts. Not
- applicable to single-series charts.
-
-
-
-
-
-
- Note that all the data in a ChartEnvironment type structure is initialized
- by calling the DefaultChart routine. If your program does not call
- DefaultChart, it must explicitly define every variable in the chart
- environment -- a tedious and unnecessary procedure. The recommended method
- for adjusting the appearance of your chart is to initialize variables for
- the proper chart type by calling the DefaultChart routine, and then
- reassign selected environment variables such as Titles.
-
-
- Palettes
-
- The Presentation Graphics toolbox displays each data series in a way that
- makes it discernible from other series. It does this by defining separate
- "palettes" to determine the color, line style, fill pattern, and plot
- characters for each different data series in a chart. There is also a
- palette of line styles used to determine the appearance of window borders
- and grid lines.
-
- The Presentation Graphics toolbox maintains a set of default palettes. What
- appears in the default palettes depends on the mode specified in the
- ChartScreen routine, which in turn depends on the BASIC screen modes
- supported by the host system. Figure 6.8 illustrates the default palettes
- for a screen mode that permits four colors (indexed as 0,1,2,3).
-
- 2097bfff-----------------------------------------------------
- Each column in Figure 6.8 represents one of the Presentation Graphics
- toolbox palettes. When a data series is displayed on a chart, one value from
- each column in the chart is used to determine the corresponding
- characteristic. Therefore, each of rows 1-15 in Figure 6.8 represents the
- characteristics that would be displayed for one of up to 15 data series in a
- chart displayed on the specified hardware.
-
- Note
-
- Don't confuse the Presentation Graphics toolbox palettes with BASIC's
- PALETTE statement (used to map colors other than the defaults to the color
- attributes for EGA, VGA, and MCGA adapters). The Presentation Graphics
- toolbox data series palettes are specific to the Presentation Graphics
- toolbox.
-
- The Presentation Graphics toolbox provides three routines that you can use
- to customize the palettes if you wish. Because they are declared in the
- include file chrtb.bi (as follows), you can invoke them without a call
- statement and without parentheses around the argument list:
-
- GetPaletteDef PaletteC% ( ), PaletteS% ( ), PaletteP$ ( ), PaletteCH% (
- ), PaletteB% ( )
- SetPaletteDef PaletteC% ( ), PaletteS% ( ), PaletteP$ ( ), PaletteCH% (
- ), PaletteB% ( )
- ResetPaletteDef
-
- All the parameters are one-dimensional arrays of length cPalLen (starting
- with the subscript 0 and extending to subscript 15). GetPaletteDef lets you
- access the current palette values. SetPaletteDef can be used to substitute
- custom values within the palette arrays. ResetPaletteDef reinstates the
- default values. GetPaletteDef is the sub procedure that gets the array
- values.
-
- When you invoke ChartScreen to reset the video mode, the arrays are
- initialized to their default values. Once ChartScreen has filled the arrays
- with the default values for the specified screen mode, you can change values
- in any of the arrays. You use GetPaletteDef to transfer the default values
- to your array variables, then invoke SetPaletteDef after you assign your
- custom values to the arrays you want to change. ResetPaletteDef restores
- the internal chart palette to the original default values, so you need not
- save the values of your first GetPaletteDef call for resetting defaults. If
- you try to call any of these routines before an initial call to
- ChartScreen, an error is generated. The parameters of GetPaletteDef and
- SetPaletteDef are defined in the following table:
-
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Parameter Definition
- ────────────────────────────────────────────────────────────────────────────
- PaletteC%( ) Integer array corresponding to
- color number palette entries.
- Changes here change colors of
- items like data lines and text.
-
- PaletteS%( ) Integer array determining
- appearance of lines in
- multi-series line graphs (for
- example, solid, dotted, dashed,
- etc.).
- Parameter Definition
- ────────────────────────────────────────────────────────────────────────────
- etc.).
-
- PaletteP$( ) String array determining the
- bit-map pattern of the filled-in
- areas in pie, bar, and column
- charts.
-
- PaletteCH%( ) Integer array specifying which
- ASCII character is used on a graph
- for the plot points of each data
- series in a multi-series line
- graphs.
-
- PaletteB%( ) Integer array used to set lines in
- the display that don't appear as
- data lines within a graph, for
- example window borders and grid
- lines.
-
- Parameter Definition
- ────────────────────────────────────────────────────────────────────────────
-
-
-
-
- The following five sections further describe each of the parameters in the
- preceding list, and effects of calls to the SetPaletteDef routine using
- non-default values in the arrays.
-
- Note
-
- If a pie chart has more than 15 slices, slice 16 will have the same color or
- fill pattern as slice 2; slice 17 will have the same color or fill pattern
- as slice 3, and so on. Similarly, if the background is not the default, one
- slice of the pie may be the same color as the background. See the section
- "Fill Patterns" later in this chapter for an explanation of how to change
- these elements.
-
-
- Colors
-
- One of the elements used to distinguish one data series from another is
- color. The possible colors correspond to pixel values valid for the current
- graphics mode. (See column 1 in Figure 6.8. Refer to Chapter 5, "Graphics,"
- for a description of pixel values.) Each row in Figure 6.8 contains pixel
- values that refer to these available colors. These color codes are the
- values placed in the PaletteC%() array when ChartScreen is called. The
- color code in each entry (i.e. the individual elements of the PaletteC%()
- array) then determines the color used to graph the data series associated
- with the entry.
-
- Say for example, you have a chart with several lines representing different
- series of data. The default background color for the chart is represented by
- the code in the PaletteC%(0) element, the color for the first line
- corresponds to the code in the PaletteC%(1) element, the color for the
- second line corresponds to the code in the PaletteC%(2) element, and so
- forth. These colors are also used for labels, titles, and legends.
-
- The first color is always black, which is the pixel value for the screen
- background color. The second is always white. The remaining elements are
- repeating sequences of available pixel values, beginning with 1.
-
-
- The values in the PaletteC%() array are passed to a BASIC color statement
- by the internal charting routines. Each value represents three things: a
- display attribute, a pixel value, and a color attribute.
-
- For example, calling ChartScreen 1 with a CGA system (320 X 200 graphics)
- provides four colors for display. Pixel values from 0 to 3 determine the
- possible pixel colors--say, black, cyan, magenta, and white respectively. In
- this case the first eight available color values would be as follows:
-
- ╓┌────────────┌────────────┌─────────────────────────────────────────────────╖
- Color index Pixel value Color
- ────────────────────────────────────────────────────────────────────────────
- 0 0 Black
- 1 3 White
- 2 1 Cyan
- 3 2 Magenta
- 4 3 White
- 5 1 Cyan
- Color index Pixel value Color
- ────────────────────────────────────────────────────────────────────────────
- 5 1 Cyan
- 6 2 Magenta
- 7 3 White
-
-
-
-
- Notice that the sequence of available colors repeats from the third element.
- The first data series in this case would be plotted in white, the second
- series in cyan, the third series in magenta, the fourth series again in
- white, and so forth.
-
- Video adapters such as the EGA allow 16 on-screen colors. This allows the
- Presentation Graphics toolbox to graph more series without duplicating
- colors.
-
- You can use SetPaletteDef to change the color code assignments. For
- example, in the preceding CGA example, if you didn't want the color red to
- appear in your chart, you could refill the elements of the PaletteC % ()
- array as follows:
-
- PaletteC %(3) = 3 : PaletteC %(4) = 1 : PaletteC %(5) = 3 :
- PaletteC %(6) = 1 PaletteC %(7) = 3
-
- However, if more than two data series appeared in the graph, the lines
- representing series after the first two would repeat the colors of the first
- two. To differentiate these lines clearly, you would have to adjust the
- available line styles (described in the following section "Line Styles") for
- the palettes. In types of charts other than line charts (e.g. pie charts),
- changing color assignments would also necessitate modifying the available
- fill patterns (described in the section "Fill Patterns") as well as the line
- styles available. Note that the colors in pie, bar, and column charts are
- not determined by the available colors but by color attributes of the fill
- patterns. Reassigning the values in the PaletteC%() array changes only the
- outline of a bar, column, or pie slice.
-
-
- Line Styles
-
- The Presentation Graphics toolbox matches the available colors with a
- collection of different line styles (column two in Figure 6.8). These are
- the values in PaletteS%() array (the second parameter to the GetPaletteDef
- routine ). Entries for the line styles define the appearance of lines such
- as axes and grids. Lines can be solid, dotted, dashed, or of some
- combination.
-
- Each palette entry (each entry in the PaletteS% column) is a code that
- refers to one of the line styles in the same way that each entry in the
- PaletteC% column is a color code that refers to a color index. The style
- code value in a palette is applicable only to line graphs and lined scatter
- diagrams. The style code determines the appearance of the lines drawn
- between points.
-
- The palette entry's style code adds further variety to the lines of a
- multi-series graph. It is most useful when the number of lines in a chart
- exceeds the number of available colors. For example, a graph of nine
- different data series must repeat colors if only three foreground colors are
- available for display. However, the style code for each color repetition
- will be different, ensuring that none of the lines look the same. As
- mentioned previously, if you modified the repetition pattern for the colors,
- you would have to adjust the line styles repetition pattern as well.
- Extending the example of the previous section, if you limited your chart
- lines to two of the available three foreground colors, you would need to
- adjust the style pool so that differentiation by type of line would begin
- earlier. You would do this by changing the PaletteS % () array as follows:
-
- PaletteS%(0) = &HFFFF : PaletteS%(1) = &HFFFF : PaletteS%(2) = &HFFFF
- PaletteS%(3) = &HF0F0 : PaletteS%(4) = &HF0F0 : PaletteS%(5) = &HF060
- PaletteS%(6) = &HF060 : PaletteS%(7) = &HCCCC
-
- Fill Patterns
-
- The Presentation Graphics toolbox environment also maintains a default
- collection of fill patterns (column three, PaletteP $ ( ), in Figure 6.8).
- Fill patterns determine the fill design for column, bar, and pie charts.
- These are the values in the PaletteP $ ( ) array (the third parameter to
- the GetPaletteDef routine). The PaletteP$( ) array values are used within
- the Presentation Graphics toolbox as the paint parameters to the PAINT
- statement. Manipulating the fill patterns is trickier than the colors and
- line styles, because the fill patterns determine both the pattern and the
- color of a fill. The discussion in this section depends on an understanding
- of information in the section "Painting with Patterns: Tiling" in Chapter 5,
- "Graphics," which describes how to manipulate "bit maps" in BASIC. The
- example in the section "Pattern Editor" in that chapter is also instructive.
-
- Each string in the PaletteP $ () array contains information that is passed
- as the paint parameter to the PAINT statement which then defines the fill
- pattern (and the color of the fill, if color is available), for the data
- series associated with the palette.
-
- You can change the fill color and pattern for pie, column, and bar charts
- using the MakeChartPattern$ and GetPattern$ routines in combination with
- the GetPaletteDef and SetPaletteDef routines.
-
-
- To change fill pattern and color, first create a one-dimensional string
- array with 0 to cPalLen elements just as with the PaletteC%() and
- Palettes%() integer arrays. For example:
-
- DIM Fills$(0 to cPalLen)
-
- After calling GetPaletteDef with Fills$() as the third argument, use the
- MakeChartPattern$ routine to construct the values to pass as elements of the
- Fills$() array. MakeChartPattern$ has the following syntax:
-
- MakeChartPattern$( RefPattern$, Foreground%, Background%)
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Parameter Description
- ────────────────────────────────────────────────────────────────────────────
- Env Environment variable of
- ChartEnvironment type.
-
- Cat$() String array of category
- specifications for x-axis.
-
- Val!() Two-dimensional array of
- single-precision numbers
- representing numeric data to be
- plotted.
-
- N% Number of rows in the
- two-dimensional array (that is,
- the number of elements in the
- Parameter Description
- ────────────────────────────────────────────────────────────────────────────
- the number of elements in the
- first dimension).
-
- First% Integer representing the first
- column of data (that is, which
- element of the second array
- dimension represents the first
- data series).
-
- Last% Integer representing the final
- column of data (that is, which
- element of the second array
- dimension represents the last data
- series)..
-
- SerLabels$() and Labs$() String arrays that describe the
- labels for each data series in the
- legend of the chart. There is one
- string for each of the columns in
- Parameter Description
- ────────────────────────────────────────────────────────────────────────────
- string for each of the columns in
- the Val!() array.
-
- ValX!() Two-dimensional array of
- single-precision numbers
- representing the x-axis part of
- each scatter-chart point.
-
- ValY!() Two-dimensional array of
- single-precision numbers
- representing the y-axis part of
- each scatter-chart point.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
-
- Chapter 7: Programming with Modules
-
- This chapter shows you gain more control over your programming projects
- by dividing them into modules. Modules provide a powerful organizing
- function by letting you divide a program into logically related parts
- (rather than keeping all the code in one file).
-
- This chapter will show you how to use modules to:
-
- ■ Write and test new procedures separately from the rest of the program.
-
- ■ Create libraries of your own SUB and FUNCTION procedures that can be
- added to any new program.
-
- ■ Combine routines from other languages (such as C or MASM) with your
- BASIC programs.
-
- ■ Preserve memory when working within QBX.
-
-
-
- Why Use Modules?
-
- A module is a file that contains an executable part of your program. A
- complete program can be contained in a single module, or it can be divided
- among two or more modules.
-
- In dividing a program into modules, logically related sections are placed in
- separate files. This organization can speed and simplify the process of
- writing, testing, and debugging.
-
- Dividing your program into modules has these advantages:
-
- ■ Procedures can be written separately from the rest of the program,
- then combined with it. This arrangement is especially useful for
- testing the procedures, since they can then be checked outside the
- environment of the program.
-
- ■ Two or more programmers can work on different parts of the same
- program without interference. This is especially helpful in managing
- complex programming projects.
-
- ■ As you create procedures that meet your own specific programming
- needs, you can add these procedures to their own module. They can then
- be reused in new programs simply by loading that module.
-
- ■ Multiple modules simplify software maintenance. A procedure used by
- many programs can be in one library module; if changes are needed, the
- procedure only has to be modified once.
-
- ■ Modules can contain general-purpose procedures that are useful in a
- variety of programs (such as procedures that evaluate matrixes, send
- binary data to a communications port, alter strings, or handle
- errors). These procedures can be stored in modules, then used in new
- programs simply by loading the appropriate module into QBX.
-
-
-
- Main Modules
-
- The module containing the first executable statement of a program is called
- the "main module." This statement is never part of a procedure because
- execution cannot begin within a procedure.
-
- Everything in a module except SUB and FUNCTION procedures is "module-level
- code." This includes declarations and definitions, executable program
- statements and module-level error-handling routines. Figure 7.1 illustrates
- the relationship between these elements.
-
-
- Non-Main Modules and Procedure-Level Modules
-
- A non-main module can have module-level and procedure-level code. The
- module-level code consists of metacommands (such as $INCLUDE), declarations
- and definitions (including COMMON statements), and any event-trapping or
- module-level error-handling routine.
-
- A non-main module does not have to contain module-level code. It can consist
- of nothing but SUB and FUNCTION procedures. Indeed, such a procedures-only
- module is the most important use of modules. Here's how to create one:
-
- 1. Invoke QBX without opening or loading any files.
-
- 2. Write all the SUB and FUNCTION procedures you wish, but don't enter
- any module-level code. (Any error- or event-trapping routines and
- BASIC declarations needed are exceptions.)
-
- 3. Choose Save As from the File menu to name and save this module.
- To move procedures from one module to another:
-
- 4. From QBX, load the files containing the procedures you want to move. 2
- menu to load it, too. If it doesn't exist, choose Create File from the
- File menu to make the new file.
-
- 5. Choose SUBs from the View menu and the Move option to transfer the
- procedures from the old to the new file. This transfer is made final
- when you quit QBX and respond Yes to the dialog box that asks whether
- you want to save the modified files; otherwise, the procedures remain
- where they were when you started.
-
-
-
- Loading Modules
-
- In QBX you can load as many modules as you wish (limited only by the
- available memory) by choosing Load File from the File menu. All the
- procedures in all the loaded modules can be called from any other procedure
- or from module-level code. No error occurs if a module happens to contain a
- procedure that is never called.
-
- QBX begins execution with the module-level code of the first module loaded.
- If you want execution to begin in a different module, choose Set Main Module
- from the Run menu. Execution halts at the END statement in the designated
- main module.
-
- The ability to choose which module-level code gets executed is useful when
- comparing two versions of the same program. For example, you might want to
- test different user interfaces by putting each in a separate module. You can
- also place test code in a module containing only procedures, then use the
- Set Main Module command to switch between the program and the tests.
-
- You do not have to keep track of which modules your program uses.
- Whenever you choose Save All from the File menu, QBX creates (or updates)
- a .MAK file, which lists all the modules currently loaded. The next time
- the main module is loaded by choosing Open Program from the File menu,
- QBX consults this .MAK file and automatically loads the modules listed
- in it.
-
-
- Using DECLARE with Multiple Modules
-
- The DECLARE statement has several important functions in BASIC. Using a
- DECLARE statement will do the following:
-
- ■ Specify the sequence and data types of a procedure's parameters. ■ En
- that the arguments agree with the parameters in both number and data
- type.
-
- ■ Identify a FUNCTION procedure's name as a procedure name, not a
- variable name.
-
-
- QBX has its own system for automatically inserting the required DECLARE
- statements into your modules. The section "Checking Arguments with the
- DECLARE Statement" in Chapter 2, "SUB and FUNCTION Procedures," explains the
- features and limitations of this system.
-
- Despite QBX's automatic insertion of the DECLARE statement, you may wish to
- create a separate include file that contains all the DECLARE statements
- required for a program. You can update this file manually as you add and
- delete procedures or modify your argument lists.
-
-
- Note
-
- If you write your programs with a text editor (rather than in QBX) and
- compile from the command line, you must insert DECLARE statements manually.
-
-
- Accessing Variables from Two or More Modules
-
- You can use the SHARED attribute to make variables accessible at module
- level and within that module's procedures. If these procedures are moved to
- another module, however, these variables are no longer shared.
-
- You could pass these variables to each procedure through its argument list.
- This may be inconvenient, though, if you need to pass a large number of
- variables.
-
- One solution is to use COMMON statements, which enable two or more modules
- to access the same group of variables. The section "Sharing Variables with
- SHARED" in Chapter 2, "SUB and FUNCTION Procedures," explains how to do
- this.
-
- Another solution is to use a TYPE... END TYPE statement to combine all the
- variables you wish to pass into a single structure. The argument and
- parameter lists then have to include only one variable name, no matter how
- many variables are passed.
-
-
- If you are simply splitting up a program and its procedures into separate mod
- these approaches works well. If, on the other hand, you are adding a
- procedure to a module (for use in other programs), you should avoid using a
- COMMON statement. Modules are supposed to make it easy to add existing
- procedures to new programs; COMMON statements complicate the process. If a
- procedure needs a large group of variables, it may not belong in a separate
- module.
-
-
- Using Modules During Program Development
-
- When you start a new programming project, you should first look through
- existing modules to see if there are procedures that can be reused in your
- new software. If any of these procedures aren't already in a separate
- module, you should consider moving them to one.
-
- As your program takes shape, newly written procedures automatically become
- part of the program file (that is, the main module). You can move them to a
- separate module for testing or perhaps add them to one of your own modules
- along with other general-purpose procedures that are used in other programs.
-
- Your program may need procedures written in other languages. (For example,
- MASM is ideal for direct interface with the hardware, FORTRAN has almost any
- math function you might want, Pascal allows the creation of sophisticated
- data structures, and C provides structured code and direct memory access.)
- These procedures are compiled and linked into a Quick library for use in
- your program. You can also write a separate module to test Quick library
- procedures in the same way you would test other procedures.
-
-
- Compiling and Linking Modules
-
- The end product of your programming efforts is usually a stand-alone
- executable file. You can create one in QBX by loading all of a program's
- modules, then choosing Make EXE File from the Run menu.
-
- You can also compile modules from the command line using the BASIC Compiler
- (BC), then use LINK to combine the object code. Object files from code
- written in other languages can be linked at the same time.
-
-
- Note
-
- When you choose Make EXE File from QBX, all the module-level code and every
- procedure currently loaded is included in the executable file, whether or
- not the program uses this code. If you want your program to be as compact as
- possible, you must unload all unneeded module-level code and all unneeded
- procedures before compiling. The same rule applies when using BC to compile
- from the command line; all unused code should be removed from the files.
-
-
-
- Quick Libraries
-
- Although Microsoft Quick libraries are not modules, it is important that you
- understand their relationship to modules.
-
- A Quick library contains nothing but procedures. These procedures can be
- written in BASIC or any other Microsoft language.
-
- A Quick library contains only compiled code. (Modules contain BASIC source
- code.) The code in a Quick library can come from any combination of
- Microsoft languages. The chapter "Creating and Using Quick Libraries"
- explains how to create Quick libraries from object code and how to add new
- object code to existing Quick libraries.
-
- Quick libraries have several uses:
-
- ■ They provide an interface between BASIC and other languages.
-
- ■ They allow designers to hide proprietary software. Updates and
- utilities can be distributed as Quick libraries without revealing the
- source code.
-
- ■ They load faster and are usually smaller than modules. If a large
- program with many modules loads slowly, converting the modules other
- than the main module into a Quick library will improve loading
- performance.
-
-
- Note, however, that modules are the easiest way to work on procedures during
- development because modules are immediately ready to run after each edit;
- you don't have to recreate the Quick library. If you want to put your BASIC
- procedures in a Quick library, wait until the procedures are complete and
- thoroughly debugged.
-
- When a Quick library is created, any module-level code in the file it was
- created from is automatically included. However, other modules cannot access
- this code, so it just wastes space. Before converting a module to a Quick
- library, be sure that all module-level statements (except any error or event
- handlers and declarations that are used by the procedures) have been
- removed.
-
-
- Note
-
- Quick libraries are not included in .MAK files and must be loaded with the
- /L option when you run QBX. A Quick library has the file extension .QLB.
- During the process of creating the Quick library, an object-module library
- file with the extension .LIB is created. This file contains the same code as
- the Quick library but in a form that allows it to be linked with the rest of
- the program to create a stand-alone application.
-
- If you use Quick libraries to distribute proprietary code (data-manipulation
- procedures, for example), be sure to include the object-module library files
- (.LIB) so that your customers can create stand-alone applications that use
- these procedures. Otherwise, they will be limited to running applications
- within the QBX environment.
-
-
- Creating Quick Libraries
-
- You can create a Quick library of BASIC procedures with the Make Library
- command from the Run menu. The Quick library created contains every
- procedure currently loaded, whether or not your program calls it. (It also
- contains all the module-level code.) If you want the Quick library to be
- compact, be sure to remove all unused procedures and all unnecessary
- module-level code first.
-
- You can create as many Quick libraries as you like, containing whatever
- combination of procedures you wish. However, only one Quick library can be
- loaded into QBX at a time. (You would generally create application-specific
- Quick libraries, containing only the procedures a particular program needs.)
- Large Quick libraries can be created by loading many modules, then choosing
- Make Library from the Run menu.
-
- You can also compile one or more modules with the BC command and then link
- the object code files to create a Quick library. Quick libraries of
- procedures written in other languages are created the same way. In linking,
- you are not limited to one language; the object-code files from any number
- of Microsoft languages can be combined in one Quick library. Chapter 19,
- "Creating and Using Quick Libraries," explains how to convert object-code
- files (.OBJ) into Quick libraries.
-
-
- Tips for Good Programming with Modules
-
- You can use modules in any way you think will improve your program or help
- organize your work. The following suggestions are offered as a guide:
-
- ■ Think and organize first.
-
- ■ When you start on a new project, make a list of the operations you
- want to be performed by procedures. Then look through your own
- procedure library to see if there are any you can use, either as is or
- with slight modifications. Don't waste time "reinventing the wheel."
-
- ■ Write generalized procedures with broad application.
-
- ■ Try to write procedures that are useful in a wide variety of programs.
- Don't, however, make the procedure needlessly complex. A good
- procedure is a simple, finely honed tool.
-
- ■ It is sometimes useful to alter an existing procedure to work in a new
- program. This might require modifying programs you've already written,
- but it's worth the trouble if the revised procedure is more powerful
- or has broader application.
-
- ■ When creating your own procedure modules, keep logically separate
- procedures in separate modules.
-
- ■ It makes sense to put string-manipulation procedures in one module,
- matrix-handling procedures in another, and data-communication
- procedures in a third. This arrangement avoids confusion and makes it
- easy to find the procedure you need.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 8: Error Handling
-
- ────────────────────────────────────────────────────────────────────────────
-
- This chapter explains how to intercept and deal with errors that occur while
- a program is running. Using these methods, you can protect your program from
- such errors as opening a nonexistent file or trying to use the printer when
- it is out of paper.
-
- ■ After reading this chapter, you will know how to:
-
- ■ Enable error trapping.
-
- ■ Write an error-handling routine to process the trapped errors.
-
- ■ Return control from an error-handling routine.
-
- ■ Trap errors at the procedure and module level.
-
- ■ Trap errors in programs composed of more than one module.
-
-
-
- Why Use Error Handling?
-
- Error handling is the process of intercepting and dealing with errors that
- occur at run time and cause your program to stop executing. A typical error
- would be if a stand-alone program attempted to save data on a disk that was
- out of space. For example:
-
- OPEN "A:RESUME" FOR OUTPUT AS #1
- PRINT #1, MyBusinessResume$
- CLOSE #1
-
- In this case, since there is no provision for error handling, BASIC issues
- the message
-
- Disk Full and terminates. The operator's data, stored in the string
- variable MyBusinessResume$, and all other data in memory, is lost.
-
- To avoid this situation, you can use BASIC's error handling features to
- intercept errors and take action once they occur. For example, the full disk
- problem could be handled by making the following changes to the code (the
- details of this example are described in the next section):
-
- ON ERROR GOTO Handler' Write document to disk.
- OPEN "A:RESUME" FOR OUTPUT AS #1
- PRINT #1, MyBusinessResume$CLOSE #1
- ON ERROR GOTO Handler' Turn on error trapping.
- OPEN "A:RESUME" FOR OUTPUT AS #1' Write document to disk.
- PRINT #1, MyBusinessResume$
- CLOSE #1
- END
- ' Branch here if an error occurs
- Handler:
- ' Have operator get another disk if this one's full.
- IF ERR = 61 THEN
- CLOSE #1
- KILL "A:RESUME"
- PRINT "Disk in drive A too full for operation."
- PRINT "Insert new formatted disk."
- PRINT "Press any key when ready"
- Pause$ = INPUT$(1)
- OPEN "A:RESUME" FOR OUTPUT AS #1
- RESUME
- ELSE
- PRINT "Unanticipated error number";ERR ;"occurred."
- PRINT "Program Terminated."
- END
- END IF
-
- Your program can correct many kinds of operator errors using methods such as
- those shown in this example. You can also use error handling for program
- control and for gaining information about devices (monitors, printers,
- modems) that are connected to the computer.
-
-
- How to Handle Errors
-
- There are three steps necessary for handling errors:
-
- 1. Set the error trap by telling the program where to branch to when an
- error occurs.
-
-
-
- In the example in the preceding section, this is accomplished by the
- ON ERROR GOTO statement which directs the program to the label
- Handler.
-
- 2. Write a routine that takes action based on the specific error that has
- occurred -- the error-handling routine.
-
-
-
- In the example in the preceding section, the Handler routine did this
- using a IF THEN statement. When it encountered a disk full error, it
- prompted the operator for a new disk. If that wasn't the solution to
- the error, it terminated.
-
- 3. Exit the error-handling routine.
-
-
-
- In the example in the preceding section, the RESUME statement was
- used to branch back to the line where the disk full error occurred.
- Data was then written to a new disk.
-
-
- Details on how to perform these steps are given in the following sections.
-
-
- Setting the Error Trap
-
- There are two types of error traps -- module level and procedure level. Both
- traps become enabled when BASIC executes the ON LOCAL ERROR GOTO
- statement. Once enabled, the module-level trap remains enabled for the
- duration of the program (or until you turn it off). The procedure-level trap
- is enabled only while the procedure containing it is active -- that is until
- an EXIT SUB or END SUB statement is executed for that procedure. See the
- section "Procedure- vs. Module-Level Error Handling" later in this chapter
- for a discussion of when to use procedure- and module-level trapping.
-
- To enable a module-level trap, use the ON ERROR GOTO line statement, where
- line is a line number or label identifying the first line of an
- error-handling routine. Place the statement in the code where you want error
- trapping enabled, usually at the beginning of the main module. As soon as
- BASIC executes this line, the trap is enabled.
-
- To enable a procedure-level trap, add the LOCAL keyword to the statement so
- the syntax becomes ON LOCAL ERROR GOTO line. Place the statement in the
- procedure where you want error trapping enabled. As soon as BASIC executes
- this statement, the trap is enabled until an EXIT SUB or END SUB statement
- for this procedure is encountered.
-
- Important
-
- The line number 0 when used in any ON LOCAL ERROR GOTO statement does not
- enable error handling. It has two other purposes: if it does not appear in
- an error-handling routine, it turns off error handling (see the section
- "Turning Off Error Handling" later in this chapter for details); if it
- appears in an error-handling routine, it causes BASIC to issue the error
- again.
-
-
- Writing an Error-Handling Routine
-
- This first statement in an error-handling routine contains the label or line
- number contained in the ON LOCAL ERROR GOTO line statement. For the
- example in this section, the label PrintErrorHandler is used.
-
- Locate the error-handling routine in a place where it cannot be executed
- during normal program flow. A module-level error-handling routine is
- normally placed after the END statement. A procedure-level error-handling
- routine, is normally placed between the EXIT SUB and END SUB statements,
- as shown in the example in this section.
-
- To find out which error occurred use the ERR function. It returns a numeric
- code for the program's most recent run-time error. (See Appendix D, "Error
- Messages," in the BASIC Language Reference for a complete list of the
- error codes associated with run-time errors.) By using ERR in combination
- with SELECT CASE or IF THEN statements, you can take specific action for
- any error that occurs.
-
- Example
-
- In the following code, the PrintList procedure sends the array List$() to
- the printer. The ERR function is used to determine which message should be
- sent to the operator so the problem can be corrected.
-
-
- SUB PrintList(List$())
- ON LOCAL ERROR GOTO PrinterErrorHandler' Enable error trapping.
- FOR I% = LBOUND(List$) to UBOUND(List$)
- LPRINT List$(I%)
- NEXT I%
- EXIT SUB
- PrinterErrorHandler: ' Error-handling routine
- ' Use ERR to determine which error
- ' caused the branch. Send out the appropriate message.
- SELECT CASE ERR
- CASE 25
- ' 25 is the code for a Device fault error, which occurs
- ' if the printer is offline or turned off.
- PRINT "Printer offline or turned off"
- CASE 27
- ' 27 is the code for an Out of paper error:
- PRINT "Printer is out of paper."
- CASE 24
- ' 24 is the code for a Device timeout error, which
- ' occurs if the printer cable is disconnected.
- PRINT "Printer cable is disconnected."
- CASE ELSE
- PRINT "Unknown error."
- EXIT SUB
- END SELECT
-
- PRINT "Please correct the problem and"
- PRINT "Press any key when ready."
- Pause$ = INPUT$(1)
- ' Go try again after operator fixes the problem.
- RESUME
-
- END SUB
-
- Exiting an Error-Handling Routine
-
- To exit from an error-handling routine, use one of the statements shown in
- Table 8.1.
-
- RESUME NEXTReturns to the statement immediately following the one that
- caused the error or immediately following the last call out of the
- error-handling procedure or module. ERROR ERRInitiates a search of the
- invocation path for another error-handling routine. ON ERROR GOTO 0Initiates
- a search of the invocation path for another error-handling routine.
- RESUME returns to the statement that caused the error or the last call out
- of the error-handling procedure or module. Use it to repeat an operation
- after taking corrective action.
-
- The RESUME NEXT statement returns to the statement immediately following
- the one that caused the error or immediately following the last call out of
- the error-handling procedure or module. The difference between RESUME and
- RESUME NEXT is shown for a module-level error-handling routine in a
- single-module program in Figure 8.1.
-
- Use RESUME NEXT to ignore the error. For example, this code performs modulo
- 64K arithmetic by ignoring the overflow error.
-
- ON ERROR GOTO Handler
- ' Add two hexadecimal numbers.
- Result% = &H1234 + &HFFFF
- PRINT HEX$( Result%)
- END
- Handler:
- ' If an overflow occurs, keep going.
- RESUME NEXT
-
- Sometimes, if an error occurs in a loop, you need to restart an operation
- rather than continue from where you left off with the RESUME statement.
- This would be true if a Disk full error occurs during execution of the
- following procedure which saves an array called ClientNames$ on a floppy
- disk.
-
- SUB SaveOnDisk (ClientNames$())
- ON LOCAL ERROR GOTO Handler
- Restart:
- OPEN "A:CLIENTS.TXT" FOR OUTPUT AS #1
- ' Save array of client names to disk.
- FOR I% = LBOUND(ClientNames$) TO UBOUND(ClientNames$)
- PRINT #1, ClientNames$(I%)
- NEXT I%
- CLOSE #1
- EXIT SUB
- Handler:
- ' Have operator get another disk if this one's full.
- IF ERR = 61 THEN
- CLOSE #1
- KILL "A:CLIENTS.TXT"
- PRINT "Disk Full. Insert formatted disk"
- PRINT "Press any key when ready"
- Pause$ = INPUT$(1)
- RESUME Restart
- ELSE
- ' Unanticipated error. Simulate the error & look for
- ' another handler.
- ERROR ERR
- END IF
-
- END SUB
-
- In this case, the RESUME line statement was used, where line is any valid
- line number (except 0) or label. This allowed the error-handling routine to
- erase the incomplete file on the full disk, prompt the operator for a new
- disk and then return to the line labelled Restart to print the file from the
- beginning.
-
-
- This last example also illustrates how to use ERROR ERR, the recommended
- way of handling an unanticipated error. When BASIC executes this statement,
- it simulates the occurrence of the error again, but this time the error
- occurs in the error-handling routine itself. In this case, BASIC searches
- back through the invocation path (if there is one) for another
- error-handling routine. If one is found, the program continues from that
- point. Otherwise, the appropriate error message is issued and the program
- terminates. The "invocation path" is a list of procedures that have been
- invoked in order to arrive at the program's current location. The invocation
- path is found on the stack and is also called the execution stack. (See the
- section "Unanticipated Errors" later in this chapter for details on how
- BASIC searches back through the invocation path.)
-
- Note
-
- To maintain compatibility with previous releases, this version of Microsoft
- BASIC allows you to use ON LOCAL ERROR GOTO 0 in an error-handling
- routine to initiate a search of the invocation path. But because this same
- statement is used outside of error-handling routines to turn off error
- trapping, your coding will be clearer if you always use ERROR ERR within an
- error-handling routine. See "Turning Off Error Trapping" later in this
- chapter for examples of this other usage.
-
-
- Procedure- Vs. Module-Level Error Handling
-
- For many applications, procedure-level error handling is preferred. This is
- because procedures tend to be organized by task (video display driver, line
- printer, disk I/O, etc.), and errors are also task-related. Therefore
- program organization can be simpler and more straightforward when the two
- are grouped together.
-
- Examples
-
- This first example outlines how error trapping can be organized for several
- independent procedures; one for disk I/O, one for computations, and one for
- operator input. The module-level error-handling routine LastResort is used
- to take emergency action for an unanticipated error that the procedure-level
- error-handling routines don't deal with.
-
- ' Module-level code.
- ON ERROR GOTO LastResort
- ' Main program executes here.
- .
- .
- .
- END
-
- LastResort:
- ' Module-level error-handling routine
- ' It makes a last attempt to deal with unanticipated errors trapped in
- ' procedure-level error-handling routines.
- .
- .
- .
- END
- SUB MathProcessor
- ON LOCAL ERROR GOTO MathHandler
- ' Calculations are performed in this SUB.
- .
- .
- .
- EXIT SUB
- MathHandler:
- SELECT CASE ERR
- CASE 11
- ' Routine to handle divide by zero goes here.
- .
- .
- .
- RESUME NEXT
- CASE 6
- Routine to handle overflows goes here.
- .
- .
- .
- RESUME NEXT
- CASE ELSE
- ' Unanticipated error.
- ERROR ERR
- END SELECT
- END SUB
-
- SUB DiskDriver
- ON LOCAL ERROR GOTO DiskHandler
- ' Disk read and write performed in this SUB.
- .
- .
- .
- EXIT SUB
- DiskHandler:
- SELECT CASE ERR
- CASE 72
- ' Routine to handle damaged disk goes here.
- .
- .
- .
- RESUME
- CASE 71
- ' Routine to handle open drive door goes here.
- .
- .
- .
- RESUME
- CASE 57
- ' Routine to handle internal disk drive problem goes here.
- .
- .
- .
- RESUME
- CASE 61
- ' Routine to handle full disk goes here.
- .
- .
- .
- RESUME
- CASE ELSE
- ' Unanticipated error.
- ERROR ERR
- END SELECT
- END SUB
-
- FUNCTION GetFileName
- ON LOCAL ERROR GOTO FileNameErrorHandler
- ' Code to get a filename from the operator goes here.
- .
- .
- .
- EXIT FUNCTION
- FileNameErrorHandler:
- SELECT CASE ERR
- CASE 64
- ' Routine to handle an illegal filename goes here.
- .
- .
- .
- RESUME
- CASE 76
- ' Routine to handle a non-existent path goes here.
- .
- .
- .
- RESUME
- CASE ELSE
- ' Unanticipated error--try searching invocation path.
- ERROR ERR
- END SELECT
- END FUNCTION
-
- If you have several related procedures where the same kinds of errors may
- occur, it makes more sense to write one error-handling routine and put it at
- the module level. This is outlined in the following example which sends
- different kinds of data to the line printer.
-
- ON ERROR GOTO HandleItAll
- DECLARE SUB PrintNumericArray (StudentArray& ())
- DECLARE SUB PrintStringArray (ClientArray$ ())
- DIM Students& ( 1 to 100, 1 to 2), Clients$ (1 to 100, 1 to 2)
- ' Main-module-level code goes here. It acquires data from an operator
- ' and puts it in the previously dimensioned arrays.
- .
- .
- .
- CALL PrintNumericArray (Students&() )
- CALL PrintStringArray (Clients&() )
- END
- HandleItAll:
- ' Branch here to handle an error in either SUB
- SELECT CASE
- CASE 25
- ' Routine for handling printer off or disconnected goes here.
- .
- .
- .
- RESUME
- CASE 68
- ' Routine for handling printer being offline goes here.
- .
- .
- .
- RESUME
- CASE 27
- ' Routine for handling printer out of paper goes here.
- .
- .
- .
- RESUME
- CASE ELSE
- ERROR ERR
- END SELECT
-
- SUB PrintNumericArray (StudentArray& ())
- ' This prints a 2D numeric array of student numbers and test scores.
- FOR I% = 1 to 100
- LPRINT StudentArray&( I%, 1), StudentArray& (I%, 2)
- NEXT I%
- END SUB
-
- SUB PrintStringArray (ClientArray$ ())
- ' This prints a 2D string array of client names and zip codes.
- FOR I% = 1 to 100
- LPRINT ClientArray$ ( I%, 1), ClienttArray$ (I%, 2)
- NEXT I%
- END SUB
-
- Error Handling in Multiple Modules
-
- Microsoft BASIC allows you to detect and handle errors that occur in
- multiple-module programs. To see how this works, try tracing through the
- following code. It begins in the main module where it handles a device I/O
- error. It then calls the procedure Module2Sub in the second module which
- calls the Module3Sub procedure in the third module. An Out of memory error
- occurs in Module3Sub which is handled at the module level. The program
- returns to Module3Sub which exits back to Module2Sub in the second module.
- Here a Type mismatch error occurs that is handled at the procedure level.
- The program then returns to Module2Sub which exits back to the main module
- and ends.
-
- '=========================================================
- 'MODULE #1 (MAIN)
- '=========================================================
- ' Identify external procedure
- DECLARE SUB Module2Sub ()
- ON ERROR GOTO MainHandler
- ERROR 57' Simulate occurrence of error 57
- ' (Device I/O error).
- CALL Module2Sub
- PRINT "Back in main-module after handling all errors."
- END
- MainHandler:
- PRINT "Handling error"; ERR; "in main-module-level handler."
- RESUME NEXT
- ' =========================================================
- 'MODULE #2
- ' =========================================================
- ' Identify external procedure
- DECLARE SUB Module3Sub ()
-
- SUB Module2Sub
- ON LOCAL ERROR GOTO Module2Handler
- CALL Module3Sub
- ERROR 13' Simulate a Type mismatch error.
- EXIT SUB
-
- Module2Handler:
- PRINT "Handling error"; ERR; "in module 2 procedure-level handler."
- RESUME NEXT
-
- END SUB
-
- ' =========================================================
- 'MODULE #3
- ' =========================================================
- Module3Handler:
- PRINT "Handling error"; ERR; "in module 3 module-level handler."
- RESUME NEXT
-
- SUB Module3Sub
- ON ERROR GOTO Module3Handler
- ERROR 7' Simulate an Out of memory error.
- END SUB
-
-
- Output
-
- Handling error 57 in main-module-level handler
- Handling error 7 in module 3 module-level handler
- Handling error 13 in module 2 procedure-level handler
- Back in main module after handling all errors.
-
- In the preceding example, note the error-handling technique employed in the
- third module. The ON ERROR GOTO statement is used in a procedure without
- the LOCAL keyword. This is the only way to enable a module-level
- error-handling routine in a module other than the main module.
-
-
- Unanticipated Errors
-
- When an error occurs and there is no error-handling routine within the
- current scope of the program, BASIC searches the invocation path for an
- error-handling routine.
-
- BASIC follows these steps when an unanticipated error is encountered:
-
- 1. BASIC searches each frame starting with the most recently invoked and
- continuing until an error-handling routine is found or the path is
- exhausted.
-
- 2. BASIC searches across module boundaries. Before crossing into another
- module, it searches the module-level code even if that code is not in
- the invocation path.
-
- 3. If no error-handling routine is found, the program terminates.
-
- 4. If an enabled error-handling routine is found, program execution
- continues in that error-handling routine. If the error-handling
- routine contains a RESUME or RESUME NEXT statement, the program
- continues as shown in Table 8.2.
-
-
- error-handling
- routineSingle module programMultiple module program
- Examples
-
- To understand how program flow is affected when unanticipated errors occur,
- consider the following example which performs this calculation: (3 * 3) + 2.
- It does this by passing the numbers 2 and 3 to a procedure called Total.
- This procedure calls Square. This second procedure squares the number 3 and
- returns to Total with the answer. Total then adds the number 2 to the answer
- and returns to the module level where the answer is printed.
-
- DECLARE FUNCTION Total% (A%, B%)
- DECLARE FUNCTION Square% (B%)
- DEFINT A-Z
- ' Go do some calculations with the numbers 2 and 3.
- Answer = Total(2, 3)
- ' Show us the results.
- PRINT "(3 * 3) + 2 = "; Answer
- END
-
- FUNCTION Square (B)
- ' Find the square of B.
- Square = B * B
- END FUNCTION
-
-
- FUNCTION Total (A, B)
- ON LOCAL ERROR GOTO Handler
- ' Go square B, then add A to the result.
- Result = Square(B)
- Total = Result + A
- EXIT FUNCTION
- Handler:
- ' Ignore overflow errors.
- IF ERR = 6 THEN
- RESUME NEXT
- ELSE
- ERROR ERR
- END IF
- END FUNCTION
-
- If no error occurs in our example we get the expected result:
-
- (3 * 3) + 2 = 11
-
- But suppose an error occurs in the Square function, as simulated by the
- following code:
-
- FUNCTION Square (B)
- ' Simulate the occurrence of an overflow error.
- ERROR 6
- ' Find the square of 3.
- Square = B ^ 2
- END FUNCTION
-
- This time when Square is called, an error occurs before the calculation is
- made. BASIC finds no error-handling routine in this procedure, so it
- searches back through the invocation path for the closest available enabled
- error-handling routine. It finds one in Total, so it starts that
- error-handling routine. This error-handling routine has code for dealing
- with ERROR 6
-
- -- an overflow error. In this case, it does a RESUME NEXT. Execution then
- continues in Total at the line following the call to Square, and the program
- proceeds as before. But when the result is printed, we get:
-
- (3 * 3) + 2 = 2
-
- This is because BASIC never returned to the Square procedure to make the
- calculation. This illustrates an important point: when BASIC encounters a
- RESUME NEXT statement in a procedure-level error-handling routine, it always
- returns to a statement in that procedure. This can be troublesome if you
- have a missing error-handling routine. In other words, your program may
- continue running, but it may not run as intended.
-
- To see another example of how BASIC searches for error-handling routines,
- make the procedure-level error-handling routine in Total into a module-level
- error-handling routine so that the example looks like this:
-
-
- DECLARE FUNCTION Total% (A%, B%)
- DECLARE FUNCTION Square% (B%)
- DEFINT A-Z
- ON ERROR GOTO MainHandler
- ' Go do some calculations with the numbers 2 and 3.
- Answer = Total(2, 3)
- ' Show us the results.
- PRINT "(3 * 3) + 2 = "; Answer
- END
- MainHandler:
- ' Ignore overflow errors.
- IF ERR = 6 THEN
- RESUME NEXT
- ELSE
- ERROR ERR
- END IF
-
- FUNCTION Square (B)
- ' Simulate the occurrence of an error.
- ERROR 6
- ' Find the square of B.
- Square = B * B
- END FUNCTION
-
- FUNCTION Total (A, B)
- ' Go square B, then add A to the result.
- Result = Square(B)
- Total = Result + A
- END FUNCTION
-
- This time BASIC doesn't find an error-handling routine in the Total
- procedure so it looks further back and finds one at the module level. When
- the RESUME NEXT statement is executed in the MainHandler procedure, the
- program returns to the Square procedure and begins executing at the Square =
- B * B statement. Program flow then continues as in the original example that
- executed without an error. Thus the result this time is correct:
-
- (3 * 3) + 2 = 11
-
- The reason this last example works is because the error-handling routine was
- at the module level. This illustrates another important point: in a single
- module program, whenever BASIC encounters a RESUME NEXT statement in a
- module-level error-handling routine, it always returns to the statement
- directly following the one where the error occurred.
-
- To see how unanticipated errors are handled in multiple-module programs,
- change the preceding example into two modules as follows:
-
-
- '================================================================
- 'MODULE #1
- '================================================================
- DEFINT A-Z
- DECLARE FUNCTION Total (A, B)
- DECLARE FUNCTION Square (B )
- ON ERROR GOTO Handler
- ' Go do some calculations with the numbers 2 and 3.
- Answer = Total ( 2, 3)
- ' Show us the results.
- PRINT "(3 * 3) + 2 = "; Answer
- END
-
- Handler:
- ' Ignore overflow errors.
- IF ERR = 6 THEN
- RESUME NEXT
- ELSE
- ERROR ERR
- END IF
-
- '=================================================================
- 'MODULE #2
- '=================================================================
- FUNCTION Total (A, B)
- ' Go square B, then add A to the result.
- Result = Square( B)
- Total = Result + A
- END FUNCTION
- FUNCTION Square ( B)
- ' Find the square of B.
- ERROR 6
- Square = B * B
- END FUNCTION
-
- Now when BASIC searches for an error-handling routine, it finds one in the
- first module. The RESUME NEXT causes the program to return to the line
- following the last executed line in that module. Therefore program execution
- continues with the PRINT statement and the result is:
-
- (3 * 3) + 2 = 0
-
- To see a final demonstration of program flow after dealing with an
- unanticipated error, move the module-level error-handling routine to the
- second module as shown here:
-
-
- '================================================================
- 'MODULE #1
- '================================================================
- DEFINT A-Z
- DECLARE FUNCTION Total (A, B)
- DECLARE FUNCTION Square (B )
- ON ERROR GOTO Handler
- ' Go do some calculations with the numbers 2 and 3.
- Answer = Total ( 2, 3)
- ' Show us the results.
- PRINT "(3 * 3) + 2 = "; Answer
- END
- '=================================================================
- 'MODULE #2
- '=================================================================
- Handler:
- ' Ignore overflow errors.
- IF ERR = 6 THEN
- RESUME NEXT
- ELSE
- ERROR ERR
- END IF
-
- FUNCTION Total (A, B)
- ' Enable the error handler at the module level
- ON ERROR GOTO Handler
- ' Go square B, then add A to the result.
- Result = Square( B)
- Total = Result + A
- END FUNCTION
- FUNCTION Square ( B)
- ' Find the square of B.
- ERROR 6
- Square = B * B
- END FUNCTION
-
- This time BASIC finds an error-handling routine in the same module as where
- the error occurred. It therefore returns to the Square procedure and
- produces the correct answer:
-
- (3 * 3) + 2 = 11
-
- Guidelines for Complex Programs
-
- Because BASIC makes such a thorough attempt to find missing error-handling
- routines, the following guidelines should be observed when writing complex
- programs with extensive operator interfaces:
-
- ■ Write a "fail safe" error-handling routine for the main module of your
- program application. This will be executed if a search for an
- error-handling routine is unsuccessful and winds up back at the main
- module level. The error-handling routine should make an attempt to
- save the operator's data before the program terminates.
-
- ■ Use procedure-level error-handling routines wherever possible to deal
- with anticipated errors. Errors caught in a procedure can usually be
- corrected more easily there.
-
- ■ Put an ERROR ERR statement in all procedure-level error-handling
- routines and in all module-level error-handling routines outside of
- the main module in case there is no code in the error-handling routine
- to deal with a specific error. This lets your program try to correct
- the error in other error-handling routines along the invocation path.
-
- ■ Use the STOP statement to force termination if you don't want a
- previous procedure or module to trap the error.
-
-
-
- Errors Within Error- and Event-Handling Routines
-
- An error occurring within an error-handling routine is treated differently
- depending on whether the error-handling routine is for an error or an event:
-
- ■ If an error occurs in an error-handling routine, BASIC begins
- searching the invocation path using the principles demonstrated in the
- section "Unanticipated Errors" earlier in this chapter.
-
- ■ If an error occurs in an event-handling routine, including any
- procedure called by the event-handling routine, BASIC also searches
- the invocation path, but only as far back as the event frame. If it
- can't find an error-handling routine in that part of the invocation
- path, or in the module-level code, it terminates.
-
-
-
- Delayed Error Handling
-
- At times you may need to detect errors but not handle them until a certain
- section of code is finished executing. Do this with the ON ERROR RESUME
- NEXT statement which tells BASIC to record the error but not interrupt the
- program.
-
- You then can write a routine for dealing with the errors that can be
- executed whenever it is convenient. The routine can tell if an error
- occurred by the value of ERR. If ERR is not zero, an error has occurred
- and the error-handling routine can take action based on the value of ERR as
- shown by the following example:
-
- CONST False = 0, True = NOT False
- ' Don't let an error disrupt the program.
- ON ERROR RESUME NEXT
- Condition% = False
- DO UNTIL Condition% = True
- ' A long calculation loop that exits when the
- ' variable Condition becomes true.
- .
- .
- .
- LOOP
- ' Now see if an error occurred and if so take action.
-
- SELECT CASE ERR
- CASE 0
- ' No error--Don't send a message. Continue with program.
- Goto MoreProgram
- ' Find out which error it is and deal with it.
- CASE 6
- PRINT "An overflow has occurred"
- CASE 11
- PRINT "You have attempted to divide by zero"
- END SELECT
- PRINT "Enter 'I' to ignore, anything else to quit"; S$
- S$ = input(1)
- IF UCASE$(S$) <> "I" THEN END
-
- MoreProgram:
- ' Program continues here after checking for errors.
- .
- .
- .
- END
-
- There are two points to remember when doing this kind of error handling:
-
- ■ The routine that detects and deals with the error (ERR) is different
- from the error-handling routines we have been discussing -- it does
- not use any of the RESUME statements.
-
- ■ The error number contained in ERR is the number of the most recent
- error. Any other errors that occurred earlier in the preceding loop
- are lost.
-
-
- Another reason for using ON ERROR RESUME NEXT is to tailor the error
- handling to each statement, or a group of related statements, in your code
- rather than having a single error-handling routine per procedure. This is
- outlined by the following example which opens a file, converts it to ASCII
- text, and sends it to the line printer:
-
- SUB ConvertToASCII (Filename$)
- ON LOCAL ERROR RESUME NEXT
- ' Try to open the specified file. Correct errors if they occur.
- OPEN Filename$ FOR INPUT AS #1
- IF ERR < > 0 THEN
- OpenErrorHandler:
- ' Routine to identify and deal with file-open errors.
- .
- .
- .
- END IF
- FOR Counter% = 1 to LOF(1)
- ' Read in a character and correct errors if they occur.
- S$ = INPUT$(1, #1)
- IF ERR < > THEN
- InputError-handling routine:
- ' Routine to identify and deal with file input errors.
- .
- .
- .
- END IF
- ASCII% = ASC( S$) AND &H7F
- ' Print a character and correct errors if they occur.
- LPRINT CHR$(Ascii%);
- IF ERR < > THEN
- ' Routine to identify and deal with printer errors.
- .
- .
- .
- NEXT Counter%
- PrinterErrorHandler:
- END SUB
-
- Turning Off Error Handling
-
- To turn off error handling, use the ON LOCAL ERROR GOTO 0 statement.
- Once BASIC executes this statement, errors are neither trapped nor detected.
- If the LOCAL keyword is used, then error handling is turned off only within
- the procedure where the statement appears. Otherwise error handling is
- turned off for the current module error-handling routine.
-
- Important
-
- The only place you cannot turn off error handling is in the error-handling
- routine itself. If BASIC encounters an ON ERROR GOTO 0 statement there, it
- will treat it the same as an ERROR ERR statement and begin searching back
- through the invocation path for another error-handling routine.
-
- Example
-
- The following example turns off error handling in the procedure DemoSub.
-
- SUB DemoSub
- ON ERROR GOTO SubHandler
- ' Error trapping is enabled.
- ' Errors need to be caught and corrected here.
- .
- .
- .
- ON ERROR GOTO 0
- ' Error trapping is turned off here because it's not needed.
- .
- .
- .
- ON ERROR GOTO SubHandler
- ' Error trapping is enabled again.
- .
- .
- .
- EXIT SUB
- SubHandler:
- ' Error-handling routine goes here.
- .
- .
- .
- RESUME
- END SUB
-
- Additional Applications
-
- Besides handling operator errors, you can also use error handling for
- program control. Although not recommended as a general rule, occasionally it
- is a convenient method. As an example of this, consider the following code
- which gets a number from the operator and returns the arc tangent. Because
- the arc tangent of zero or any multiple of produces a Division by zero
- error, an error-handling routine is employed to ignore the result of the
- computation when the operator enters one of those values.
-
- CONST False = 0, True = NOT False
- INPUT "Enter a number";Number
- ON ERROR GOTO Handler
- NoError = True
- ArcTangent = 1 / (TAN (Number))
- PRINT "The arctangent of "; Number; "is ";
- ' If the answer is a real number then print it.
- IF NoError THEN
- PRINT ArcTangent
- ' Otherwise tell the operator the answer is undefined (infinite).
- ELSE PRINT "undefined"
- END IF
- END
-
- Handler:
- NoError = False
- RESUME NEXT
-
- Another use for error handling is to gain information, unavailable with
- other techniques, about the computer on which your application is running.
- For example, the following procedure Adapter, tells the operator which
- display adapter is installed, based on the errors produced by various
- SCREEN statements.
-
- DEFINT A-Z
-
- SUB Adapter
- ON LOCAL ERROR GOTO Handler
- CONST False = 0, True = NOT FALSE
- ' Use an array to keep track of our test results.
- DIM Mode( 1 to 13)
-
- ' Try screen modes and see which work.
- FOR ModeNumber = 1 to 13
- ' Assume the test works unless you get an error.
- Mode (ModeNumber) = True
- SCREEN ModeNumber
- NEXT ModeNumber
-
- ' Reset the screen after testing it.
- SCREEN 0, 0
- WIDTH 80
-
- ' Using test results figure out which adapter is out there.
- ' Tell operator which one he has.
- ' (See tables in SCREEN statement section of BASIC Language Reference
- ' to understand why this logic works.)
- PRINT "You have a";
- IF Mode(13) THEN
- IF Mode(7) THEN
- PRINT "VGA";
- UsableModes$ = "0-2, 7-13."
- ELSE
- PRINT "MCGA";
- UsableModes$ = "0-2, 11 & 13."
- END IF
- ELSE
- IF Mode(7) THEN
- PRINT "EGA";
- UsableModes$ = "0-2, 7-10."
- ELSE
- IF Mode(3) THEN
- PRINT "Hercules";
- UsableModes$ = "3."
- END IF
- IF Mode(4) THEN
- PRINT "Olivetti";
- UsableModes$ = "4."
- END IF
- IF Mode(1) THEN
- PRINT "CGA";
- UsableModes$ = "0-2."
- ELSE
- PRINT "MDPA";
- UsableMode$ = "0."
- END IF
- END IF
- END IF
- PRINT "Graphics card that supports screen mode(s) "; UsableModes$
- EXIT SUB
-
- ' Branch here when test fails and change text result.
- Handler:
- Mode (ModeNumber) = False
- RESUME NEXT
- END SUB
-
- Output with VGA Adapter
-
- You have a VGA Graphics card that supports screen modes(s) 0-2, 7-13.
-
- Note
-
- The list of screen modes produced by the preceding example may be incomplete
- for some non-IBM VGA and EGA adapters.
-
-
- Trapping User-Defined Errors
-
- There are many error codes that are not used by Microsoft BASIC. By using an
- unused error code in an ERROR statement, you can let BASIC's error handling
- logic control program flow for special error conditions you anticipate.
-
- Example
-
- The following simplified example uses an error-handling routine in a
- procedure called CertifiedOperator to control the operator's access to a
- network. The procedure checks to see that the operator's password is
- "Swordfish" and that the first four numbers of the account number are 1234.
- If the operator doesn't enter the correct information after three attempts,
- the procedure returns false and the network connection is not made.
-
- 'PASSWRD.BAS
- CONST False = 0, True = NOT False
- DECLARE FUNCTION CertifiedOperator%
-
- IF CertifiedOperator = False THEN
- PRINT "Connection Refused."
- END
- END IF
-
- PRINT "Connected to Network."
- ' Main program continues here.
- .
- .
- .
- END
-
- FUNCTION CertifiedOperator%
- ON LOCAL ERROR GOTO Handler
- ' Count the number of times the operator tries to sign on.
- Attempts% = 0
-
- TryAgain:
- ' Assume the operator has valid credentials
- CertifiedOperator = True
- ' Keep track of bad entries
- Attempts% = Attempts% + 1
- IF Attempts% > 3 then ERROR 255
- ' Check out the operator's credentials
- INPUT "Enter Account Number"; Account$
- IF LEFT$ (Account$, 4) <> "1234" THEN ERROR 200
-
- INPUT "Enter Password"; Password$
- IF Password$ <> "Swordfish" THEN ERROR 201
- EXIT SUB
-
- Handler:
- SELECT CASE
- ' Start over if account number doesn't have "1234" in it.
- CASE 200
- PRINT "Illegal account number. Please re-enter both items."
- RESUME TryAgain
- ' Start over if the password is wrong.
- CASE 201
- PRINT "Wrong password. Please re-enter both items."
- RESUME TryAgain
- ' Return false if operator makes too many mistakes.
- CASE 255
- CertifiedOperator% = FALSE
- EXIT SUB
- END SELECT
-
- END SUB
-
- Compiling from the Command Line
-
- When compiling from the command line, you must use one or both of these
- error-handling options:
-
- ■ Use /E if your code contains:
-
- ■ ON LOCAL ERROR GOTO
-
- ■ RESUME line
-
- ■ Use /X if your code contains:
-
- ■ RESUME 0
-
- ■ RESUME NEXT
-
- ■ ON LOCAL ERROR RESUME NEXT
-
-
- Note
-
- You will get compilation errors if you do not use /E and /X in the preceding
- situations.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
-
- Chapter 9: Event Handling
-
- This chapter explains how to detect and respond to events that
- occur while your program is running. This process is called "event
- handling." After reading this chapter, you will know how to:
-
- ■ Specify an event to trap and enable event handling.
-
- ■ Write a routine to process the trapped event.
-
- ■ Return control from an event-handling routine.
-
- ■ Write a program that traps any keystroke or combination of keystrokes.
-
- ■ Trap music events and user-defined events.
-
- ■ Trap events in programs composed of more than one module.
-
-
-
- @AB@%Event Trapping Vs. Polling
-
- Many times during program execution, an event occurs which requires the
- program to suspend normal operation and take some action. The event could be
- the operator pressing the Ctrl+Break key combination, the arrival of data at
- the communications port, or the ending of a phrase of music playing in the
- background of a computer game.
-
- There are two ways to detect these events: polling and event trapping. To
- understand the difference, imagine we are in a loop which will continue
- forever unless the operator presses Ctrl+C. To detect this with polling, you
- write code that must be executed repeatedly. In this example, the INKEY$
- function is performed every time the loop is executed:
-
- DO
- ' Normal flow of program occurs here.
- .
- .
- .
- ' Loop until operator presses Ctrl+C (ASCII 03).
- LOOP UNTIL INKEY$ = CHR$(3)
- ' Program interrupted by the operator stops here.
- END
-
- Although polling works for the preceding example, it can degrade performance
- to check for events this way. And if the loop is too big, you might miss
- events that occur too quickly.
-
- A better alternative
- for many cases is to use event trapping. This scheme allows BASIC to do the
- detection on an interrupt basis and redirect the program as soon as the
- interrupt is detected. For the preceding scenario, the event trap would look
- like the following (the details of this example are described in the next
- section):
-
-
- ' Define the key depression to look for (Ctrl+C),
- ' where to go when it's pressed,
- ' and turn on event trapping.
- KEY 15, CHR$(4) + CHR$(46)
- ON KEY (15) GOSUB Handler
- KEY (15) ON
- DO
- ' Normal program flow occurs here.
- .
- .
- .
- LOOP
- ' Branch here when Ctrl+C is pressed.
- Handler:
- END
-
- How to Trap Events
-
- There are three steps necessary for event trapping:
-
- 1. Set the trap by telling the program where to branch to when a specific
- event occurs.
-
- In the preceding example, this is accomplished by the ON KEY (15)
- GOSUB statement which directs the program to the label Handler.
-
- 2. Turn on event trapping for the particular event you want.
-
- In the preceding example, the KEY (15) ON does this.
-
- 3. Write a routine that takes action based on the specific event that has
- occurred--the event-handling routine.
-
- In the preceding example, the action was very simple: the program is
- terminated with the END statement. At other times, you may need to go
- back to the main program. In that case, you put a RETURN statement at
- the end of the event-handling routine.
-
- If you are trapping a predefined event (such as the pressing of one of the
- function keys), only the three preceding steps are required. Otherwise, you
- need another line of code to define the event that is to be trapped. This
- was done in the preceding example with the KEY statement which informed
- BASIC that the key we were looking for was Ctrl+C.Examples of trapping
- predefined and user-defined events can be found throughout this
- chapter.
-
-
- Where to Put the Event-Handling Routine
-
- The event-handling routine must be in the module-level code. This is the
- only place where BASIC will look for it. If you accidentally put it in a
- BASIC procedure, you will get a Label not found error at run time.
-
- Usually the event-handling routine goes after the END statement as in this
- sample fragment:
-
- END
- SampleHandler:
- PRINT "You have pressed the F1 Key."
- PRINT "It caused a branch to the event-handling routine."
- RETURN
-
- The event-handling routine is located here so that it does not get executed
- during normal program flow. You could also put it above the END statement
- and skip around it with the GOTO statement, but putting the event-handling
- routine after END is the better programming practice.
-
-
- Trapping Preassigned Keystrokes
-
- To detect any of the following preassigned keystrokes and route program
- control to a key-press routine, you need both of the following statements in
- your program:
-
- ON KEY( n%) GOSUB line
- KEY( n%) ON
-
-
- The following two lines cause the program to branch to the KeySub routine
- each time the F2 function key is pressed:
-
- ON KEY(2) GOSUB KeySub
- KEY(2) ON
-
- The following four lines cause the program to branch to the DownKey routine
- when the Down direction key is pressed and to the UpKey routine when the Up
- Arrow key is pressed:
-
- ON KEY(11) GOSUB UpKey
- ON KEY(14) GOSUB DownKey
- KEY(11) ON
- KEY(14) ON
- .
- .
- .
- Important
-
- For compatibility with previous versions of BASIC, the ON KEY (n) GOSUB
- statement traps all function key depressions whether or not they are
- shifted. This means that you cannot use event trapping to distinguish
- between, for example, F1, Ctrl+F1, Alt+F1 and Shift+F1.
-
-
-
- Trapping User-Defined Keystrokes
-
- In addition to providing the preassigned key numbers 1 - 14 (plus 30 and 31
- with the 101-key keyboard), BASIC allows you to assign the numbers 15 - 25
- to any of the remaining keys on the keyboard. The key can be any single key
- such as the lowercase "s," or it can be a key combination, such as Ctrl+Z,
- as explained in the next two sections.
-
-
- Defining and Trapping a Non-Shifted Key
-
- To define and trap a single key, use these three statements:
-
- KEY n% , CHR$(0) + CHR$( code%)
- ON KEY( n%) GOSUB line
- KEY( n%) ON
-
- Here, n% is a value from 15 to 25, and code% is
- the scan code for that key. (See Appendix A in the BASIC Language
- Reference for a listing of keyboard scan codes.) The CHR(0) function,
- used in the first line, tells BASIC that the trapped key is a single key.
-
- The following example causes the program to branch to the TKey routine each
- time the user presses the lowercase "t":
-
- ' Define key 15 (the scan code for "t" is decimal 20):
- KEY 15, CHR$(0) + CHR$(20)
-
- ' Define the trap (where to go when "t" is pressed):
- ON KEY(15) GOSUB TKey
- KEY(15) ON' Turn on detection of key 15.
-
- PRINT "Press q to end."
- DO ' Idle loop: wait for user to
- LOOP UNTIL INKEY$ = "q"' press "q", then exit.
- END
-
- TKey:' Key-handling routine
- PRINT "Pressed t."
- RETURN
-
-
- Defining and Trapping a Shifted Key
-
- This is how to trap the following key combinations:
-
- KEY n% , CHR$( keyboardflag%) + CHR$( code%)
- ON KEY( n%) GOSUB line
- KEY( n%) ON
-
- Here, n% is a value from 15 to 25, code% is the
- scan code for the primary key, and keyboardflag% is the
- sum of the individual codes for the special keys pressed.
-
- For example, the following statements turn on trapping of Ctrl+S. Note these
- statements are designed to trap the Ctrl+S (lowercase) and Ctrl+Shift+S
- (uppercase) key combinations. To trap the uppercase S, your program must
- recognize capital letters produced by holding down the Shift key, as well as
- those produced when the Caps Lock key is active, as shown here:
-
- ' 31 = scan code for S key
- ' 4 = code for Ctrl key
- KEY 15, CHR$(4) + CHR$(31)' Trap Ctrl+S.
-
- ' 5 = code for Ctrl key + code for Shift key
- KEY 16, CHR$(5) + CHR$(31)' Trap Ctrl+Shift+S.
-
- ' 68 = code for Ctrl key + code for CAPSLOCK
- KEY 17, CHR$(68) + CHR$(31)' Trap Ctrl+CAPSLOCK+S.
-
- ON KEY (15) GOSUB CtrlSTrap' Tell program where to
- ON KEY (16) GOSUB CtrlSTrap' branch (note: same
- ON KEY (17) GOSUB CtrlSTrap' routine for each key).
-
- KEY (15) ON' Activate key detection for
- KEY (16) ON' all three combinations.
- KEY (17) ON
- .
- .
- .
-
- The following statements turn on trapping of Ctrl+Alt+Del:
-
- ' 12 = 4 + 8 = (code for Ctrl key) + (code for Alt key)
- ' 83 = scan code for Del key
- KEY 20, CHR$(12) + CHR$(83)
- ON KEY(20) GOSUB KeyHandler
- KEY(20) ON
- .
- .
- .
-
- Note in the preceding example that the BASIC event trap overrides the normal
- effect of Ctrl+Alt+Del (system reset). Using this trap in your program is a
- handy way to prevent the user from accidentally rebooting while a program is
- running.
-
- If you use a 101-key keyboard, you can trap any of the keys on the dedicated
- keypad by assigning the string as to any of the n% values from 15 to 25:
-
- CHR$( 128) + CHR$( code%)Example
-
- The following example shows how to trap the Left Arrow keys on the dedicated
- cursor keypad and the numeric keypad.
-
- ' 128 = keyboard flag for keys on the
- ' dedicated cursor keypad
- ' 75 = scan code for Left Arrow key
-
- KEY 15, CHR$(128) + CHR$(75)' Trap Left key on
- ON KEY(15) GOSUB CursorPad' the dedicated
- KEY(15) ON ' cursor keypad.
-
- ON KEY(12) GOSUB NumericPad' Trap Left key on
- KEY(12) ON ' the numeric keypad.
-
- DO: LOOP UNTIL INKEY$ = "q"' Start idle loop.
- END
-
- CursorPad:
- PRINT "Pressed Left key on cursor keypad."
- RETURN
-
- NumericPad:
- PRINT "Pressed Left key on numeric keypad."
- RETURN
-
-
- Trapping Music Events
-
- When you use the PLAY statement to play music, you can choose whether the
- music plays in the foreground or in the background. If you choose foreground
- music (which is the default) nothing else can happen until the music
- finishes playing. However, if you use the MB (Music Background) option in a
- PLAY music string, the tune plays in the background while subsequent
- statements in your program continue executing.
-
- The PLAY statement plays music in the background by feeding up to 32 notes
- at a time into a buffer, then playing the notes in the buffer while the
- program does other things. A "music trap" works by checking the number of
- notes currently left to be played in the buffer. As soon as this number
- drops below the limit you set in the trap, the program branches to the first
- line of the specified routine.
-
- To set a music trap in your program, you need the following statements:
-
- ON PLAY( queuelimit% ) GOSUB line
- PLAY ON
- PLAY "MB"
- PLAY commandstring$
- .
- .
- .
-
- Here, queuelimit% is a number between 1 and 32. For example, this fragment
- causes the program to branch to the MusicTrap routine whenever the number of
- notes remaining to be played in the music buffer goes from eight to seven:
-
- ON PLAY(8) GOSUB MusicTrap
- PLAY ON
- .
- .
- .
- PLAY "MB"' Play subsequent notes in the background.
- PLAY "o1 A# B# C-"
- .
- .
- .
- MusicTrap:
- .' Routine to play addition notes in the background.
- .
- .
- RETURN
-
-
- Important
-
- A music trap is triggered only when the number of notes goes from
- queuelimit% to queuelimit -\~1 . For example, if the music buffer in the
- preceding example never contained more than seven notes, the trap would
- never occur. In the example, the trap happens only when the number of notes
- drops from eight to seven.
-
-
- Example
-
- You can use a music-trap routine to play the same piece of music repeatedly
- while your program executes, as shown in the following example:
-
- ' Turn on trapping of background music events:
- PLAY ON
-
- ' Branch to the Refresh subroutine when there are fewer than
- ' two notes in the background music buffer:
- ON PLAY(2) GOSUB Refresh
-
- PRINT "Press any key to start, q to end."
- Pause$ = INPUT$(1)
-
- ' Select the background music option for PLAY:
- PLAY "MB"
-
- ' Start playing the music, so notes will be put in the
- ' background music buffer:
- GOSUB Refresh
-
- I = 0
-
- DO
-
- ' Print the numbers from 0 to 10,000 over and over until
- ' the user presses the "q" key. While this is happening,
- ' the music will repeat in the background:
- PRINT I
- I = (I + 1) MOD 10001
- LOOP UNTIL INKEY$ = "q"
-
- END
-
- Refresh:
-
- ' Plays the opening motive of
- ' Beethoven's Fifth Symphony:
- Listen$ = "t180 o2 p2 p8 L8 GGG L2 E-"
- Fate$ = "p24 p8 L8 FFF L2 D"
- PLAY Listen$ + Fate$
- RETURN
-
-
- Trapping a User-Defined Event
-
- This section uses assembly language examples and calls to the DOS operating
- system. You may want to skip it if you are unfamiliar with these items.
-
- Trapping a user-defined event involves writing a non-BASIC routine, such as
- in Microsoft Macro Assembler (MASM) or C, to define the event and inform
- BASIC when the event occurs. Once this is done, and the routine is
- installed, program flow continues much as in the preceding examples but uses
- these statements instead:
-
- ON UEVENT GOSUB line
- UEVENT ONExample
-
- As an example of trapping a user-defined event, suppose you have a special
- task that needs to be done every 4.5 seconds. The following code
- accomplishes this, using the system timer chip which provides an interrupt
- to the CPU 18.2 times per second. The interrupt is DOS number 1CH. The
- address where DOS expects to find the far pointer to the interrupt service
- routine is 0:70H.
-
- The code requires three MASM routines. The first one, SetInt, informs DOS
- that a new interrupt service routine ErrorHandler is to be executed whenever
- interrupt 1CH occurs.
-
- The second routine, EventHandler, is called 18.2 times per second. It
- increments a counter. After 82 interrupts (4.5 seconds) it calls the
- SetUevent routine which is loaded from the BASIC main library during
- compilation. The routine sets a flag indicating that a user event has
- occurred. When BASIC encounters the flag, it causes the BASIC program to
- branch to the SpecialTask routine indicated in the ON UEVENT GOSUB
- statement.
-
- The third routine, RestInt, restores the original service routine for the
- interrupt when the BASIC program terminates.
-
- .model medium, basic ; Stay compatible
- .data; with BASIC.
- .code
- SetIntprocuses ds ; Get old interrupt vector
- mov ax, 351CH ; and save it.
- int 21h
- mov word ptr cs:OldVector, bx
- mov word ptr cs:OldVector + 2, es
-
- push cs; Set the new
- pop ds; interrupt vector
- lea dx, Eventhandler ; to the address
- mov ax, 251CH ; of our service
- int 21H; routine.
- ret
- SetIntendp
-
- public EventHandler ;
- Make the following routine public
-
- EventHandler proc; for debugging.
- extrn SetUevent: proc; Define BASIC library routine.
- push bx
- lea bx, cs:TimerTicks; See if 4.5 secs have passed.
- inc byte ptr cs:[bx]
- cmp byte ptr cs:[bx], 82
- jnz Continue
- mov byte ptr cs:[bx], 0; if true, reset counter,
- pushax ; save registers, and
- pushcx ; have BASIC
- pushdx ; set the user
- pushes ; event flag.
- callSetUevent
- pop es
- pop dx; Restore registers.
- pop cx
- pop ax
- Continue:
- pop bx
- jmp cs:OldVector; Continue on with the
- ; old service routine.
-
- TimerTicks db 0 ; Keep data in code segment
- OldVector dd 0; where it can be found no
- ; matter where in memory the
- EventHandler endp; interrupt occurs.
-
- RestIntprocuses ds; Restore the old
- lds dx, cs:OldVector; interrupt vector
- movx, 251CH ; so things will
- int 21h; keep working when
- ret ; this BASIC program is
- RestIntendp; finished.
- end
-
- The BASIC program shown here provides an outline of how our special task is
- performed using the UEVENT statement. The program first installs the
- interrupt, sets the path to the BASIC event-handling routine and then
- enables event trapping.
-
- ' Declare external MASM procedures.
- DECLARE SUB SetInt
- DECLARE SUB RestInt
- ' Install new interrupt
- service routine.
- CALL SetInt
-
- ' Set up the BASIC event handler.
- ON UEVENT GOSUB SpecialTask
- UEVENT ON
-
- DO
- ' Normal program operation occurs here.
- ' Program ends when any key is pressed.
- LOOP UNTIL INKEY$ <> ""
-
- ' Restore old interrupt service routine before quitting.
- CALL RestInt
-
- END
-
- ' Program branches here every 4.5 seconds.
- SpecialTask:
- ' Code for performing the special task goes here, for example:
- PRINT "Arrived here after 4.5 seconds."
- RETURN
-
-
- Generating Smaller, Faster Code
-
- Event trapping adds execution time and code length to a BASIC program. To
- make your programs smaller and faster, you can turn off event trapping in
- sections where it is unnecessary. Do this with the EVENT OFF statement, as
- shown in the following example. When EVENT OFF is encountered by the
- compiler, it stops generating code to trap events, but the events will still
- be detected. When the compiler encounters the EVENT ON statement, code is
- inserted to re-enable the traps. Any previously detected event, and any
- newly occurring event, will cause the program to branch to the appropriate
- event-handling routine.
-
- ON KEY (1) GOTO Handler1
- ON KEY (2) GOTO Handler2
- KEY (1) ON
- KEY (2) ON
- ' All events are trapped here.
- .
- .
- .
- EVENT OFF
- ' Events are still detected but no longer trapped.
- ' Code generated for these statements is smaller and faster.
- .
- .
- .
- EVENT ON
- ' Events are trapped again including previously detected ones.
- .
- .
- .
- END
- Handler1:
- ' F1 key event-handling routine goes here.
- RETURN
- Handler2:
- ' F2 key event-handling routine goes here.
- RETURN
-
-
- Turning Off and Suspending Specific Event Traps
-
- If you want to selectively turn off certain event traps and leave others on,
- use the event OFF statement. The event occurring after an event OFF
- statement has been executed is then ignored. This is shown by the following
- example where the F1 key trap is turned off, but the trap for F2 is left on:
-
- ON KEY (1) GOTO Handler1
- ON KEY (2) GOTO Handler2
- KEY (1) ON
- KEY (2) ON
- ' Both key traps are turned on here.
- .
- .
- .
- KEY (1) OFF
- ' The F1 trap is turned off and ignored here, but we still trap F2.
- .
- .
- .
-
- KEY (1) ON
- ' Now we can trap them both again.
- .
- .
- .
- END
- Handler1:
- ' F1 key event-handling routine goes here.
- RETURN
- Handler2:
- ' F2 key event-handling routine goes here.
- RETURN
-
- Sometimes you need to suspend event trapping, without turning it off. This
- allows you to record events that occur and take action on them after a
- specific time period has elapsed.
-
- To suspend event trapping, use the event STOP statement as demonstrated in
- the next example. In the following example, after the event STOP statement
- is encountered, if the timer event occurs, there is no branch to the
- event-handling routine. However, the program remembers that the event
- occurred, and as soon as trapping is turned back on with event ON, it
- immediately branches to the ShowTime routine.
-
- ' Once every minute (60 seconds),
- ' branch to the ShowTime routine:
- ON TIMER(60) GOSUB ShowTime
-
- ' Activate trapping of the 60-second event:
- TIMER ON
- .
- .
- .
- TIMER STOP' Suspend trapping.
- ' A sequence of lines you don't want interrupted,
- ' even if 60 or more seconds elapse.
- .
- .
- .
- TIMER ON' Reactivate trapping.
- ' If a timer event occurs above, it will now
- ' be handled by the ShowTime routine.
- .
- .
- .
- END
-
-
- ShowTime:
-
- ' Get the current row and column position of the cursor,
- ' and store them in the variables Row and Column:
- Row = CSRLIN
- Column = POS(0)
-
- ' Go to the 24th row, 20th column, and print the time:
- LOCATE 24, 20
- PRINT TIME$
-
- ' Restore the cursor to its former position
- ' and return to the main program:
- LOCATE Row, Column
- RETURN
-
- Events Occurring Within Event-Handling Routines
-
- If an event occurs during an event-handling routine, and the event trap is
- on for the new event, then the program branches to the event-handling
- routine for this new event. For instance, in the first example in the
- preceding section, if the F2 key is pressed while Handler1 is executing, the
- program branches to Handler2. When Handler2 is finished, the program returns
- to Handler1 and then goes back to the main program.
-
- The only time that branching doesn't occur in a event-handling routine is if
- both events are the same. In this case branching cannot occur because
- event-handling routines execute an implicit event STOP statement for a
- given event whenever program control is in the routine. This is followed by
- an implicit event ON for that event when program control returns from the
- routine.
-
- For example, if a key-handling routine is processing a keystroke, trapping
- the same key is suspended until the previous keystroke is completely
- processed by the routine. If the user presses the same key during this time,
- this new keystroke is remembered and trapped after control returns from the
- key-handling routine.
-
-
- Event Trapping Across Modules
-
- Events whose traps are turned on in one module are detected and trapped in
- any module that is running. This is demonstrated in the following program
- where a trap set for the F1 function key in the main module is triggered
- even when program control is in the other module.
-
- ' =========================================================
- 'MODULE
- ' =========================================================
- ON KEY (1) GOSUB GotF1Key
- KEY (1) ON
- PRINT "In main module. Press c to continue."
-
- DO: LOOP UNTIL INKEY$ = "c"
- CALL SubKey
-
- PRINT "Back in main module. Press q to end."
- DO : LOOP UNTIL INKEY$ = "q"
- END
-
- GotF1Key:
- PRINT "Handled F1 keystroke in main module."
- RETURN
-
- ' =========================================================
- 'SUBKEY MODULE
- ' =========================================================
- SUB SubKey STATIC
- PRINT "In module with SUBKEY. Press r to return."
-
- ' Pressing F1 here still invokes the GotF1Key
- ' subroutine in the MAIN module:
- DO : LOOP UNTIL INKEY$ = "r"
- END SUB
-
- Output
-
- In main module. Press c to continue.
- Handled F1 keystroke in main module.
- In module with SUBKEY. Press r to return.
- Handled F1 keystroke in main module.
- Back in main module. Press q to end.
- Handled F1 keystroke in main module.
-
-
- Compiling Programs From the Command Line
-
- When compiling code containing any of these statements from the command line
- you must use one of the compiler options described in Table 8.1.
-
- ON event GOSUB
- event ON
- EVENT ON
-
-
- The /V option detects events sooner, however the program will run slower and
- take up more memory. As an alternative, you can add labels to your program
- at the places where you need detection and compile with the /W option as
- shown in this example:
-
- ON KEY (1) GOSUB F1Handler
- KEY (1) ON
- .
- .
- .
- ' Check for an event here.
- Checkpoint1:
- ' Continue processing without checking.
- .
- .
- .
- DO UNTIL Condition%
- ' Check for event every time we loop.
- Checkpoint2:
- .
- .
- .
- LOOP
- ' No more event checking.
- .
- .
- .
- END
- F1Handler:
- ' Code to take action when F1 is pressed goes here.
- RETURN
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 10: Database Programming with ISAM
- ────────────────────────────────────────────────────────────────────────────
-
- Microsoft BASIC gives you the power and flexibility of Indexed Sequential
- Access Method (ISAM) through a group of straightforward statements and
- functions that are part of the BASIC language. ISAM statements and functions
- provide an efficient and simple method for quickly accessing specific
- records in large and complex data files. This chapter describes ISAM, its
- statements and functions, and how to use them in programs that access and
- manipulate the records in ISAM database files. These statements and
- functions make it easy for your programs to manage database files as large
- as 128 megabytes.
-
- When you finish this chapter, you'll understand:
-
- ■ What ISAM is, and when and why it is useful.
-
- ■ The new and modified BASIC statements used for ISAM file access and
- manipulation.
-
- ■ A general approach to creating, accessing, and manipulating records in
- ISAM databases.
-
- ■ The structure of an ISAM file.
-
- ■ Using indexes to work with data records as though they were sorted in
- many ways.
-
- ■ Using EMS (expanded memory) with ISAM programs.
-
- ■ Using transaction statements in applications with complex block
- processing requirements.
-
- ■ Converting existing database code to ISAM code.
-
- ■ Using ISAM utilities to convert your sequential and database files to
- ISAM format, to compact ISAM databases, repair damaged databases, and
- exchange tables between database files and sequential files.
-
-
- Note
-
- ISAM is supported only in MS-DOS. You cannot use it in OS/2 programs.
-
-
- What Is ISAM?
-
- When a program uses or modifies records stored in a file, it often has to
- sort and re-sort the records in various ways. When a file contains many
- complex records, sorting can require substantial program code and a great
- deal of processing time. ISAM is an approach to creating and maintaining a
- special data file, in which the way records typically need to be sorted can
- be easily defined and efficiently stored along with the records themselves.
- This means your program doesn't have to re-sort the records each time the
- file is used or each time you want a different perspective on the records.
-
- In addition to your data records, an ISAM file contains information that
- describes and facilitates access to each data record. Much of this
- information is maintained in "tables" and "indexes." Tables serve many
- purposes, including allowing quick access to any of the values of a specific
- data record. Indexes represent various ways of ordering the presentation of
- records in a table, and they permit you to easily access a whole record by
- the value of a field in the record.
-
- ISAM statements and functions manipulate, present, and manage the records in
- ISAM data files. ISAM's record-searching and ordering algorithms are faster
- and more efficient than routines that you might create in BASIC to perform
- these tasks, so it not only saves you significant programming effort, but
- improves the speed and capacity of many database programs as well.
-
- For database applications, ISAM files are more convenient and efficient than
- random-access files because they allow you to access the file as though the
- records were ordered in a variety of different ways. The next several
- sections compare ISAM to other types of files and introduce some concepts
- and terms that are helpful in understanding ISAM. (Traditional sequential
- and random-access files are discussed in Chapter 3, "File and Device I/O.")
-
-
- ISAM Statements and Procedures
-
- The statements for performing ISAM file tasks are integrated into the BASIC
- language. In most cases, new statements have been added for ISAM. In a few
- cases, existing statements have simply been expanded. The following list
- categorizes the ISAM statements by the type of task for which you use them:
-
- ╓┌─────────────────────────────────────┌─────────────────────────────────────╖
- Task Statements
- ────────────────────────────────────────────────────────────────────────────
- File and table creation/access OPEN, CLOSE, DELETETABLE, TYPE...
- END TYPE
-
- Task Statements
- ────────────────────────────────────────────────────────────────────────────
- Controlling presentation order of CREATEINDEX, GETINDEX$, SETINDEX,
- data (indexing) DELETEINDEX
-
- Position change relative to the MOVEFIRST, MOVELAST, MOVENEXT,
- current record MOVEPREVIOUS, TEXTCOMP
-
- Position change by field value SEEKGT, SEEKGE, SEEKEQ
-
- Table information BOF, EOF, LOF, FILEATTR
-
- Data exchange INSERT, RETRIEVE, UPDATE, DELETE
-
- Transaction processing BeginTrans, Committrans, CheckPoint,
- RollBack, SavePoint
-
-
-
-
-
-
-
- Some ISAM statement usage rules parallel BASIC rules, while others are more
- specific due to the characteristics of the ISAM file. For example, the BASIC
- LOF function, which returns the length of a sequential file or the number
- of records in a random-access file, returns the number of records in the
- specified table when used on an ISAM file.
-
-
- The TYPE... END TYPE statement is used to define the structure of the
- record variables that will be used to exchange data between your program and
- the ISAM file. The elements in a TYPE... END TYPE statement can have any
- user-defined type or BASIC data type except variable-length strings and
- dynamic arrays. However, the TYPE... END TYPE statement used for ISAM
- access cannot contain BASIC's SINGLE type. Floating-point numeric elements
- in a TYPE... END TYPE statement used for ISAM access must have DOUBLE
- type. Fixed-point decimal numeric elements can have BASIC's new currency
- type.
-
- Similarly, when you name the elements of the user-defined type, you must
- name them according to the ISAM naming convention (which is a subset of the
- BASIC identifier-naming convention). The name of the user-defined type
- itself, however, is a BASIC identifier and follows the BASIC naming
- convention. Similarly, some arguments to ISAM statements follow BASIC naming
- conventions, while others follow the ISAM subset (described in the section
- "ISAM Naming Convention" later in this chapter).
-
- Note
-
- The ISAM statements and functions are integrated into the BASIC language.
- However, within the QBX environment the ISAM statements are recognized, but
- cannot be executed unless you invoke a terminate-and-stay-resident (TSR)
- program before starting QBX. Using a TSR allows QBX to provide full ISAM
- support, but only when your programs need it. For programs that don't use
- ISAM, not loading (or unloading) the TSR saves substantial memory.
-
-
- ISAM Vs. Other Types of File Access
-
- ISAM files are often used in place of random-access files because ISAM
- provides more flexible access to any arbitrary record within the database.
- Although it is not an ASCII text file, an ISAM file is a sequential-access
- file. When an ISAM file is opened, BASIC uses ISAM routines that handle all
- interaction between the operating system and the actual file. ISAM organizes
- your data records into a structure called a table. You can think of this
- table as a series of horizontal "rows," each row corresponding to a full
- data record. The table is also divided into vertical "columns," each column
- corresponding to one of the fields in your data records.
-
- With random-access files you use the GET statement to access a record by
- its record number (which represents the order in which the record was
- inserted into the file). Random access does not provide access to records
- based on the values in specific fields within the record. When you access an
- ISAM file, ISAM's indexing and SEEK operand statements allow you to test
- the values in a specified group of "vertical" fields (columns) against a
- stated condition. Put another way, a random-access file is accessible by row
- number only, like a list. With an ISAM file you can access records either by
- relative position (row) or by the contents of any field in a specified
- column. The ISAM statements give you the ability to access specific records
- in the file with the speed of a random-access file, but with a great deal
- more flexibility.
-
-
- The following list contrasts the unit of access used by BASIC file types:
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- File type Access unit
- ────────────────────────────────────────────────────────────────────────────
- Binary By byte
-
- Sequential By line or by byte
-
- Random By record number (i.e., row only)
-
- ISAM By position, or by the value of
- any field (or group of fields)
- within a table
-
-
-
-
-
- ISAM also differs from other record-indexing approaches because all the
- information relating to the records is contained in a single file that also
- contains the data records themselves. This can greatly facilitate user
- management of complicated databases without sacrificing speed and
- convenience.
-
-
- The ISAM Programming Model
-
- The following chart describes the general sequence of steps used in ISAM
- database programming. It compares the ISAM model and statements to the
- corresponding tasks and statements used with random-file access.
-
- ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
- Task to be performed ISAM approach Random-file approach
- ────────────────────────────────────────────────────────────────────────────
- Associate program Use TYPE... END TYPE, Use TYPE... END TYPE,
- variables with database and DIM. and DIM.
- records.
-
- Access records. Use OPEN to access a Use OPEN to access a
- table. file..
- Task to be performed ISAM approach Random-file approach
- ────────────────────────────────────────────────────────────────────────────
- table. file..
-
- Change presentation Use CREATEINDEX and/or No support provided.
- order of records SETINDEX. Requires sorting code,
- according to value in a unless record-insertion
- specified field. order is adequate.
-
- Specify record to work Use MOVE dest to move Use GET to retrieve a
- with. by row or SEEK operand record by record number,
- to move to a record or if that is not
- containing a specified adequate, requires
- field value. searching code to
- determine which record
- to GET.
-
-
-
-
-
-
- ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- Data exchange. Use RETRIEVE to assign Use GET and PUT for
- a record from a table simple fetching and
- to program variables. overwriting of existing
- Use UPDATE to assign records. To delete a
- program variables to a record, you typically
- record in a table. Use code to mark it for
- INSERT to insert a deletion; write a
- record in a table. Use temporary file that
- DELETE to delete a omits it; then delete
- record from a table. the original file; and
- When you overwrite, finally, rename the
- insert, or delete temporary file with the
- records, ISAM handles original filename. To do
- all table and index a simple insert of a
- maintenance record, you must code to
- transparently. keep track of the number
- of records, then insert
- each new record as
- ────────────────────────────────────────────────────────────────────────────
- each new record as
- number n+1. Inserting at
- a specific position
- requires code to swap
- records.
-
- Change presentation Use CREATEINDEX or Requires sorting code.
- order of records to get SETINDEX.
- a different perspective
- on data.
-
- Close the file(s). Use CLOSE for tables. Use CLOSE.
-
-
-
-
-
-
- ISAM Concepts and Terms
-
- Many terms used in describing ISAM are familiar, however when used with ISAM
- many terms have specialized connotations. For example, when using random
- file access, one typically thinks of a file as a collection of logically
- related records. In ISAM such a collection of records is called a "table,"
- since an ISAM disk file (called a "database") can contain multiple and
- distinct collections of records. This section explains some fundamental ISAM
- concepts and terms.
-
- Field A single data item constituent of a record.
-
- Record A collection of logically related data-item fields. The association
- of the fields is defined by a TYPE... END TYPE statement in your program.
-
- Row Synonym for record. When placed in an ISAM table, the collection of
- fields in a specific record is referred to as a row. Thus, a row in a table
- corresponds to a single data record. See Figure 10.1.
-
- Table An ordered collection of records (rows), each of which contains a
- single data record. The records in a table have some logical relationship to
- one another. The default order of records in the table corresponds to the
- order in which records were added.
-
- Column Each column in a table has a name. A column in a table is the
- collection of all fields having the same column name. Thus, a column is a
- vertical collection of fields in the same way a row is a horizontal
- collection of fields.
-
- Database A collection of tables and indexes contained in a disk
- file.
-
- Index An independent structure within the ISAM file created when a
- CREATEINDEX statement is executed. Each index represents an alternative
- order for presentation of the records in the table. The order is based on
- the relative values of each data item in the column (or columns) specified
- in the CREATEINDEX statement. You might want to think of an index as a
- "virtual table," that is, a virtual ordering of the table's data records. An
- index must have a name, and may have other attributes. Any index you create
- is saved and maintained as part of the database until it is explicitly
- deleted. Figure 10.2 illustrates a table. Beside the table, a list of
- positions indicates where each record actually resides in the table. Beneath
- the table, a diagram illustrates how an index on one of the columns (the
- Invoice column) changes the presentation order of the data when the index is
- specified.
-
- Specifying an index is a separate step in which the order of the indexed
- column (or columns) is imposed on any presentation of the table's records.
- To present a table's records in the index's order, you first specify that
- index in a setindex statement. If you were to create, and then specify the
- index on the Invoice column in the preceding figure, the records would be
- presented in the order shown in the final part of Figure 10.2, with the
- record at table position 3 first, followed by the record at table position
- 6, followed by the record at table position 2, and so on.
-
- NULL Index The default index for a table; that is, the presentation order
- of the records when no user-created index is specified. When the NULL index
- is in effect (for instance, when the table is first opened), the order of
- the records is the order in which they were inserted into the table. In
- Figure 10.2, this order is illustrated by the table itself.
-
- Record Order The actual physical order of records on disk is arbitrary
- because whenever records are deleted from a table, their physical disk
- positions are filled by the next records inserted in the table. This
- optimizes access speed and disk-space usage. For example, if you delete the
- third record added to the table, then add the sixth record, the sixth record
- would be placed in the physical disk position previously occupied by the
- third record as shown in Figure 10.3.
-
- Insertion Order The order in which the records are inserted into
- the table. This order corresponds to the order imposed by the NULL index
- (the default index).
-
- Presentation Order The apparent order imposed on the table by the current
- index. Note that file-space optimization has the side effect that
- subordering of records when an index is applied corresponds to the actual
- physical order of records on the disk. If you want presentation order to
- include specific subordering, use a combined index.
-
- Combined Index An index that is based on more than a single column.
- Specifying a combined index enforces a specific subordering on the
- presentation order of the records.
-
- Indexed Value The value (or combination of values) that determines a
- record's position on a particular index. While an index is a collection of
- indexed values, there is one indexed value for each record (row). Therefore,
- with a combined index, the indexed value is the combination of the
- constituent fields of the index.
-
- Key Value A value against which indexed values are tested when using a
- SEEK operand statement to seek a record that meets the specified condition.
-
- Unique Index An index requiring that each indexed value (i.e., each field
- in the column on which the index is defined) be different from all others.
- When you use the CREATEINDEX statement to create an index, you can specify
- it as "unique." For example, if you specified a unique index on ClientCode
- column in the table in Figure 10.2, ISAM would generate a trappable error if
- the value of any ClientCode field ever duplicated the value of any other
- ClientCode field in the table. You could use a unique index to prevent a
- user from assigning the same ClientCode to two different clients.
-
- Current Position The focus of activity in a table. Understanding
- "position" in the table is important because, in all cases, position is
- relative to some structure within the table that doesn't exist in other
- BASIC files. In an ISAM table, "currency" (meaning the current position, not
- the new CURRENCY data type) is determined by several factors. Since
- multiple indexes may be created on any table, the current position depends
- on which index has been specified. There is no concept of a "current table"
- in ISAM, but there is always a "current index" for each open ISAM table. If
- a table contains any records, one record is the "current record," except in
- two cases:
-
- ■ There is no current record at the beginning or end of the table (when
- BOF or EOF return true).
-
- ■ There is no current record after the unsuccessful execution of a SEEK
- operand statement (because eof then returns true).
-
-
- Otherwise, every open table has a current index and a current record (if it
- contains any records).
-
- Current Index The index whose sorting order determines the order of
- appearance of a table's records. When a table is first opened, the NULL
- index is the current index. Once an index is created on a column or
- combination of columns, specifying it as the current index imposes the
- sorting order of that column or combination of columns on the presentation
- order of all records in the table. That index remains the current index on
- that table until it is deleted, a different index is specified for that
- table, or the table is closed. Every open table has a current index.
-
- Current Record The focus of activity between your program and an ISAM
- table. Directly following a setindex statement, the current record is the
- record with the smallest indexed value, according to the index specified.
- ISAM provides many statements for making different records current, and for
- altering the current record. At any time, one and only one record can be the
- current record in each open table. When the NULL index is specified with
- setindex, the current record is the record that was inserted into the table
- first.
-
- Focus The focus of data exchange (that is, the record affected by an ISAM
- statement that fetches, overwrites, inserts, or deletes data in a table).
- The focus is always the current record, as determined by the current index.
-
- ISAM Engine Routines used by ISAM to access and maintain the database.
-
- ISAM File A database created and maintained using ISAM.
-
- Data Dictionary Tables and indexes used by the ISAM engine in maintaining
- the database.
-
-
- ISAM Components
-
- ISAM works by applying routines contained in the ISAM engine to a physical
- disk structure called an ISAM file. The following sections describe these
- components and the table/index model for record access.
-
-
- The ISAM Engine
-
- The ISAM engine creates a table representing your data records to enhance
- the storage of and rapid access to each record. The relationship between the
- individual records in the table and what is actually in memory as your
- program runs is determined by the ISAM engine. This lets you easily
- manipulate the records of very large files as though they all fit in memory
- at once. The actual order of physical storage of records in the file is
- unimportant because ISAM allows you to deal with the records as though they
- were stored in a variety of convenient ways.
-
- Your program can present the records in the file according to the sorting
- order of any column in the table simply by creating, and then specifying an
- index. Using the SETINDEX statement is functionally equivalent to sorting
- the records according to the values of the constituent fields of the indexed
- column (or combination of columns). When indexes are created, they become
- part of the database. They can then be saved as part of the database, or
- deleted. When saved, the ISAM file contains the indexes you've created, in
- addition to the tables containing the data records themselves. Indexes are
- described in detail in the section "Creating and Specifying Indexes on Table
- Columns" later in this chapter.
-
-
- The Parts of the ISAM File
-
- ISAM places your data records in the table (or tables) you specify. Each
- table represents a group of logically related records, but the logic of the
- groupings is completely up to you. For example, it might be useful to keep
- one table in the database for your inventory and another for clients.
-
- Information describing an ISAM file is maintained within the file in a set
- of system tables called the data dictionary. The data dictionary itself is
- invisible to your program, and you never have to deal with it if you don't
- want to. In fact, it is safest to simply let the ISAM engine handle all
- interaction with the data dictionary, since corrupting it could destroy your
- database. Information in the data dictionary includes table names, indexes
- and index names, column names, and all the other information used by ISAM to
- access and manipulate the records in response to the ISAM statements.
-
- You can create any number of tables within a database file, although the
- number of tables you can open simultaneously has an upper limit of 13 and
- decreases each time an additional database is opened. A practical maximum of
- four databases can be opened at once. The section "Using Multiple Files"
- describes how many tables can be opened, relative to the number of open
- databases.
-
-
- ISAM File Allocation and Growth
-
- Because an ISAM file contains descriptive information, it has some file-size
- overhead. Additionally, to optimize access speed and flexibility, ISAM files
- grow periodically in large chunks (32K per chunk), rather than in
- record-sized increments as single records are added. A database contains a
- header of about 3K. Each table has 4K of overhead beyond its actual data
- records; each index requires at least 2K. The data dictionary consists of
- five system tables plus eight system indexes, resulting in a total initial
- overhead of about 39K. Therefore, the smallest ISAM file is 64K. Though an
- ISAM file with a single record is 64K, there is considerable room for adding
- data records within that 64K file before the next 32K chunk is added. The
- initial combination of system tables and system indexes is about 39K; the
- remaining 25K are used for your data records and the new indexes and tables
- you create.
-
-
- When to Use ISAM
-
- For data files too large to completely load into memory, ISAM vastly
- simplifies file manipulation because ISAM support replaces the kind of
- sorting that can only otherwise be accomplished efficiently by loading all
- data records in memory simultaneously. This makes ISAM an excellent method
- for dealing with large amounts of data which require sorted access. An ISAM
- file can be as large as 128 megabytes. ISAM handles all the work of moving
- portions of such a huge file in and out of memory during record
- manipulation.
-
- Whenever data records contain many fields that need to be examined in a
- variety of ways, using ISAM simplifies the programming. Although you can
- write code to sort or index random-access files, ISAM integrates these tasks
- for you with high-level statements that manipulate sophisticated file
- structures. This lets you easily manipulate records by the values in
- specific fields, and is far more flexible than a random file's
- one-dimensional ordering by record number. (For an example of how much BASIC
- code just one index for a random file requires, see the program INDEX.BAS
- listed in Chapter 3, "File and Device I/O.") However, if disk space is at a
- premium, don't automatically choose ISAM for short, easily-sorted files of
- relatively constant size. These may be better handled using other methods
- (for example, by creating and using hash tables). However, if you need to
- sort on different fields at different times, or if you need very fast access
- to records according to complex subsorting orders, the benefits of the ISAM
- file quickly make up for its overhead. Also, consider that for very large
- files, the amount of descriptive information relative to actual records
- remains relatively constant, so the percentage of the file devoted to
- overhead decreases progressively.
-
-
- The Table/Index Model
-
- In ISAM, tables and indexes represent various fundamental arrangements of
- the data records. When you insert a record in an ISAM table, its place in
- the table is the result of a process that optimizes file size and speed of
- access. References to the record are immediately placed in all of that
- table's existing indexes, including the NULL index, so the presentation
- order of all records is always internally consistent. The default order for
- a table is the chronological order of insertion.
-
- Specifying an index other than the NULL index orders the records of the
- table by the sorting order of each field in the indexed column (or
- combination of columns). For example, if each row in a table contains five
- columns, you can create indexes on any, or all, or any combination of the
- five columns. When you specify one of these indexes (with SETINDEX), you
- impose the sort order of the index on the presentation order of the records.
- (The sort order is either numeric or alphabetic, depending on the data type
- of the column or columns). Specifying a different index changes the
- presentation order of the records. As you add and remove records from the
- table, ISAM maintains all relevant information about the table. Figure 10.4
- illustrates a simple table in which each record is a collection of six
- user-defined fields. The table can be represented as four rows, each having
- six columns.
-
- The values in each field in the Number column represent the order in which
- each record was added to the table. In practice, you would probably never
- define such a column, since insertion order, the NULL index, is the default
- ordering of the records in an ISAM table, but it is included here for
- illustrative purposes. In a random-access file, the Number column would
- correspond to the record number, and would be the only way you could
- reference a record without writing special code to sort the file. However,
- because this is an ISAM table, you can use an index to specify a
- presentation order that corresponds to the sort order of any of the columns.
-
- Suppose you wanted to organize a celebration, and you wanted to compile an
- invitation list that included only women. With just the OPEN, CREATEINDEX,
- and SETINDEX statements you could create and specify an index on the Sex
- column. Since F sorts before M, all the women in the table would be
- presented before any of the men, as shown in Figure 10.5.
-
- With this order the program could easily start from the first record,
- display its data, then use the MOVENEXT statement to make each successive
- record the current record. Conversely, if you wanted to invite only men, the
- program could start from the last record (using the MOVELAST statement),
- and then use MOVEPREVIOUS to traverse the records in reverse order. As the
- program displayed each previous record, you could choose among the men.
-
- An index can be specified on each column of a table. Figure 10.6 presents
- the table information indexed on the Sport column.
-
- You can also create combined indexes by "combining" the values in several
- fields so records appear sorted, first by one field, then sorted by another,
- and so on. For example, you could create and specify a combined index that
- presented the records sorted first by the Phone column, then by the Birthday
- column. This would present the records sorted first by household, then
- within each household, by the order of the birthdays of each person with the
- same phone number, as shown in Figure 10.7.
-
- The preceding examples are for illustrative purposes. Normally, when
- designing a program you would provide the user with several useful indexes
- on the records, rather than designing the program to let the user create
- indexes as needed. However, if you want to let users create their own
- indexes, you can do so using the ISAM statements and functions.
-
-
- A Sample Database
-
- The following sections describe a table within an ISAM database file that
- could be used by a library to keep track of its inventory of books. Examples
- demonstrate how to create or open the database and view the records from
- several different perspectives. (This program, BOOKLOOK.BAS, as well as its
- associated .MAK file (BOOKLOOK.MAK), secondary modules (BOOKMOD1.BAS,
- BOOKMOD2.BAS, BOOKMOD3.BAS), database file (BOOKS.MDB), and include file
- (BOOKLOOK.BI) are included on the disks supplied with Microsoft BASIC. When
- you ran the Setup program, they were placed in the directory you specified
- for BASIC source and include files.
-
-
- Designing the BookStock Table
-
- Inventory maintenance of a book-lending library can be used to illustrate
- the ISAM approach. Assume that the patrons of the library are concerned only
- with books dealing with the BASIC programming language. For example, you can
- create a database containing a single table that includes pertinent
- information about all the library's books about BASIC. Figure 10.8
- illustrates the form such a table might take.
-
-
- Creating, Opening, and Closing a Table
-
- The statements used for creating, opening, and closing databases and tables
- are the familiar BASIC TYPE... END TYPE, OPEN, and CLOSE statements.
- However, they are used differently with ISAM files than with other types of
- files.
-
-
- Naming the Columns of the Table
-
- The first step in creating a table is to include an appropriate TYPE... END
- TYPE statement in the declarations part of your program. The name you use
- for this user-defined type is an argument to the OPEN statement that
- creates the database file and the table, and may be used subsequently
- whenever the table is opened. When the table is first created, the names
- used for the elements in the TYPE... END TYPE statement become the names of
- the corresponding columns in the table (See Figure 10.8).
-
-
- Specifying the Data Types of the Columns
-
- What can appear in a column of a table is determined by the column's data
- type. For instance, a column having INTEGER type can accept whole numbers
- in the normal integer range. Similarly, a column having STRING type can
- contain a string as large as 32K. Assigning data types to the columns makes
- it possible for ISAM to create the indexes that can be used to change the
- presentation order of the table's records.
-
- Note
-
- Although you can fetch and write data that has the following characteristics
- to an ISAM table, you cannot create indexes on them:
-
- ■ STRING columns longer than 255 bytes.
-
- ■ Columns with aggregate (i.e., array) type.
-
- ■ Columns with structure (i.e ., user-defined) type.
-
-
- Each column in a table has the data type specified in the TYPE... END TYPE
- statement used as the tabletype in the OPEN statement that created the
- table. Data types you specify in an ISAM TYPE... END TYPE statement must be
- one of those shown in Table 10.1.
-
- Note that BASIC's SINGLE data type is not legal in ISAM; use DOUBLE or
- CURRENCY instead. The following declaration can be used in a program that
- creates or accesses the sample BookStock table.
-
- TYPE Books
- IDnumAS DOUBLE ' ID number for this copy
- PriceAS CURRENCY ' Original cost of book
- Edition AS INTEGER ' Edition number of book
- Title AS STRING * 50' The title of the book
- PublisherAS STRING * 50' The Publisher's name
- AuthorAS STRING * 36' The author's name
- END TYPE
-
- Although BASIC would accept element identifiers up to 40 characters long,
- the elements in this statement must follow the ISAM naming convention (see
- the section "ISAM Naming Convention" later in this chapter), since they will
- become the names of columns within the ISAM database file. The actual name
- of the user-defined type can be any valid BASIC identifier however, because
- it is never actually used within the ISAM file.
-
-
- Data Type Coercion
-
- Although BASIC performs considerable data type coercion in other situations,
- the only coercion performed between your BASIC program and ISAM is in
- relation to seek operand statements, and even then only between integer and
- long values. Therefore, if a long value is expected by ISAM, and you pass an
- integer, the integer will be coerced to a long, and no type-mismatch error
- is generated. However, if you try to pass a long when ISAM expects an
- integer, coercion may result in an Overflow error. In other situations, such
- as passing a currency value when a double is expected, a Type mismatch error
- is generated. Since BASIC's default data type is single precision numeric,
- passing a literal (even 0) to ISAM can cause a type mismatch (since single
- is not a valid ISAM data type). In such a case, you should append the
- type-declaration character for the type expected by ISAM to the number. Even
- if you reset the default data type with a def type statement, it is a good
- idea to screen the types of all numbers passed to ISAM to make sure they are
- properly typed and that they will fit within the range of the expected type.
-
-
- Opening the BookStock Table
-
- The declaration of the user-defined type is all the preparation a simple
- program needs to prepare for opening an ISAM database and table. The
- following code can now be used to create or open the table within the ISAM
- file:
-
- ' You could write code here to check to see if the file exists,
- ' then open the file if it does or display a message if it doesn't.
-
- OPEN "BOOKS.MDB" FOR ISAM Books "BookStock" AS # 1
-
- Using OPEN and CLOSE with ISAM
-
- To open a table, you use the traditional BASIC OPEN statement with
- arguments and clauses specific to ISAM. Whenever you open a table, you must
- specify the database file that contains the table. The syntax for an ISAM
- OPEN is as follows:
-
- OPEN database$ FOR ISAM tabletype tablename$ AS # filenumber%
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- database$ a string expression representing a
- DOS filename, so it follows
- operating-system file-naming
- restrictions. This argument can
- include a drive letter and a path.
-
- tabletype A BASIC identifier that specifies
- a user-defined type already
- declared in the program. Note that,
- unlike the other arguments, it
- cannot be a string expression.
-
- tablename$ A string expression that follows
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- tablename$ A string expression that follows
- the ISAM naming convention.
-
- filenumber% An integer within the range 1 -
- 255, the same as in the
- traditional BASIC OPEN statement.
- Note that filenumber% is
- associated with both the
- tablename of the table being
- opened and the database file (
- database$) itself containing the
- table. Therefore, the same
- database$ can appear in any number
- of OPEN statements, each of which
- opens a different table (with a
- unique filenumber%) in the same
- database file. You can use
- FREEFILE to get available values
- for filenumber%.
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- for filenumber%.
-
-
-
-
-
- String arguments are not case sensitive, so you can use inconsistent
- capitalization in any of these references.
-
- The close statement is the same for an ISAM database as for any other file:
-
- CLOSE # filenumber% , # filenumber% ...
-
- Opening a Table
-
- The FOR ISAM clause simply replaces the FOR OUTPUT (or APPEND, or INPUT)
- clause used for other sequential-file access. The ISAM engine then handles
- all file interaction.
-
- The behavior of an OPEN... FOR ISAM statement is similar to OPEN... FOR
- OUTPUT or OPEN... FOR APPEND with other types of sequential files. For
- example, if database$ does not yet exist as a disk file, it is created by
- the OPEN statement. Similarly, if tablename does not exist within the
- database, the OPEN statement creates a table of that name within the
- database, and opens it. The tabletype argument must identify a user-defined
- type previously declared in the program with a TYPE... END TYPE
- statement. This precludes writing programs that permit the end user to
- design custom tables at run time. If an ISAM OPEN statement fails, all ISAM
- buffers are written to disk and any pending transactions are committed (See
- the section "Block Processing with Transactions" later in this chapter for
- information on transactions.)
-
- Note
-
- You cannot lock an ISAM database using open...for isam. However, you can
- open a database that has been designated read-only by some other process. If
- your program opens such a file, certain ISAM statements will generate
- errors, including: delete, deleteindex, deletetable, createindex, insert,
- and update. These statements cause Permission denied error messages.
-
-
- Closing a Table
-
- A CLOSE statement with filenumber% as an argument closes the tablename$
- associated with filenumber%. CLOSE with no arguments closes all open
- tables (and any other files, ISAM or otherwise). Any ISAM CLOSE statement
- causes all pending transactions to be committed. (See the section "Block
- Processing with Transactions" later in this chapter for information on
- transactions.)
-
-
- The Attributes of filenumber%
-
- A program that opens an ISAM table can open other files for other types of
- access. In such cases, you may need to determine at some point which files
- (or tables), associated with which file numbers, are open for which types of
- access. FILEATTR has the following syntax:
-
- FILEATTR( filenumber%, attribute%)
-
- When you use this function, you pass the number of the file or table you
- want to know about as the first argument, and either 1 or 2 as attribute%.
- If you pass a 1, the value returned in FILEATTR is 64 if filenumber% was
- opened as an ISAM table.
-
-
- Other return values indicate the file was opened for another mode, as
- follows:
-
- ╓┌───────────────────────┌───────────────────────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 1 INPUT
- 2 OUTPUT
- 4 RANDOM
- 16 APPEND
- 32 BINARY
- 64 ISAM
-
-
-
-
- With an ISAM file, if you pass a 2 as attribute%, FILEATTR returns zero.
-
-
- Defining a Record Variable
-
- Although it isn't necessary to do it at the same time you open the database,
- you eventually need to define a record variable having the proper
- user-defined type for the table. This variable is used in transferring data
- between your program and the ISAM file. In the case of the Books type, it
- could look like this:
-
- DIM Inventory AS Books
-
- Creating and Specifying Indexes on Table Columns
-
- Much of the power of ISAM derives from the ease with which the apparent
- order of data records can be changed. This is accomplished by specifying a
- previously created "index" on a column (or columns) in a SETINDEX
- statement.
-
- If you don't specify an index on a table, the default index (the NULL index)
- is used, and the apparent order of the records is the order in which the
- records were added to the file. Therefore, when you initially open a table,
- the current index is the NULL index until (and unless) you specify a
- different index. You create your own indexes with the CREATEINDEX
- statement, using the following syntax:
-
- CREATEINDEX # filenumber%, indexname$, unique%, columnname$ ,
- columnname$...
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- filenumber% The integer used to open the table
- on which the index is to be
- created .
-
- indexname$ A string expression that follows
- the ISAM naming conventions. The
- index is known by indexname$
- until explicitly deleted.
-
- unique% A numeric expression. A non-zero
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- unique% A numeric expression. A non-zero
- value specifies a unique index on
- the column, meaning that no values
- in any of that column's fields can
- duplicate any of the others. A
- value of zero for this argument
- means the indexed values need not
- be unique.
-
-
-
-
-
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- columnname$ A string expression following the
- ISAM naming convention that
- specifies the column to be indexed.
- ────────────────────────────────────────────────────────────────────────────
- specifies the column to be indexed.
- Note that multiple columnname$
- entries do not create multiple
- independent indexes, but rather
- create a single combined index.
- Each succeeding columnname$
- identifies a subordinate sorting
- order for the records (when that
- index is specified).
-
-
-
-
-
- The CREATEINDEX statement is used only once for each index. If you try to
- create an index that already exists for the table, a trappable error is
- generated.
-
- Once an index exists, you use the SETINDEX statement to make it the current
- index (thereby imposing its order on the presentation order of the records).
- SETINDEX has the following syntax:
-
- SETINDEX # filenumber% , indexname$
-
- An indexname$ argument is mandatory in the CREATEINDEX statement, but
- optional with SETINDEX. If you do not specify an index name with SETINDEX,
- the NULL index becomes the current index. Immediately after execution of
- SETINDEX, the specified index is the current index, and the current record
- becomes the record having the lowest sorting value in that column.
-
- Note
-
- Comparisons made by ISAM when sorting strings differ somewhat from those
- performed by BASIC. When collating, the case of characters is not
- significant, and trailing blank spaces are stripped from a string before
- comparison is made. In strings that are otherwise identical, accents are
- significant, and collating is performed based on the choice you made when
- running the Setup program. English, French, German, Portuguese, and Italian
- comprise the default group. Dutch and Spanish each have their own collating
- orders, and the Scandinavian languages (Danish, Norwegian, Finnish,
- Icelandic, and Swedish) comprise the fourth group. See Appendix E,
- "International Character Sort Order Tables," in the BASIC Language
- Reference for specifics of each group.
-
-
- Indexes on BookStock's Columns
-
- For example, assuming the BookStock table illustrated in Figure 10.8 was
- opened as # 1, you could use the CREATEINDEX statement to create the index
- TitleIndexBS (on the table's Title column) as follows:
-
- CREATEINDEX 1, "TitleIndexBS", 0, "Title"
-
- After this definition, you can use setindex to make this index the current
- index as follows:
-
- SETINDEX 1, "TitleIndexBS"
-
- Once specified as current, the index represents a virtual table in which the
- data records are ordered according to the values in the Title column. This
- index imposes an alphabetic presentation order on the table. Therefore, all
- copies of books entitled QuickBASIC Made Easy would appear in sequence, and
- precede copies of QuickBASIC ToolBox, and so forth.
-
-
- Creating a Unique Index
-
- In the BookStock table, the IDnum column contains numbers for each copy of
- each book in the library. When new copies of a book are acquired, each is
- given a unique number. In this example all copies of QuickBASIC ToolBox
- have a whole number part of 15561, but each has a different fractional part.
- The following statement creates an index on this column and passes a
- non-zero value as the unique% argument, ensuring that no duplicate IDnum
- values can be entered for any copies of any books:
-
- CREATEINDEX 1, "IDIndex", 1, "IDnum"
-
- If there are already duplicates in the column, a trappable error is
- generated when your program attempts to execute the createindex statement.
- If your program ever attempts to update the table with a duplicate value in
- a field in a unique index, a trappable error is generated.
-
- In determining whether string values are duplicates, comparisons are
- case-insensitive and trailing blanks are ignored. Accented letters are not
- duplicates of their unaccented counterparts.
-
-
- Subordering of Records Within an Indexed Column
-
- There are many cases in which a user might need or expect a specific
- subordering. For example, when browsing a group of customer orders, if you
- are traversing an index based on account numbers, you might expect the
- actual orders associated with a specific customer name to be in the order in
- which the records were added. To ensure that records are presented in the
- way your user expects, you can create a combined index.
-
- In the BookStock table example, the idea of multiple records representing
- multiple copies of a specific book in the library means that the
- TitleIndexBS index created previously would be suitable for a librarian who
- wanted to see quickly how many copies of a specific book the library owned.
- The librarian also might want to have the books presented in the order in
- which they were purchased. Creating, then specifying a combined index on the
- Title and Author columns would present all of the books with a specific
- title/author combination grouped together. When you index on a column that
- contains the same value in more than one record, the subordering of records
- with the same value for the column (in this case, the combination of Title
- and Author columns) is unpredictable because many table entries may have
- been inserted and deleted at different times. The books would appear in the
- order in which their records actually appear on the disk. ISAM optimizes for
- space by allowing new records to be placed on the disk in space previously
- occupied by deleted records. Therefore, although the title/author combined
- index would group the presentation of all copies of books titled QuickBASIC
- ToolBox and written by D. Hergert, it would present them in the order in
- which they appear on the disk.
-
- However, the IDnum for each copy would correspond to the dates when each was
- added to the collection (assuming the library did not reuse an old IDnum
- once an old copy of a book was replaced). A combined index that included the
- Title, Author, and IDnum columns would present the records with the oldest
- copy appearing first among that group of titles by that author, and so on.
-
-
- Similarly, if the library had purchased three hard-bound and five paperback
- copies, the difference would show up as a significant differential in price.
- In presenting these books in the database, a librarian might want to have
- all the hard-bound copies appear in sequence, separated from the paperback
- copies. A combined index on the Title, Author, and Price columns would
- create that presentation order (assuming paperbacks of a specific title are
- always cheaper than their hardbound counterparts). If the IDnum column were
- added to create a Title, Author, Price, IDnum index, then all the paperbacks
- would appear in the order in which they were added to the collection before
- any of the hard-bound copies.
-
-
- Creating a Combined Index
-
- A combined index can be created using as many as 10 columns in a table by
- listing multiple columnname$ arguments in the CREATEINDEX statement. When
- such a multi-column index is the current index, the records appear as though
- first sorted by the first columnname$. Then those records whose first
- indexed values are identical appear as though sorted by the next
- columnname$, and so on. The following example creates a combined index:
-
- CREATEINDEX 1 , "BigIndex", 0, "Title", "Author", "IDnum"
-
- When SETINDEX is used to specify BigIndex as the current index, the records
- appear sorted first by Title. The same title might appear in the database
- for several books by different authors, but making the Author the second
- part of the combined index would keep all those by a particular author
- grouped. Finally, giving the IDnum as the last part of the index would cause
- the oldest copy of the desired book (by the given author) to be presented
- first in its group.
-
- You can designate a combined index as unique. If you do so, only the
- combination must be unique. For example, a unique index on the Author and
- Title columns would permit any number of occurrences of the same author or
- the same title, but only for one instance of the same author and the same
- title.
-
-
- Null Characters Within Indexed Strings in a Combined Index
-
- If you place null characters within strings in columns that are components
- of a combined index, there are situations in which the order of the index
- may deviate from what you expect. This is rare, but results from the fact
- that ISAM uses the null character as a separator in combined indexes. For
- instance, if the last character in a string field is a null, and its index
- is combined with one whose first character is a null, and in all other ways
- they are the same, the two fields will compare equal. This applies only to
- combined indexes. There is no restriction on null characters in an ISAM
- string, but you should be aware of this situation if you plan to use null
- characters in strings of indexed columns.
-
-
- Practical Considerations with Indexes
-
- Remember that each time an insert, delete, or update statement is executed,
- every index in the affected table is adjusted to reflect the changed state
- of the records. In the normal course of moving through a database and making
- changes to records, the time needed to adjust indexes would not be
- noticeable to a user. However, the time required by an automated process
- that makes these types of changes may be significantly affected by the
- number of indexes in the table. In some cases it may make sense to delete
- unnecessary indexes before such a process begins, then recreate them when it
- is finished.
-
-
- Restrictions on Indexing
-
- Part of the information ISAM maintains is the data type of each column in
- each table. These data types are stored in the data dictionary so the ISAM
- engine can make valid comparisons when sorting records in an index. ISAM can
- index columns having up to 255 bytes in combined length. Therefore, you can
- create an index on a string column having a length of up to 255 bytes, or a
- combined index whose constituent columns total a little less than 255 bytes
- or less (there is a little overhead associated with each constituent index).
- Columns having array or structure (that is, user-defined) type cannot be
- indexed.
-
- Attempting to create an index on a column with array or structure type, or
- on a STRING column longer than 255 characters, or defining a combined index
- whose total length is greater than 255 bytes, causes a trappable error.
-
-
- Determining the Current Index
-
- The GETINDEX$ function lets you find out what the current index is.
- GETINDEX$ has the following syntax:
-
- GETINDEX$ ( filenumber% )
-
- The filenumber% argument is an integer identifying any open table.
- GETINDEX$ returns a string representing the name of the current index. If
- the value returned in GETINDEX$ is a null string (represented by ""), then
- the current index is the NULL index.
-
- In a complex program, it may become difficult to predict which index is the
- current index on a specific table. Although SETINDEX is a single call, it
- is usually more efficient to test the current index with GETINDEX$ first,
- then only use SETINDEX if you actually want a different index. The
- following fragment illustrates this:
-
- IF GETINDEX$(TableNum%) <> "MyIndex" THEN
- SETINDEX TableNum%, "MyIndex"
- END IF
-
- Even though the preceding is more code than simply using SETINDEX, it is
- usually more efficient. Note also that the effect on the current record may
- be different depending on whether the setindex statement is executed. If the
- current index is already MyIndex, the current record will be the same (on
- that index) as it was previously. If the setindex statement is executed,
- the current record will be the one that sorts lowest in the index.
-
-
- Transferring and Deleting Record Data
-
- The syntax for the data-manipulation statements is similar to that for
- setindex:
-
- DELETE # filenumber%
- RETRIEVE # filenumber%, recordvariable UPDATE # filenumber%,
- recordvariable INSERT # filenumber%, recordvariable
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- filenumber% The integer used to open the table
- whose current record you want to
- remove, fetch, or overwrite. In
- the case of an insertion, it is
- the table in which you want the
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- the table in which you want the
- record inserted.
-
- recordvariable A variable of the user-defined
- type corresponding to the table
- into which the current record
- values are placed, or with which
- the current record is to be
- overwritten. In the case of an
- insertion, recordvariable is the
- record you wish to insert. Its
- elements (in its TYPE... END TYPE
- declaration) may be exactly the
- same as those of the table, or a
- subset of them. Subsets of
- recordvariable are discussed in
- the section "Record Variables as
- Subsets of a Table's Columns"
- later in this chapter.
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- later in this chapter.
-
-
-
-
-
- RETRIEVE, UPDATE, and DELETE all refer to the current record. The data
- transfer statements all take the data in recordvariable and either place it
- in the table ( UPDATE and INSERT) or fetch the current record ( RETRIEVE)
- and place its data into recordvariable.
-
- DELETE removes the current record from the specified table, and all
- affected indexes are adjusted appropriately. Following a deletion, if the
- current record was not the last record in the current index, the new current
- record is the record that immediately succeeded the deleted record. If the
- deleted record was the last record, no record is current, and EOF returns
- true.
-
- When you use RETRIEVE, the contents of the current record are assigned to
- recordvariable.
-
- When you use UPDATE, the contents of recordvariable overwrite the current
- record, and all affected indexes are adjusted appropriately.
-
- A trappable error occurs if no record is current when a DELETE, RETRIEVE,
- or UPDATE statement is executed.
-
- INSERT places the contents of recordvariable in the table, then adjusts
- all affected indexes appropriately. A newly inserted record assumes its
- appropriate position in the current index. Therefore, if you display the
- current record immediately after an insertion, the record displayed is the
- same record that was displayed prior to the insertion, not the newly
- inserted record. To see the new record, execute a setindex statement to make
- the null index current, then execute a movelast statement, then display the
- current record. The insert statement itself does not affect positioning. A
- trappable error occurs if you try to insert a record containing a duplicate
- value in a column on which a unique index exists.
-
-
- The Current Position
-
- The current position within a table depends on that table's current index.
- When you specify an index with setindex, you specify the table (with the
- filenumber% argument) and the index name. After setindex is executed, the
- current record is the first record on the specified index. The current
- record is the focus of data exchange.
-
-
- Changing the Current Index
-
- After opening a table, and until you specify an index, the current index is
- the NULL index. To change to another index, use the setindex statement. It
- has the following syntax:
-
- setindex # filenumber%, , indexname$
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- # The optional number character.
-
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- filenumber% The integer used to open the table
- for which you want to set a new
- current index.
-
- indexname$ A string expression naming a
- previously created index. If
- indexname is omitted, the NULL
- index becomes the current index,
- otherwise indexname becomes the
- current index.
-
-
-
-
-
-
- Making a Different Record Current
-
- ISAM permits you to make records current either by their position within the
- current index (using a move dest statement), or by testing field value(s) in
- the current index against key value(s) you supply (in a seek operand
- statement).
-
-
- Setting the Current Record By Position
-
- The MOVE dest statements let you make a record in the specified table
- current based on its position in the current index. Use the BOF and EOF
- functions to test the current position in the table. When a move is made, it
- is relative to the current position on the table specified by the
- filenumber% argument.
-
-
- The syntax for the MOVE dest statements and the position-testing functions
- is as follows:
-
- MOVEFIRST # filenumber%
- MOVELAST # filenumber%
- MOVENEXT # filenumber%
- MOVEPREVIOUS # filenumber% EOF ( filenumber% ) BOF ( filenumber% )
-
- Each record in a table has a previous record and a next record, except the
- records that are first and last according to the current index. Given the
- current index, the beginning of the table is the position preceding the
- first record; the end of the table is the position following the last
- record.
-
- The effect of any of the MOVE dest statements, or position-testing
- functions, is relative to the current position in the table specified by
- filenumber%. If there is a record following the current record, MOVENEXT
- makes it the current record. If there is a record preceding the current
- record, MOVEPREVIOUS makes it the current record. An attempt to use
- MOVENEXT from the last record in the table, or to use MOVEPREVIOUS from the
- first record in the table moves the position to the end of file, or the
- beginning of file, respectively.
-
- You can test for the end-of-file and beginning-of-file conditions with the
- eof and bof functions. eof returns true (-1) when the current position is
- beyond the last record on the current index; bof returns true (-1) when the
- current position precedes the first record on the current index.
-
- If the current record is not already the first or last in the table, then
- MOVEFIRST and MOVELAST make those records current. When a table contains no
- records, both Bof and eof return true (-1). If the table has no records, an
- attempt to execute any of the move dest statements will fail and eof will
- return true.
-
-
- Displaying the BookStock Table
-
- When your program specifies an index for the first time after opening a
- table, the current record is the one that sorts first in the index. Some
- preliminary BASIC code, plus the ISAM RETRIEVE, BOF, EOF, MOVENEXT, and
- MOVEPREVIOUS statements, are all you need to allow the user to move through
- an open table and view its records.
-
-
- A Typical ISAM Program
-
- Although you can write your program to allow users to create their own
- database files, it is more typical to supply the program with a database
- file having no records, with the filename hard-coded into the program. This
- database would contain all the predefined indexes you anticipate your user
- would need. Once the file contains these indexes, your program doesn't need
- the code used to create them. Supplying an empty database file that contains
- all the necessary indexes simplifies user interaction with the program, and
- also means ISAM can use less memory. For more information on the different
- ways you can include ISAM support in your programs, and how to use it during
- program development, see the sections "Starting ISAM for Use in QBX" and
- "Using ISAM with Compiled Programs" later in this chapter.
-
- The following example shows the module-level code of a program that opens
- several tables, including the BookStock table (discussed earlier) in the
- BOOKS.MDB database. It allows the user to view the records for all the books
- in a variety of orders, depending on which index is chosen. Only the
- module-level code and the Retriever procedure appear here. Other procedures,
- most of which control the user interface (to let the user add, edit, and
- search for specific records), are called as necessary. You can see those
- procedures in the disk files listed in the .MAK file BOOKLOOK.MAK (including
- BOOKLOOK.BAS, the main module; BOOKMOD1.BAS; BOOKMOD2.BAS; and
- BOOKMOD3.BAS). As noted previously, these example files are included on the
- Microsoft BASIC distribution disks and may be copied to your hard disk
- during Setup.
-
- Note that the error-handling routine at the bottom of the code handles error
- 86, Illegal operation on a unique index. When an attempt is made to update
- or insert a record containing a duplicate value for a unique index, the
- error-handling routine prompts the user to enter a new value.
-
- To view the BOOKLOOK.BAS sample application, move to the directory where it
- was installed during Setup and invoke the ISAM TSR and QBX by typing the
- following two lines:
-
- PROISAM /I b:24qbx BOOKLOOK
-
- The /Ib argument to PROISAM specifies the number of buffers ISAM will need
- to manipulate data. Options for the ISAM TSR are fully explained in the
- section "Starting ISAM for Use in QBX" later in this chapter. You can see
- the effect of a combined index by using the Title+Author+ID index on the
- BookStock table in the example. The library contains five copies of the
- title Structured BASIC Applied to Technology by Thomas A. Adamson. Using
- the combined index, the various copies of the book are presented in ID
- number order, whether you are moving forward in the table or backwards. If
- you just use the Title index (or any of the other indexes for which the
- fields are duplicates), the order moving forward is probably what you would
- expect, but the order moving backward may surprise you. This illustrates the
- fact that specific subordering is only guaranteed when a specific combined
- index is current.
-
- The Retriever procedure illustrates fetching a record from the database and
- placing it in a defined recordvariable. CheckPosition updates the
- Viewing/Editing keys box when the first or last record in the table is
- reached.
-
-
- The MakeOver procedure referred to in the error-handling routine is not
- included in the listing, but illustrates how indexes can be created. You can
- use MakeOver (and the Reader procedure that it calls) to create an ISAM
- database containing the same tables as BOOKS.MDB, but containing records
- read from text files. The text files must be in the appropriate directory,
- but they don't all have to contain records. For example, you wouldn't
- necessarily want to start a database with entries in the BooksOut table. If
- the database already exists, the new records are appended to the appropriate
- table. In that case, the Duplicate definition error is generated when an
- attempt is made to create the indexes. The error is trapped, and the
- procedure ends. The fields of the text files should be comma delimited, and
- strings should be enclosed in double quotation marks.
-
-
- Setting the Current Record by Condition
-
- You can specify conditions to be met when making a record current with the
- SEEKGT, SEEKGE, and SEEKEQ statements. Their syntax is summarized as
- follows:
-
- SEEK operand filenumber% , keyvalue , keyvalue...
-
- Depending on the operand and the current index, these statements make the
- first matching record in the table specified by filenumber the current
- record. A match occurs when an indexed value fulfills the operand condition
- with respect to the specified keyvalue.
-
- The following table indicates the operation associated with each of the
- operand specifiers:
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Statement Makes this record current
- ────────────────────────────────────────────────────────────────────────────
- SEEKGT The first record whose indexed
- value is greater than keyvalue.
-
- SEEKGE The first record whose indexed
- value is greater than, or equal to,
- keyvalue.
-
- SEEKEQ The first record whose indexed
- value equals keyvalue.
-
-
-
-
-
- The keyvalue expression should have the same data type as the column
- represented by the current index. Although type coercion is performed
- between integer and Long values, you can experience overflow errors if you
- rely on automatic type coercion. With all other types, a keyvalue error is
- detected when a value is of the wrong data type, and a Type mismatch error
- results. If the current index is a combined index, and the number of
- keyvalue values exceeds the number of constituent columns in the combined
- index, a Syntax error error message is generated when the program runs. A
- trappable error is generated if you attempt to execute a SEEK operand
- statement while the NULL index is the current index.
-
- If the number of keyvalue values is fewer than the number of constituent
- columns in a combined index, the missing values are replaced by a value less
- than the smallest possible value, and the outcome depends on the operand.
- For example, a SEEKEQ will fail (and EOF will return true). A SEEKGE or
- SEEKGT will perform the seek based on whatever keyvalue values are
- supplied, and will find the first record that matches the supplied keyvalue
- values. For example, assume the following type, variable, and index are
- created:
-
-
- TYPE ForExample
- FirstName AS STRING * 20
- LastNameAS STRING * 25
- END TYPE
- DIM ExampleVariable AS ForExample
- CREATEINDEX TableNum%, "FullNameIndex", 0, FirstName, LastName
- SETINDEX TableNum%, "FullNameIndex"
-
- If you execute the following statement, the seek will fail because no last
- name was provided:
-
- SEEKEQ TableNum%, "Tom"
-
- If you execute the following statement, the seek will find the first record
- for which FirstName is "Tom":
-
- SEEKGT TableNum%, "Tom"
-
- When a SEEK operand statement fails (that is, when no match is made), there
- is no current record, and eof is set to true. Therefore, any immediately
- succeeding operation that depends on the current record (such as a
- RETRIEVE, DELETE, INSERT, MOVENEXT, or MOVEPREVIOUS) will cause a
- trappable error. You can prevent generation of this error by only executing
- statements that depend on the existence of a current record if EOF returns
- false.
-
-
- Seeking on Strings and ISAM String Comparison
-
- When seeking a string, ISAM performs comparisons on a case-insensitive
- basis. Trailing blanks are ignored. This is a less strict comparison than
- that made by the BASIC equality operator. Additionally, international
- conventions are observed (see Appendix E, "International Character Sort
- Order Tables," in the BASIC Language Reference for more information). The
- TEXTCOMP function allows you to perform string comparisons within your
- program in the same way they are compared by ISAM. Its syntax is as follows:
-
- TEXTCOMP ( string1$, string2$ )
-
- The string1$ and string2$ arguments are string expressions. TEXTCOMP
- returns -1 if string1$ compares less than string2$, 1 if string1$
- compares greater than string2$, and 0 if the two strings compare equal.
- Only the first 255 characters of the respective strings are compared. For
- instance, if BigRec is the name of the recordvariable into which RETRIEVE
- places records from the BookStock table, and you want to print a quick list
- of the titles in the table that begin with the word QuickBASIC, you could
- use code like the following to find the first qualified title, then print
- the ensuing qualified titles:
-
- (Code appears in printed book, and in \SAMPCODE\BASIC directory)
-
-
- Because the comparison performed by TEXTCOMP is case-insensitive, all
- variations of titles whose first word is QuickBASIC will be printed.
-
- Example
-
- The following listing begins with the fragment of the module-level code of
- BOOKLOOK.BAS that handles the SEEKFIELD case, which is selected when the
- user chooses Find Record. First the user is prompted to choose an index by
- ChooseOrder, the same procedure called in the REORDER case. Then, the
- SeekRecord procedure is called. It prompts the user to enter a value to
- search for on the chosen index. After the user enters the value, he or she
- is prompted to choose the condition (=, >, >=, <, or <=) that controls the
- search. The default search is set in this example to use SEEKEQ, although
- the SEEKGE statement would be a better default in many cases.
-
- SeekRecord calls procedures including ValuesAccepted (to make sure the input
- values have the same format as values in the tables), ClearEm and ShowIt (to
- show users what will be sought), GetKeyVals (in case the user is supplying
- values for a combined index), and GetOperand (to let the user choose whether
- the seek will be based on equality, greater or less than, greater than or
- equal to, or less than or equal to). Note that ShowIt shows what the user
- has entered in its appropriate field, not data from the database file
- itself. Other procedures include MakeString, DrawScreen, ShowRecord (which
- shows a database record), ShowMessage, EraseMessage, and IndexBox, all of
- which keep the user interface updated with each operation. Since names are
- in a single field, with last name first, TransposeName checks the format of
- a name entered and puts it in the proper format for searching or displaying.
-
-
- A Multi-Table Database
-
- So far, only the BookStock table has been shown in the BOOKS.MDB database.
- Even in the database of a hypothetical library, it would make sense to have
- another table in BOOKS.MDB to hold information about each library-card
- holder and a third table to hold information that relates specific copies of
- books to card holders who may have borrowed them. In fact, CardHolders is a
- reasonable name for one table, and BooksOut will do for the other. Figure
- 10.9 illustrates a possible design for the CardHolders table. Figure 10.10
- illustrates the BooksOut table.
-
- The user-defined type by which a program gains access to the CardHolders
- table looks as follows:
-
- TYPE CardHolders
- CardNum AS INTEGER
- Zip AS LONG
- TheName AS STRING * 36
- CityAS STRING * 26
- StreetAS STRING * 50
- StateAS STRING * 2
- END TYPE
-
- The user-defined type by which a program gains access to the BooksOut table
- can have element names that duplicate those in other tables, since they are
- accessed using dot notation. The OutBooks type looks as follows:
-
- type BooksOut
- IDnum AS DOUBLE
- CardNum AS LONG
- DueDateAS DOUBLE
- end type
- Figure 10.10 The BooksOut Table in BOOKS.MDB
-
- When these tables are added, BOOKS.MDB contains three tables. The BookStock
- table is related to the CardHolders table through the IDnum column in the
- BooksOut table. For inventory purposes, you might manipulate just the
- BookStock table, and for doing a mailing of new-titles notices you could
- manipulate only the CardHolders table. To find out which card holder has
- withdrawn which copy of a particular book, you can get the IDnum from the
- BookStock table, then look up that IDnum in the BooksOut table. If the book
- was overdue, you could get the CardNum value from the BooksOut table, then
- look up that card number in the CardNum column in the CardHolders table to
- get information about the borrower.
-
-
- Example
-
- As your librarian traverses the BookStock table, he or she might want to
- check the due date on some of the books. Pressing the W key calls the
- GetStatus procedure to look up the book ID number in the BooksOut table and
- retrieve the corresponding BooksOut record. The ShowStatus procedure is
- called to display the due date of the book. Note that ShowStatus currently
- displays the date in raw serial form, as it appears in the table. To convert
- a serial date for normal display, you can replace the expression
- STR$(ValueToShow) with appropriate calls to the date and time function
- library (DTFMTER.QLB), supplied with Microsoft BASIC.
-
- BOOKLOOK.BAS contains other routines that use information from all the
- tables to automate library procedures. For example, the BooksBorrowed
- procedure is accessible when the CardHolders table is being displayed. If
- the user presses B (for Books Outstanding), BooksBorrowed compiles a list of
- the books checked out to that card holder. LendeeProfile gives information
- on the borrower of the title currently displayed from the BookStock table.
- The BorrowBook procedure (not shown in the following listing) allows a book
- to be checked out simply by typing in the user's name. If the user is not a
- valid card holder, a warning is displayed. If the user has a library card,
- BorrowBook displays the card holder's information so it can be checked to
- see what the due date will be, and also to see if the personal information
- needs to be updated. The ReturnBook procedure (not shown in the following
- listing) displays the name of the borrower, and calculates and displays any
- fines that may be due.
-
-
- Deleting Indexes and Tables
-
- The DELETEINDEX and DELETETABLE statements let you delete an index or a
- table from the database. These statements have the following syntax:
-
- DELETEINDEX filenumber%, indexname$
-
- DELETETABLE database$, tablename$
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- filenumber% A numeric expression representing
- the table on which the index to be
- deleted was created.
-
- indexname$ A string expression representing
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- indexname$ A string expression representing
- the name of the index to be
- deleted.
-
- database$ A string expression representing
- the name of the database
- containing the table to be deleted.
-
- tablename$ A string expression representing
- the name of the table to be
- deleted.
-
-
-
-
-
- It isn't always easy to anticipate which columns a user may want to index
- when using a database. As an advanced feature of a program you might want to
- let a user create indexes during run time. If so, DELETEINDEX can be used
- to remove the specified index from the database when it is no longer needed.
-
-
- You can use the DELETETABLE statement to delete an old table when you no
- longer need any of its data records. Once you delete a table and close the
- database, there is no way to recover the records. You should assume that the
- table is deleted from the file immediately upon execution of this statement,
- rather than at some future time (for example, when the file is closed). Note
- that when you delete a table, all information (including indexes, etc.) in
- the data dictionary relating to the table is also deleted.
-
- You cannot practically write routines to permit a user to create custom
- tables during run time, since the user-defined type that describes a table
- must already exist when the program begins. Although it isn't covered here,
- you can write routines that permit a user to create a table that duplicates
- the columns of a table already in the database. For example, you might want
- to permit copying of a subset of a table's records to a table of the same
- type.
-
- Executing deletetable or deleteindex commits any pending transactions.
-
-
- ISAM Naming Convention
-
- Some parts of an ISAM database require names (for example, tables, columns,
- and indexes), and these names must conform to the ISAM naming convention.
- The ISAM convention is essentially a subset of the BASIC convention, as
- shown in the following table:
-
- ╓┌─────────────────────────────────────┌─────────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 30 characters or fewer. 40 characters or fewer.
-
- Alphanumeric characters only, Alphanumeric characters, plus the
- including A-Z, a-z, and 0-9. BASIC type-declaration characters,
- where appropriate (variables and
- functions).
-
- Must begin with alphabetic character. Must begin with alphabetic character,
- but only DEF FN functions can begin
- with "fn."
-
- ────────────────────────────────────────────────────────────────────────────
- No special characters allowed. The period is not allowed in the
- names of elements within a
- user-defined type. Since these are
- the names BASIC and ISAM have in
- common, there is no conflict.
-
- Not case sensitive. Not case sensitive.
-
-
-
-
-
-
- Starting ISAM for Use in QBX
-
- Microsoft BASIC includes two terminate-and-stay-resident (TSR) programs,
- PROISAM.EXE and PROISAMD.EXE. You can use either of these programs to place
- the ISAM routines in memory when you develop or run database programs within
- the QBX environment. The benefit of this approach is that, when you are
- working on programs or modules that don't need ISAM, the amount of memory
- required by QBX is substantially reduced.
-
- PROISAM.EXE contains all ISAM routines needed to run most database programs.
- It does not contain the "data dictionary" statements -- CREATEINDEX,
- DELETEINDEX, and deletetable. It contains a restricted version of the
- open... FOR ISAM statement that will open a database or table, but will not
- create it if it does not already exist. Since you often will not need to
- program the creation and deletion of indexes and tables within an end-user
- program, PROISAM.EXE is usually sufficient.
-
- PROISAMD.EXE contains all the ISAM routines.
-
- You use the following syntax to start either PROISAM.EXE or PROISAMD.EXE:
-
- {PROISAM | PROISAMD} /Ib: pagebuffers /Ie: emsreserve /Ii: indexes /D
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- /Ib: pagebuffers Increases the amount of
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- /Ib: pagebuffers Increases the amount of
- conventional memory reserved for
- ISAM's buffers. The defaults are 6
- pagebuffers (12K of 2K pages) for
- PROISAM, and 9 pagebuffers (18K
- of 2K pages) for PROISAMD. There
- is also 3 - 5K used for data by
- PROISAM and 14 - 16K for PROISAMD.
- Maximum value for pagebuffers is
- 512. However, since DOS only
- provides 640K, this maximum is not
- possible in conventional memory.
- Determine the optimal value for a
- specific program by
- experimentation. Note that the
- default values for pagebuffers
- are the minimums necessary for an
- ISAM program, not the optimal or
- average values. If you (or your
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- average values. If you (or your
- anticipated users) don't have EMS,
- more than the default number of
- pagebuffers may be necessary for
- running the program. Even if a
- program runs with the default
- number of pagebuffers, specifying
- the most buffers possible improves
- ISAM performance. Note, however,
- that if no EMS is available, too
- high a value for /Ib could
- allocate so much memory to ISAM
- buffers that the TSR would not be
- able to remove itself from memory
- when invoked with the /D option.
- When EMS is available, the amount
- specified with /Ib is taken first
- from EMS, and if that is not
- sufficient, the rest is taken from
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- sufficient, the rest is taken from
- conventional memory.
-
- /Ie: emsreserve If you have expanded memory, ISAM
- will automatically use up to 1.2
- megabytes of it for buffers (that
- is, it will place up to 512 2K
- pagebuffers, plus about 10 percent
- overhead space, into EMS). ISAM
- takes as much EMS memory as
- possible by default, which frees
- conventional memory for other uses
- while improving performance. The
- number given in the /Ie option
- lets you specify how much expanded
- memory should be reserved for
- purposes other than ISAM. This
- limits the amount of EMS that ISAM
- uses for pagebuffers, since ISAM
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- uses for pagebuffers, since ISAM
- will take only EMS between the
- emsreserve specified and the total
- EMS available. The default value
- for emsreserve is 0. State values
- in kilobytes; for example, /Ie:500
- specifies 500K should be left
- available for other purposes. In
- practice, you only need to specify
- /Ie if your program code (or a
- loaded Quick library) actually
- manages EMS memory. In such a case,
- you should also specify the /Es
- option when starting QBX or when
- compiling with BC. Specifying a
- value of -1 for /Ie reserves all
- EMS for other uses, and none is
- used by ISAM.
-
- Argument Description
- ────────────────────────────────────────────────────────────────────────────
- /Ii: indexes Specifies the number of non-NULL
- indexes used in the program. Use
- this option if your program has
- more than 30 indexes. If you don't
- specify a value for this option,
- ISAM assumes your database
- contains no more than 30 defined
- indexes. If this value is too low,
- your program may fail. Maximum
- permissible value is 500.
-
- /D Removes the ISAM TSR from memory.
-
-
-
-
-
-
-
- Note
-
- When you use transactions, ISAM keeps a transaction log. The name of the log
- file is guaranteed to be unique, so multiple ISAM programs can be run in
- simultaneous windows in an operating environment like Windows 386 without
- the danger of conflicts within the log files. In versions of DOS earlier
- than 3.0 however, the name of the file is ~proisam.LOG, and it is created in
- the /TMP directory by default; otherwise it goes in the current directory.
- If you set your /TMP environment variable to a RAM drive, transaction
- logging will be faster. Don't confuse this log file with the PROISAM.EXE
- TSR. If a log file appears in your current directory, you can delete it;
- ISAM overwrites the old log file each time a transaction is initiated.
-
-
- Estimating Minimum ISAM Buffer Values
-
- When you load the ISAM TSR, it requires memory beyond its disk-file size
- because it reserves a certain amount of memory for buffers in which it does
- most of its work. The actual amount of buffer space reserved depends on
- which version of the TSR you are using, and the number you specify for the
- /Ib and /Ii options. The defaults represent the absolute minimum required
- for a minimal ISAM program. Having more buffers available always improves
- performance, and in some cases may be necessary for a program to run at all.
- Additional buffers improve performance because, when the buffers are full,
- some of their contents is written back to the disk. This causes a disk
- access to update the disk file, and another access when the material that
- was swapped out has to be swapped back in. The swapping system is based on a
- least-recently-used (LRU) algorithm. The more buffers that are available,
- the less likely it is that any particular piece of material will need to be
- swapped in or out. To get a basic idea of the minimum number of buffers your
- program needs, use the maximum of 9 or 6 (the default buffer settings,
- depending on whether you use PROISAM or PROISAMD), or the following formula:
-
- pagebuffers = 1 + w + x + 4 y + 8 z
-
- In the preceding formula:
-
- w = the maximum number of open tables containing data
-
- x = total of non-NULL indexes used in the program
-
- y = 1, if INSERT or UPDATE statements are executed, otherwise 0
-
- z = 1, if a CREATEINDEX statement is executed, otherwise 0
-
- Depending on the density of ISAM statements in any section of code, it is
- possible that the default number of buffers will not be adequate to handle
- the necessary processing.
-
- Any EMS (up to 1.2 megabytes) that is available is used for ISAM buffers.
- This leaves an equivalent amount of conventional memory for other purposes.
- Note however, that only the ISAM buffers are placed in EMS, the ISAM code
- (represented approximately by the disk-file size) itself resides in
- conventional memory. Use the /Ie option to reserve any EMS that may be
- needed when your program actually manages EMS internally, or works
- concurrently with any other programs that use EMS.
-
-
- ISAM and Expanded Memory (EMS)
-
- As noted above, ISAM uses conventional and expanded memory as long as the
- expanded memory conforms to the Lotus-Intel-Microsoft Expanded Memory
- Specification (LIM 4.0). Using expanded memory correctly can enhance both
- the performance and capacity of programs. The actual management of expanded
- memory is done for you by BASIC within the limits you set with the /Ie ISAM
- option and the /Es QBX option. If expanded memory is available, ISAM uses
- the difference between the total and the amount you specify with the /Ie
- option, up to a maximum of about 1.2 megabytes.
-
- There are several factors to consider in using the /Ib and /Ie ISAM options
- (and the /Es and /Ea QBX options), including the following:
-
- ■ The system on which you develop a program may have different memory
- resources than the system on which your user runs the program.
-
- ■ If your program performs explicit EMS management, or uses a library
- that does, you need to reserve the necessary EMS when starting the TSR
- or compiling the program. QBX and BC have options dealing with EMS
- (/Es and /Ea) that interact with ISAM's use of EMS in certain
- situations. You may need to use the /Es option when invoking QBX or
- BC.
-
-
- In order to provide for users who may not have EMS, you should always
- specify an optimal setting for the /Ib ISAM option. Beyond this, in most
- cases, it should suffice to allow the defaults to determine the apportioning
- of EMS between ISAM and other EMS usage. The EMS defaults are designed to
- make the best use of whatever combinations of conventional memory and EMS
- may be available. In general, trying to optimize values between /Ib and /Ie
- only makes sense if your program itself actually performs expanded memory
- management, in which case you should be sure there is enough EMS available
- for it at run time.
-
- For example, if you want ISAM to use 22 buffers, the buffers require 44K
- (each buffer requires 2K) plus up to 5K for data in PROISAM.EXE, and up to
- 16k for data with PROISAMD.EXE. The 22 buffers plus overhead for PROISAM.EXE
- will require about 49K. However, if EMS is available and you don't specify a
- value for /Ie, ISAM will use one megabyte of expanded memory. If you have
- only one megabyte of expanded memory available, and you have written your
- program to explicitly manage 500K of that, you need to specify 500 as a
- value for the /Ie option. This reserves the amount of expanded memory your
- program manages. If an end user of the program has no EMS available beyond
- the 500K you have reserved, all the memory needed for the ISAM pagebuffers
- is taken from conventional memory.
-
- The actual algorithm used by ISAM for apportioning buffers and EMS is as
- follows:
-
- 1. Reserve EMS as specified by /Ie. If less EMS is available than is
- specified, reserve all EMS.
-
- 2. Allocate non-buffer memory needed by ISAM. Take this first from
- available EMS, then, if that is insufficient, take the remainder from
- conventional memory.
-
- 3. Allocate the number of buffers specified by the /Ib option, first from
- EMS, then, if that is insufficient, from conventional memory.
-
- 4. If more EMS is available than was needed to satisfy /Ib, keep
- allocating buffers from EMS until all EMS is consumed, or until the
- ISAM limit is reached (512 buffers, or about 1.2 megabytes including
- overhead).
-
- 5. Release the EMS reserved in step 1 (by the /Ib option) for use by
- other programs.
-
-
- When you create an executable file from within QBX, whether or not the
- program will need to have the TSR invoked depends on options you chose for
- ISAM during Setup, as explained in the next section.
-
-
- Using ISAM with Compiled Programs
-
- There are several types of executable files you can produce for ISAM
- programs, depending on your needs and the needs of your users. Programs that
- require the presence of a TSR make sense if you are distributing several
- distinct ISAM-dependent programs on the same disk. Each individual program
- could be significantly smaller if all made use of the ISAM routines from the
- TSR. If you compile programs so they need the run-time module, you can have
- the ISAM routines linked in as part of the run-time module, or have the user
- start one of the TSR programs before starting the application.
-
- In any of these cases, you have a choice of TSRs. PROISAMD.EXE contains all
- the ISAM routines, including the data dictionary routines (for creating and
- deleting indexes, tables, databases, etc.). At various times, not all the
- routines are necessary. For example, if you create a program and supply an
- empty database file (one with tables and indexes, but no data records), your
- program would have no real need to create tables or indexes, since they
- would already exist in the file. In such cases, you could supply the smaller
- TSR program (called PROISAM.EXE) to conserve memory. Table 10.2 describes
- the requirements for various ISAM configurations.
-
-
- When you ran the Setup program, you had the opportunity to choose libraries
- that would create executable files containing all the ISAM routines. You
- could also choose an option that created run-time modules that contained all
- the ISAM routines. If you didn't choose these options, your executable files
- (and run-time modules) will require your users to run either PROISAM.EXE or
- PROISAMD.EXE before using the database programs. To qualify for Case 1 in
- Table 10.2, you should have specified the full ISAM or reduced ISAM option
- for BCL70 mso.LIB. Then, when the executable file is created, you need to
- have either PROISAMD.LIB or PROISAM.LIB in your current directory or library
- search path. For Case 2 you should have chosen "ISAM Routines in TSR" during
- Setup. In this case you don't need to have PROISAM.LIB or PROISAMD.LIB
- accessible when the executable file is created. You made the same selections
- regarding run-time modules during Setup (cases 3 and 4).
-
- When you compile a stand-alone program from the command line (that is, one
- that does not require the presence of the TSR at run time), you can use /Ib,
- /Ie, and /Ii as options to the BC command. Their syntax and general effects
- are the same with the stand-alone program as described in the previous
- section. If your program uses run-time overlays, the EMS is automatically
- allocated for the overlays first, before ISAM. If you don't want overlays to
- use EMS you can link the program with NOEMS.OBJ and overlays will be swapped
- to disk instead. If overlays use EMS, ISAM will take whatever remains after
- EMS allocation for the overlays -- up to 1.2 megabyte. If your program does
- internal EMS memory management, it can only be done from within a
- non-overlayed module. However, in such a case, you should probably link with
- NOEMS.OBJ. Also, remember to compile with the /Es option to BC. You should
- always specify a correct value for /Ii if your program uses more than 30
- indexes.
-
- However, note that sharing expanded memory between ISAM and other uses
- inhibits ISAM performance, since the ISAM buffers and other EMS usage must
- use the same EMS window to access the expanded memory. This means that with
- each call to the ISAM library, the EMS state must be saved and restored. If
- you must share EMS memory between ISAM and other things, use the relative
- amounts that optimize ISAM performance. In such cases, use the /Es option to
- guarantee EMS save and restore with mixed-language code that manages EMS.
-
- Important
-
- When you compile a program from within QBX, only the QBX options are passed
- to the compiler. This is fine if an ISAM stand-alone program will use the
- TSR, since the buffer and index options are specified when the TSR is
- invoked. However, if the program is to have the ISAM routines included in
- the executable file, you must compile the program's main module from the
- command line, and specify the appropriate /I and /E options as arguments to
- BC.
-
-
- Practical Considerations when Using EMS
-
- Note that the ISAM /Ie option and the QBX options /E, /Ea, and /Es have the
- effect of reserving EMS for programs that use internal EMS management (or
- other applications), rather than specifically limiting the amount of EMS
- used by the program for which the option is supplied. ISAM uses EMS to
- improve performance by radically reducing the frequency of disk access. In
- general, the automatic apportioning of conventional and EMS memory should
- cover the widest range of situations best, because with each allocation of
- EMS, whatever is available is used whenever it can be.
-
- During development of a very large program, it may be more beneficial to
- reserve most available EMS for QBX (except the minimum ISAM needs for
- buffers and indexes), since the speed of ISAM is probably not as important
- as the ability to have QBX place units of code in EMS, thus increasing the
- potential size of the source files you can fit in QBX. However, since QBX
- only places units of code in the 0.5-16K range in expanded memory, this is
- only optimal if your coding style is to use small to moderate code units (
- SUB and FUNCTION procedures, and module-level code). In a compiled program,
- the ISAM performance in the executable file is the most important feature,
- so compiling with high values for /Ib (just to provide for users with no
- EMS) and no specification for /Ie should offer the best results. ISAM never
- uses more than 1.2 megabytes, so all remaining EMS is automatically
- available for other uses. Such other uses include your program's code units,
- arrays up to 16K (if /Ea is specified), or explicit EMS management within
- the program.
-
- Note however, that dividing EMS between ISAM and other uses slows ISAM and
- QBX performance to some degree. It may make sense during program
- development, but might not be satisfactory in a compiled program. If you
- want this type of sharing, use the /Ie option to reserve EMS for the
- overlays, plus the /Es option to ensure EMS saving and restoration. EMS in a
- compiled program is automatically used for run-time overlays (if you use
- them). To prevent EMS sharing, compile with BC without using the /Ie option
- or /Es, and specify /E:0 to prevent use of EMS for anything but ISAM. For
- more information on using the /Es option for QBX, see Chapter 3, "Memory
- Management for QBX," in Getting Started. Run-time overlays are discussed in
- Chapters 15, "Optimizing Program Size and Speed," and 18, "Using LINK and
- LIB," of this book.
-
- Note
-
- BASIC releases EMS when the program terminates due to a run-time error, as
- well as an END, stop, or system statement. If a program terminates for some
- other reason while EMS is being used, that portion of EMS will not be
- available again until the EMS manager is restarted. If the EMS manager is
- the one used in Microsoft Windows 386, you can simply exit from Windows,
- then start Windows again to recover the EMS. If your EMS manager is started
- by an entry in a CONFIG.SYS file, you may need to reboot to recover use of
- EMS.
-
-
- TSRs and Installation/Deinstallation Order
-
- If you (or your users) will be using other TSR programs besides the ISAM
- TSR, they should be installed before the ISAM TSR. The reason for this is
- that the ISAM TSR is only needed when the ISAM program is run. If you finish
- with your ISAM program and have installed another TSR after ISAM, you will
- have to remove any more-recently installed TSR programs before you can
- successfully remove the ISAM TSR. Otherwise, the /D option to the ISAM TSR
- will remove ISAM from memory, but the memory cannot be used by the other
- programs, and the operating system may be destabilized. If you attempt to
- remove TSRs in an improper order, a warning message is displayed.
-
-
- Block Processing Using Transactions
-
- To accommodate data entry errors, ISAM includes three transaction statements
- and one transaction function that allow you to restore a database to a
- previous state. By using these in conjunction with the CHECKPOINT statement
- (which lets you explicitly write all open databases to disk) you can enhance
- the integrity of your user's databases.
-
- When you use UPDATE to change a record in a table, the change is made
- immediately. However, the actual writing of data to disk is done at periodic
- intervals determined by the ISAM engine. The checkpoint statement requires
- no argument. It simply forces the current state of all open databases to be
- written to disk.
-
- Conversely, you can code your program to allow a user (or a routine in the
- program) to retract a sequence of operations either selectively or as a
- block. Using transactions (block processing) can help ensure consistency to
- operations performed on multiple tables and multiple databases.
-
- The following table briefly describes these block-processing statements:
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Statement Description
- ────────────────────────────────────────────────────────────────────────────
- BeginTrans Starts a transaction log of all
- operations.
- Statement Description
- ────────────────────────────────────────────────────────────────────────────
- operations.
-
- CommitTrans Ends maintenance of the
- transaction log.
-
- SavePoint Marks points within the
- transaction log to which the
- transaction can be rolled back.
-
- RollBack All Restores the state of the database
- to what it was at a specified save
- point or at the beginning of the
- transaction.
-
-
-
-
-
-
- Specifying a Transaction Block
-
- Bracketing certain portions of your code with begintrans and committrans
- statements provides a mechanism to retract all changes made to a database
- within the transaction block. No results of processing within the block will
- become part of the database unless everything resulting from processing in
- the block becomes part of the database. By following the block with a
- CHECKPOINT statement, you can guarantee that all results of the block are
- written immediately to disk. Save points allow you to define points within
- transactions to which the state of the database can be rolled back. Don't
- confuse a save point with the checkpoint command. The savepoint function
- doesn't write records to disk. It simply returns integer identifiers for
- each of the markers it sets in the transaction log.
-
-
- The Transaction Log
-
- The BEGINTRANS statement causes ISAM to start logging every change made to
- the database. Note that ISAM only logs changes made to the database -- it
- does not keep track of execution flow of your program. After BEGINTRANS is
- executed, changes are still made to the database, but ISAM can backtrack
- through those changes by referring to the transaction log. Included in the
- log entries are each of the save points you set with the SAVEPOINT
- function. If a ROLLBACK statement is executed at some point within the
- transaction block, ISAM checks the log and restores the database to the
- state it was in when the specified save point was executed. This includes
- removing any changes that were made to data records since the save point,
- and restoring all indexes to the state they were in at the save point.
- BEGINTRANS and COMMITTRANS take no arguments.
-
-
- Using Save Points
-
- Since ISAM maintains only one transaction log, you cannot nest one
- transaction within another. However, the ability to set multiple save points
- within a transaction supplies similar functionality with greater
- flexibility. While BEGINTRANS and COMMITTRANS serve as block delimiters
- for multiple ISAM data exchange calls, you can use save points to delimit
- smaller data-exchange blocks within a transaction. The savepoint function
- takes no argument, but returns an integer that identifies the save point
- that was set.
-
- ROLLBACK uses two forms, as shown in the following table:
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- ROLLBACK savepoint The savepoint is an integer
- identifier corresponding to a save
- point returned by the SAVEPOINT
- function. The effect of a
- ROLLBACK statement is the
- restoration of the database to the
- state it was at the named save
- point. If no savepoint is
- specified, the rollback proceeds
- to the next available save point.
-
- ROLLBACK ALL Restores the database to the state
- it had when the most recent
- begintrans was executed.
-
-
-
-
-
- If your program executes a ROLLBACK statement outside a transaction block,
- specifies a non-existent save point, or executes a qualified ROLLBACK when
- there are no save points within the transaction block, a trappable error is
- generated. Data dictionary operations (for example, DELETEINDEX) cannot be
- rolled back, since no record of them is kept in the transaction log.
-
- A transaction can be committed without an explicit COMMITTRANS being
- executed. For example, if there is an error in an attempt to open an ISAM
- table or database, a CLOSE statement is implicitly executed on the table or
- database. Any time an ISAM CLOSE is performed (either explicitly or
- implicitly), any pending transaction is committed. It is not good practice
- to execute table-level and database-level operations within a transaction,
- since errors can commit the transaction indirectly. Even if an error doesn't
- occur, an implict CLOSE may occur, committing the transaction. For example,
- if you delete a table from a database, and there is no other table open
- within the database, an implicit CLOSE is performed on the database. Such a
- CLOSE causes all pending transactions (even in another open database) to be
- committed. You should limit the use of transactions as a programming tool
- for controlling record-level operations.
-
- The following example code illustrates a transaction block abstracted from
- the program BOOKLOOK.BAS. The initial fragment from the module-level code
- intercepts the code representing what the user wants to do and calls the
- EditCheck procedure to determine whether a transaction is pending, to be
- begun, or to be committed.
-
- The EDITRECORD case in the BOOKLOOK.BAS module-level code is the only case
- that uses transactions. Each time the user presses enter after editing a
- field in a table, a savepoint statement is executed just before the record
- is updated. The value returned by savepoint is saved in an element of the
- array variable Marker, as long as the user keeps editing fields, without
- performing other menu operations, such as displaying or searching for a new
- record. savepoint statements are executed after each succeeding edit. When
- the user makes a menu choice other than Edit, the transaction is committed.
- Any time prior to the commitment, the user can choose to Undo edits within
- the transaction, either as a group (rollback all), or singly in the reverse
- order from which they were entered, by pressing U (Undo) or Ctrl+U (Undo
- All).
-
-
- Maintaining Physical and Logical Data Integrity
-
- The "physical integrity" of a database is what guarantees you will be able
- to use the database. ISAM maintains this physical integrity as a matter of
- course whenever you use the database. However, circumstances can intervene
- that corrupt a database. For example, power to your system could be
- interrupted while ISAM is in the process of actually writing data to disk.
- When such a "crash" occurs while a file is open, the consequences are
- unpredictable. For example, even if no drastic damage is done, the crash may
- occur before all relevant indexes in the database can be updated. Similarly,
- some physical mishap could corrupt the file while you are not working on the
- database. If someone opened the file with another program such as a word
- processor, and modified it, its physical integrity would be compromised. In
- these types of situations, you can use the ISAMREPR utility to recover the
- undamaged parts of the database and restore its physical integrity. Making
- frequent backups of database files is an important element of maintaining
- physical integrity. You can do this with any commercial backup program, or
- simply with the operating system COPY command, since all the parts of an
- ISAM database are contained within a single disk file.
-
-
- ISAM speed depends on the fact that it writes changes to the disk
- periodically, rather than immediately. At the same time, something like an
- equipment failure could occur between the time a change is made in a table
- and the time the change is written to disk. In such a case, the data would
- be lost. This type of loss can occur when the program sits idle for a while
- after changes are made to a table. To minimize the danger, BASIC checks the
- amount of time that passes and compares that to the number of times the
- keyboard is polled while a program is sitting in an "idle loop." For
- example, when INKEY$ or an INPUT statement is used to poll the keyboard
- for input, if a certain number of keyboard checks are made, or a certain
- amount of time passes without a keystroke, ISAM writes all changed buffers
- to disk. As soon as a keystroke occurs or buffers are flushed, the checking
- process starts anew.
-
- During transactions, the changes are actually made to the file on the normal
- basis. Their purpose is as a programming aid, rather than a form of
- integrity insurance. If changes are rescinded by rollbacks before the
- transaction is committed, the transaction log is used to restore the
- database to the proper state. If an equipment failure occurs before a
- transaction is committed, the disk file represents the state to which the
- transaction had progressed, rather than the state prior to the transaction.
- This means that the changes cannot be rescinded by rollbacks if the failure
- occurs within the transaction. Conversely, when a transaction concludes, not
- everything is necessarily written immediately to the physical disk file. The
- ISAM engine performs disk writes using algorithms that give priority to
- performance. Therefore, there may be a lag between the time when a
- transaction is committed and time the final pieces of data are written to
- disk. As in other situations, if no keyboard input occurs within a certain
- period after the transaction is committed, ISAM automatically writes the
- state of the tables to disk. In the event of a loss of power between the end
- of the transaction and automatic disk write, changes not yet written to disk
- can be lost. This could include some part of the end of the transaction.
- Therefore, although this eventuality is very unlikely, ISAM internally
- cannot guarantee a per-transaction level of data integrity.
-
- In a program compiled with the /D option, an implicit CHECKPOINT statement
- is performed each time a DELETE, INSERT, UPDATE, or ISAM CLOSE statement
- is executed. If you are very concerned with obtaining maximum data
- integrity, and are willing to sacrifice speed, compile your program with /D.
-
- You can write code to enhance the logical and physical integrity of your
- database. The CHECKPOINT statement forces a physical write to disk of all
- data in the ISAM buffers. However, with a large database, placing
- CHECKPOINT statements in too many points in a program can significantly
- inhibit performance.
-
-
- Record Variables as Subsets of a Table's Columns
-
- You can open a table with a tabletype that is a subset of a record within
- the table. To associate the subset data type with the columns in the table,
- you specify it as the tabletype argument to the OPEN statement you use to
- open the table. When you actually fetch a record from the table, you specify
- the variable (of type tabletype) in a RETRIEVE statement. That variable
- must have the same type as the tabletype argument. If it is not the same
- type (even though it may be a valid subset in the sense that the element
- names are precisely the same, etc.), a trappable error occurs. Note however,
- that error checking in the QBX environment is more elaborate than in
- programs compiled from the command line. In a separately compiled program
- such an error may not be generated. In the BOOKLOOK.BAS example discussed
- earlier, if you wanted to open the table BookStock, but not access values in
- the Publisher and Price columns, you could declare a user-defined type as
- follows:
-
- TYPE SmallStock
- IDnumAS DOUBLE
- Title AS STRING * 50
- Author AS STRING * 36
- END TYPE
-
- The order in which the elements are specified is unimportant as long as the
- names are the same. You can still use indexes based on all the columns in
- the table, but you would not be able to transfer values to and from fields
- in the Publisher and Price columns.
-
- ISAM transfers values between a table and its corresponding structured
- variable by name, rather than by position. Therefore, in creating a subset
- the order of the elements can vary, as long as the names are the same. In
- other words, you can subtract columns as long as you preserve the original
- names precisely, regardless of their position. If the data types associated
- with the element names do not correspond to those in the table, or if a
- column name is spelled differently, a trappable error occurs. Therefore, you
- cannot simply change the data type, or the name, of a column.
-
- Note that if the length of one of the strings was greater than the length
- originally declared in BookStock, an error would be generated. Once the
- subset type is declared, you can open the BookStock table in BOOKS.MDB as
- follows:
-
- OPEN "books.mdb" FOR ISAM SmallStock "BookStock" AS #1
-
- Using Multiple Files: "Relational" Databases
-
- Because ISAM maintains everything you need for a database in the multiple
- tables in the single database file, you rarely need to work with other
- files. Since a single ISAM database file can be as large as 128 megabytes,
- most of what other database systems do with multiple files can be handled in
- a single ISAM database file. In a program that accesses multiple files,
- table names in different files can be identical because an ISAM table name
- is maintained internally as a combination of the database name plus the
- table name.
-
- When you open multiple database files, the number of tables that can be
- opened simultaneously depends on how many database files are open, as shown
- in the following table:
-
- ╓┌──────────────┌─────────────────────────────┌──────────────────────────────╖
- Number of open databases Maximum tables among
- databases
- ────────────────────────────────────────────────────────────────────────────
- /I Specifies that an ISAM table
- is to be created from an
- ASCII text file. /I stands
- for "import."
-
- /E Specifies that an ASCII text
- file is to be created from
- an ISAM table. /E stands for
- "export.""
-
- /H or /? Displays help for using the
- ISAMIO utility. Anything
- following these options in
- the command line is ignored.
-
- asciifile Names the ASCII file to be
- Number of open databases Maximum tables among
- databases
- asciifile Names the ASCII file to be
- imported (/I) or exported
- (/E).
-
- databasename Names the database file into
- which the table should be
- placed (/I) or from which
- the data for the ASCII file
- should be taken (/E).
-
- tablename Names the table within the
- database file into which the
- records from the ASCII file
- should be placed (/I), or
- the table within the
- database from which the data
- for the ASCII file should be
- taken (/E).
-
- Number of open databases Maximum tables among
- databases
- /A Specifies that data being
- imported (/I) should be
- appended to tablename. If
- tablename does not exist, an
- error message is displayed.
- If /A is not specified,
- ISAMIO imports the data into
- the named table based on the
- table description given in
- specfile (described later in
- this table). If no specfile
- is named (or found), an
- error message is displayed.
-
- /C When an ISAM table is being
- imported (/I) from an ASCII
- file, /C specifies that the
- table's column names should
- Number of open databases Maximum tables among
- databases
- table's column names should
- be taken from the first row
- of data in the ASCII file.
- If any of the specified
- column names are
- inconsistent with the ISAM
- naming convention, ISAMIO
- terminates and displays an
- error message. When an ISAM
- table is being exported (/E),
- /C specifies that the
- table's column names should
- appear in the ASCII file as
- the first row of data (when
- an ASCII file is being
- created from an ISAM table).
- If /A and /C are specified,
- an error message appears. If
- /C is not specified when a
- Number of open databases Maximum tables among
- databases
- /C is not specified when a
- table is imported, ISAMIO
- interprets the first row in
- asciifile as the beginning
- of the data records, and
- looks for column names in
- specfile. If /C is not
- specified when a table is
- exported, the column names
- are not exported.
-
- /F : width Stipulates the data being
- imported (/I) is of fixed
- width, or that data being
- exported (/E) should be
- exported in fixed-width
- format (i.e., no separators
- appear in the data file).
- The size of the fixed width
- Number of open databases Maximum tables among
- databases
- The size of the fixed width
- fields are specified in the
- first field of the specfile,
- if /F is specified. If you
- don't use /F, the fields are
- assumed to be comma
- delimited, with double
- quotation marks enclosing
- string data. When exporting
- fixed width, width
- specifies the width of
- binary fields. The default
- width is 512.
-
- specfile A file that specifies the
- data type and size (for
- strings, arrays, and
- user-defined types) for each
- column of a table. The
- Number of open databases Maximum tables among
- databases
- column of a table. The
- file's format is as follows:
- fixedwidthsize, type ,
- size , columnname Fields
- can be separated with spaces
- or commas. The
- fixedwidthsize may only
- appear if the /F option was
- specified. The other
- arguments appear only if the
- /A option was not specified
- (otherwise, it is ignored).
- The type is one of the
- indexable ISAM data types.
- In the case of arrays,
- user-defined types, and
- strings longer than 255
- characters, specify type as
- binary. The columnname is
- Number of open databases Maximum tables among
- databases
- binary. The columnname is
- any valid ISAM column name,
- but is ignored if the /C
- option is given. If
- specfile is not valid, a
- descriptive error message is
- displayed. Valid
- designations for type
- include binary, integer,
- long, real, and currency;
- you can also specify
- variabletext (vt), and
- variablestring (vs). If the
- type is one of the latter,
- the size field must appear.
- If specfile appears on the
- command line when exporting,
- a specfile suitable for
- importing is created. To see
- Number of open databases Maximum tables among
- databases
- importing is created. To see
- an example of a specfile,
- you can export an existing
- table, such as one of the
- system tables, with a
- command line having the
- following form:isamio /e nul
- databasename msysobjects
- conThis line sends the
- contents of the system table
- to NUL, then prints the
- specfile to the screen.
-
- /D Specifies that a db/LIB file
- is to be converted.
-
- /M Specifies that an MS/ISAM
- file is to be converted.
-
- Number of open databases Maximum tables among
- databases
- /B Specifies that a Btrieve
- database is to be converted.
-
- filename The name of a data file to
- be converted.
-
- tablename The name of the ISAM table
- into which the converted
- records will be organized.
- This name must follow the
- ISAM naming convention.
-
- databasename The name of the ISAM
- database file into which the
- table will be placed.
-
- specfile You must supply this file BASICtype, size,
- when converting Btrieve and columnnameThe BASIC type is
- Number of open databases Maximum tables among
- databases
- when converting Btrieve and columnnameThe BASIC type is
- MS/ISAM files. It has the the term used by Btrieve to
- following form: identify the data type; the
- size is the length of the
- field in the Btrieve format.
- The columnname is any valid
- ISAM column name. The size
- is ignored for all types
- except String.
-
-
-
-
-
- If databasename does not exist, it is created. The utility uses the file
- utilities supplied with the database package that created the file. For
- example, the Btrieve TSR must be loaded when conversion is attempted. If the
- other-product file utilities are not available to ISAMCVT, a message is
- displayed. To convert the indexes of the original Btrieve, MS/ISAM, or
- db/LIB file, run ISAMCVT on the file that contains the index and name the
- ISAM table and database to which the index applies.
-
- Example entries in a Btrieve specfile might look as follows:
-
- string 4 StringCol
- integer 2 IntColumn
- Long 10 LongColumn
- Double 5 DoubleCol
-
- In addition to Double and Single, DMBF and SMBF (for the corresponding
- Microsoft binary format) are also valid Btrieve column types.
-
- When the conversion is done, you can open the tables from within a BASIC
- program and begin using them right away. The following table describes how
- the data types associated with the old file map to the TYPE... END TYPE
- variables you will be using in your BASIC program:
-
- db/LIB BtrieveMS/ISAMBASIC's ISAM
- The Repair Utility
-
- The Microsoft BASIC package includes the (ISAMREPR.EXE) utility to help
- recover databases that become corrupted. ISAMREPR can only restore physical
- integrity to a database (i.e., consistency among the tables in the
- database). ISAM does not pre-image changes made in a database and write them
- to a temporary file. Therefore, it is not possible to restore individual
- records entered if a crash occurs between the time the records are entered
- in the table and the next physical disk write. If this type of situation is
- a major concern, you can reduce the chance of losing such records by
- compiling programs with the /D option and making judicious use of checkpoint
- statements in your program.
-
- When ISAMREPR restores the database, it systematically goes through every
- table and index and recreates the database, using every piece of internally
- consistent information in the file. If anything is found that cannot be
- reconciled with the other information in the file, it is deleted. This
- restores consistency to the database. There is a chance that you will need
- to recreate some indexes. Similarly, it is possible that some blocks of data
- will never be reconciled with the rest of the database, and will therefore
- be lost.
-
- The syntax for ISAMREPR.EXE is as follows:
-
- ISAMREPR databasename
-
- The databasename is the filename of the database you need to repair.
- ISAMREPR restores physical integrity to the database and prints messages to
- the screen whenever it takes an action that results in the loss of data.
- These messages describe the types of problems that were discovered and
- corrected. You can redirect this output to a file. The messages that may
- appear, with descriptions, are included in the following table:
-
- ╓┌─────────────────────────────────────┌─────────────────────────────────────╖
- Message Explanation
- ────────────────────────────────────────────────────────────────────────────
- Table name was truncated: data lost During structural analysis of the
- table's data pages, an inconsistent
- page caused the table to be
- truncated as of the last uncorrupted
- page. This message is only given
- once for any affected table.
-
- One or more records were deleted One of ISAM file's internal tables
- Message Explanation
- ────────────────────────────────────────────────────────────────────────────
- One or more records were deleted One of ISAM file's internal tables
- from table name (MSysObjects, MSysIndexes, or
- MSysColumns) was found to have
- inconsistent data, or during the
- structural analysis of a table's
- data pages, an inconsistent data
- page was removed from the table
- (which resulted in the deletion of
- any table records on that page).
-
- One or more long values were deleted "Long values" refer to strings
- from table name longer than 255 characters, arrays,
- and user-defined types (not to the
- LONG data type). Their connections
- to the table were corrupted, so they
- were deleted . This message is only
- given once for any affected table.
-
- Cannot repair name: Not a database Repair process has been aborted
- Message Explanation
- ────────────────────────────────────────────────────────────────────────────
- Cannot repair name: Not a database Repair process has been aborted
- file , because the file was not
- recognizable as a database.
-
- Cannot repair database name: Repair process has been aborted
- Uncorrectable problems because the database cannot be
- repaired. Some common reasons
- include: system tables not found in
- the expected locations; any of the
- system tables is structurally
- inconsistent; information to
- reconstruct system data is
- unavailable; rebuilt system data is
- inconsistent; records describing
- system tables, columns, or indexes
- are inconsistent or missing.
-
- Repair of name completed Repair process completed.
- successfully
- Message Explanation
- ────────────────────────────────────────────────────────────────────────────
- successfully
-
-
-
-
-
- A repair may also be aborted for reasons having nothing to do with the state
- of the database. Messages resulting in such cases include (but are not
- limited to): Disk full, Out of memory, and File not found.
-
- When you use the ISAMREPR utility it requires additional space within your
- database to accomplish its work. This adds at least 32K to the size of the
- database. Do not run the utility if your disk does not have this amount of
- space available in the current working directory. ISAMREPR deletes
- inconsistent records in tables, but does not compact after doing so.
- Compacting a database is described in the next section.
-
-
- The ISAMPACK Utility
-
- When tables or records are deleted from a database (either by your program,
- or the ISAMREPR utility), the size of your disk file does not change.
- Instead, the deleted data is marked, and ISAM begins to reuse the space in
- the file as you add to the database. The ISAMPACK utility performs two
- functions. First, if there is a total of 32K of data marked for deletion,
- ISAMPACK actually shrinks the disk file in increments of 32K. If there is
- not 32K of data marked for deletion, ISAMPACK has no effect on the size of
- the disk file. However, any time you run ISAMPACK, it removes records marked
- for deletion and then copies the database, table by table, and index by
- index into a database having the same name (if no newdatabasename is
- specified). The effect of compaction is improved performance, in the same
- way that compacting a hard disk improves performance.
-
- As it compacts the database, ISAMPACK prints a report to the screen that
- lists the database's tables (including the types and maximum lengths of each
- of their columns), and the number of records in each table. It also lists
- (by table), all the database's indexes, the columns they are based on, and
- whether or not each one is unique. You can redirect this report to a file if
- you choose. The syntax for ISAMPACK.EXE is as follows:
-
- ISAMPACK databasename newdatabasename
-
- The databasename is the filename of the ISAM disk file. The
- newdatabasename is an optional alternate name for the compacted database. If
- no newdatabasename is given, the original database file is renamed with
- the filename extension .BAK either appended to databasename or replacing
- the original extension.
-
-
- Converting Btrieve Code
-
- If you have been using Btrieve as a database file manager, you may find that
- the ISAM integrated into Microsoft BASIC is a convenient and far less
- complicated substitute.
-
- If you've read the preceding portions of this chapter, you probably have a
- good idea already of how using ISAM can clean up your file-access code. With
- ISAM, the interface between your program and your database consists only of
- the ISAM statements and the structured variables you define to transfer
- values between your program and database tables. Using ISAM requires no
- elaborate initialization, and using ISAM statements is much more direct than
- passing a long list of arguments to BTRV. When you use ISAM, you don't need
- any of the following:
-
- ■ DEF SEG
-
- ■ ISAM manages memory addressing for you.
-
- ■ OPEN nul
-
- ■ With ISAM, you only have to worry about your actual database file.
-
- ■ FIELD statements
-
- ■ ISAM lets you use real, structured variables for records.
-
- ■ Operation codes
-
- ■ ISAM provides easy-to-use (and understand) statements for database
- access and manipulation.
-
- ■ Status codes
-
- ■ Errors in ISAM are trapped like any other BASIC errors.
-
- ■ FCB addresses and buffer lengths
-
- ■ ISAM handles all DOS interactions invisibly.
-
- ■ Key buffers and key numbers
-
- ■ ISAM uses indexes and maintains them for you.
-
- ■ Position blocks
-
- ■ ISAM handles file position invisibly.
-
-
- Your database files are created from within your BASIC programs, as are all
- tables and indexes. Although you do need to invoke a TSR before loading QBX
- when you want to develop database code within the environment, when you
- create a stand-alone version of the database program, you can have all file
- management support built into the executable file, so your user never has to
- do anything but fire up the program to work with a database.
-
- Similarly, because the ISAM data dictionary and all your tables of data are
- saved within the same disk file, you don't have to worry about keeping track
- of multiple files. Btrieve's transaction processing is limited to enhancing
- data integrity. ISAM's savepoint and rollback features help insure data
- integrity, but even more importantly, they simplify programming in which you
- want to allow a user to rescind a block of data exchanges.
-
- However, Btrieve offers the following features not yet available in ISAM:
-
- ■ Support for multi-user networks
-
- ■ Some versions of Btrieve support simultaneous multiple-user access to
- the same file, while the ISAM in BASIC does not.
-
- ■ Automatic logical integrity protection
-
- ■ Btrieve uses a pre-imaging system of temporary files for ensuring
- logical record integrity and consistency among files. ISAM guarantees
- only physical integrity. If your system crashes in the middle of a
- database operation, your ISAM file automatically maintains its
- internal consistency, but the one or two most recent edits to records
- may be lost. Since updates to records take place simultaneously in the
- record tables and the data dictionary, the possibility of inconsistent
- files is reduced greatly. The worst that can happen to a ISAM file is
- the loss of the latest edits to several recently modified records. You
- can minimize the effects of such losses by careful use of the
- CHECKPOINT statement, but unwritten records can be lost as a result of
- system crashes.
-
- ■ Same database across different disks
-
- ■ Btrieve permits you to extend a database across several different
- disks. While the maximum size (128 megabytes) of a single ISAM file
- means you will probably never have to partition a database in this
- way, if you have been using this Btrieve feature, you will have to
- redesign your database for ISAM.
-
- ■ Seek in descending order
-
- ■ When you use Get Lower from somewhere other than the second record in
- an index, Btrieve moves to the first match it makes by descending down
- the records in the index. ISAM does not offer an equivalent statement.
- If your code relies heavily on Btrieve's descending-order seeking, you
- can substitute combinations of SEEKEQ and MOVEPREVIOUS.
-
- ■ Null key
-
- ■ In Btrieve, when you designate a key as NULL, it is omitted from the
- index. There is no way to omit records having no value from the
- sorting order of a given index in Microsoft ISAM. Records with zero
- value in the current ISAM index simply sort as though they had the
- lowest value for that index. If your program relies on null keys in
- Btrieve, you will need to recode in ISAM to produce the same behavior.
-
-
-
- The following table illustrates the correspondence between Btrieve operation
- codes and the ISAM statements and functions:
-
- ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
- Btrieve code Description BASIC equivalent
- Btrieve code Description BASIC equivalent
- ────────────────────────────────────────────────────────────────────────────
- 0 (Open) Makes file available OPEN statement makes
- for access. tables accessible within
- the database file.
-
- 1 (Close) Releases Btrieve file. CLOSE statement closes
- ISAM tables (and its
- database file).
-
- 2 (Insert) Inserts a new record in INSERT statement
- the file. inserts record into ISAM
- table.
-
- 3 (Update) Overwrites current UPDATE statement.
- record.
-
- 4 (Delete) Deletes current record. DELETE statement.
-
- 5 (Get Equal) Fetches the first SEEKEQ + retrieve
- record whose field statements fetches the
- Btrieve code Description BASIC equivalent
- ────────────────────────────────────────────────────────────────────────────
- record whose field statements fetches the
- value matches the first matching record in
- specified key value.. the current index.
-
- 6 (Get Next) Fetches the record MOVENEXT + RETRIEVE
- immediately following statements.
- the current record.
-
- 7 (Get Previous) Fetches the record MOVEPREVIOUS +
- immediately preceding RETRIEVE statements.
- the current record.
-
- 8 (Get Greater) Fetches the first SEEKGT + retrieve
- record whose field statements fetch the
- value exceeds the first matching record in
- specified key value. the current index.
-
- 9 (Get Greater or Fetches the first SEEKGE + retrieve
- Equal) record whose field statements fetch the
- Btrieve code Description BASIC equivalent
- ────────────────────────────────────────────────────────────────────────────
- Equal) record whose field statements fetch the
- value equals or exceeds first matching record in
- the specified key value. the current index.
-
- 10 (Get Less Than) Fetches the first SEEKGE + MOVEPREVIOUS
- record whose field + retrieve fetch the
- value is less than the first matching record in
- specified key value. the current index.
-
-
-
-
-
-
- ╓┌────────────────────────┌────────────────────────┌─────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 11 (Get Less Than or Fetches the first SEEKGT + MOVEPREVIOUS
- Equal) record whose field + retrieve fetch the
- value is less than or first matching record in
- ────────────────────────────────────────────────────────────────────────────
- value is less than or first matching record in
- equals the specified the current index.
- key value.
-
- 12 (Get Lowest) Fetches the first MOVEFIRST + RETRIEVE
- record. statements.
-
- 13 (Get Highest) Fetches the last record. MOVELAST + RETRIEVE
- statements..
-
- 14 (Create) Creates a Btrieve file. OPEN statement. In
- BASIC the database files
- (and tables) are created
- by the OPEN statement,
- if they don't already
- exist.
-
- 15 (Stat) Returns number of LOF returns the number
- records in the file, of records in the
- plus other file specified table.
- ────────────────────────────────────────────────────────────────────────────
- plus other file specified table.
- characteristics.
-
- 16 (Extend) Allows a file to be No equivalent.
- continuous across two
- drives.
-
- 17 (Set Directory) Change current CHDRIVE, CHDIR.
- directory.
-
- 18 (Get Directory) Returns current CURDIR$.
- directory.
-
- 19 (Begin Transaction) Marks start of a block BEGINTRANS statement.
- . of related operations.
-
- 20 (End Transaction) Marks end of a block of COMMITTRANS statement.
- related operations.
-
- 21 (Abort Transaction) Restores file to its ROLLBACKALL.
- ────────────────────────────────────────────────────────────────────────────
- 21 (Abort Transaction) Restores file to its ROLLBACKALL.
- condition prior to the
- beginning of the
- transaction.
-
- 22 (Get Position) Returns position of the ISAM has no equivalent
- current record. because there are no
- record numbers in ISAM.
-
- 23 (Get Direct) Fetches the record with ISAM has no equivalent
- the specified record because there are no
- number. record numbers in ISAM.
-
- 24 (Step Direct) Fetches the record in ISAM has no equivalent
- the next physical because physical
- location, regardless of location is not a
- the index. meaningful mapping in
- ISAM.
-
-
-
-
-
-
- ╓┌──────────────────┌───────────────────────────┌────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 25 (Stop) Unloads Btrieve record In compiled BASIC programs
- manager. use of an external database
- manager is optional. After
- using ISAM within the QBX
- environment, you should
- unload the TSR with its /D
- option.
-
- 26 (Version) Returns the Btrieve No equivalent.
- version number.
-
- 27 - 30 +100
-
-
-
-
-
-
-
- Run-Time Error Messages and Codes
-
- The following BASIC errors may occur as a result of ISAM statements:
-
- ╓┌───┌───────────────┌───────────────┌──────┌───────────────┌────────────────╖
- Special ISAM Code Error message explanation (if
- any)
- ────────────────────────────────────────────────────────────────────────────
- 2 Syntax error Some syntax
- errors are not
- detected until
- run time. For
- example,
- supplying too
- few keyvalues
- to a seek
- operand
- Special ISAM Code Error message explanation (if
- any)
- ────────────────────────────────────────────────────────────────────────────
- operand
- statement.
-
- 5 Illegal Many possible
- function call causes.
-
- 6 Overflow Can occur when
- automatic
- coercion is
- performed
- between
- integer and
- long data
- entering the
- ISAM file.
-
- 7 Out of memory /Ib: or /Ii
- set too small
- Special ISAM Code Error message explanation (if
- any)
- ────────────────────────────────────────────────────────────────────────────
- set too small
- or too large;
- or after EMS
- has been
- allocated for
- ISAM, there is
- not enough
- left for QBX
- to use for
- text tables.
-
- 10 Duplicate An attempt was
- definition made to
- execute a
- createindex
- statement for
- an index that
- already exists
- Special ISAM Code Error message explanation (if
- any)
- ────────────────────────────────────────────────────────────────────────────
- already exists
- in the
- database.
-
- 13 Type mismatch Elements of
- the
- recordvariable
- are
- inconsistent
- with the types
- of the columns
- in the table.
-
-
-
-
-
-
- ╓┌───┌───────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 16 String formula too complex
-
- 52 Bad file mode Attempted an ISAM operation on a
- non-ISAM file.
-
- 54 Bad filename or number The specified file number does not
- identify an ISAM table or database
- file.
-
- 55 File already open The specified table or file is
- already open; or you specified a
- non-ISAM file in a deletetable
- statement.
-
- 64 Bad filename Table name or database name
- exceeds legal length or contains
- illegal character.
-
- 67 Too many files You have tried to open more than
- ────────────────────────────────────────────────────────────────────────────
- 67 Too many files You have tried to open more than
- the maximum number of files. There
- are no more file handles available.
-
- 70 Permission denied You attempted to open a file that
- was locked, or attempted to
- perform a file operation on a
- read-only file
-
- 73 Feature unavailable User forgot to start the ISAM TSR
- before starting program, or tried
- to perform a data-dictionary
- operation using the reduced TSR
- (PROISAM, rather than PROISAMD) or
- .LIB configuration.
-
- 76 Path not found The path was invalid; for example,
- a named directory did not exist.
-
- 81 Invalid name Table or index name is too long or
- ────────────────────────────────────────────────────────────────────────────
- 81 Invalid name Table or index name is too long or
- contains illegal characters.
-
- 82 Table not found A table was specified that is not
- in the database, for example, in a
- deletetable statement.
-
-
-
-
-
-
- ╓┌───┌───────────────────────────────────┌───────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 83 Index not found The index specified by SETINDEX
- was not associated with the
- specified table.
-
- 84 Invalid column The name specified for a column in
- a createindex statement does not
- ────────────────────────────────────────────────────────────────────────────
- a createindex statement does not
- exist.
-
- 85 No current record Occurs typically following an
- unsuccessful seek operand
- statement or move dest to end of
- file or beginning of file.
-
- 86 Duplicate value for unique index An attempt was made to create a
- unique index on a column that
- already contained duplicate
- values; or a user attempted to
- enter a duplicate value in a
- column for which a unique index
- exists.
-
- 87 Invalid operation on NULL index For example, it is illegal to
- execute a seek operand statement
- while the NULL index is current.
-
- ────────────────────────────────────────────────────────────────────────────
- 88 Database inconsistent There is a problem in the database
- -- run the ISAMREPR utility.
-
-
-
-
-
-
-
- ───────────────────────────────────────────────────────────────────────────
- Chapter 11: Advanced String Storage
-
- This chapter explains when and how to use far strings as well as how
- to manipulate far strings to accomplish the following programming tasks:
-
- ■ Read and write far string data using PEEK, POKE, BSAVE, and BLOAD.
-
- ■ Make pointers used for mixed-language programming.
-
- ■ Maximize string storage space.
-
-
-
- Far Strings Vs. Near Strings
-
- In previous versions of Microsoft BASIC, all variable-length string data was
- stored in near memory, or what assembly language programmers call DGROUP.
- This is a relatively small portion of total memory (a maximum of 64K).
- Besides containing variable-length strings, DGROUP also contained the rest
- of the simple variables -- integers, floating-point numbers and fixed
- strings, all constants, and the stack. Even when the only variables you used
- were variable-length strings, your maximum data capacity was limited to
- approximately 40K.
-
- This version of Microsoft BASIC supports "far strings" -- variable-length
- strings stored outside of DGROUP in multiple segments of far memory. This
- gives you 64K for far string processing in the main module, plus several
- additional 64K blocks depending on the specific program you write. And, by
- removing variable-length strings from DGROUP, you create more room for other
- simple variables as well.
-
- The following table shows the key differences between near and far string
- storage.
-
- As you can see, you can have up to 192K of far string storage: 64K for
- module-level strings, 64K for procedure-level strings, and 64K for
- strings declared with the COMMON statement. If you are doing a
- recursive procedure, you can actually use additional segments as
- well -- one for each invocation. The exact programming techniques needed
- to achieve these increased capacities are explained in the section
- "Maximizing String Storage Space" later in this chapter.
-
-
- Note
-
- This chapter pertains only to variable-length strings. Fixed-length strings
- have not changed with this release of BASIC. For ease of reading, the word
- "string" is always used in this chapter to mean "variable-length string."
-
-
- When to Use Far Strings
-
- For many programming applications, using far strings is the preferred
- storage method. It gives you more space for variable-length strings and
- frees DGROUP to handle more integers, floating-pointing numbers, and
- fixed-length strings.
-
- If your variable-length string requirements are limited, however, there are
- times when you are better off with near strings. One such case is when you
- need all available memory for code. Another is when you have very large
- arrays that are memory intensive. For these instances, using near strings
- frees at least 64K. (For details on how BASIC stores far strings, see the
- section "Data Structure and Space Allocation.")
-
- Where you have very few strings and want to decrease code size, you can use
- near strings instead of the longer code for far strings.
-
-
- Selecting Far Strings
-
- When compiling from within QBX, far strings are the default. If you want
- variable-length strings stored in DGROUP, cancel the Strings in Far Memory
- selection in the Make EXE File dialog box.
-
- When compiling programs from the command line, DGROUP storage is the
- default. To use far strings, add the /Fs option to the BASIC Compiler (BC)
- command line.
-
- All programs running within the QBX environment use far string storage; no
- other option is available. All Quick libraries must be made using the /Fs
- option as well.
-
-
- Direct Far-String Processing
-
- For most applications, far-string programming requires no new techniques.
- BASIC automatically takes care of far-string memory management. However, if
- you are using either the BLOAD, BSAVE, POKE, or PEEK statement for
- direct processing of far strings, you must first set the current segment
- address to the address of the far string being manipulated. You can use the
- SSEG ( stringvariable$) function to return the segment address of
- stringvariable$.
-
- As an example of direct
- processing, suppose you have initialized the following string:
-
-
- A$ = STRING$(1024,65)
-
- To save it on disk using BSAVE, you do this:
-
- DEFINT A-Z
- DEF SEG = SSEG(a$)
- Offset = SADD(a$)
- Length = LEN(a$)
- BSAVE "bigstg.txt", Offset, Length
- You must also set the current segment when using BLOAD with a far string.
-
- DEFINT A-Z
- ' Intialize a string variable to the correct length
- ' by computing the source size.
- OPEN "bigstg.txt" FOR INPUT AS #1
- Length = LOF(1)
- A$ = STRING$(Length, 0)
- ' Calculate location of destination.
- DEF SEG = SSEG(A$)
- offset = SADD(A$)
- BLOAD "bigstg.txt", Offset
- The following two examples use DEF SEG in conjunction with PEEK and POKE
- on far string data. These examples insert one string into another and are a
- direct-processing emulation of the MID$ statement.
-
- DEFINT A-Z
- ' Create a string of As and Bs.
- A$ = STRING$(20, 65)
- B$ = STRING$(40, 66)
- ' Calculate their offsets.
- Offset1 = SADD(A$)
- Offset2 = SADD(B$)
- ' Insert 10 As in the string of Bs.
- ' Same as MID$(B$, 11, 10) = A$.
- DEF SEG = SSEG(A$)
- FOR I = 11 to 20
- Temp = PEEK(Offset1)
- POKE Offset2 + I - 1, Temp
- NEXT I
-
- The preceding example needed only one use of the DEF SEG statement. That is
- because both strings were in the same segment. In the next example, one
- string is in the main-module string segment and the other is in the segment
- declared COMMON. This requires a separate DEF SEG statement for the source
- and the destination.
-
- DEFINT A-Z
-
- COMMON A$
- ' Create a string of As and Bs.
- A$ = STRING$(20, 65)
- B$ = STRING$(40, 66)
- ' Calculate their offsets.
- Offset1 = SADD(A$)
- Offset2 = SADD(B$)
- ' Insert 10 As in the string of Bs.
- ' Same as MID$(b$, 11, 10) = A$.
- SourceSegment = SSEG(A$)
- DestinationSegment = SSEG(B$)
- FOR I = 11 to 20
- DEF SEG = SourceSegment
- Temp = PEEK(Offset1 + I - 11)
- DEF SEG = DestinationSegment
- POKE Offset2 + I - 1, Temp
- NEXT I
- Important
-
- Although shown here in examples, direct manipulation of far strings is
- recommended only as a last resort when standard BASIC programming techniques
- cannot achieve the desired result. Extreme caution is advised in these cases
- for the following reasons:
-
- ■ BASIC moves string locations during run time. Therefore the SSEG and
- SADD functions need to be executed immediately before using BLOAD,
- BSAVE, PEEK, and POKE.
-
- ■ BASIC can detect whether a string has changed length. Never use POKE
- on data beyond the last character in a far string or you will get a
- String Space Corrupt error. If this occurs in the QBX environment, QBX
- will terminate, and you should reboot your computer before restarting
- QBX.
-
- ■ Far string descriptors have a different format than near string
- descriptors. If you attempt to locate far string data by using PEEK
- to look at the descriptor, you will not be able to find the data. If
- your applications pass far strings extensively or accessed the string
- descriptor in the past to obtain information, see Chapter 13,
- "Mixed-Language Programming with Far Strings," for the correct new way
- to do this.
-
-
-
- Calculating Far-String Memory Space
-
- When used with far strings, the FRE function provides new information to
- give you more program control. FRE( stringexpression$) compacts string
- storage space and then returns the space remaining in the segment containing
- stringvexpression$. FRE(" stringliteral") also compacts string storage
- space, but it returns the space available for temporary string storage.
- Temporary string storage is used whenever a string expression is created,
- typically to the right or left of the equal sign or as an argument to a
- function or statement. Here are some examples:
-
- PRINT A$ + B$
-
- CALL StringManipulator( (A$) )
- A$ = A$ + "$"
- In certain instances, you may want to use the FRE function to see if there
- is enough string space for a given operation. Suppose you are going to load
- a string from a file and then combine it with another string. You can check
- the space requirements this way:
-
- OPEN "bigstg1" FOR INPUT AS #1
- ' Skip I/O operation if out of space.
- IF LOF(1) <= FRE(A$) THEN
- INPUT #1, A$
- ' Before concatenating, make two checks.
- ' First see if there's enough temporary storage space.
- IF FRE("") >= LEN(A$) + LEN(B$) THEN
- ' Second, see if A$'s segment has room to store the new variable.
- IF FRE(A$) >= LEN(B$) THEN
- ' There's room to do the operation, so do it.
- A$ = A$ + B$
- END IF
- END IF
- END IF
- Note
-
- The output of the FRE function changes, depending on whether or not far
- strings are selected. The following table shows the differences.
-
- FRE(-2)Unused stack spaceUnused stack space FRE(-3)Available EMSAvailable
- EMS FRE(any other numeric)Unused space in DGROUPCan't use with far
- strings
-
- Using Far-String Pointers
-
- Far strings are passed to procedures written in other languages through the
- use of data pointers. These pointers are obtained with the SSEG, SADD, and
- SSEGADD functions.
-
- To pass separate segment and offset pointers, use SSEG for the segment and
- SADD for the offset. This is shown in the following example, where BASIC
- passes a string to MASM, which prints it on the screen:
-
- DEFINT A-Z
- DECLARE SUB PrintMessage(BYVAL Segment, BYVAL Offset)
- ' Create the message with the "$" terminator--
- ' the DOS print service routine requires it.
- A$ = "This is a short example of a message" + "$"
- ' Call the MASM procedure with pointers
- CALL PrintMessage(SSEG(A$), SADD(A$))
-
- ;************************ PRINT MESSAGE ****************
- ; This MASM procedure prints a BASIC far string on the screen.
- ; Define use and ordering of segments so it's compatible with BASIC.
- .modelmedium, basic
- ; Set up some stack space--necessary if making this into a quick library.
- .stack
- .code
- ; Define a public procedure that inputs two word
- ; variables.
- publicprintmessage
- printmessageprocuses ds, segmnt, offst
- ; Tell DOS print routine where the string is
- movax, segmnt
- movds, ax
- movdx, offst
- ; Call DOS print routine and return to BASIC
- movah, 9
- int21h
- ret
- printmessageendp
- end
-
- Note
-
- This example uses features of MASM version 5.1, including the .MODEL
- directive which establishes compatible naming, calling, and passing
- conventions. It also uses simplified segment directives which eliminate the
- need to separate GROUP and ASSUME directives. The new PROC directive is
- employed. It includes new arguments that specify automatically saved
- registers, define arguments to procedures, and set up test macros to use for
- the arguments. The PROC directive also causes the proper type of return to
- be generated automatically based on the chosen memory model and cleans up
- the stack.
-
- In the next example, a
- far pointer to a string is passed to a C routine, which prints the string
- data. The far pointer is returned by the SSEGADD function. The far pointer
- is a double word with the segment contained in the high word and the offset
- contained in the low word.
-
-
- ' Declare external C procedure using correct naming
- ' and parameter passing conventions
- DEFINT A-Z
- DECLARE SUB PrintMessage CDECL (BYVAL FarString AS LONG)
- ' Create the message as an ASCIIZ string, as
- ' required by the C printf function.
- A$ = "This is a short example of a message" + CHR$(0)
- ' Tell C to print the string addressed by the far pointer
- CALL PrintMessage(SSEGADD(A$))
- /********************** PRINT MESSAGE *******************
- * This C routine prints a BASIC far string on the screen.
- * Use standard i/o header */
- #include <stdio.h>
-
- /* Define a procedure which inputs a string far pointer */
- void printmessage (char far *farpointer)
- {
- /* print the string addressed by the far pointer */
- printf( "%s\n", farpointer);
- }
- Notice that near and far pointers passed to FORTRAN, C, MASM, and other
- languages are treated by those languages as unsigned values, whereas BASIC
- has no such data type ( INTEGER, LONG, SINGLE, and DOUBLE data types are
- signed). This presents no problem as long as the pointers are assigned their
- values from the SSEG, SADD and SSEGADD functions. There can be problems,
- however, if pointers are assigned values directly from certain types of
- expressions. For example, suppose A$ is a string that exists in segment
- 42000 at offset 40000. The following code passes the string to MASM:
-
- DEFINT A-Z
- DECLARE SUB PrintMessage(BYVAL Segment, BYVAL Offset)
- Segment = SSEG(A$)
- Offset = SADD(A$)
- CALL PrintMessage(Segment, Offset)
- The preceding method works correctly, but what if you directly assign the
- pointers with the following code?
-
- Segment = 42000
- Offset = 40000
- In this case, these
- lines produce an Overflow error message because the maximum value for an
- integer data type is 32,767. To make direct assignments, use hexadecimal
- numbers:
-
-
- ' set the segment using the hex equivalent of 40000 decimal.
- Segment = &H9C40
- ' Set the offset using the hex equivalent of 42000 decimal.
- Offset = &HA410
-
- Maximizing String Storage Space
-
- For applications requiring 128K of strings, the easiest way to create this
- much space is to keep half in the module-level string segment and half in
- the string segment declared with COMMON. For example:
-
- COMMON C$, D$
- A$ = STRING$(32700,65)
- B$ = STRING$(32700,66)
- C$ = STRING$(32700,67)
- D$ = STRING$(32700,68)
-
- To get another 64K, call a procedure to create the rest of the strings. If
- you need to refer to procedure- and module-level strings at the same time,
- then share them, and do your string processing in the procedure. For
- example:
-
- SUB BigStrings
- SHARED A$, B$, C$, D$
- E$ = STRING$(32700,69)
- F$ = STRING$(32700,70)
- ' Place to do processing of a$, b$, c$, d$, e$ and F$
- .
- .
- .
- END SUB
-
- One problem with using large procedure-level strings is that it uses up
- temporary string storage space -- the place where string expressions are
- kept -- because both occupy the same data segment. Therefore, the largest
- string expression plus the total procedure-level string space cannot exceed
- 64K. To prevent Out of String Space errors, use the methods described in
- the section "Calculating Far-String Memory Space" later in this chapter.
-
- Another way to avoid an
- error is to create far string arrays while in the procedure. They get their
- own 64K segment:
-
-
- SUB BigStrings
- SHARED A$, B$, C$, D$
- DIM E$(9), F$(9)
- FOR I% = 0 to 9
- E$(I%) = STRING$(3210,69 + i%)
- F$(I%) = STRING$(3210,70 + i%)
- NEXT I%
- ' Place to do processing of a$, B$, C$, D$, E$() and F$()
- .
- .
- .
- END SUB
- The previous method can be expanded upon to fill all available memory space
- with strings. It works because BASIC creates a new string segment for every
- invocation of a procedure -- in other words, each time the procedure is
- called during recursion. Here is the idea:
-
- DEFINT A-Z
- DECLARE SUB ManyStrings (n)
- ' Compute the # of 64K blocks available.
- N = FRE(-1) \ 65536
- CALL ManyStrings(N)
-
- SUB ManyStrings(N)
- DIM G$(1 to 1), H$(1 to 1)
- G$(1) = STRING$(32700, 71)
- H$(1) = STRING$(32700, 72)
- N = N - 1
- IF N > 0 THEN CALL ManyStrings(n)
- END SUB
- This creates 64K of strings for each recursion. A limitation is that the
- only strings that can be accessed are the ones that are dimensioned at the
- current level of recursion. In theory, this can be overcome by passing the
- strings from the previous level of recursion when the next call is made. But
- in reality, this makes for complex code, as demonstrated by the following:
-
- ' Define arrays which will be passed to each new level
- ' of recursion.
- DECLARE SUB BigStrings (N%, S1$(), S2$(), S3$(), S4$())
- DEFINT A-Z
- DIM S1$(1 TO 2), S2$(1 TO 2), S3$(1 TO 2), S4$(1 TO 2)
- ' Compute the # of 64K
- blocks available in far memory.
-
- N = FRE(-1) \ 65536
- CLS
- ' Quit if not enough memory.
- IF N < 1 THEN
- PRINT "Not enough memory for operation."
- END
- END IF
-
- ' Start the recursion.
- CALL BigStrings(N, S1$(), S2$(), S3$(), S4$())
-
- SUB BigStrings (N, S1$(), S2$(), S3$(), S4$())
- ' Create a new array (up to 64K) for each level of recursion.
- DIM A$(1 TO 2)
- ' Have N keep track of recursion level.
- SELECT CASE N
- ' When at highest recursion level, process the strings.
- CASE 0
- PRINT S1$(1); S1$(2); S2$(1); S2$(2); S3$(1); S3$(2); S4$(1); S4$(2)
- CASE 1
- A$(1) = "Each "
- A$(2) = "word "
- S1$(1) = A$(1)
- S1$(2) = A$(2)
- CASE 2
- A$(1) = "pair "
- A$(2) = "comes "
- S2$(1) = A$(1)
- S2$(2) = A$(2)
- CASE 3
- A$(1) = "from "
- A$(2) = "separate "
- S3$(1) = A$(1)
- S3$(2) = A$(2)
- CASE 4
- A$(1) = "recursive "
- A$(2) = "procedures."
- S4$(1) = A$(1)
- S4$(2) = A$(2)
- END SELECT
-
- ' Keep going until
- we're out of memory.
-
- IF N > 0 THEN
- N = N - 1
- ' For each recursion, pass in previously created arrays.
- CALL BigStrings(N, S1$(), S2$(), S3$(), S4$())
- END IF
-
- END SUB
-
- Output
-
- Each word pair comes from separate recursive procedures.
-
- In this last example, the variable N has several important functions. In the
- beginning it contains the total number of 64K blocks available for strings.
- Thus, during execution of the SELECT CASE code in the SUB procedure, it
- can prevent Out of String Space errors. The N variable also keeps track of
- the level of recursion and ends the recursion at the appropriate time.
-
- As you can see, once the code reaches the highest recursive level, the user
- can process all the strings he has created. This occurs when CASE 0 is true.
- In this example, all the created strings are printed on the screen.
-
- As stated earlier, each string array can be up to 64K. They are kept short
- here to make the demonstration practical.
-
-
- Far Strings and Older Versions of BASIC
-
- If you want to use far strings in modules written with previous versions of
- BASIC, recompile them using the /Fs option. (See the preceding section,
- "Selecting Far Strings," for details.) The only time you have to change any
- code is when your module uses the VARSEG or VARPTR function. If the
- VARSEG function is used to obtain the string data segment, replace it with
- the SSEG function. If you use the VARPTR function to access the string
- descriptor, remember that a far string descriptor has a different format
- than a near string descriptor. You will have to make code changes. (For
- suggestions on how to handle this, see Chapter 13, "Mixed-Language
- Programming with Far Strings.")
-
- If you are linking new code containing far strings with older code
- containing near strings, you must recompile the old code using the /Fs
- option. Otherwise the program will return an error message Low Level
- Initialization and terminate.
-
-
- Data Structure and Space Allocation
-
- This section provides details about far strings which may prove helpful when
- doing mixed language programming or direct processing of far string data.
- Additional information can be found in Chapters 13, "Mixed-Language
- Programming with Far Strings" and 15, "Optimizing Program Size and Speed."
-
-
-
-
-
- Far-string data structure consists, in part, of a 4-byte string descriptor
- located in DGROUP and the data located in multiple segments of far memory.
- The string descriptor contains information that BASIC uses to manage the
- data as it changes length and location during run time. The exact structure
- of the string descriptor is unavailable.
-
- Whenever a far string (such as A$) is used in a program, the string refers
- to the offset address in DGROUP of the string descriptor. Thus if the string
- descriptor of A$ is at offset &H2000, that is what gets pushed on the stack
- during the following call:
-
- CALL DemoSub (A$)
-
- In assembly language, the equivalent would be:
-
- movax, 2000H
- pushax
- callDemoSub
-
- For all far string arrays, the array descriptor and all string descriptors
- -- one for each element in the array--reside in DGROUP. All string data is
- in far memory.
-
- Each string segment, besides storing the string data, contains a small
- amount of overhead used for string management. The overhead consists of 64
- bytes plus an additional 6 bytes per string in the segment.
-
- The exact number of 64K segments used to store string data is dependent on
- where the strings are created and how they are declared. This can be
- summarized as follows:
-
- ■ All far string data declared with COMMON resides in a separate 64K
- segment.
-
- ■ All other far strings created at the module-level, whether simple or
- in arrays, reside in a separate 64K segment.
-
- ■ All string arrays created at the procedure level reside in a separate
- 64K segment. The arrays are local to the procedure and exist only
- until the procedure is exited. During recursion, arrays created at all
- levels exist up to and within the most deeply nested level. During the
- exit process, when the routine returns to a previous level, arrays
- used in the exited level are cleared.
-
- ■ All simple strings created in any procedure reside in a single,
- separate 64K segment.
-
-
- Note
-
-
- The segment for procedure-level strings is also used for temporary strings.
- Temporary strings are created for all string expressions that appear
- anywhere in BASIC code. When using large string expressions, therefore, you
- may have to reduce the number of procedure-level strings to avoid running
- out of space in this segment. For an example of how to monitor this
- activity, see the section "Calculating Far-String Memory Space."
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 12: Mixed-Language Programming
- ────────────────────────────────────────────────────────────────────────────
-
- Mixed-language programming is the process of combining programs from two or
- more source languages. For example, mixed-language programming allows you to
- use Microsoft Macro Assembler (MASM) to enhance your BASIC programs. You can
- develop most of your program rapidly using BASIC, then use assembly language
- for routines that are executed many times and must run with utmost speed.
- Similarly, you can call your own Microsoft C, Pascal, and Fortran routines
- from within BASIC programs.
-
- This chapter assumes that you know the languages you wish to combine, and
- that you know how to write, compile, and link multiple-module programs with
- these languages. When you finish this chapter, you will understand:
-
- ■ General issues important in mixed-language programming.
-
- ■ Calling between BASIC and other Microsoft high-level languages.
-
- ■ Passing parameters in interlanguage calls.
-
- ■ Differences in how BASIC, Pascal, FORTRAN, and C handle numeric and
- string data.
-
- ■ Calling between BASIC and assembly language.
-
- Information in this chapter assumes you are compiling your BASIC
- modules using the command-line compiler, using "near strings" (that
- is, without the /Fs option). If you do not plan to use the /Fs option,
- the information in this chapter should be all you need to combine
- modules written in Microsoft BASIC, C, Pascal, FORTRAN, and Macro
- Assembler.
-
- ■ Using "far strings" (compiling with the /Fs option) vastly increases
- the amount of space you can use for strings. However, because the
- string descriptors used for near strings and far strings are
- completely different, rules for mixed-language programming differ
- depending on which string model is used. Microsoft BASIC includes a
- set of mixed-language string-handling routines you can use in all your
- programming to assure portability among modules. For information on
- "far strings" and using the mixed-language string routines, see
- Chapters 11, "Advanced String Storage" and 13, "Mixed-Language
- Programming with Far Strings."
-
- ■ Note also that data storage differs for some kinds of data depending
- on whether you are using the command-line compiler or working within
- the QBX program-development environment. A table in the section
- "Special Data Types" later in this chapter summarizes these
- differences.
-
-
- Warning
-
- Routines intended for use in Quick libraries must use far strings. Non-BASIC
- Quick libraries written for QuickBASIC version 4.5 and earlier may have to
- be rewritten to use far strings before they can be used in the QBX
- environment. Also, you cannot use the /Ea option in QBX if you are using a
- Quick library that contains a non-BASIC routine that receives a BASIC array.
-
-
- Note
-
- These restrictions apply when creating mixed-language programs with
- Microsoft BASIC:
-
- Some combinations of languages may produce a Symbol defined more than once
- error when compiled for use with the BASIC run-time module. To avoid this,
- compile your program as a stand-alone executable (use the /O compiler
- option).
-
- Modules created with Microsoft QuickPascal are not compatible with any
- other language and cannot be linked into a mixed-language program
-
- You cannot link Pascal modules compiled with /Fpa with BASIC modules.
- Programs that include Pascal modules cannot use alternate math.
-
-
- Organizing Mixed-Language Programs
-
- The way you organize mixed-language programs depends on whether you run your
- program from within the QBX program-development environment, or compile from
- the command line using BASIC Compiler (BC). When your program calls
- other-language routines from within the QBX environment, the other-language
- routines must first be compiled and linked into a Quick library, and the
- Quick library must be loaded in QBX, as described in Chapter 19, "Creating
- and Using Quick Libraries."
-
- If you compile and link your program from the DOS command line, your
- other-language routines do not have to be part of a library. However, the
- Microsoft Library Manager (LIB) is provided for this purpose if you find it
- more convenient. See Chapters 16, "Compiling with BC," 17, "About Linking
- and Libraries," and 18,"Using LINK and LIB," for information on compiling,
- linking, and managing libraries.
-
- Note
-
- It is especially important that other-language procedures be thoroughly
- debugged before being incorporated in a Quick library. The QBX tracing
- commands do not step into Quick-library procedures when tracing through a
- program, so debugging them from within the environment is not possible. For
- source-level debugging of mixed-language programs, compile and link the
- modules from the command line with the /Zi and /CO options (or using the
- appropriate settings in the QBX Make EXE dialog box). You can then use the
- Microsoft CodeView debugger to debug the mixed-language program at their
- source level.
-
-
- Mixed-Language Programming Elements
-
- Microsoft languages have special keywords that facilitate mixed-language
- programming. To use these keywords, you must understand certain fundamental
- issues.
-
- After explaining the context of a mixed-language call, the following
- sections describe: how the languages differ and how to resolve these
- differences. The three fundamental mixed-language programming requirements
- are discussed:
-
- ■ The naming convention
-
- ■ The calling convention
-
- ■ Parameter passing
-
-
-
- Finally, issues relating to compiling and linking are discussed (including
- use of different memory models with C-language routines).
-
-
- Making Mixed-Language Calls
-
- Mixed-language programming always involves a function or procedure call. For
- example, a BASIC main module may need to execute a specific task that you
- would like to program separately. However, instead of calling a BASIC
- subprogram, you decide to call a C function.
-
- Mixed-language calls necessarily involve multiple modules. Instead of
- compiling all of your source modules with the same compiler, you use
- different compilers. In the situation mentioned earlier, you could compile
- the main-module source file with BC, another source file (written in C) with
- the C compiler, and then link the two object files using LINK.
- Alternatively, you could compile the C function, and then link it into a
- Quick library and call the function from a program running within the QBX
- environment.
-
- Any mixed language program that includes a BASIC module must have a BASIC
- main module. This is because BASIC requires that the environment be
- initialized in a unique way. No other language performs this initialization.
-
- Figure 12.1 illustrates the syntax of a mixed-language call in which a BASIC
- main module calls a C function.
-
- 46f1nt??
-
-
- See the DECLARE statement in the BASIC Language Reference for more
- information.
-
- Despite syntactic differences, BASIC FUNCTION and SUB procedures are very
- similar to subroutines, procedures and functions in other Microsoft
- languages. The principal difference is that C, Pascal, FORTRAN functions,
- and BASIC FUNCTION procedures (and assembly language procedures) can all
- return values, whereas the BASIC SUB procedure, FORTRAN SUBROUTINE, and
- Pascal procedure cannot. Table 12.1 shows the correspondence between routine
- calls in different languages.
-
- For example, a BASIC module can make a SUB procedure call to a C function
- declared with the void keyword in place of a return type. BASIC should make
- a FUNCTION procedure call in order to call a C function that returns a
- value; otherwise, the return value is lost.
-
- Note
-
- In this chapter, "routine" refers to any C function, BASIC SUB or FUNCTION
- procedure, or assembly language procedure that can be called from another
- module.
-
- BASIC DEF FN functions and GOSUB subroutines cannot be called from another
- language.
-
-
- Naming Convention Requirement
-
- The calling program and the called routine must agree on the names of
- identifiers. Identifiers can refer to routines (functions, procedures, and
- subroutines) or to variables that have a public or global scope. Each
- language alters the names of identifiers.
-
- "Naming convention" refers to the way a compiler alters the name of the
- routine (or a public variable) before placing it in an object file.
- Languages may alter the identifier names differently. You can choose between
- several naming conventions to ensure that the names in the calling routine
- agree with those in the called routine. If the names of public variables or
- called routines are stored differently in any of the object files being
- linked, LINK will not be able to find a match. It will instead report
- unresolved external references.
-
- It is important that you adopt a compatible naming convention when you issue
- a mixed-language call. If the name of the called routine is stored
- differently in any of the object files being linked, then LINK is unable to
- find a match and reports an unresolved external reference.
-
-
- Microsoft compilers place machine code into object files; but they also
- place there the names of all routines and variables that need to be accessed
- publicly. That way, LINK can compare the name of a routine called in one
- module to the name of a routine defined in another module and recognize a
- match. Names are stored in ASCII format.
-
- BASIC, Pascal, and FORTRAN translate each letter to uppercase. BASIC drops
- its type-declaration characters ( %, &, !, #, @, $). BASIC preserves
- the first 40 characters of any name; FORTRAN and Pascal recognize the first
- 31 characters.
-
- Note
-
- Microsoft FORTRAN prior to version 5.0 truncated identifiers to six
- characters. As of version 5.0, FORTRAN retains up to 31 characters of
- significance unless you use the /4Yt option. Microsoft Pascal prior to
- version 4.0 preserved only the first eight characters of a name. As of
- version 5.0, Pascal preserves the first 31 characters.
-
- The C compiler does not translate any letters to uppercase, but it inserts a
- leading underscore
-
- ( _ ) in front of the name of each routine. C preserves only the first 31
- characters of a name.
-
- If a name is longer than the language recognizes, additional characters are
- simply not placed in the object file. Also, when the mixed-language keyword
- CDECL is specified in the BASIC DECLARE statement, periods within a name
- are converted to underscores by BASIC (in addition to adding the leading
- underscore).
-
- Differences in naming conventions are dealt with automatically by
- mixed-language keywords, as long as you follow two rules:
-
- n
-
- If you use any FORTRAN routines that were compiled with the $TRUNCATE
- metacommand enabled or with the /4Yt command-line option, make all
- names six characters or less. Make all names six characters or less
- when using FORTRAN routines compiled with versions of the FORTRAN
- compiler prior to version 5.0.
-
- n
-
- Do not use the /NOIGNORECASE (/NOI) LINK option (which causes LINK to
- treat identifiers in a case-sensitive manner). With C modules, this
- means that you must be careful not to rely upon differences between
- uppercase and lowercase letters when programming.
-
-
-
- The Microsoft C compiler drivers CL and QCL always set the /NOI option
- for the link stage when compiling and linking. This can be a problem
- if your C module contains mixed-case identifiers. For example, the C
- compiler translates the identifier Name to _Name, preserving the
- capital N. When BASIC, Pascal, and FORTRAN implement the C convention,
- they don't preserve case, they simply translate the characters to
- lowercase -- so in this case, they would translate Name to _name. The
- identifiers _Name and _name do not match when /NOI is set. To avoid
- problems, make all your C-program identifiers lowercase, or link as a
- separate stage (i.e. use LINK, rather than CL or QCL to link), and
- make sure not to specify /NOI.
-
-
- Note
-
- You use the command-line option /Gc (generate Pascal-style function calls)
- when you compile your C modules, or if you declare a function or variable
- with the pascal keyword, the compiler will translate your identifiers to
- uppercase.
-
- In the preceding figure, the BASIC Compiler inserts a leading underscore in
- front of Prn as it places the name into the object file, because the CDECL
- keyword directs the BASIC Compiler to use the C naming convention. BASIC
- will also convert all letters to lowercase when this keyword is used.
- (Converting letters to lowercase is not part of the C naming convention;
- however, it is consistent with the programming style of many C programs.)
-
-
- Calling-Convention Requirement
-
- "Calling convention" refers to the way a language implements a call. The
- choice of calling convention affects the actual machine instructions that a
- compiler generates in order to execute (and return from) a function or
- procedure call.
-
- The calling convention is a low-level protocol. It is crucial that the two
- routines concerned (the routine issuing a call and the routine being called)
- recognize the same protocol. Otherwise, the processor may receive
- inconsistent instructions, thus causing unpredictable behavior.
-
- The use of a calling convention affects programming in two ways:
-
-
- The calling routine uses a calling convention to determine in what
- order to pass arguments (parameters) to another routine. This
- convention can either be the default for the language, or specified in
- a mixed-language interface. In the following example, the CDECL
- keyword in the BASIC declaration of the C function overrides the
- default BASIC convention and causes the parameters to be passed in the
- order in which a C function normally expects to receive them:
-
-
- DECLARE Func1 CDECL (N%, M%)
-
-
- The called routine uses a calling convention to determine in what
- order to receive the parameters passed to it. With a C function, this
- convention can be specified in the function definition. In the
- following example the fortran keyword in the function definition
- overrides the default C convention, and causes the C function to
- receive the parameters consistent with the default
- BASIC/FORTRAN/Pascal convention:
-
-
- int fortran func2 (int x, int y)
- {
- /* body of C function would go here */
- }
-
-
- In other words, the way the function is declared in BASIC determines which
- calling convention BASIC uses. However, in C the calling convention can be
- specified in the function definition. The two conventions must be
- compatible. It is simplest to adopt the convention of the called routine.
- For example, a C function would use its own convention to call another C
- function but must use the BASIC convention to call BASIC. This is because
- BASIC always uses its own convention to receive parameters. Because the
- BASIC and C calling conventions are different, you can change the calling
- convention in either the caller or the called routine, but not in both.
-
-
- Effects of Calling Conventions
-
- Calling conventions dictate three things:
-
-
- The way parameters are communicated from one routine to another (in
- Microsoft mixed-language programming, parameters or pointers to the
- parameters are passed on the stack)
-
-
- The order in which parameters are passed from one routine to another
-
-
- The part of the program responsible for adjusting the stack
-
-
-
- Order in Which Arguments Are Pushed (BASIC, FORTRAN, Pascal)
-
- The BASIC, FORTRAN and Pascal calling conventions push parameters onto the
- stack in the order in which they appear in the source code. For example, the
- following BASIC statement pushes argument A onto the stack first, then B,
- and then C:
-
- CALL Calc( A, B, C )
-
- These conventions also specify that the stack is adjusted by the called
- routine just before returning control to the caller. Figures 12.3 and 12.4
- illustrate how the calling conventions work at the assembly language level.
- Note that the stack grows downward.
-
-
- Order in Which Arguments Are Pushed (C)
-
- The C calling convention pushes parameters onto the stack in the reverse
- order from their appearance in the source code. For example, the following C
- function call pushes c onto the stack, then b and finally a:
-
- calc( a, b, c );
-
-
- In contrast with the other high-level languages, the C calling convention
- specifies that a calling routine always adjusts the stack immediately after
- the called routine returns control.
-
- The BASIC, FORTRAN, and Pascal conventions produce slightly less object
- code. However, the C convention makes calling with a variable number of
- parameters possible. (Because the first parameter is always the last one
- pushed, it is always on the top of the stack; therefore it has the same
- address relative to the frame pointer, regardless of how many parameters
- were actually passed.)
-
- Note
-
- The C-compiler fastcall keyword, which specifies that parameters are to be
- passed in registers, is incompatible with programs written in other
- languages. Avoid using fastcall or the /Gr command-line option for C
- functions that you intend to make public to BASIC, FORTRAN, or Pascal
- programs.
-
-
- Parameter-Passing Requirements
-
- The routines in program must agree on the calling convention and the naming
- convention; they must also agree on the method in which they pass
- parameters. It is important that your routines send parameters in the same
- way to ensure proper data transmission and correct program results.
-
-
- Microsoft compilers support three methods for passing a parameter:
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- Method Description
- ────────────────────────────────────────────────────────────────────────────
- Near reference Passes a variable's near (offset)
- address. This address is expressed
- Method Description
- ────────────────────────────────────────────────────────────────────────────
- address. This address is expressed
- as an offset from the beginning of
- the default data segment (DGROUP).
- This method gives the called
- routine direct access to the
- variable itself. Any change the
- routine makes to the parameter
- changes the variable in the
- calling routine. BASIC passes by
- near reference as the default. See
- Chapters 11, "Advanced String
- Storage," and 13, "Mixed Language
- Programming with Far Strings," for
- information on passing strings
- stored in far memory.
-
- Far reference Passes a variable's far (segmented)
- address.This method is similar to
- passing by near reference, except
- Method Description
- ────────────────────────────────────────────────────────────────────────────
- passing by near reference, except
- that a longer address is passed.
- This method is slower than passing
- by near reference but is necessary
- when you pass data that is stored
- outside the default data segment.
- (This is an issue in BASIC or
- Pascal only if you have
- specifically requested far memory.
- See Table 12.5 in the section
- "Special Data Types," later in
- this chapter and Chapters 11,
- "Advanced String Storage," and 13,
- "Mixed-Language Programming with
- Far Strings," for information on
- when BASIC and QBX store data in
- far memory).
-
- Value Passes only the variable's value,
- Method Description
- ────────────────────────────────────────────────────────────────────────────
- Value Passes only the variable's value,
- not its address. With this method,
- the called routine knows the value
- of the parameter but has no access
- to the original variable. Changes
- to a value passed by a parameter
- have no affect on the value of the
- parameter in the calling routine.
-
-
-
-
-
- These different parameter-passing methods mean that you must consider the
- following when programming with mixed languages:
-
- ■ You need to make sure that, for each parameter, the called routine and
- the calling routine use the same method for passing and receiving the
- argument. In most cases, you will need to check the parameter-passing
- defaults used by each language and possibly make adjustments. Each
- language has keywords or language features that allow you to change
- parameter-passing methods.
-
- ■ You may want to choose a specific parameter-passing method rather than
- using the defaults of any language. Table 12.2
-
-
-
- 1 When a Pascal or C attribute is applied to a FORTRAN routine, passing by
- value becomes the default.
-
- See Chapters 11, "Advanced String Storage," and 13, "Mixed-Language
- Programming with Far Strings," for information on how strings in far memory
- are passed.
-
- Using the BYVAL keyword for a parameter in the BASIC declaration of an
- other-language routine enables you to perform a true "pass by value" to the
- other-language routine, as explained in the section "Using the Parameter
- List" later in this chapter.
-
-
- Compiling and Linking
-
- After you have written your source files and decided on a naming convention,
- a calling convention, and a parameter-passing convention, you are ready to
- compile and link individual modules.
-
-
- Compiling with Correct Memory Models
-
- With BASIC, FORTRAN, and Pascal, no special options are required to compile
- source files that are part of a mixed-language program. With C, not all
- memory models are compatible with other languages.
-
- BASIC, FORTRAN, and Pascal use only far (segmented) code addresses.
- Therefore, you must use one of two techniques with C programs that call one
- of these languages: compile C modules in medium, large, or huge model (using
- the /A x command-line options), because these models also use far code
- addresses; or apply the far keyword to the definitions of C functions you
- make public. If you use the /A x command-line option to specify medium,
- large, or huge model, all your function calls become far by default. This
- means you don't have to declare your functions explicitly with the far
- keyword. (Note that you must also declare extern functions far, as well as
- public functions.)
-
- Choice of memory model affects the default data pointer size in C and
- FORTRAN, although this default can be overridden with the near and far
- keywords. With C and FORTRAN, the choice of memory model also affects
- whether data items are located in the default data segment; if a data item
- is not located in the default data segment, it cannot be passed by near
- reference.
-
-
- For more information about code and data address sizes in C, refer to the
- Microsoft C documentation.
-
-
- Linking with Language Libraries
-
- In most cases, you can easily link modules compiled with different
- languages. However, if any module in a program is a BASIC module, the main
- module of the program must be a BASIC module. When you link a program that
- contains a BASIC module, the BASIC main module must appear first on the LINK
- command line. Do any of the following to ensure that all required libraries
- link in the correct order:
-
- ■ Put all language libraries in the same directory as the source files.
-
- ■ List directories containing all needed libraries in the LIB
- environment variable.
-
- ■ Let LINK prompt you for libraries.
-
-
- In each of these cases (assuming the BASIC module appeared first on the LINK
- command line), LINK finds libraries in the order that it requires them. If
- you enter the library names on the command line, make sure you enter them in
- an order that allows LINK to resolve your program's external references.
- Here are some points to observe when specifying libraries on the command
- line:
-
- ■ If you are listing BASIC libraries on the LINK command line, specify
- those libraries first.
-
- ■ If you are using FORTRAN to write one of your modules, you need to
- link with the /NOD (no default libraries) option, and explicitly
- specify all the libraries you need on the LINK command line. You can
- also specify these libraries with an automatic-response file (or batch
- file), but you cannot use a default-library search.
-
- ■ If your program uses FORTRAN and C, specify the library for the most
- recent of the two language products first. In addition, make sure that
- you choose a C-compatible library when you install FORTRAN.
-
-
- The following example shows how to link three modules, mod1, mod2, and mod3,
- with a user library, GRAFX; the BASIC run-time library, BCL70ENR.LIB; the C
- run-time library, LLIBCE; and the FORTRAN run-time library, LLIBFORE:
-
- LINK /NOD mod1 mod2 mod3,,,BCL70ENR+GRAFX+LLIBCE+LLIBFORE
-
- Important
-
- Microsoft QuickC version 1.0 used medium model by default when you chose
- Compile from the Run menu and Obj from the Output Options. QuickC version
- 2.0 uses small model by default. Also when compiling from the command line
- with either the QCL or CL commands, you must specify the correct memory
- model. When small model is used, your C object files will not be compatible
- with your BASIC object files.
-
- Linking with a C library containing graphics will result in Duplicate
- definition errors. Don't include graphics in C libraries that will be linked
- with BASIC.
-
-
- BASIC Calls to High-Level Languages
-
- Microsoft BASIC supports calls to routines written in Microsoft C, FORTRAN,
- and Pascal. This section describes the necessary syntax for calling these
- languages, then gives examples of each combination of BASIC with other
- languages. For simplicity in illustrating concepts, only integers are used
- as parameters in these examples. The section ends with a description of
- restrictions on the use of functions from the C standard library. Consult
- this section if the C functions called in your program use any system or
- memory-allocation library functions.
-
- See the section "Handling Data in Mixed-Language Programming" later in this
- chapter for information on how to pass specific kinds of data.
-
-
- The BASIC Interface to Other Languages
-
- The BASIC DECLARE statement provides a flexible and convenient interface to
- other languages. It was introduced in Microsoft QuickBASIC version 4.0.
- Earlier versions of BASIC that do not provide the DECLARE statement also do
- not provide libraries that are compatible with other languages. The DECLARE
- statement is summarized in the following section.
-
-
- The DECLARE Statement
-
- The DECLARE statement's syntax differs slightly for FUNCTION and SUB
- procedures. For FUNCTION procedures, the DECLARE statement's syntax is as
- follows:
-
- DECLARE FUNCTION name CDECL ALIAS " aliasname"( parameterlist)
-
- For SUB procedures, use this syntax for the DECLARE statement:
-
- DECLARE SUB name CDECL ALIAS " aliasname"( parameterlist)
-
- The name argument is the name that appears in the BASIC source file for the
- SUB or FUNCTION procedure you wish to call. Here are the recommended steps
- for using the DECLARE statement to call other languages:
-
- 1. For each distinct interlanguage routine you plan to call, include a
- DECLARE statement at the beginning of the module-level code of any
- module in which the routine is called. (QBX cannot automatically
- generate DECLARE statements for other-language routines.) For
- example, your program may call the subprogram Maxparam five different
- times, each time with different arguments. However, you need to
- declare Maxparam just once for each module. The DECLARE statements
- must be placed near the beginning of the module, preceding all
- executable statements. A good way to do this is with an include file.
-
- 2. If you are calling a routine defined in a C module, use CDECL in the
- DECLARE statement (unless the C routine is defined with the pascal or
- fortran keyword). The CDECL keyword directs BASIC to use the C
- naming and calling conventions during each subsequent call to name.
-
-
-
- 3. If you are calling a C function with a name containing characters that
- would be illegal in BASIC (for example, the underscore), you can use
- the ALIAS feature, discussed in the next section. If you use the
- CDECL keyword, you can use a period in place of the underscore. BASIC
- then replaces the period with an underscore.
-
- 4. Use the parameter list to specify how each parameter is to be passed.
- See the section "Using the Parameter List" later in this chapter for
- information on how to use a parameter list.
-
- 5. Once the routine is properly declared, call it just as you would a
- BASIC sub or function procedure.
-
-
- The other syntax elements are explained in the following sections.
-
-
- Using ALIAS
-
- As noted in the preceding section, the use of the ALIAS keyword may be
- necessary if you want to use an underscore as part of the C identifier.
- Similarly, though it is not likely to be a problem, C, FORTRAN, and Pascal
- place fewer characters of a name into an object file than BASIC (31 for all
- versions of C, FORTRAN version 5.0, and Pascal version 4.0, in addition to
- the leading underscore), which places up to 40 characters of a name into an
- object file.
-
- Note
-
- You do not need the ALIAS feature to remove the type-declaration characters
- ( %, &, !, #, @, $). BASIC automatically removes these characters when
- it generates object code. Thus, Fact% in BASIC matches fact in C.
-
- The ALIAS keyword directs BASIC to place aliasname into the object file,
- instead of name. The BASIC source file still contains calls to name.
- However, these calls are interpreted as if they were actually calls to
- aliasname.
-
- Example
-
- In the following example, BASIC places the aliasname quad_result, rather
- than the name QuadResult, into the object code. This avoids the use of a
- mixed-case identifier for the C function, but provides the same type of
- recognizability as the BASIC name.
-
- DECLARE FUNCTION QuadResult% ALIAS "quad_result" (a, b, c)
-
- Using the Parameter List
-
- The following is the syntax for parameterlist. Note that you can use BYVAL
- or SEG, but not both:
-
- { BYVAL | SEG} variable AS type ,{ BYVAL | SEG} variable AS type...
-
- Use the BYVAL keyword to declare a value parameter. In each subsequent
- call, the corresponding argument will be passed by value (the default method
- for C modules).
-
-
- Note
-
- BASIC provides two ways of "passing by value." In BASIC-only programs you
- can simulate passing by value by enclosing the argument in parentheses, as
- follows:
-
- CALL Holm((A))
-
- This method actually creates a temporary value, whose address is passed. The
- BYVAL keyword provides the only true method of passing by value, because
- the value itself is passed, not an address. Using BYVAL is the only way to
- make a BASIC program compatible with a non-BASIC routine that expects a
- value parameter. BYVAL is only for interlanguage calls; it cannot be used
- in calls between BASIC routines.
-
- Use the SEG keyword to declare a far-reference parameter. In each
- subsequent call, the far (segmented) address of the corresponding argument
- will be passed. See the DECLARE statement in the BASIC Language Reference
- for information and cautions on the use of the SEG keyword.
-
- You can choose any legal name for variable, but only the type associated
- with the name has any significance to BASIC. As with other variables, the
- type can be indicated with a type-declaration character ( %, &, !, #, @,
- $), in an AS type clause, or by implicit declaration.
-
- The AS type clause overrides the default type declaration of variable. The
- type field can be INTEGER, LONG, SINGLE, DOUBLE, STRING, CURRENCY or
- a user-defined type. Or it can be ANY, which directs BASIC to permit any
- type of data to be passed as the argument.
-
- Examples
-
- In the following example, Calc2 is declared as a C routine that takes three
- arguments: the first two are integers passed by value, and the last is a
- single-precision real number passed by value.
-
- DECLARE FUNCTION Calc2! CDECL (BYVAL A%, BYVAL B%, BYVAL C!)
-
- The following example declares a subprogram, Maxout, that takes an integer
- passed by far reference and a double-precision real number passed by value.
-
- DECLARE SUB Maxout (SEG Var1 AS INTEGER, BYVAL Var2 AS DOUBLE)
-
- Alternative BASIC Interfaces
-
- Though the DECLARE statement provides a particularly convenient interface,
- there are other methods of implementing mixed-language calls.
-
- Instead of modifying the behavior of BASIC with CDECL, you can modify the
- behavior of C by applying the pascal or fortran keyword to the function
- definition. (These two keywords are functionally equivalent.) Or, you can
- compile the C module with the /Gc option, which specifies that all C
- functions, calls, and public symbols use the BASIC/FORTRAN/Pascal
- convention.
-
-
- For example, the following C function uses the BASIC/FORTRAN/Pascal
- conventions to receive an integer parameter:
-
- int pascal fun1(n)
- int n;
- {
- }
-
- You can specify parameter-passing methods even though you omit the DECLARE
- statement or omit the parameter list, or both, as follows:
-
- ■ You can make the call with the CALLS statement. The CALLS statement
- causes each parameter to be passed by far reference.
-
- ■ You can use the BYVAL and SEG keywords in the argument list when you
- make the call.
-
-
- In the following example, BYVAL and SEG have the same meaning that they
- have in a BASIC DECLARE statement. When you use BYVAL and SEG this way,
- however, you need to be careful because neither the type nor the number of
- parameters will be checked (as they would be if there were a DECLARE
- statement). Also note that, if you do not use a DECLARE statement, you must
- use either the fortran or pascal keyword in the C function definition, or
- compile the C function with the /Gc option.
-
- CALL Fun2(BYVAL Term1, BYVAL Term2, SEG Sum);
-
- Note
-
- BASIC provides a system-level function, B_OnExit, that can be called from
- other-language routines to log a termination procedure that will be called
- when a BASIC program terminates or is restarted when a Quick library is
- present. See the section "B_OnExit Routine" later in this chapter for more
- information.
-
-
- BASIC Calls to C
-
- This section applies the steps outlined earlier to two example programs. An
- analysis of programming considerations follows each example.
-
-
- Calling C from BASIC with No Return Value
-
- The following example demonstrates a BASIC main module calling a C function,
- maxparam. The function maxparam returns no value, but adjusts the lower of
- two arguments to equal the higher argument.
-
- ' BASIC source file - calls C function returning no value
- '
- ' DECLARE Maxparam as subprogram, since there is no return value
- ' CDECL keyword causes Maxparam call to be made with C
- ' conventions. Integer parameters passed by near reference
- ' (BASIC default).
-
- DECLARE SUB Maxparam CDECL (A AS INTEGER, B AS INTEGER)
- '
- X% = 5
- Y% = 7
- PRINT USING "X% = ## Y% = ##";X% ;Y% ' X% and Y% before call
- CALL Maxparam(X%, Y%) ' Call C function
- PRINT USING "X% = ## Y% = ##";X% ;Y% ' X% and Y% after call END
-
- /* C source file */
- /* Compile in MEDIUM or LARGE memory model */
- /* Maxparam declared VOID because no return value */
- void maxparam(p1, p2)
- int near *p1; /* Integer params received by near ref. */
- int near *p2; /* NEAR keyword not needed in MEDIUM model. */
- {
- if (*p1 > *p2)
- *p2 = *p1;
- else
- *p1 = *p2;
- }
-
- You should keep the following programming considerations in mind when
- calling C from BASIC with no return value:
-
- nNaming conventions
-
- The CDECL keyword causes Maxparam to be called with the C naming
- convention (as _maxparam).
-
- ■ Calling conventions
-
- ■ The CDECL keyword causes Maxparam to be called with the C calling
- convention, which pushes parameters in the reverse order to the
- way they appear in the source code.
-
- ■ Parameter-passing methods
-
- ■ Since the C function maxparam may alter the value of either
- parameter, both parameters must be passed by reference. In this
- case, near reference was chosen; this method is the default for
- BASIC (so neither BYVAL nor SEG is used) and is specified in C
- by using near pointers.
-
-
- Far reference could have been specified by applying SEG to each argument in
- the DECLARE statement. In that case, the C parameter declarations would use
- far pointers.
-
-
- Calling C from BASIC with a Function Call
-
- The following example demonstrates a BASIC main module calling a C function,
- fact. This function returns the factorial of an integer value.
-
- ' BASIC source file - calls C function with return value
- '
- ' DECLARE Fact as function returning integer (%)
- ' CDECL keyword causes Fact% call to be made with
- ' C conventions. Integer parameter passed by value.
-
- DECLARE FUNCTION Fact% CDECL (BYVAL N AS INTEGER)
- '
- X% = 3
- Y% = 4
- PRINT USING "The factorial of X% is ####"; Fact%(X%)
- PRINT USING "The factorial of Y% is ####"; Fact%(Y%)
- PRINT USING "The factorial of X%+Y% is ####"; Fact%(X%+Y%)
- END
-
- /* C source file */
- /* Compile in MEDIUM or LARGE model */
- /* Factorial function, returning integer */
- int fact(n)
- int n; /* Integer passed by value, the C default */
- {
- int result = 1;
- while (n > 0)
- result *= n--; /* Parameter n modified here */
- return(result);
- }
-
- You should keep the following programming considerations in mind when
- calling C from BASIC with a function call:
-
- ■ Naming conventions
-
- The CDECL keyword causes Fact to be called with the C naming
- convention (as _fact).
-
- n
-
- Calling conventions
-
- The CDECL keyword causes Fact to be called with the C calling
- convention, which pushes parameters in reverse order.
-
-
-
- ■ Parameter-passing methods
-
- The preceding C function should receive the parameter by value.
- Otherwise the function will corrupt the parameter's value in the
- calling module. True passing by value is achieved in BASIC only by
- applying BYVAL to the parameter (in the DECLARE statement in this
- example); in C, passing by value is the default (except for arrays).
-
-
-
- BASIC Calls to FORTRAN
-
- This section applies the steps previously outlined to two example programs.
- An analysis of programming considerations follows each example.
-
-
- Calling FORTRAN from BASIC -- Subroutine Call
-
- The following example demonstrates a BASIC main module calling a FORTRAN
- subroutine, MAXPARAM. The subroutine returns no value, but adjusts the lower
- of two arguments to equal the higher argument.
-
- ' BASIC source file - calls FORTRAN subroutine
- '
- DECLARE SUB Maxparam ALIAS "MAXPAR" (A AS INTEGER, B AS INTEGER)
- '
- ' DECLARE as subprogram, since there is no return value
- ' ALIAS used because some FORTRAN versions recognize only the
- ' first 6 characters
- ' Integer parameters passed by near reference (BASIC default).
- '
- X% = 5
- Y% = 7
- PRINT USING "X% = ## Y% = ##";X% ;Y% ' X% and Y% before call.
- CALL Maxparam(X%, Y%) ' Call FORTRAN function
- PRINT USING "X% = ## Y% = ##";X% ;Y% ' X% and Y% after call.
- END
-
- C FORTRAN source file, subroutine MAXPARAM
- C
- SUBROUTINE MAXPARAM (I, J)
- INTEGER*2 I NEAR
- INTEGER*2 J NEAR
- C
- C I and J received by near reference, because of NEAR attribute
- C
- IF (I .GT. J) THEN
- J = I
- ELSE
- I = J
- ENDIF
- END
-
- ■ Naming conventions
-
- ■ By default, BASIC places all eight characters of Maxparam into the
- object file, yet some versions of FORTRAN place only the first six.
- This potential conflict is resolved with the ALIAS feature: both
- modules place MAXPAR into the object file.
-
- ■ Calling conventions
-
- ■ BASIC and FORTRAN use the same convention for calling.
-
- ■ Parameter-passing methods
-
- ■ Since the subprogram Maxparam may alter the value of either parameter,
- both arguments must be passed by reference. In this case, near
- reference was chosen; this method is the default for BASIC (so neither
- BYVAL nor SEG is used) and is specified in FORTRAN by applying the
- NEAR attribute to each of the parameter declarations.
-
-
- Far reference could have been specified by applying SEG to each argument in
- the DECLARE statement. In that case, the NEAR attribute would be omitted
- from the FORTRAN code.
-
-
- Calling FORTRAN from BASIC -- Function Call
-
- The following example demonstrates a BASIC main module calling a FORTRAN
- function, FACT. This function returns the factorial of an integer value.
-
- ' BASIC source file - calls FORTRAN function
- '
- DECLARE FUNCTION Fact% (BYVAL N AS INTEGER)
- '
- ' DECLARE as function returning integer(%).
- ' Integer parameter passed by value.
- '
- X% = 3
- Y% = 4
- PRINT USING "The factorial of X% is ####"; Fact%(X%)
- PRINT USING "The factorial of Y% is ####"; Fact%(Y%)
- PRINT USING "The factorial of X%+Y% is ####"; Fact%(X%+Y%)
- END
-
- C FORTRAN source file - factorial function
- C
-
- FUNCTION FACT (N)
- INTEGER*2 I
- INTEGER*2 TEMP
- TEMP = 1
- DO 100 I = 1, N
- TEMP = TEMP * I
- 100 CONTINUE
- FACT = TEMP
- RETURN
- END
- ■ Naming conventions
-
- ■ There are no conflicts with naming conventions because the function
- name, FACT, does not exceed the number of characters recognized by any
- version of FORTRAN. The type declaration character (%) is not placed
- in the object code.
-
- ■ Calling conventions
-
- ■ BASIC and FORTRAN use the same convention for calling.
-
- ■ Parameter-passing methods
-
- ■ When a parameter is passed that should not be changed, it is generally
- safest to pass the parameter by value. True passing by value is
- specified in BASIC by applying BYVAL to an argument in the DECLARE
- statement; in FORTRAN, the VALUE attribute in a parameter declaration
- specifies that the routine will receive a value rather than an
- address.
-
-
-
- BASIC Calls to Pascal
-
- This section applies the steps outlined previously to two example programs.
- An analysis of programming considerations follows each example.
-
-
- Calling Pascal from BASIC -- Procedure Call
-
- The following example demonstrates a BASIC main module calling a Pascal
- procedure, Maxparam. Maxparam returns no value, but adjusts the lower of two
- arguments to equal the higher argument.
-
- ' BASIC source file - calls Pascal procedure
- '
- ' DECLARE as subprogram, since there is no return value.
- ' Integer parameters passed by near reference (BASIC default).
- '
- DECLARE SUB Maxparam (A AS INTEGER, B AS INTEGER)
- X% = 5
- Y% = 7
- PRINT USING "X% = ## Y% = ##";X% ;Y% ' X% and Y% before call.
- CALL Maxparam(X%, Y%) ' Call Pascal function.
- PRINT USING "X% = ## Y% = ##";X% ;Y% ' X% and Y% after call.
- END
-
- { Pascal source code - Maxparam procedure. }
-
- module Psub;
- procedure Maxparam(var a:integer; var b:integer);
-
- { Two integer parameters are received by near reference. }
- { Near reference is specified with the VAR keyword. }
-
- begin
- if a > b then
- b := a
- else
- a := b
- end;
- end.
- ■ Naming conventions
-
- ■ Note that name length is not an issue because Maxparam does not exceed
- eight characters.
-
- ■ Calling conventions
-
- ■ BASIC and Pascal use the same calling convention.
-
- ■ Parameter-passing methods
-
- ■ Since the procedure Maxparam may alter the value of either parameter,
- both parameters must be passed by reference. In this case, near
- reference was chosen; this method is the default for BASIC (so neither
- BYVAL nor SEG is used) and is specified in Pascal by declaring
- parameters as VAR.
-
-
- Far reference could have been specified by applying SEG to each argument in
- the DECLARE statement. In that case, the VARS keyword would be required
- instead of VAR.
-
-
- Calling Pascal from BASIC -- Function Call
-
- The following example demonstrates a BASIC main module calling a Pascal
- function, Fact. This function returns the factorial of an integer value.
-
- ' BASIC source file - calls Pascal function
- '
- ' DECLARE as function returning integer (%).
- ' Integer parameter passed by value.
- '
- DECLARE FUNCTION Fact% (BYVAL N AS INTEGER)
- '
-
- X% = 3
- Y% = 4
- PRINT USING "The factorial of X% is ####"; Fact%(X%)
- PRINT USING "The factorial of Y% is ####"; Fact%(Y%)
- PRINT USING "The factorial of X%+Y% is ####"; Fact%(X%+Y%)
- END
-
- { Pascal source code - factorial function. }
-
- module Pfun;
- function Fact (n : integer) : integer;
-
- { Integer parameters received by value, the Pascal default. }
-
- begin
- Temp := 1;
- while n > 0 do
- begin
- Temp := Temp * n;
- n := n - 1; { Parameter n altered here. }
- end;
- end;
- Fact := Temp;
- end.
- ■ Naming conventions
-
- ■ Note that name length is not an issue because fact does not exceed
- eight characters.
-
- ■ Calling conventions
-
- ■ BASIC and Pascal use the same calling convention.
-
- ■ Parameter-passing methods
-
- ■ The Pascal function in the preceding example should receive a
- parameter by value. Otherwise the function will corrupt the
- parameter's value in the calling module. True passing by value is
- achieved in BASIC only by applying BYVAL to the parameter; in Pascal,
- passing by value is the default.
-
-
-
- Restrictions on Calls from BASIC
-
- BASIC has a much more complex environment and initialization procedure than
- the other high-level languages. Interlanguage calling between BASIC and
- other languages is possible only because BASIC intercepts a number of
- library function calls from the other language and handles them in its own
- way. In other words, BASIC creates a host environment in which the C, Pascal
- and FORTRAN routines can function.
-
-
- However, BASIC is limited in its ability to handle some C function calls.
- Also FORTRAN and Pascal sometimes perform automatic memory allocation that
- can cause errors that are hard to diagnose.The following sections consider
- three kinds of limitations: C memory-allocation functions, which may require
- a special declaration, implicit memory allocation performed by Pascal and
- FORTRAN, and a few specific C-library functions, which cannot be called at
- all.
-
-
- Memory Allocation
-
- If your C module is medium model and allocates memory dynamically with
- malloc(), or if you execute explicit calls to nmalloc() with any memory
- model, then you need to include the following lines in your BASIC source
- code before you call C:
-
- DIM mallocbuf%(0 TO 2047)
- COMMON SHARED /NMALLOC/ mallocbuf%()
-
- The array can have any name; only the size of the array is significant.
- However, the name of the common block must be NMALLOC. In QBX environments,
- you need to put this declaration in a module that you incorporate into a
- Quick library. See Chapter 19, "Creating and Using Quick Libraries," for
- more information on Quick libraries.
-
- The preceding example has the effect of reserving 4K of space ( 2 bytes *
- 2048) in the common block NMALLOC. When BASIC intercepts C malloc calls,
- BASIC allocates space out of this common block.
-
- Warning
-
- This common block is also used by FORTRAN and Pascal routines that perform
- dynamic memory allocation in connection with things like opening files or
- declaring global strings. Depending on the circumstances, however, the 4K of
- space may not be sufficient, and the error Insufficient heap space may be
- generated. If this happens, increase the amount of space in NMALLOC using
- increments of 512 or 1024.
-
- When you make far-memory requests in mixed-language programs, you may find
- it useful to call the BASIC intrinsic function SETMEM first. This function
- can be used to reduce the amount of memory BASIC is using, thus freeing
- memory for far allocations. (An example of this use of SETMEM appears in
- the BASIC Language Reference and in the online Help for SETMEM.)
-
- Important
-
- When you call the BASIC CLEAR statement, all space allocated with near
- malloc calls is lost. If you use CLEAR at all, use it only before any calls
- to malloc.
-
-
- Incompatible Functions
-
- The following C functions are incompatible with BASIC and should be avoided:
-
- ■ All forms of spawn() and exec()
-
- ■ system()
-
- ■ getenv()
-
- ■ putenv()
-
-
- Calling these functions results in the BASIC error message Advanced feature
- unavailable.
-
- In addition, you should not link with the xVARSTK.OBJ modules (where x is
- a memory model) which C provides to allocate memory from the stack.
-
- Note
-
- The global C run-time variables environ and _pgmptr are defined as NULL. All
- functionality of these variables, as well as the functions noted previously,
- can be emulated using BASIC statements and intrinsic functions.
-
-
- Allocating String Space
-
- Other-language routines can allocate dynamic string space by calling the
- GetSpace$ FUNCTION procedure:
-
- FUNCTION GetSpace$ (x) STATIC
- GetSpace$ = STRING$(x, CHR$(0))
- END FUNCTION
-
- The GetSpace$ procedure returns a near pointer to a string descriptor that
- points to x bytes of string space. Because this space is managed by BASIC,
- it can move any time BASIC language code is executed. Therefore, the space
- must be accessed through the string descriptor, and the string descriptor
- must not be modified by other-language code. To release this space, pass the
- near pointer to the string descriptor to the FreeSpace SUB procedure:
-
- SUB FreeSpace(a$) STATIC
- A$ = ""
- END SUB
-
- Note that the preceding procedures deal with pointers to string descriptors,
- not string data. String descriptors are always in DGROUP, and therefore
- always accessed through near pointers. Although variable-length string data
- is stored in far memory within the QBX environment (or when you compile a
- program with /Fs) the string descriptors are still in DGROUP. For more
- information on far strings and mixed-language programming, see Chapters 11,
- "Advanced String Storage," and 13, "Mixed-Language Programming with Far
- Strings."
-
-
- To return a string that is the result of a routine in another language, you
- can return a near pointer to a static string descriptor that is declared in
- the other-language code. A better method is to use the mixed-language string
- routines described in Chapter 13, "Mixed Language Programming with Far
- Strings." Although they are described in relation to far strings (for which
- they are mandatory), they are just as useful for near strings and should be
- used with new code. Because BASIC moves such strings around, the static
- string descriptor allocated by the other-language code becomes invalid after
- the function returns (or makes a call to any BASIC procedure).
-
- Calling BASIC from other languages is described in the section "Calls to
- BASIC from Other Languages" later in this chapter. Constraints on
- dynamic-memory allocation in other-language routines (see the sections
- "Restrictions on Calls from BASIC" and "Memory Allocation" earlier in this
- chapter) still apply despite the use of a function like GetSpace$.
-
-
- Performing I/O on BASIC Files
-
- Other-language routines can perform input and output on files opened by the
- BASIC OPEN statement by calling BASIC procedures. The following example is
- a BASIC SUB that can be called to print an integer to a BASIC file opened
- as Fileno%:
-
- SUB DoPrint(Fileno%, X%) STATIC
- PRINT #Fileno%, X%
- END SUB
-
- For constraints on direct file I/O in other-language routines, see the
- sections "Restrictions on Calls from BASIC" and "Memory Allocation" earlier
- in this chapter.
-
-
- Events and Errors
-
- BASIC events including COM, key, TIMER, and PLAY may occur during
- execution of other-language code. The other-language code can allow such
- events to be handled by periodically calling a BASIC routine (this routine
- could be empty), or as follows:
-
- ■ When compiling with the BC command, compile the BASIC procedure with
- the /V or /W option selected .
-
- ■ Within the QBX environment, simply specify event-handling syntax in
- the procedure itself, then use the Run menu's Make EXE File or Make
- Library command to create an object file, or to incorporate the
- procedure into a Quick library.
-
-
- The following BASIC SUB procedure lets you create BASIC errors:
-
- SUB MakeError(X%) STATIC
- ERROR X%
- END SUB
-
-
- When your other-language routine passes the error number to this procedure,
- the ERROR statement is executed and BASIC recovers the stack back to the
- previous call to non-BASIC code. The BASIC statement containing the error
- ERROR X% is the statement that RESUME would re-execute. RESUME NEXT would
- re-execute at the following statement. See Chapter 8, "Error Handling" for
- more information on using new Microsoft BASIC error-handling features.
-
- Calling BASIC from other languages is described in the following section
- "Calls to BASIC from Other Languages."
-
-
- Calls to BASIC from Other Languages
-
- Microsoft C, FORTRAN, and Pascal can call routines written in Microsoft
- BASIC, if the main program is in BASIC. The following sections describe the
- necessary syntax for calling BASIC from other languages. Only simple
- parameter lists are used.
-
- See the section "Handling Data in Mixed-Language Programming" later in this
- chapter for information on how to pass particular kinds of data.
-
-
- Other Language Interfaces to BASIC
-
- Because they share similar calling conventions, calling BASIC procedures
- from Pascal and FORTRAN is straightforward. With FORTRAN, you need only
- write an interface for each BASIC procedure that will be called, then call
- them as needed. When calling BASIC from Pascal, declare the BASIC routine
- with an extern procedure or function declaration (whichever is
- appropriate).
-
- Remember that, although BASIC can pass data in several ways, it can only
- receive data that is passed by near reference. Therefore, data passed to any
- BASIC procedure must be passed as a near pointer. If your version of FORTRAN
- recognizes only the first 6 characters of a name, you should use BASIC's
- ALIAS feature if the routine you are calling has a name longer than FORTRAN
- can recognize.
-
- Observe the following rules when you call BASIC from C, FORTRAN or Pascal:
-
- Start in a BASIC main module. You need to use the DECLARE statement
- to provide an interface to the other-language module.
-
- If the other language is C or Pascal , declare the BASIC routine as
- extern, and include type information for parameters. Use either the
- fortran or pascal keyword in the C declaration of the BASIC procedure
- to override the default C calling convention. If the other language is
- FORTRAN, use the INTERFACE statement to create the interfaces to
- BASIC routines.
-
- Make sure that all data is passed as near pointers. BASIC can pass
- data in a variety of ways, but it is unable to receive data in any
- form other than near reference.
-
- With near pointers, the program assumes that the data is in the
- default data segment (DGROUP). If you want to pass data that is not
- in the default data segment, then first copy the data to a variable
- that is in the default data segment (this is only a consideration
- with large-model C programs).
-
- Compile the C language modules in medium or large memory models.
-
-
- Note
-
- All other-language-to-BASIC calling for programs within the QBX environment
- must be confined within a Quick library. In other words, a C function can
- call a BASIC procedure within the Quick library, but it cannot call a
- procedure defined within the QBX environment itself.
-
-
- Calling BASIC from C
-
- The C interface to BASIC is more complicated than the FORTRAN or Pascal
- interfaces. It uses standard C prototypes, with the fortran or pascal
- keyword. Using either of these keywords causes the routine to be called with
- the BASIC/FORTRAN/Pascal naming and calling conventions. The following steps
- are recommended for executing a mixed-language call from C:
-
- 1. Write a prototype for each mixed-language routine called. The
- prototype should declare the routine extern for the purpose of
- program documentation.
-
- Instead of using the fortran or pascal keyword, you can simply
- compile with the Pascal calling convention option (/Gc). The /Gc
- option causes all functions in the module to use the
- BASIC/FORTRAN/Pascal naming and calling conventions (except where you
- apply the cdecl keyword).
-
- 2. Pass near pointers to variables when calling a BASIC routine. You can
- obtain a pointer to a variable with the address-of ( &) operator.
-
- In C, array names are always translated into pointers to the first
- element of the array; hence, arrays are always passed by reference.
- However, although BASIC arrays are referenced through near pointers,
- the pointer points to an array descriptor, not the array data itself.
-
-
- Therefore, other-language arrays cannot be passed directly to BASIC.
- The prototype you declare for your function ensures that you are
- passing the correct length address (that is, near or far). In BASIC
- the address must be near.
-
- 3. Issue a function call in your program as though you were calling a C
- function.
-
- 4. Always compile the C module in either medium, large, or huge model, or
- use the far keyword in your function prototype. This ensures that a
- far (intersegment) call is made to the routine.
-
-
-
- There are two rules of syntax that apply when you use the fortran or
- pascal keyword:
-
- The fortran and pascal keywords modify only the item immediately to
- their right.
-
- The near and far keywords can be used with the fortran and pascal
- keywords in prototypes. The sequences fortran far and far fortran
- are equivalent.
-
-
- The keywords pascal and fortran have the same effect on the program; using
- one or the other makes no difference except for internal program
- documentation. Use fortran to declare a FORTRAN routine, pascal to declare
- a Pascal routine, and either keyword to declare a BASIC routine.
-
- The following example declares func to be a BASIC, Pascal, or FORTRAN
- function taking two short parameters and returning a short value.
-
- extern short pascal far func( short near * sarg1, short near * sarg2 );
-
- The following example declares func to be pointer to a BASIC, Pascal, or
- FORTRAN procedure that takes a long parameter and returns no value. The
- keyword void is appropriate when the called routine is a BASIC sub
- procedure, Pascal procedure, or FORTRAN subroutine, since it indicates that
- the function returns no value.
-
- extern void ( fortran far * func )( long near * larg );
-
- The following example declares func to be a far BASIC FUNCTION procedure,
- Pascal function, or FORTRAN function. The routine receives a double
- parameter by reference (because it expects a pointer to a double) and
- returns a int value.
-
- int far pascal func( near double * darg );
-
- The following example is equivalent to the preceding example ( pascal far is
- equivalent to far pascal).
-
- int pascal far func( near double * darg );
-
- When you call a BASIC procedure, you must use the FORTRAN/Pascal conventions
- to make the call. (However, if your C function calls FORTRAN or Pascal, you
- have a choice. You can make C adopt the conventions described in the
- previous section, or you can make the FORTRAN or Pascal routine adopt the C
- conventions.) The call must be a far call. You can insure this either by
- compiling in medium (or larger) model, or by using the far keyword, as
- shown in the preceding example.
-
-
- Example
-
- The following example demonstrates a BASIC program that calls a C function.
- The C function then calls a BASIC function that returns twice the number
- passed it and a BASIC subprogram that prints two numbers.
-
- ' BASIC source
- DEFINT A-Z
- DECLARE SUB Cprog CDECL()
- CALL Cprog
- END
- ' This is the BASIC FUNCTION called in Cprog().
- FUNCTION Dbl(N) STATIC
- Dbl = N*2
- END FUNCTION
- ' This is the BASIC SUB called in Cprog().
- SUB Printnum(A,B) STATIC
- PRINT "The first number is ";A
- PRINT "The second number is ";B
- END SUB
-
-
-
- /* C source; compile in medium or large model to insure far calls*/
- extern int fortran dbl(int near *);
- extern void fortran printnum(int near *, int near *);
- void cprog()
- {
- int near a = 5; /* NEAR guarantees that the data */
- int near b = 6; /* will be placed in default */
- /* data segment (DGROUP) */
- printf("Two times 5 is %d\n", dbl(&a));
- printnum(&a, &b);
- }
-
- In the preceding example, note that the addresses of a and b are passed,
- since BASIC expects to receive addresses for parameters. Also note that the
- keyword near is used to declare each pointer in the C function declaration
- of printnum; this keyword would be unnecessary if it was known that the C
- module was compiled in medium model rather than large.
-
- Calling and naming conventions are resolved by the CDECL keyword in BASIC's
- declaration of Cprog, and by fortran in C's declaration of dbl and
- printnum.
-
-
- Calling BASIC from FORTRAN
-
- The following example illustrates the process of calling a BASIC routine
- from FORTRAN. First, a call must be made from BASIC to FORTRAN, then the
- FORTRAN routine can call BASIC routines.
-
- Example
-
- In this example the FORTRAN subroutine calls a BASIC function that returns
- twice the number passed to it, then calls a BASIC sub procedure that prints
- two numbers.
-
- ' BASIC source
- defint a-z
- declare SUB Fprog ()
- call fprog
- END
- function dbl (N) static
- Dbl = N * 2
- end sub
-
-
- sub printnum(A,B)
- print "the first number is " ; A
- print "the second number is " ; B
- end sub
-
- cfortran subroutine
- CCalls a BASIC function that receives one integer
- Cand a BASIC sub that takes two integers.
- C
- interface to integer * 2 function dbl (n)
- integer * 2 n [neaR]
- end
- C
- calias attribute may necessary since BASIC recognizes more
- Cthan six characters of the name "Printnum" (but FORTRAN
- Cversion may not).
- C
- interface to subroutine printn [alias: 'Printn'] (N1, N2)
- integer * 2 N1 [neaR]
- integer * 2 N2 [neaR]
- end
- c
- cparameters must be declared NEAR in the parameter
- cdeclarations; BASIC receives only 2-byte pointers.
- c
- subroutine fprog
- integer * 2 dbl
- integer * 2 A, B
- a = 5
- b = 6
- write (*,*) 'Two times 5 is ' , dbl(a)
- call printn(a,b)
- end
-
- In the preceding example, note that the near attribute is used in the
- fortran routines, so that near addresses will be passed to basic instead of
- far addresses.
-
- Calling BASIC from Pascal
-
- The following example illustrates the process of calling a BASIC routine
- from Pascal. First, a call must be made from BASIC to Pascal, then the
- Pascal routine can call BASIC routines.
-
- Example
-
- In this example the Pascal procedure calls a BASIC function that returns
- twice the number passed to it, then calls a BASIC sub procedure that prints
- two numbers.
-
- ' BASIC source
- defint a-z
- declare SUB Fprog ()
- call fprog
- END
- function dbl (N) static
- Dbl = N * 2
- end sub
-
- sub printnum(A,B)
- print "the first number is " ; A
- print "the second number is " ; B
- end sub
-
- { *Pascal procedure *}
- { *Calls a BASIC function and a BASIC sub*}
-
- module pproc ;
- procedure pprog() ;
-
-
- function Dbl (var n:integer) : integer ; extern ;
- procedure Printnum (var n1,n2:integer) ; extern ;
- var a,b:integer ;
- begin
- a := 5
- b := 6 ;
- writeln ('Two times 5 is ' , Dbl (a) )
- Printnum(a,b)
- end
- end.
-
- Note that in the preceding example, every argument in the external
- declarations must be declared var, since BASIC can only receive near
- pointers as parameters.
-
-
- Handling Data in Mixed-Language Programming
-
- This section discusses naming and calling conventions in a mixed-language
- program. It also describes how various languages represent strings,
- numerical data, arrays, and logical data.
-
-
- Default Naming and Calling Conventions
-
- Each language has its own default naming and calling conventions, listed in
- Table 12.3
-
-
- BASIC Conventions
-
- When you call BASIC routines, you must pass all arguments by near reference
- (near pointer).
-
- You can modify the conventions observed by BASIC routines that call C
- functions by using the DECLARE, BYVAL, SEG, and CALLS keywords. For more
- information about the use of these keywords, see the BASIC Language
- Reference.
-
-
- FORTRAN Conventions
-
- You can modify the conventions observed by FORTRAN routines that call BASIC
- by using the INTERFACE keyword. For more information about the use of
- mixed-language keywords, see the Microsoft FORTRAN Reference.
-
-
- Pascal Conventions
-
- You can modify the conventions observed by Pascal routines that call BASIC
- by using the VAR, CONST, ADR, VARS, CONSTS, and ADS keywords. For more
- information about the use of these keywords, see the Microsoft Pascal
- Compiler User's Guide.
-
-
- Passing Data by Reference or Value
-
- The preceding sections introduced the general concepts of passing by
- reference and passing by value. They also noted that, by default, BASIC
- passes by reference, and C passes by value.
-
- This section further describes language features that override the default.
- For example, using the BYVAL keyword in a DECLARE statement causes BASIC
- to pass a given parameter by value rather than by reference.
-
- The next section summarizes parameter-passing methods for BASIC, discussing
- how to pass arguments by value, by near reference, and by far reference.
- Then, the same issues are discussed for C. To write a successful
- mixed-language interface, you must consider how each parameter is passed by
- the calling routine, and how each is received by the called routine.
-
-
- BASIC Arguments
-
- The default for BASIC is to pass all arguments by near reference.
-
- Note
-
- Every BASIC SUB or FUNCTION procedure always receives data by near
- reference. The rest of this section summarizes how BASIC passes arguments.
-
-
- Passing BASIC Arguments by Value
-
- An argument is passed by value when the called routine is first declared
- with a DECLARE statement in which the BYVAL keyword is applied to the
- argument. Arrays and user-defined types cannot be passed by value in BASIC.
-
-
- Passing BASIC Arguments by Near Reference
-
- The BASIC default is to pass by near reference. Use of SEG, SSEG, BYVAL,
- or CALLS changes this default. Note that when you pass an array or a
- string, you are passing the descriptor (which is always in DGROUP), so even
- if the data is stored in far memory, the descriptor is passed by near
- reference.
-
-
- Passing BASIC Arguments by Far Reference
-
- Using SEG to modify a parameter in a preceding DECLARE statement causes
- BASIC to pass that parameter by far reference. When CALLS is used to invoke
- a routine, BASIC passes each argument in a call by far reference.
-
- Examples
-
- The following example passes the first argument, A%, by value, the second
- argument, B%, by near reference, and the third argument, C%, by far
- reference:
-
- DECLARE SUB Test(BYVAL A%, B%, SEG C%)
- CALL Test(X%, Y%, Z%)
-
- The following example passes each argument by far reference:
-
- CALLS Test2(X%, Y%, Z%)
-
- C Arguments
-
- The default for C is to pass all arrays by reference (near or far, depending
- on the memory model) and all other data types by value. C uses far data
- pointers for compact, large, and huge models, and near data pointers for
- small and medium models.
-
-
- Passing C Arguments by Value
-
- The C default is to pass all nonarrays (which includes all data types other
- than those explicitly declared as arrays) by value.
-
-
- Passing C Arguments by Reference (Near or Far)
-
- In C, passing a pointer to an data item is equivalent to passing the data
- item by reference.
-
- After control passes to the called function, each reference to the
- parameter is prefixed by an asterisk (*).
-
- Note
-
- To pass a pointer to a data item, prefix the parameter in the call statement
- with an ampersand (&). To receive a pointer to an data item, prefix the
- parameter's declaration with an asterisk (*). In the latter case, this may
- mean adding a second asterisk (*) to a parameter which already has an
- asterisk (*). For example, to receive a pointer by value, declare it as
- follows:
-
- int *ptr;
-
- To receive the same pointer to an integer by reference, declare it as
- follows:
-
- int **ptr;
-
- The default for arrays is to pass by reference.
-
-
- Effect of Memory Models on Size of Reference
-
- In C, near reference is the default for passing pointers in small and medium
- models. Far reference is the default in the compact, large, and huge models.
-
- Near pointers can be specified with the near keyword, which overrides the
- default pointer size. However, if you are going to override the default
- pointer size of a parameter, then you must explicitly declare the parameter
- type in function prototypes as well as function definitions.
-
- Far pointers can be specified with the far keyword, which overrides the
- default pointer size.
-
-
- FORTRAN Arguments
-
- The FORTRAN default is to pass and receive all arguments by reference. The
- size of the address passed depends on the memory model.
-
-
- Passing FORTRAN Arguments by Value
-
- A parameter is passed by value when declared with the value attribute. This
- declaration can occur either in a fortran interface statement (which
- determines how to pass a parameter) or in a function or subroutine
- declaration (which determines how to receive a parameter).
-
- A function or subroutine declared with the Pascal or C attribute will pass
- by value all parameters declared in its parameter list (except for
- parameters declared with the reference attribute). This change in default
- passing method applies to function and subroutine definitions, as well as to
- an interface statement.
-
-
- Passing fortran Arguments by Reference (Near or Far)
-
- Passing by reference is the default, however, if either the c or Pascal
- attribute is applied to a function or subroutine declaration, then you need
- to apply the reference attribute to any parameter of the routine that you
- want passed by reference.
-
-
- Use of Memory Models and FORTRAN Reference Parameters
-
- Near reference is the default for medium-model FORTRAN programs; far
- reference is the default for large- and huge-model programs.
-
- Note
-
- Versions of fortran prior to 4.0 always compile in large memory model. You
- can apply the near attribute to reference parameters in order to specify
- near reference.You can apply the far attribute to reference parameters in
- order to specify far reference. These keywords enable you to override the
- default. They have no effect when they specify the same method as the
- default. You may need to apply more than one attribute to a given parameter.
- If so, enclose both attributes in brackets, separated by commas:
-
- REAL*4 X [ near, reference]
-
- Pascal Arguments
-
- The Pascal default is to pass all arguments by value.
-
-
- Passing Pascal Arguments by Near Reference
-
- Parameters are passed by near reference when declared as var or const.
-
- Parameters are also passed by near reference when the adr of a variable, or
- a pointer to a variable, is passed by value. In other words, the address of
- the variable is first determined. Then, this address is passed by value.
- (This is essentially the same method employed in C.)
-
-
- Passing Pascal Arguments by Far Reference
-
- Parameters are passed by far reference when declared as vars or consts.
- Parameters are also passed by far reference when the ads of a variable is
- passed by value.
-
-
- Numeric and String Data
-
- This section discusses passing and receiving different kinds of data.
- Discussion includes the differences in string format and methods of passing
- strings between BASIC and other languages.
-
-
- Integer and Real Numbers
-
- Integer and real numbers are usually the simplest kinds of data to pass
- between languages. However, the type of numeric data is named differently in
- each language; furthermore, not all data types are available in every
- language, and another type may have to be substituted in some cases.
-
- Table 12.4 shows equivalent data types in BASIC, C, FORTRAN, and Pascal.
-
-
- Warning
-
- As noted in Table 12.4, C sometimes performs automatic data conversions
- which other languages do not perform. You can prevent C from performing such
- conversions by declaring a variable as the only member of a structure and
- then passing this structure. For example, you can pass a variable x of type
- float by first declaring the structure as follows:
-
- struct {
- float x;
- } x_struct;
-
- If you pass a variable of type char or float by value and do not take this
- precaution, then the C conversion may cause the program to fail.
-
-
- Strings
-
- Strings are stored in a variety of formats. Therefore, some transformation
- is frequently required to pass strings between languages. This section
- presents the string format(s) used in each language, and then describes
- methods for passing strings within specific combinations of languages.
- Microsoft BASIC includes several special string manipulation routines
- designed to simplify passing strings between modules written in different
- languages. They are described in Chapter 13, "Mixed-Language Programming
- with Far Strings."
-
-
- BASIC String Format
-
- Near strings (strings generated by the command-line compiler when you do not
- specify the /Fs option) are stored in BASIC as 4-byte string descriptors, as
- shown in Figure 12.7.
-
- The first field of the string descriptor contains a 2-byte integer
- indicating the length of the actual string text. The second field contains
- the near address of this text. This address is an offset into the default
- data area (DGROUP).
-
-
- Within the QBX environment the near-string model is never used. Instead, QBX
- (and the command-line compiler when using the /Fs option) stores the data of
- variable-length strings in far memory. Using far strings is described in
- detail in Chapters 11, "Advanced String Storage," and 13, "Mixed-Language
- Programming with Far Strings." Briefly, however, the difference is that with
- far strings, the address of the string data cannot fit into the 2 bytes
- normally used for the string-data address in a near-string descriptor.
- Instead, the DGROUP string descriptor for a far string contains "handles"
- (pointers representing two levels of indirection) to the information
- necessary to retrieve the string. Although the size and location of both
- types of string descriptors is the same, the information in a descriptor of
- a far string is totally different from the information in a string
- descriptor for a near string. BASIC retains the convention of having all
- descriptors in DGROUP, and at the same time increase the amount of available
- string space.
-
- Note
-
- You cannot mix BASIC modules compiled for near strings with modules compiled
- for far strings. You can use BASIC modules compiled with the /Fs option in a
- mixed-language program if the other-language routines never try to
- manipulate BASIC strings by mimicking a BASIC string descriptor. If your
- other-language routines need to manipulate BASIC strings, you need to
- rewrite the source code since old methods of mimicking near-string
- descriptors in other languages will fail when the BASIC module uses far
- strings. You can guarantee portability among all modules by always using the
- Microsoft BASIC mixed-language string routines, and always assuming modules
- will be compiled with the /Fs option. If mixed-language source code is
- written assuming far strings, and for some reason you want to recompile the
- modules using the near string model, they will still work properly. In
- near-string code, addresses are assigned by BASIC's string-space management
- routines. These management routines need to be available to reassign this
- address whenever the length of the string changes or memory is compacted,
- yet these management routines are only available to BASIC. Therefore, other
- languages should not alter the length, or the address, of a BASIC string.
- See Chapter 13, "Mixed-Language Programming with Far Strings," for more
- information on far strings and mixed-language programming.
-
-
- C String Format
-
- C stores strings as simple arrays of bytes and uses a terminating null
- character (numerical 0, ASCII NUL) as a delimiter. For example, consider the
- string declared as follows:
-
- char str[] = "String of text"
-
- The string is stored in 15 bytes of memory as shown in Figure 12.8:
-
- Since str is an array like any other, it is passed by reference, just as
- other C arrays are.
-
-
- FORTRAN String Format
-
- FORTRAN stores strings as a series of bytes at a fixed location in memory.
- There is no delimiter at the end of the string. Consider the string declared
- as follows:
-
- STR = "String of text"
-
- The string is stored in memory as shown in Figure 12.9:
-
- FORTRAN passes strings by reference, as it does all other data.
-
- Note
-
- Be careful using FORTRAN's variable length strings in mixed-language
- programming. The temporary variable used to communicate string length is not
- accessible to other languages. When passing such a string to another
- language, you need to design a method by which the target routine can find
- the end of the string. For example, if the target routine were in C, you
- could append an ASCII NUL to terminate the string before passing it.
-
-
- Pascal String Format
-
- Pascal has two types of strings, each of which uses a different format: a
- fixed-length type STRING and the variable-length type LSTRING. Fixed
- length string format is exactly like the FORTRAN format described in the
- preceding section. The variable-length strings are stored with the length of
- the string in the first byte, followed immediately by the string data
- itself. For example, consider the LSTRING declared as follows:
-
- VAR STR:LSTRING(14);
- STR := 'String of text'
-
- The string is stored in 15 bytes of memory, as shown in Figure 12.10:
-
- Passing Strings Between BASIC and Other Languages
-
- When a BASIC string (such as A$) appears in an argument list, BASIC passes
- the address of a string descriptor rather than the actual string data. The
- BASIC string descriptor is not compatible with the string formats of other
- languages. Because no other language handles strings the way BASIC does, you
- cannot pass strings between BASIC and the other languages in their native
- forms. In previous versions of BASIC you had two choices: pass the address
- of the BASIC string data to the other language or mimic the form of the
- BASIC string descriptor in the other language, then use that to access the
- string as BASIC would access one of its own strings. Either of these methods
- worked, but greatly limited how you could work with strings in each
- language. You can still use either method when working with near strings in
- BASIC, but you must use BASIC's new string manipulation routines (described
- in Chapter 13, "Mixed-Language Programming with Far Strings") when working
- with far strings. These routines also improve the reliability and
- flexibility of interlanguage manipulation of near strings, and should always
- be used for new code. Although the old methods are described in succeeding
- sections, they are not recommended for new code.
-
- Warning
-
- When you pass a string from BASIC to another language, the called routine
- should under no circumstances alter the length or address of the string.
- Other languages lack BASIC's string-space management routines. Therefore,
- altering the length of a BASIC string is liable to corrupt parts of the
- BASIC string space. Changes that do not affect length and address, however,
- are safe. If the routine that receives the string calls any BASIC routine,
- the address of the string data may change. The second field of the string
- descriptor maintained by BASIC is then updated with the new address of the
- string text.
-
-
- Passing Strings from BASIC
-
- When you compile BASIC modules without the /Fs option, both the string
- descriptor and the string data are stored in DGROUP. Within the QBX
- environment (and when you compile a program using the /Fs compiler option),
- Microsoft BASIC stores variable-length string information in far memory.
- There is still a string descriptor in DGROUP, but it contains different
- information than a descriptor for a near string. Microsoft BASIC includes
- two new functions sseg and ssegadd you can use to retrieve the segment of a
- string, and the complete far address of far string data, respectively. You
- can use SSEG in combination with SADD (or just use SSEGADD by itself) to
- obtain values for directly addressing the data of strings in far memory.
-
- Note
-
- For far strings, ssegadd performs the same role as sadd does for near
- strings. Both of these (and sseg) return values. The seg keyword is used
- differently. It is neither a statement nor a function, and is simply used to
- indicate that an address that is being passed is in fact a far (4-byte)
- address. The section "The BASIC Interface to Other Languages" earlier in
- this chapter describes using seg in interlanguage declarations and calls.
-
-
- The SADD, SSEG, SSEGADD and LEN functions extract parts of BASIC string
- descriptors. sseg returns the segment of part of the address of the data of
- a variable-length string. SADD extracts the complete address of the data of
- a string stored in DGROUP (near memory), or the offset of the string data of
- a string stored in far memory. SSEGADD returns the complete (segment and
- offset) address of a string whose data is stored in far memory. LEN
- extracts the length of any variable-length string. The results of these
- functions can then be passed to other languages.
-
- BASIC should pass the result of the SADD SSEG, or ssegadd functions by
- value. Bear in mind that the string's address, not the string itself, is
- passed by value. This amounts to passing the string itself by reference. The
- BASIC module passes the string address, and the other module receives the
- string address. The addresses returned by SADD and sseg are declared as
- type integer; the address returned by SSEGADD is declared as a long. These
- are equivalent to C's near and far pointer types, respectively.
-
- Pass LEN(A$) as you would normally pass a 2-byte integer. Use values
- returned by SADD, sseg or SSEGADD immediately because several BASIC
- operations may cause movement of strings in memory. Note that SADD cannot
- be used with fixed-length strings. See Appendix B, "Data Types, Constants,
- Variables, and Arrays," for more information.
-
-
- Passing BASIC Strings to C
-
- Before attempting to pass a BASIC string to C, you should first make the
- string conform to C-string format by appending a null byte on the end with
- an expression as follows:
-
- A$ = A$ + CHR$(0)
-
- You can use either of the following two methods for passing a near string
- from BASIC to C:
-
- Pass the string address and string length as separate arguments, using
- the SADD and LEN functions. (If you are linking to a C run-time
- library routine, this is the only workable method.) In the following
- example, SADD(A$) returns the near address of the string data. This
- address must be passed by value, since it is equivalent to a pointer
- (even though it is treated by BASIC as an integer). Passing by
- reference would attempt to pass the address of the address, rather
- than the address itself.
-
- DECLARE SUB Test CDECL(BYVAL S%, BYVAL N%)
- CALL Test(SADD(A$), LEN(A$))
-
- void Test(s, n)
- char near *s;
- int n;
- { /* body of function */
-
- }
-
- ■ C must receive a near pointer since only the near (offset) address is
- being passed by BASIC. Near pointers are the default pointer size in
- medium-model C.
-
- If the string is a near string, pass the string descriptor itself,
- with a call statement as follows:
-
- CALL Test2(A$) In this case, the C function must declare a structure
- for the parameter that has the appropriate fields (address and length) for a
- BASIC string descriptor. The C function should then expect to receive a
- pointer to a structure of this type.
-
- Note that any calls back to BASIC from within the preceding C function may
- invalidate the string-descriptor information. This method of mimicking BASIC
- string descriptors should not be used in new code. Use the BASIC
- string-manipulation routines described in Chapter 13, "Mixed-Language
- Programming with Far Strings," instead.
-
-
- Passing C Strings to BASIC
-
- When a C string appears in an argument list, C passes the address of the
- string. (A C string is just an array and so is passed by reference.) C can
- still pass near string data to BASIC in the form of a string descriptor.
- However, when the BASIC program uses far strings (/Fs option when compiling
- from command line, or by default in QBX), the special string manipulation
- routines described in Chapter 13, "Mixed-Language Programming with Far
- Strings," must be used.
-
- To use the string descriptor method (for near strings only), first allocate
- a string in C; then, create a structure that mimics to a BASIC string
- descriptor. Pass this structure by near reference, as in the following
- example:
-
- char cstr[] = "ABC";
- struct {
- int sd_len;
- char *sd_addr;
- } str_des;
- str_des.sd_len = strlen(cstr);
- str_des.sd_addr = cstr;
- bsub(&str_des);
-
-
- As noted previously, this method still works for BASIC programs that do not
- use the far strings feature of Microsoft BASIC. If you've successfully used
- this method with old code, it will still work. However, the Microsoft BASIC
- routines discussed in Chapter 13, "Mixed-Language Programming with Far
- Strings," can be used for both near and far strings, and make passing
- strings between language modules simpler and more reliable. In all cases,
- make sure that the string originates in C, not in BASIC. Strings originating
- in BASIC are subject to being moved around in memory during BASIC string
- management.
-
-
- Passing BASIC Strings to Fortran
-
- Fortran's variable-length strings are unique and cannot be a part of a
- mixed-language interface. Use SADD to pass the address of the BASIC string.
- The FORTRAN routine should declare a character variable of the same length
- (which is fixed).
-
- DECLARE SUB Test(BYVAL S%) ' or use S# if the address is far
- A$ = "abcd"
- CALL Test (SADD(A$))' or SSEGADD if string is far
- .
- .
- .
- C FORTRAN SOURCE
- C
- SUBROUTINE TEST(STRINGA)
- CHARACTER*4 STRINGA [NEAR]
-
- In the preceding example, SADD(A$) must be passed by value, since it is
- actually an address, not an integer. Note that the fortran declaration
- CHARACTER*4 STRINGA [NEAR] declares a fixed-length parameter received by
- near reference. See Chapter 13, "Mixed-Language Programming with Far
- Strings," for information on passing BASIC far strings.
-
-
- Passing Fortran Strings to BASIC
-
- FORTRAN cannot directly pass strings to BASIC because BASIC expects to
- receive a string descriptor when passed a string. Yet there is an indirect
- method for passing FORTRAN strings to BASIC, if the BASIC program does not
- use far strings. First, allocate a fixed-length string in FORTRAN, declare
- an array of two 2-byte integers, and treat the array as a string descriptor.
- Next, assign the length of the string to the first element, and assign the
- address of the string to the second element (using the LOC function).
- Finally, pass the integer array itself by reference. BASIC can receive and
- process this array just as it would a string descriptor
-
- If you've successfully used this method with old code, it will still work
- with near-string programs. However, the Microsoft BASIC routines discussed
- in Chapter 13, "Mixed-Language Programming with Far Strings," can be used
- for near and far strings, and make passing strings between language modules
- simpler and more reliable. In all cases, make sure that the string
- originates in FORTRAN, not in BASIC. Strings originating in BASIC are
- subject to being moved around in memory during BASIC string management.
-
-
- Passing BASIC Strings to Pascal
-
- The same technique used for passing strings to FORTRAN can be used to pass a
- BASIC string to Pascal when the BASIC program uses near strings. However,
- the Pascal routine should declare the string as a VAR parameter in order to
- receive the string by near reference. See Chapter 13, "Mixed-Language
- Programming with Far Strings." The Pascal code must declare the fixed-length
- type string (4) in a separate statement, then use the declared type in a
- procedure declaration as show by the following example:
-
- DECLARE SUB Test(BYVAL S%)' or use S& if the address is far
- A$ = "abcd"
- CALL Test(SADD(A$))' or use SSEGADD if string is far
- type stype4=string(4);
- procedure Test (VAR StringA:stype4);{near string}
-
- Passing Pascal Strings to BASIC
-
- To pass a Pascal string to near-string BASIC program, you can first allocate
- a string in Pascal. Next, create a record identical to a BASIC string
- descriptor. Initialize this record with the string length and address, and
- then pass the record by near reference. If the BASIC program uses far
- strings, use the string manipulation routines described in Chapter 13,
- "Mixed-Language Programming with Far Strings."
-
- If you've successfully used this method with old code, it will still work
- with near-string programs. However, the Microsoft BASIC routines discussed
- in Chapter 13, "Mixed-Language Programming with Far Strings," can be used
- for both near and far strings, and make passing strings between language
- modules simpler and more reliable. In all cases, make sure that the string
- originates in Pascal, not in BASIC. Strings originating in BASIC are subject
- to being moved around in memory during BASIC string management.
-
-
- Special Data Types
-
- This section considers special types of data that are either arrays or
- structured types (that is, data types that contain more than one field).
-
-
- Arrays
-
- When you program in only one language, arrays do not present special
- problems; the language is consistent in its handling of arrays. When you
- program with more than one language, however, you need to be aware of two
- special problems that may arise with arrays:
-
- ■ Arrays are implemented differently in BASIC than in other Microsoft
- languages, so that you must take special precautions when you pass an
- array from BASIC to another language (including assembly language). A
- reference to an array in BASIC is really a reference to an array
- descriptor. Array descriptors are always in DGROUP. Array data
- however, is sometimes stored in DGROUP and sometimes in far memory.
- Further, array-data storage differs slightly in QBX from array-data
- storage in the command-line compiler. Table 12.5 summarizes array-data
- storage for both QBX and the command-line compiler.
-
-
-
- Remember, string arrays are arrays of string descriptors, not arrays of
- strings themselves. No matter where the actual strings are stored, the array
- descriptor is always in DGROUP. The array of the string descriptors referred
- to by the array descriptor is in DGROUP as well. With arrays of near strings
- (the default in a program compiled from the command line), the strings
- themselves are also always in DGROUP, but with far strings the actual
- strings are in far memory. Since QBX always uses far strings, the actual
- strings are always in far memory in QBX.
-
- Arrays are declared and indexed differently in each language.
-
-
-
- Passing Arrays from BASIC
-
- Most Microsoft languages permit you to reference arrays directly. In C, for
- example, an array name is equivalent to the address of the first element.
-
- This simple implementation is possible because the location of data for an
- array never changes.
-
- BASIC uses an array descriptor, however, which is similar in some respects
- to a BASIC string descriptor, but far more complicated. The array descriptor
- is necessary because BASIC may shift the location of array data in memory,
- as BASIC handles memory allocation for arrays dynamically. See Appendix B,
- "Data Types, Constants, Variables, and Arrays," for specific information and
- cautions about passing BASIC arrays.
-
- C, FORTRAN, and Pascal have no equivalent of the BASIC array descriptor.
- More importantly, they lack access to BASIC's space-management routines for
- arrays. Therefore, you may safely pass arrays from BASIC only if you follow
- three rules:
-
-
- Pass the array's address by applying the varptr function to the first
- element of the array, then pass the result by value. To pass the
- address of data stored in far memory, get the segment with varseg, the
- offset with varptr, then pass both by value. The target language
- receives the address of the first element, and considers it to be the
- address of the entire array. It can then access the array with its
- normal array-indexing syntax. The example following this list
- illustrates this approach.
-
-
-
- Alternatively, only a far reference to an array can be passed by passing
- its first element by far reference as in the following fragments:
-
-
- CALLS Proc1 (A(0,0))
- CALL Proc2 (SEG A(0,0)) n
-
- The routine that receives the array must not, under any circumstances,
- make a call back to BASIC. If it does, then the location of the array
- data may change, and the address that was passed to the routine
- becomes meaningless.
-
- BASIC may pass any member of an array by value. With this method, the
- preceding precautions do not apply.
-
-
- Example
-
- The following example passes an array from BASIC to FORTRAN:
-
- ' basic source file
- defint a-z
- dim a( 1 to 20)
- declare sub ArrFix(byval Addr as integer)
- .
- .
- .
- call ArrFix (varptr (a(1)) )
- print a(1)
- end
-
- cfortran source file
-
- c
-
- subroutine arrfix (arr)
-
- integer * 2 arr [neaR] (20)
-
- arr(1) = 5
-
- end
-
-
- In the preceding example, assuming the program is compiled from the command
- line, BASIC considers that the argument passed is the near address of an
- array element. FORTRAN considers it to be the near address of the array
- itself. Both assumptions are correct. You can use essentially the same
- method for passing BASIC arrays to Pascal or C. The parameter was declared
- BYVAL and AS INTEGER because a near (2-byte) address needed to be passed.
- To pass a far address, you could use the following code instead:
-
- declare sub ArrFix(byval SegAdd AS Integer, byval Addr as integer)
- call ArrFix (varseg( A(0) ), varptr( a(0) ) )
-
-
- The first field is the segment returned by varseg. If you use cdecl then be
- sure to pass the offset address before the segment address, because cdecl
- causes parameters to be passed in reverse order:
-
- declare sub ArrFix(byval Addr as integer byval,SegAdd AS Integer)
- call ArrFix ( varptr( a(0) ) , varseg( A(0) ) )
-
- Note
-
- You can apply the LBOUND and UBOUND functions to a BASIC array, to
- determine lower and upper bounds, and then pass the results to another
- routine. This way, the size of the array does not need to be determined in
- advance. See the BASIC Language Reference for more information on the
- LBOUND and UBOUND functions.
-
-
- Array Indexing and Declaration
-
- Each language varies somewhat in the way that arrays are declared and
- indexed. Array indexing is a source-level consideration and involves no
- transformation of data. There are three differences in the way elements are
- indexed by each language:
-
-
- The way an array's lower bound is specified differs among Microsoft
- languages.
-
- By default, FORTRAN indexes the first element of an array as 1. BASIC
- and C index it as 0. Pascal lets you begin indexing at any integer
- value. Recent versions of BASIC and FORTRAN also give you the option
- of specifying lower bounds at any integer value.
-
-
- The way the number of elements in an array is declared is different in
- BASIC than for the other languages. For example, in BASIC, the
- constants that are used in the array declaration DIM Arr%(5,5)
- represent the upper bounds of the array. Therefore, the last element
- is indexed as Arr%(5,5). The constants used in a C array declaration
- represent the actual number of elements in each dimension, not upper
- bounds as they do in BASIC. Therefore, the last element in the C array
- declared as int arr[5][5] is indexed as arr[4][4], rather than as
- arr[5][5].
-
-
- Some languages vary subscripts in row-major order; others vary
- subscripts in column-major order. This issue only affects arrays with
- more than one dimension. When you traverse an array in row-major
- order, you access each column of a row before moving on to the next
- row. Therefore, with row-major order (the only choice with C and
- Pascal) each element of the rightmost subscript is accessed before the
- leftmost subscript changes. When you traverse an array in column-major
- order, you access each row of a column before moving on to the next
- column. Thus, with column-major order (used by BASIC by default, and
- the only choice in FORTRAN), each element of the leftmost subscript is
- accessed before the rightmost subscript changes. Thus, in C the first
- four elements of an array declared as X [3][3], are:
-
-
- X[0][0] X[0][1] X[0][2] X[1][0]
-
- In BASIC, the corresponding four elements are:
-
- X(0,0) X(1,0) X(2,0) X(0,1)
-
- Similarly, the following references both refer to the same place in memory
- for an array:
-
- arr1[2][8] /* in C */
- Arr1(8,2) ' in BASIC
-
- The preceding examples assume that the C and BASIC arrays use lower bounds
- of 0.
-
-
- Declaring Arrays
-
- Table 12.6 shows equivalences for array declarations in each language. In
- this table, i represents the number of elements in the leftmost dimension
- for column-major arrays and I is used for the leftmost dimension of
- row-major arrays. Similarly, J represents the number of elements in the
- rightmost dimension for column-major arrays and j is used for row-major
- arrays.
-
-
- Compiling BASIC Modules for Row-Major Array Storage
-
- When you compile a BASIC program with the bc compiler, you can select the /R
- compile option, which specifies that row-major order is to be used, rather
- than the default column-major order. BASIC is the only Microsoft language
- that permits you to specify how arrays should be stored. The /R option is
- available only when compiling from the command line. The only choice for
- array storage in QBX (or when compiling from within QBX using the Make EXE
- command) is column-major storage.
-
-
- Array Data in Memory
-
- The following code could be used in BASIC to fill a 4 x 5 array with
- integers from 0 through 19:
-
- DIM Arr%(3,4) AS integer
- for I% = 0 to 3
- for J% = 0 to 4
- Arr%(I%, J%) = number%
- print Arr%(I%, J%)
- Number%= Number%+1
- Next J%
- next I%
-
- Because BASIC uses column-major array storage by default, the numbers would
- be stored in contiguous memory locations. However, the order of the data, as
- placed in memory, is not the same as the numeric sequence (0\- - 19) of the
- data. Because the arrangement in memory is column major, it would be as
- shown in Figure 12.11.
-
- 5f17bfffHowever, if compiled from the command line with BASIC's /R option,
- the array shown in the preceding section would be stored as shown in Figure
- 12.12:
-
- 5f14bfffWithin the QBX environment only the default can be used; the /R
- option is not available.
-
-
- Passing Arrays Between Modules
-
- Only BASIC allows you to choose the order in which arrays are stored.
- Therefore, when you pass an array from BASIC to a language that expects
- arrays to be stored in row-major order, the easiest thing to do is to use
- the /R option when compiling the BASIC module. The next easiest thing to do
- is to reverse the order of the dimensions in the array declaration of one of
- the languages. For example, if you wanted to pass the BASIC array described
- in the preceding section to C function contained in a Quick library loaded
- in the QBX environment, this would be the only option.
-
-
- Given the address of the first element of the BASIC array shown in the
- preceding BASIC code, a C function could use the following code to print out
- its elements in the order in which they would be printed out in the BASIC
- code:
-
- int arr[5][4] ;/* Note the number of elements = 5 */
- int number = 0 , I = 0, j = 0 ;/* in the major dimension, but the */
- for (I =0; I = 4 ; I++)/* major-dimension upper bound = 4 */
- for (j =0; j = 3 ; j++)
- printf( "%d ", arr[I][j] ) ;
-
- The data stored by BASIC is not actually reordered by the C code. Instead
- the dimensions in the C array are declared in reverse order from those in
- the BASIC declaration. To access an element in an array, compilers use a
- formula similar to the following:
-
- starting address + ((MajorUpperBound * MajorSubscript) + MinorSubscript) *
- scale
-
- MajorUpperBound is the number of elements in the major dimension (in the
- default BASIC case, the right, or column dimension). MajorSubscript is the
- actual major index value of the element you want to access (with the BASIC
- default, the right index value). MinorSubscript is the actual minor index
- value of the element you want to access (in the case of BASIC, the left
- index value). The scale is the size of each data element, for example an
- integer is 2 bytes, a long integer is 4 bytes, etc.
-
- Example
-
- The following references all refer to the same place in memory for an array:
-
- arr1[2][8]/* in C */
-
- Arr1[3,9]{ in Pascal, assuming lower bounds of 1 }
-
- ARR1(8,2) ' in BASIC, assuming default array storage & C lower bounds
-
- CIn FORTRAN, assuming lower bounds of 1
- ARR1(9,3)
-
- Arrays with More than Two Dimensions
-
- Describing arrays in terms of rows and columns is a convenient analogy for
- understanding two-dimensional arrays. Actual storage is simply by contiguous
- memory locations. However, the format of the declarations shown earlier in
- Table 12.6 can be extended to any number of dimensions that you may use. For
- example, the following C declaration:
-
- int arr1[2][10][15][20] ;
-
- Is equivalent to the following BASIC declaration:
-
- DIM Arr1%(19, 14, 9, 1)
-
-
- These are equivalent in the sense that the C array element represented by
- the following:
-
- arr1[k][l][m][n]
-
- Refers to the same memory location as the following BASIC array element:
-
- Arr1(n, m, l, k)
-
- Structures, Records, and User-Defined Types
-
- The C struct type, the BASIC user-defined type, the FORTRAN record (defined
- with the STRUCTURE keyword), and the Pascal record type are equivalent.
- Therefore, these data types can be passed between C, FORTRAN, Pascal, and
- BASIC.
-
- These types can be affected by the storage method. By default, C, FORTRAN,
- and Pascal use word alignment for types shorter than one word (type char
- and unsigned char). This storage method specifies that occasional bytes can
- be inserted as padding so that word and double-word data items start on an
- even boundary. (In addition, all nested structures and records start on a
- word boundary.)
-
- If you are passing a structure or record across a mixed-language interface,
- your calling routine and called routine must agree on the storage method and
- parameter-passing convention. Otherwise, data will not be interpreted
- correctly.
-
- Because Pascal, FORTRAN, and C use the same storage method for structures
- and records, you can exchange data between routines without taking any
- special precautions unless you modify the storage method. Make sure the
- storage methods agree before changing data between C, FORTRAN, and Pascal.
-
- BASIC packs user-defined types, so your other-language routines must also
- pack structures (using the /Zp command-line option or the pack pragma, in
- C) to agree. Figure 12.13 contrasts packed and word-aligned storage.
-
- In C and Pascal, you can pass structures as parameters by value or by
- reference. In BASIC, structures (called user-defined types) can only be
- passed by reference. Both the calling program and the called program must
- agree on the parameter-passing convention. See the section
- "Parameter-Passing Requirements" earlier in this chapter for more
- information about the language you are using.
-
-
- External Data
-
- External data refers to data that is static and public; that is, the data is
- stored in a set place in memory as opposed to being allocated on the stack,
- and the data is visible to other modules. Although BASIC has static data,
- BASIC has no support for public data. Therefore there is no truly external
- data in BASIC. (See the section "Common Blocks" later in this chapter for
- more information about sharing external data with BASIC and FORTRAN
- programs.)
-
-
- Pointers and Address Variables
-
- Rather than passing data directly, you may want to pass the address of a
- piece of data. Passing the address amounts to passing the data by reference.
- In some cases, such as in BASIC arrays, there is no other way to pass a data
- item as a parameter.
-
- BASIC and FORTRAN do not have formal address types. However, they do provide
- ways for storing and passing addresses.
-
- BASIC programs can access a variable's segment address with the VARSEG
- function and its offset address with the VARPTR function. The values
- returned by these intrinsic functions should then be passed or stored as
- ordinary integer variables. If you pass them to another language, pass by
- value. Otherwise you will be attempting to pass the address of the address,
- rather than the address itself. See Chapters 13, "Mixed-Language Programming
- with Far Strings," and 11, "Advanced String Storage," for information on
- passing addresses of far strings.
-
- To pass a near address, pass only the offset; if you need to pass a far
- address, you may have to pass the segment and the offset separately. Pass
- the segment address first, unless you have used CDECL in the BASIC DECLARE
- statement.
-
- FORTRAN programs can determine near and far addresses with the LOC and
- LOCFAR functions. Store the result of the LOC function as INTEGER*2 and
- the result of the LOCFAR function as INTEGER*4. As with BASIC, if you pass
- the result of LOC or LOCFAR to another language, be sure to pass by value.
-
- C programs always pass array variables by address. All other types are
- passed by value unless you use the address-of ( &) operator to obtain the
- address.
-
- The Pascal ADR and ADS types are equivalent to C's near and far pointers,
- respectively. You can pass ADR and ADS variables as ADRMEM or ADSMEM.
-
-
- Common Blocks
-
- You can pass individual members of a BASIC or FORTRAN common block in an
- argument list, just as you can with any data. However, you can also give a
- different language module access to the entire common block at once.
-
- C modules can refer to the items of a common block by first declaring a
- structure or record with fields that correspond to the common-block
- variables. Having defined a structure or user-defined type with the
- appropriate fields, the C module must then connect with the common block
- itself.
-
- To pass the address of a common block, simply pass the address of the first
- variable in the block. (In other words, pass the first variable by
- reference.) The receiving C module should expect to receive a structure by
- reference.
-
- Example
-
- In the following example, the C function initcb receives the address of the
- variable N, which it considers to be a pointer to a structure with three
- fields:
-
- ' BASIC SOURCE CODE
- '
- COMMON /Cblock/ N%,X#,Y#
- DECLARE SUB INITCB CDECL (N%)
- .
- .
- .
- CALL INITCB(N%)
- .
- .
- .
- /* C source code */
- struct block_type {
- int N;
- double x;
- double y;
- };
- void initcb(block_hed)
- struct block_type *block_hed;
- {
- block_hed->n = 1;
- block_hed->x = 10.0;
- block_hed->y = 20.0;
- }
-
- Using a Varying Number of Parameters
-
- Some C functions, most notably printf, can be called with a different
- number of arguments each time. To call such a function from another
- language, you need to suppress the type-checking that normally forces a call
- to be made with a fixed number of parameters. In BASIC, you can remove this
- type checking by omitting the parameter list from the DECLARE statement, as
- noted in the section "Alternative BASIC Interfaces" earlier in this chapter.
-
- In FORTRAN or Pascal you can call routines with a variable number of
- parameters by using the VARYING attribute in your interface to the routine
- along with the C attribute. You must use the C attribute because a
- variable number of parameters is feasible only with the C calling
- convention.
-
- Because the number of parameters is not fixed, the routine you call should
- have some mechanism for determining how many parameters to expect. Often
- this information is indicated by the first parameter. For example, the C
- function printf scans the format string passed as the first parameter. The
- number of fields in the format string determines how many additional
- parameters the function should expect. The following examples illustrate two
- ways of calling the C-library function printf.
-
- Examples
-
- In the first example a fixed number of arguments is declared, and the
- keyword BYVAL precedes each parameter in the DECLARE statement to insure
- that the true address of the string argument is passed. The example assumes
- you are using near strings in BASIC and compiling the C module in medium
- model.
-
- DEFINT A-Z
- DECLARE SUB printf CDECL (BYVAL Format, BYVAL Value)
- String1$ = "Value passed to the formatted string is %d"+CHR$(0)
- Number = 19
- CALL printf(SADD(String1$), Number)
- END
-
- The following variation of the preceding example uses a special form of the
- DECLARE statement to declare the printf function. The parentheses that
- normally contain the formal parameters are omitted from the declaration.
- This informs BASIC of two facts:
-
-
- The procedure is not written in BASIC.
-
-
- The procedure can have a variable number of arguments. The BYVAL
- keyword is still used in the program, but this time it is passed with
- each argument in the printf call.
-
-
- In both examples, the C-language format character %d is used in the
- string passed to printf, which replaces it with the value of the
- second argument.
-
-
- DEFINT A-Z
- DECLARE SUB printf CDECL
- String1$ = "Value passed to the formatted string is %d"+CHR$(0)
- Number = 19
- CALL printf(BYVAL SADD(String1$), BYVAL Number)
- END
-
- The preceding example describes the BASIC call to printf. When you link the
- object file created by such a program from the command line, LINK resolves
- the reference to the printf function when the executable file is created.
- However, if you want to call printf from within the QBX environment, you
- must make the function available within a Quick library. The simplest way to
- do this is to write a "dummy" C function that calls printf, and compile it
- (make sure you are compiling in medium model, as noted previously). Then,
- instead of linking it with a BASIC program on the link command line,
- incorporate the object file into a Quick library as shown in the following
- example:
-
- link /q dummy.obj,,,qbxqlb.lib /NOE;
-
- In this case a Quick library called dummy.qlb is created; it contains only
- the printf function. (Note that LINK will prompt you for the path to the
- correct C library if it cannot find it.) The /NOE option keeps LINK from
- misinterpreting certain symbols common to the two libraries as duplicate
- definitions. You can then make calls to printf if you load this library when
- you invoke BASIC, as follows:
-
- qbx /l dummy
-
- Using this method, you can create Quick libraries that include useful
- functions from the standard C library, as well as other-language routines
- you write yourself. See Chapter 19, "Creating and Using Quick Libraries,"
- for more information.
-
-
- B_OnExit Routine
-
- You can use B_OnExit when your other-language routines take special actions
- that need to be undone before program termination or rerunning of the
- program. For example, within the BASIC environment, an executing program
- that calls other-language routines in a Quick library may not always run to
- normal termination. If such routines need to take special actions at
- termination (for example, de-installation of previously installed interrupt
- vectors), you can guarantee that your termination routines will always be
- called if you include an invocation of B_OnExit in the routine. The
- following example illustrates such a call (for simplicity, the example omits
- error-handling code). Note that such a function would be compiled in C in
- large model.
-
- #include <malloc.h>
- extern pascal far B_OnExit(); /* Declare the routine */
- int *p_IntArray;
- void InitProc()
- {
- void TermProc(); /* Declare TermProc function */
-
- /* Allocate far space for 20-integer array: */
- p_IntArray = (int *)malloc(20*sizeof(int));
-
-
- /* Log termination routine (TermProc) with BASIC: */
- B_OnExit(TermProc);
- }
- /* The TermProc function is */
- void TermProc() /* called before any restarting */
- { /* or termination of program. */
- free(p_IntArray); /* Release far space allocated */
- } /* previously by InitProc. */
-
- If the InitProc function were in a Quick library, the call to B_OnExit
- would insure proper release of the space reserved in the call to malloc,
- should the program end before normal termination could be performed. The
- routine could be called several times, since the program can be executed
- several times from the BASIC environment. However, the TermProc function
- itself would be called only once each time the program runs.
-
- The following BASIC program is an example of a call to the InitProc
- function:
-
- DECLARE SUB InitProc CDECL
- X = SETMEM(-2048) ' Make room for the malloc memory
- ' allocation in C function.
- CALL InitProc
- END
-
- If more than 32 routines are registered, B_OnExit returns NULL, indicating
- there is not enough space to register the current routine. Note that
- B_OnExit has the same return values as the Microsoft C run-time library
- routine onexit.
-
- B_OnExit can be used with assembly language or any other-language routines
- you place in a Quick library. With programs compiled and linked completely
- from the command line, B_OnExit is optional.
-
-
- Assembly Language-to-BASIC Interface
-
- With MASM you can write assembly language modules that can be linked to
- modules developed with Microsoft BASIC, Pascal, FORTRAN, and C. This section
- outlines the recommended programming guidelines for writing assembly
- language routines compatible with Microsoft BASIC.
-
- Writing assembly language routines for Microsoft high-level languages is
- easiest when you use the simplified segment directives provided with the
- MASM version 5.0, and later. This manual assumes that you have version 5.0
- or later.
-
-
- Writing the Assembly Language Procedure
-
- You can call assembly language procedures using essentially the same
- conventions as for compiler-generated code. This section describes how you
- use those conventions to call assembly language procedures. Procedures that
- observe these conventions can be called recursively and can be debugged with
- the Microsoft CodeView debugger. The standard assembly language interface
- method, described in the following sections, consists of the following
- steps:
-
- ■ 1. Set up the procedure.
-
- ■ 2. Enter the procedure.
-
- ■ 3. Allocate local data (optional).
-
- ■ 4. Preserve register values.
-
- ■ 5. Access parameters.
-
- ■ 6. Return a value (optional).
-
- ■ 7. Exit the procedure.
-
-
- Note
-
- The MASM, version 5.0 and later, provide an include file, MIXED.INC, that
- automatically performs many of the stack-maintenance chores described in the
- next few sections. Version 5.1 and later include several keywords that
- simplify creation of a mixed-language interface. Part 1 of the Microsoft
- QuickAssembler Programmer's Guide describes the mechanics of mixed-language
- programming using the mixed-language keywords.
-
-
- Setting Up the Procedure
-
- LINK cannot combine the assembly language procedure with the calling program
- unless compatible segments are used and unless the procedure itself is
- declared properly. The following four points may be helpful:
-
-
- Use the .MODEL directive at the beginning of the source file. This
- directive automatically causes the appropriate kind of returns to be
- generated ( NEAR for small or compact model, FAR otherwise). Modules
- called from BASIC should be .MODEL MEDIUM. If you have a version of
- the MASM previous to 5.0, declare the procedure FAR.
-
-
- Use the simplified segment directives . CODE to declare the code
- segment and . DATA to declare the data segment. (Having a code segment
- is sufficient if you do not have data declarations.) If you are using
- a version of the assembler earlier than 5.0, declare the segments
- using the SEGMENT, GROUP, and ASSUME directives (described in the
- Microsoft Macro Assembler manual).
-
-
- Use the PUBLIC directive to declare the procedure label public. This
- declaration makes the procedure visible to other modules. Also, any
- data you want to make public to other modules must be declared as
- PUBLIC.
-
-
- Use the EXTRN directive to declare any global data or procedures
- accessed by the routine as external. The safest way to use code EXTRN
- declarations is to place the directive outside of any segment
- definition. However, place near data inside the data segment.
-
-
- Note
-
- If you want to be able to call the assembly language procedure from both
- BASIC and C, you can create the common interface using the CDECL keyword in
- the BASIC declaration, then following the C naming and calling conventions
- (as explained in the section "Using CDECL in calls from BASIC" earlier in
- this chapter).
-
-
- Entering the Procedure
-
- If your procedure accepts arguments or has local (automatic) variables on
- the stack, you need to use the following two instructions to set up the
- stack frame and begin the procedure:
-
- push bp
- mov bp,sp
-
- This sequence establishes BP as the framepointer. The "framepointer" is
- used to access parameters and local data, which are located on the stack.
- The value of the base register BP should remain constant throughout the
- procedure (unless your program changes it), so that each parameter can be
- addressed as a fixed displacement off of BP. SP cannot be used for this
- purpose because it is not an index or base register. Also, the value of SP
- may change as more data is pushed onto the stack.
-
- The instruction push bp preserves the value of BP. This value will be
- needed by the calling procedure as soon as the current procedure terminates.
- The instruction mov bp,sp captures the value that the stack pointer had at
- the time of entry to the procedure. This establishes that the parameter can
- be addressed.
-
-
- Allocating Local Data (Optional)
-
- An assembly procedure can use the same technique for allocating temporary
- storage for local data that is used by high-level languages. To set up local
- data space, simply decrease the contents of SP immediately after setting up
- the stack frame. To ensure correct execution, you should always increase or
- decrease SP by an even amount. Decreasing SP reserves space on the stack
- for the local data. The space must be restored at the end of the procedure
- as shown by the following:
-
- push bp
- mov bp,sp
- sub sp,space
-
- In the preceding code fragment, space is the total size in bytes of the
- local data. Local variables are then accessed as fixed, negative
- displacements off of BP.
-
-
- Example
-
- The following example uses two local variables, each of which is 2 bytes.
- SP is decreased by 4, since there are 4 bytes total of local data. Later,
- each of the variables is initialized to 0.
-
- push bp; Save old stack frame
- mov bp,sp; Set up new stack frame
- sub sp,4; Allocate 4 bytes local storage
-
- mov WORD PTR [bp-2],0
- mov WORD PTR [bp-4],0
-
- Local variables are also called dynamic, stack, or automatic variables.
-
-
- Preserving Register Values
-
- A procedure called from any of the Microsoft high-level languages should
- preserve the direction flag and the values of SI, DI, SS, and DS (in
- addition to BP, which is already saved). Any register values that your
- procedure alters should be pushed onto the stack after you set up the stack
- frame, but before the main body of the procedure. If the procedure does not
- change the value of any of these registers, then the registers do not need
- to be pushed.
-
- Warning
-
- Routines that your assembly language procedure calls must not alter the SI,
- DI, SS, DS, or BP registers. If they do, and you have not preserved the
- registers, they can corrupt the calling program's register variables,
- segment registers, and stack frame, causing program failure.
-
- If your procedure modifies the direction flag using the STD or CLD
- instructions, you must preserve the flags register.
-
- The following example shows an entry sequence that sets up a stack frame,
- allocates 4 bytes of local data space on the stack, then preserves the SI,
- DI, and flags registers.
-
- push bp ; Save caller's stack frame.
- mov bp,sp ; Establish new stack frame.
- sub sp,4 ; Allocate local data space.
- push si ; Save SI and DI registers.
- push di
- pushf ; Save the flags register.
- .
- .
- .
-
-
- In the preceding example, you must exit the procedure with the following
- code:
-
- popf ; Restore the flags register.
- pop di ; Restore the old value in the DI register.
- pop si ; Restore the old value in the SI register.
- mov sp,bp ; Restore the stack pointer.
- pop bp ; Restore the frame pointer.
- ret ; Return to the calling routine.
-
- If you do not issue the preceding instructions in the order shown, you will
- place incorrect data in registers. The rules for restoring the calling
- program's registers, stack pointer, and frame pointer are:
-
- ■ Pop all registers that you preserve in the reverse order from which
- they were pushed onto the stack. So, in the example above, SI and DI
- are pushed, and DI and SI are popped.
-
- ■ Restore the stack pointer by transferring the value of BP into SP
- before restoring the value of the frame pointer.
-
- ■ Always restore the frame pointer last.
-
-
-
- Accessing Parameters
-
- Once you have established the procedure's framepointer, allocated local data
- space (if desired), and pushed any registers that need to be preserved, you
- can write the main body of the procedure. To write instructions that can
- access parameters, consider the general picture of the stack frame after a
- procedure call, as illustrated in Figure 12.14.
-
- 6a7abfffThe stack frame
- for the procedure is established by the following sequence of events:
-
- 1. The calling program pushes each of the parameters on the stack, after
- which SP points to the last parameter pushed.
-
- 2. The calling program issues a CALL instruction, which causes the
- return address (the place in the calling program to which control
- ultimately returns) to be placed on the stack. Because BASIC always
- uses a FAR call, this address is 4 bytes long. SP now points to this
- address. With a language such as C, the address may be 2 bytes, if the
- call is a near call.
-
- 3. The first instruction of the called procedure saves the old value of
- BP, with the instruction push bp. Now SP points to the saved copy of
- BP.
-
- 4. BP is used to capture the current value of SP, with the instruction
- mov bp,sp. BP therefore now points to the old value of BP (saved on
- the stack).
-
- 5. Whereas BP remains constant throughout the procedure, SP is often
- decreased to provide room on the stack for local data or saved
- registers.
-
-
- In general, the displacement (off of BP) for a parameter X is equal to 2
- plus the size of return address plus the total size of parameters between X
- and BP.
-
-
- For example, consider a procedure that has received one parameter, a 2-byte
- address. Since the size of the return address is always 4 bytes in BASIC,
- the displacement of the parameter would be calculated as follows:
-
- argument's displacement = 2 + size of return address
-
- = 2 + 4
- = 6
-
- In other words, the argument's displacement equals 2 plus 4, or 6. The
- argument can thus be loaded into BX with the following instruction:
-
- mov bx,[bp+6]
-
- Once you determine the displacement of each parameter, you may want to use
- the EQU directive or structures to refer to the parameter with a single
- identifier name in your assembly source code. For example, the preceding
- parameter at BP+6 can be conveniently accessed if you put the following
- statement at the beginning of the assembly source file:
-
- Arg1EQU[bp+6]
-
- You could then refer to this parameter as Arg1 in any instruction. Use of
- this feature is optional.
-
- Note
-
- For far (segment plus offset) addresses, Microsoft high-level languages push
- segment addresses before pushing offset address. Furthermore, when pushing
- arguments larger than 2 bytes, high-order words are always pushed before
- low-order words and parameters longer than 2 bytes are stored on the stack
- in most-significant, least-significant order. This standard for pushing
- segment addresses before pushing offset addresses facilitates the use of the
- LES (load extra segment) and LDS (load data segment) instructions.
-
-
- Returning a Value (Optional)
-
- BASIC has a straightforward convention for receiving return values when the
- data type to be returned is simple (that is, not a floating-point value, an
- array, or structured type) and is no more than 4 bytes. This includes all
- pointers and all parameters passed by reference, as shown in the following
- list:
-
- ╓┌───────────────────┌───────────────────────────────────────────────────────╖
- ────────────────────────────────────────────────────────────────────────────
- 2-byte integer( %) AX
- 4-byte integer( &) High-order portion in DX; low-order portion in AX
- All other types Near offset in AX
-
-
-
-
-
- An assembly language procedure called by BASIC must use a special convention
- to return floating-point values, user-defined types and arrays, and values
- larger than 4 bytes, as explained in the following section:
-
-
- Numeric Return Values Other Than 2- and 4-Byte Integers
-
- In order to create an interface for numeric return values that are neither
- 2-byte integers ( %) nor 4-byte integers ( &), BASIC modules take the
- following actions before they call your procedure (assuming that the BASIC
- declarations do not specify the CDECL keyword):
-
- 1. When the call to your procedure is made, an extra parameter is passed;
- this parameter contains the offset address of the actual return value.
- This parameter is placed immediately above the return address. (In
- other words, this parameter is the last one pushed.)
-
- 2. The segment address of the return value is contained in SS and DS.
-
- The extra parameter (which contains the offset address of the return
- value) is always located at BP+6. Furthermore, its presence
- automatically increases the displacement of all other parameters by
- two, as shown in Figure 12.15.
-
- Your assembly language procedure can successfully return numeric values
- other than 2- and
-
- 4-byte integers if you follow these steps:
-
- 1. Put the data for the return value at the location pointed to by the
- return-value offset.
-
- 2. Copy the return-value offset (located at BP+6) to AX. This is
- necessary because the calling module expects AX to point to the
- return value.
-
- 3. Exit the procedure as described in the next section.
-
-
-
- Exiting the Procedure
-
- Several steps may be involved in terminating the procedure:
-
- 1. If any of the registers SS, DS, SI, or DI have been saved, these
- must be popped off the stack in the reverse order from that in which
- they were saved. If they are popped in any other order, program
- behavior is unpredictable.
-
- 2. If local data space was allocated at the beginning of the procedure,
- SP must be restored with the instruction mov sp,bp.
-
- 3. Restore BP with the instruction pop bp. This step is always
- necessary.
-
- 4. Since the BASIC calling convention is being used, you must use the
- RET n form of the instruction to adjust the stack if any parameters
- were pushed by the caller.
-
-
- Examples
-
- The following example shows the simplest possible exit sequence. No
- registers were saved, no local data space was allocated, and no parameters
- were passed to the routine.
-
- pop bp
- ret
-
- The following example shows an exit sequence for a procedure that has
- previously saved SI and DI, allocated 4 bytes of local data space, used
- the BASIC calling convention, and received 6 bytes of parameters. The
- procedure must therefore use ret 6 to restore the 6 bytes of parameters on
- the stack.
-
- push bp
- movbp,sp
- subsp,4
- pushsi
- pushdi
- .
- .
- .
- popdi; pop saved registers
- pop si
- mov sp,bp ; Free local data space.
- pop bp ; Restore old stack frame.
- ret 6 ; Exit, and remove 6 bytes of args.
-
- Note
-
- If the preceding routine had been declared with CDECL, only the RET
- (without a specification of n) would be used because with the C calling
- convention, the caller cleans up the stack.
-
-
- Calls from BASIC
-
- A BASIC program can call an assembly language procedure in another source
- file with the CALL or CALLS statement. Proper use of the DECLARE
- statement is also important. In addition to the steps outlined in the
- preceding sections, the following guidelines may be helpful:
-
-
- Declare procedures called from BASIC as FAR.
-
-
- Observe the BASIC calling conventions:
-
-
- Parameters are placed on the stack in the same order in which they
- appear in the BASIC source code. The first parameter is highest in
- memory (because it is also the first parameter to be placed on the
- stack, and the stack grows downward).
-
-
- By default, BASIC parameters are passed by reference as 2-byte
- addresses. (See Chapter 13, "Mixed-Language Programming with Far
- Strings," for information on passing strings stored in far
- memory.)
-
-
- Upon exit, the procedure must reset SP to the value it had before
- the parameters were placed on the stack. This is accomplished with
- the instruction ret n, where n is the total size in bytes of all
- the parameters.
-
-
- Observe the BASIC naming convention.
-
-
- BASIC outputs symbolic names in uppercase characters, which is also the
- default behavior of the assembler. BASIC recognizes up to 40 characters of a
- name, whereas the assembler recognizes only the first 31 (this should rarely
- create a problem).
-
- Note
-
- Microsoft BASIC provides a Quick library (called QBX.QLB and an include file
- called QBX.BI). These files contain several routines that facilitate calling
- assembly language routines from within the BASIC environment. See Chapter
- 11, "Advanced String Storage," for information on using this library.
-
- Examples
-
- In the following example, BASIC calls an assembly language procedure that
- calculates A * 2B, where A and B are the first and second parameters,
- respectively. The calculation is performed by shifting the bits in A to the
- left, B times.
-
- ' BASIC program
- '
- DEFINT A-Z
- '
- DECLARE FUNCTION Power2(A%,B%)
- '
- PRINT "3 times 2 to the power of 5 is ";
- PRINT Power2(3,5)
- END
-
-
- To understand how to write the assembly language procedure, recall how the
- parameters are placed on the stack, (shown in Figure 12.16):
-
- The return address is 4 bytes because procedures that are called
- from BASIC must be FAR. Arg 1 is higher in memory than Arg 2 because BASIC
- pushes arguments in the same order in which they appear. Also, each argument
- is passed as a 2-byte offset address, the BASIC default.
-
- The assembly language procedure can be written as follows:
-
- .MODEL MEDIUM
- .CODE
- PUBLIC Power2
- Power2 PROC
- push bp ; Entry sequence - saved old BP
- mov bp,sp ; Set stack framepointer
- ;
- mov bx,[bp+8] ; Set BX equal to address of Arg1
- mov ax,[bx] ; Load value of Arg1 into AX
- mov bx,[bp+6] ; Set BX equal to address of Arg2
- mov cx,[bx] ; Load value Arg2 into CX
- shl ax,cl ; AX = AX * (2 to power of CX)
- ; Leave return value in AX
- pop bp ; Exit sequence - restore old BP
- ret 4 ; Return, and restore 4 bytes
- Power2 ENDP
- END
-
-
- Note that each parameter must be loaded in a two-step process because the
- address of each is passed rather than the value. Also, note that the stack
- is restored with the instruction ret 4 since the total size of the
- parameters is 4 bytes. (The preceding example is simplified to illustrate
- the interlanguage interface. Code for handling possible errors, such as
- overflow, is not included, but is a significant consideration with such
- procedures.)
-
-
- Using CDECL in Calls from BASIC
-
- You can use the CDECL keyword in the DECLARE statement in your BASIC
- module to call an assembly language routine. If you do so, the C calling
- conventions, rather than those of BASIC, determine the order of the
- arguments as received in the assembly language routine, and also the manner
- in which returns are handled. The primary advantage of using CDECL is that
- then you can call the assembly language routine with a variable number of
- arguments. The technique is analogous to calling a C function from BASIC
- using CDECL. In using CDECL, observe the C calling convention:
-
-
- Parameters are placed on the stack in the reverse order to that in
- which they appear in the BASIC source code. This means the first
- parameter is lowest in memory (because the stack grows downward, and
- it is the last parameter to be placed on the stack).
-
-
- Return with a simple ret instruction. Do not restore the stack with
- ret n, since using CDECL causes the calling routine to restore the
- stack itself as soon as the calling routine resumes control. When the
- return value is not a 2-byte or 4-byte numeric value, a procedure
- called by BASIC with CDECL in effect must allocate space for the
- return value, and then place its address in AX. A simple way to
- create space for the return value is to declare it in a data segment.
-
-
- If CDECL is used in the BASIC DECLARE statement, you must name the
- assembler procedure with a leading underscore, unless you use the
- ALIAS feature.
-
-
-
- The Microsoft Segment Model
-
- If you use the simplified segment directives by themselves, you do not need
- to know the names assigned for each segment. However, versions of MASM prior
- to 5.0 do not support these directives. With older versions of the
- assembler, you should use the SEGMENT, GROUP, ASSUME, and ENDS
- directives equivalent to the simplified segment directives.
-
- Table 12.7 shows the default segment names created by each directive for
- medium model, the only model applicable to BASIC. Use of these segments
- ensures compatibility with Microsoft languages and helps you to access
- public symbols. This table is followed by a list of three steps,
- illustrating how to make the actual declarations.
-
- DATA_DATAWORDPUBLIC'DATA'DGROUPInitialized data.
- CONSTCONSTWORDPUBLIC'CONST'DGROUPUninitialized data. Microsoft compilers
- store uninitialized data separately because it can be more efficiently
- stored than initialized data. DATA?_BSSWORDPUBLIC'BSS'DGROUPConstant data.
- Microsoft compilers use this segment for such items as string and
- floating-point constants. STACKSTACKPARASTACK'STACK'DGROUPStack. Normally,
- this segment is declared in the main module for you and should not be
- redeclared.
- The following steps describe how to use Table 12.7 to create directives:
-
- 1. Use Table 12.7 to determine the segment name, align type, combine
- type, and class for your code and data segments. Use all of these
- attributes when you define a segment. For example, the code segment is
- declared as follows:
-
-
- name_TEXT SEGMENT
- WORD
- PUBLIC
- 'CODE'
-
- The name_TEXT and all the attributes are taken from Table 12.7. You
- substitute your module name for name. If the combine type is private,
- simply do not use any combine type.
-
- 2. If you have segments in DGROUP, put them into DGROUP with the GROUP
- directive, as in:
-
-
- 3. Use ASSUME and ENDS as you would normally. Upon entry, DS and SS
- both point to DGROUP; therefore, a procedure that makes use of
- DGROUP should include the following ASSUME directive:
-
-
- ASSUME CS:name_TEXT,
-
- DS:DGROUP,
-
- SS:DGROUP
-
-
-
- Note
-
- If your assembly language procedures use real numbers, they must use
- IEEE-format numbers to be compatible with QBX. This is the default for MASM,
- version 5.0 and later. With earlier versions, you must specify the IR
- command-line option or the 8087 directive.
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 13: Mixed-Language Programming with Far Strings
-
-
- This chapter provides new techniques for passing strings between BASIC
- and another language. These techniques supplement the general method
- of programming outlined in Chapter 12, "Mixed-Language Programming."
- To get the most out of the present chapter, you should understand the
- material presented in that guide -- most importantly, be familiar with the
- naming, calling, and passing conventions of BASIC and the other languages
- you are using.
-
- This chapter contains the following information:
-
- ■ A description of BASIC's string-processing routines.
-
- ■ A general string-passing model.
-
- ■ Specific examples of passing strings between Microsoft BASIC, Macro
- Assembler (MASM), C, Pascal, and FORTRAN.
-
-
-
- Considerations When Using Strings
-
- When passing strings between BASIC and another language, several
- complications arise. First of all, BASIC's sending and receiving parameters
- can only be variable-length strings; other languages use fixed-length
- strings. Furthermore, whenever a BASIC SUB procedure is receiving a string,
- it expects to be passed the address of a variable-length string descriptor,
- a data structure unique to BASIC.
-
- If the BASIC program is using far strings, a further complication arises
- because the structure of the far string descriptor is proprietary. It is not
- possible for the external routine to get information directly from the
- descriptor, or to create a far string descriptor for existing fixed-string
- data, and have BASIC recognize it as one of its own far strings.
-
- To make it easier for you to pass strings in these situations, routines in
- the BASIC run-time and stand-alone libraries are provided with Microsoft
- BASIC. These routines are described in the next section. After that, a
- general method for passing strings is outlined. This same method works for
- near and far strings. Specific examples of using this method with MASM, C,
- Pascal, and FORTRAN are then given.
-
-
- String-Processing Routines
-
- The routines described in the following sections are designed to transfer
- string data between languages, deallocate string space, and to determine the
- length and location of variable-length strings.
-
- The routines can be used by any language including BASIC and behave like any
- other external call. (See the DECLARE statement in the BASIC Language
- Reference for more information.) They are declared as external procedures
- and loaded from the appropriate BASIC run-time or stand-alone library during
- linking. For running and debugging within QBX, they need to be in a Quick
- library. The section "Passing Strings in QBX" later in this chapter
- describes how to do this.
-
-
- Transferring String Data
-
- The StringAssign routine lets you transfer string data from one language
- memory space to another. Typically it is used to transfer a BASIC
- variable-length string to a second language's fixed-length string and vice
- versa. The syntax is:
-
- CALL StringAssign( sourceaddress&, sourcelength%, destaddress&,
- destlength% )
-
- The sourceaddress& argument is a far pointer to the string descriptor if
- the source is a variable-length string, or it is a far pointer to the start
- of string data if the source is a fixed-length string. The sourcelength%
- argument is 0 for variable-length strings, otherwise it contains the length
- of the fixed-length string source in bytes. The destaddress& argument is a
- far pointer to the string descriptor if the destination is a variable-length
- string, or it is a far pointer to the start of string data if the
- destination is a fixed-length string. The destlength% argument is 0 for
- variable-length strings, otherwise it contains the length of the
- fixed-length string destination.
-
- Arguments are passed to StringAssign by value using the BYVAL keyword. The
- following is an example of the declaration:
-
- DECLARE SUB StringAssign(BYVAL Src&, BYVAL SrcLen%, BYVAL Dest&,_
- BYVAL DestLen%)
-
-
- When far pointers are not available, they can be generated by passing the
- segment and the offset separately. For example, assume that the segment,
- offset, and length of an external fixed-length string are contained in the
- variables FixedSeg%, FixedOff%, and FixedLength%. The string can be assigned
- to variable-length string A$ as follows:
-
- DECLARE SUB StringAssign(BYVAL SrcSeg%,BYVAL SrcOff%,BYVAL SrcLen%,_
- BYVAL DestSeg%,BYVAL DestOff%,BYVAL DestLen%)
- CALL StringAssign(FixedSeg%,FixedOff%,FixedLength%,VARSEG(A$),_
- VARPTR(A$),0)
-
- To assign the variable-length string A$ back to the fixed-length string,
- BASIC does the reverse:
-
- CALL StringAssign(VARSEG(A$),VARPTR(A$),0,FixedSeg%,FixedOff%,_
- FixedLength%)
-
- MASM, C, Pascal, and FORTRAN deal only with fixed-length strings. When
- programming in these languages you can use StringAssign to create a new
- BASIC variable-length string and transfer fixed-string data to it. To
- transfer a MASM string containing the word "hello" to a BASIC string, for
- example, you use this data structure:
-
- fixedstring db "Hello" ; source of data
- descriptor dd 0; descriptor for destination
-
- The second data element, descriptor, is a 4-byte string descriptor
- initialized to zero. BASIC interprets this to mean that it should create a
- new variable-length string and associate it with the address descriptor.
- Assigning the fixed-length string to it is accomplished by the following:
-
- pushds;segment of near fixed string
- leaax, fixedstring;offset of near fixed string
- pushax
- movax, 5;fixed string length
- pushax
- pushds;segment of descriptor
- leaax, descriptor ;offset of descriptor
- pushax
- xorax, ax ;0 means that destination
- pushax;is a variable-length string
- extrnstringassign: proc far;transfer the data
- callstringassign
-
- When the call to StringAssign is made, BASIC will fill in the double-word
- descriptor with the correct string descriptor.
-
-
- Note
-
- When creating a new variable-length string you must allocate 4-bytes of
- static data for a string descriptor as shown in the preceding procedure.
- Allocating the data on the stack will not work.
-
- A new variable-length string that is created with StringAssign can be used
- as an argument to a procedure. The address of the string descriptor is
- pushed on the stack and thus becomes a procedure parameter that can be
- processed like any other variable-length string. Examples of creating
- strings in all languages with the StringAssign routine can be found in the
- code examples which follow this section.
-
-
- Deallocating String Data
-
- The StringRelease routine deallocates variable-length strings created in a
- non-BASIC language by StringAssign. This frees the space in BASIC's string
- data area. You can use this routine to deallocate strings after your string
- processing tasks are completed. StringRelease has the following syntax:
-
- CALL StringRelease( string-descriptor% )
-
- The string-descriptor% argument is a near pointer to the variable-length
- string descriptor. The pointer is passed by value.
-
- To perform the release with MASM, assuming a descriptor for the
- variable-length string NoLongerNeeded exists at offset descriptor1, write
- the following code:
-
- leaax, descriptor1
- pushax
- extrnstringrelease: proc far
- callstringrelease
-
- Important
-
- StringRelease is only for variable-length strings created by a language
- other than BASIC. Never use it on strings that are created by BASIC. Doing
- so will cause unpredictable results.
-
-
- Computing String Data Addresses
-
- StringAddress is a routine that returns a far pointer to variable-length
- string data. It is the equivalent of SSEGADD. Assume that a descriptor for
- a BASIC variable-length string MissingString$ exists at offset descriptor1.
- MASM can find the far address of the string data with the following
- fragment:
-
- leaax, descriptor1
- pushax
- extrnstringaddress: proc far
- callstringaddress
-
- The far pointer is returned in DX:AX. DX holds the segment and AX holds the
- offset.
-
-
- Computing String Data Length
-
- StringLength is a routine function that returns the length of
- variable-length string data. It is the equivalent of LEN. Assume that a
- descriptor for a BASIC variable-length string LongString$ exists at the
- label descriptor1. MASM can find the length of string data with the
- following fragment:
-
- leaax, descriptor1
- pushax
- extrnstringlength: proc far
- call stringlength
-
- The length of LongString$ is returned in AX.
-
-
- Passing Variable-Length Strings
-
- Any time you need to pass variable-length strings from one language to
- another, process them, and then pass the string data back for further
- processing, the following general method may prove useful. This method uses
- the StringAssign routine which works for any calling language and also
- works whether you are passing near or far strings.
-
- 1. The first module makes the call, providing string pointers (and length
- if calling an external procedure).
-
- 2. The second module assigns the data to its own string type using the
- StringAssign routine.
-
- 3. The second module processes the data.
-
- 4. The second module assigns the output string data to the first module's
- string type using the StringAssign routine.
-
- 5. The second module returns from the call, providing string pointers
- (and length if returning to an external procedure).
-
- 6. The first module continues processing.
-
-
-
- BASIC Calling MASM
-
- This example shows how to pass variable-length strings between BASIC and
- MASM using the general method explained previously. The first module,
- MXSHKB.BAS, creates the strings A$ and B$, then passes their
- string-descriptor far pointers and data lengths to a MASM procedure named
- AddString. This procedure is contained in the file MXADSTA.ASM. The
- AddString procedure transfers the data to its own work area and then
- concatenates the two strings. Finally, AddString transfers the output to a
- BASIC variable-length string. Upon return, the BASIC module prints the
- output string.
-
-
- Important
-
- StringAssign, StringRelease, StringAddress, and StringLength may change
- the contents of the AX, BX, CX, DX, and ES registers and may change
- any flags other than the direction flag. These registers and flag should be
- saved before calling any of these string routines if their values are
- important to your MASM program.
-
- This MASM code uses the .MODEL directive which establishes compatible
- naming, calling, and passing conventions for BASIC, and it also uses
- simplified segment directives. This eliminates the need for separate GROUP
- and ASSUME directives. See Section 8.2, "Declaring Symbols External," of
- the Microsoft Macro Assembler 5.1 Programmer's Guide manual for a
- comparison of this method with one using full segment definitions. The
- version 5.1 PROC directive is employed. It includes new arguments that
- specify automatically saved registers, define arguments to the procedure,
- and set up text macros to use for the arguments. The PROC directive
- automatically generates the proper type of return based on the chosen memory
- model and cleans up the stack.
-
- '******************************MXSHKB.BAS*******************************
- DEFINT A-Z
-
- 'Define a non-BASIC procedure.
- DECLARE FUNCTION AddString$(SEG S1$,BYVAL S1Length,SEG S2$,BYVAL S2Length)
-
- 'Create the data.
- A$ = "To be or not to be;"
- B$ = " that is the question."
-
- 'Use non-BASIC function to add two BASIC far strings.
- C$ = (A$, LEN(A$), B$, LEN(B$))
-
- 'Print the result on the screen.
- PRINT C$
-
- ; This procedure accepts two far strings, concatenates them, and
- ; returns the result in the form of a far string.
- ;
- .modelmedium,basic;define memory model to match BASIC
- .stack
- .data?
- maxst = 50;maximum bytes reserved for strings
- inbuffer1dbmaxst dup(0);room for first fixed-length string
- inbuffer2dbmaxst dup(0);and second one
- outbufferdb2*maxst dup(0);work area for string processing
- .data
- shdd0;output string descriptor
- .code
- addstring procuses si di ds, s1:far ptr, s1len, s2:far ptr, s2len
-
- ;First get BASIC to convert BASIC strings into standard form:
- lesax,s1;Push far pointer
- pushes;to input string
- pushax;descriptor.
- xorax,ax;Push a zero to indicate
- pushax;it is variable length.
- pushds;Push far pointer
- leaax, inbuffer1;to destination
- pushax;string.
- movax,maxst;Push length of destination
- pushax;fixed-length string.
- extrnstringassign:proc far
- callstringassign;Call BASIC to assign
- ;variable-length string
- ;to fixed length string.
- lesax,s2;Push far pointer to
- pushes;second input string
- pushax;descriptor.
- xorax,ax;Push a zero to indicate
- pushax;it is variable length.
- pushds;Push far pointer
- leaax,inbuffer2;to second
- pushax;destination string.
- movax,maxst;Push length of destination
- pushax;fixed-length string.
-
- extrnstringassign:proc far
- callstringassign; Call BASIC to assign
- ; variable-length string
- ; to fixed length string.
- ; Concatenate strings:
- leasi,inbuffer1; Copy first string to buffer.
- leadi,outbuffer
- movax,ds
- moves,ax
- movcx,s1len
- repmovsb
- leasi,inbuffer2; Concatenate second string to
- movcx,s2len; end of first.
- repmovsb
-
- ;Get BASIC to convert result back into a BASIC string:
- pushds;Push far pointer to
- leaax,outbuffer;fixed-length result
- pushax;string.
- movax,s1len;Compute total length
- movbx,s2len;of fixed-length
- addax,bx;result string.
- pushax;Push length.
- pushds;Push far pointer
- lea ax,sh;to sh (BASIC will use
- pushax;this in StringAssign).
- xorax,ax;Push a zero for length
- pushax;indicating variable-length.
- callstringassign;Call BASIC to assign the
- ;result to sh.
- leaax,sh;Return output string pointer
- ;in ax and go back to BASIC.
- ret
-
- addstringendp
- end
-
-
- When returning to BASIC with only one string output, it is convenient for
- the MASM procedure to be a function, as in the preceding example. To pass
- back more than one string, do the following:
-
- 1. Have BASIC declare the procedure as a BASIC SUB procedure with output
- parameters included in the calling list.
-
- 2. Then call the SUB procedure with the output arguments.
-
- 3. In the MASM data segment, eliminate descriptor1 and add another output
- data block.
-
- 4. Add output parameters to the proc statement:
-
- 5. Then transfer each fixed-string output to one of the passed-in
- variable-length strings using the StringAssign routine.
-
-
-
- MASM Calling BASIC
-
- This example shows how to pass variable-length strings between MASM and
- BASIC using the general method explained in the preceding section. The first
- module is the MXSHKA.ASM file. For proper initialization, however, all
- mixed-language programs involving BASIC must start in BASIC. So startup
- begins in the second module, MXADSTB.BAS, which then calls the MASM
- procedure shakespeare.
-
- The shakespeare procedure creates the strings phrase1 and phrase2. For each
- string it also creates a data block that contains the length and a near
- pointer to the data. The elements of the data block are then passed to the
- BASIC procedure AddString by reference, along with a similar data block for
- output. The AddString procedure transfers the data to its own work area and
- then concatenates the two strings. It then transfers the output to a MASM
- fixed-length string sentence. Upon return, the MASM module prints the output
- string.
-
- ;*************************** SHAKESPEARE ******************************
- ; This program is found in file MXSHKA.ASM.
- ; It creates two strings and passes them to a BASIC procedure called
- ; AddString (in file MXADSTB.BAS). This procedure concatenates
- ; the strings and passes the result to MASM which prints it.
-
- .model medium,basic;Use same memory model as BASIC
- .stack
- .data
-
- ;Create the data
- phrase1db"To be or not to be;"
- phrase1lendw$-phrase1
- phrase1offdwphrase1
- phrase2 db" that is the question."
- phrase2lendw$-phrase2
- phrase2offdwphrase2
- sentencedb100 dup(0);Make room for return data
- sentencelen dw0;and a length indicator.
- sentenceoff dwsentence
-
- .code
- shakespeare procuses si
-
-
- ;First call BASIC to concatenate strings:
- leaax,phrase1off;Push far address of
- pushax;fixed-length string #1,
- lea ax,phrase1len;and its length.
- pushax
- lea ax,phrase2off;Do the same for the
- pushax;address of string #2,
- lea ax,phrase2len;and its length.
- pushax
- leaax,sentenceoff;Push far address of
- pushax;the return string,
- lea ax,sentencelen;and its length.
- pushax
- extrnaddstring:proc;Call BASIC function to
- calladdstring;concatenate the strings and
- ;put the result in the
- ;fixed-length return string.
-
- ; Call DOS to print string. The DOS string output routine (09H)
- ; requires that strings end with a "$" character.
- mov bx,sentencelen;Go to end of the result
- lea si,sentence ;string and add a
- mov byte ptr [bx + si],24h;"$" (24h) character.
-
- lea dx,sentence ;Set up registers
- mov ah,9;and call DOS
- int 21h ;to print result string.
- ret
-
- shakespeare endp
-
- end
- '************************XDTBBS*************
- DEFINT A-Z
-
- 'Start program in BASIC for proper initialization.
- ' Define external and internal procedures.
- DECLARE SUB shakespeare ()
- DECLARE SUB StringAssign(BYVAL srcsegment,BYVAL srcoffset,_
- BYVAL srclen,BYVAL destsegment,_
- BYVAL destoffset,BYVAL destlen)
- DECLARE SUB addstring (instrg1off,instrg1len,instrg2off,_
- instrg2len,outstrgoff,outstrglen)
- DECLARE SUB StringRelease (s$)
-
- 'Go to main routine in second language
- CALL shakespeare
-
- 'The non-BASIC program calls this SUB to add the two strings together
- SUB addstring (instrg1off,instrg1len,instrg2off,instrg2len,_
- outstrgoff,outstrglen)
-
- ' Create variable-length strings and transfer non-BASIC fixed strings
- ' to them. Use VARSEG() to compute the segment of the strings
- ' returned from the other language--this is the DGROUP segment,
- ' and all string descriptors are found in this segment (even
- ' though the far string itself is elsewhere).
-
- CALL StringAssign(VARSEG(a$), instrg1off, instrg1len, VARSEG(a$),_
- VARPTR(a$), 0)
- CALL StringAssign(VARSEG(b$), instrg2off, instrg2len, VARSEG(b$),_
- VARPTR(b$), 0)
-
- ' Process the strings--in this case, add them.
- c$ = a$ + b$
-
- ' Calculate the new output length.
- outstrglen = LEN(c$)
-
- ' Transfer string output to a non-BASIC fixed-length string.
- CALL StringAssign(VARSEG(c$), VARPTR(c$), 0, VARSEG(c$), outstrgoff,_
- outstrglen)
- END SUB
-
- BASIC Calling C
-
- This example shows how to pass variable-length strings between BASIC and C
- using the general method explained previously. The first module, MXSHKB.BAS,
- creates the strings A$ and B$, then passes their string-descriptor far
- pointers and data lengths to a C procedure called AddString. This procedure
- is contained in the file MXADSTC.C. The AddString procedure transfers the
- data to its own work area and then concatenates the two strings. It then
- transfers the output to a BASIC variable-length string. Upon return, the
- BASIC module prints the output string.
-
- '******************************MXSHKB.BAS*******************************
- DEFINT A-Z
- 'Define non-basic procedures
- DECLARE FUNCTION addstring$(SEG s1$,BYVAL s1length,SEG s2$,BYVAL s2length)
-
- 'Create the data
- A$ = "To be or not to be;"
- B$ = " that is the question."
- 'Use Non-BASIC function to add two BASIC far strings.
- C$ = addstring(A$, LEN(A$), B$, LEN(B$))
-
- 'Print the result on the screen.
-
- PRINT C$
-
- /* MXADSTC.C */
- #include <string.h>
-
- /* Function Prototypes force either correct data typing or compiler
- * warnings. Note all functions exported to BASIC and all BASIC (extern)
- * functions are declared with the far pascal calling convention.
- * WARNING: This must be compiled with the Medium memory model (/AM)
- */
- char * pascal addstring( char far *s1, int s1len,
- char far *s2, int s2len );
- extern void far pascal StringAssign( char far *source, int slen,
- char far *dest, int dlen );
-
- /* Declare global char array to contain new BASIC string descriptor.
- */
- char BASICDesc[4];
-
- char * pascal addstring( char far *s1, int s1len,
- char far *s2, int s2len )
-
- {
- char TS1[50];
- char TS2[50];
- char TSBig[100];
-
- /* Use the BASIC StringAssign routine to retrieve information
- * from the descriptors, s1 and s2, and place them in the temporary
- * arrays TS1 and TS2.
- */
- StringAssign( s1, 0, TS1, 49 );/* Get S1 as array of char */
- StringAssign( s2, 0, TS2, 49 );/* Get S2 as array of char */
-
- /* Copy the data from TS1 into TSBig, then append the data from
- * TS2.
- */
- memcpy( TSBig, TS1, s1len );
- memcpy( &TSBig[s1len], TS2, s2len );
-
- StringAssign( TSBig, s1len + s2len, BASICDesc, 0 );
-
- return BASICDesc;
- }
-
- When returning to BASIC with only one string output, it is convenient for
- the C program to be a function, as in the preceding example. To pass back
- more than one string, do the following:
-
- 1. Have BASIC declare the procedure as a BASIC SUB procedure with output
- parameters included in the calling list.
-
- 2. Call the SUB procedure with the output arguments C$ and D$.
-
- Suppose you want the original sentence returned in C$ and its reverse in D$.
- Make these modifications to the C code:
-
- 1. Change the prototype and function header for AddString to include the
- outputs, and declare it a void.
-
- 2. Eliminate the data block for the string descriptor BASICDesc[4].
- Change the StringAssign call so that TSBig is assigned to s3 instead
- of BASICDesc.
-
- 3. Add the following lines of code:
-
- TSBig[s1len + s2len] = '\0';
- strrev( TSBig );
- StringAssign( TSBig, s1len + s2len, s4, 0 );
-
- 4. Delete the return statement.
-
-
-
- C Calling BASIC
-
- This example shows how to pass variable-length strings between C and BASIC
- using the general method explained previously. The first module is the
- MXSHKC.C file. For proper initialization, however, all mixed-language
- programs involving BASIC must start in BASIC. So startup begins in a second
- module, MXADSTB.BAS, which then calls the C procedure shakespeare.
-
- The shakespeare procedure creates the strings s1 and s2. For each string it
- also creates a data block that contains the length and a near pointer to the
- data. The elements of the data block are then passed to the BASIC SUB
- procedure AddString by reference, along with a similar data block for
- output. The AddString SUB procedure transfers the data to its own work area
- and then concatenates the two strings. It then transfers the output to the C
- fixed-length string s3. Upon return, the C module prints the output string.
-
- /* MXSHKC.C */
- #include <stdio.h>
- #include <string.h>
-
- /* Prototype the shakespeare function (our function)
- * The prototypes force either correct data typing or compiler warnings.
- */
- void far pascal shakespeare( void );
- extern void far pascal addstring( char ** s1, int * s1len,
- char ** s2, int * s2len,
- char ** s3, int * s3len );
-
-
- void far pascal shakespeare( void )
- {
- char * s1 = "To be or not to be;";
- int s1len;
- char * s2 = " that is the question.";
- int s2len;
- char s3[100];
- int s3len;
- char * s3ad = s3;
-
- s1len = strlen( s1 );
- s2len = strlen( s2 );
- addstring( &s1, &s1len, &s2, &s2len, &s3add, &s3len );
-
- s3[s3len] = '\0';
- printf("\n%s", s3 );
- }
- '*************************MXADSTRB.BAS***************************
- DEFINT A-Z
-
- 'Start program in BASIC for proper initialization.
- ' Define external and internal procedures.
- DECLARE SUB shakespeare ()
- DECLARE SUB StringAssign(BYVAL srcsegment,BYVAL srcoffset,_
- BYVAL srclen,BYVAL destsegment,_
- BYVAL destoffset,BYVAL destlen)
- DECLARE SUB addstring (instrg1off,instrg1len,instrg2off,_
- instrg2len,outstrgoff,outstrglen)
- DECLARE SUB StringRelease (s$)
-
- 'Go to main routine in second language
- CALL shakespeare
-
- 'The non-BASIC program calls this SUB to add the two strings together
- SUB addstring (instrg1off,instrg1len,instrg2off,instrg2len,_
- outstrgoff,outstrglen)
-
- ' Create variable-length strings and transfer non-BASIC fixed strings
- ' to them. Use VARSEG() to compute the segment of the strings
- ' returned from the other language--this is the DGROUP segment,
- ' and all string descriptors are found in this segment (even
- ' though the far string itself is elsewhere).
-
- CALL StringAssign(VARSEG(a$), instrg1off, instrg1len, VARSEG(a$),_
- VARPTR(a$), 0)
- CALL StringAssign(VARSEG(b$), instrg2off, instrg2len, VARSEG(b$),_
- VARPTR(b$), 0)
-
-
- ' Process the strings--in this case, add them.
- c$ = a$ + b$
-
- ' Calculate the new output length.
- outstrglen = LEN(c$)
- ' Transfer string output to a non-BASIC fixed-length string.
- CALL StringAssign(VARSEG(c$), VARPTR(c$), 0, VARSEG(c$), outstrgoff,_
- outstrglen)
- END SUB
-
-
- BASIC Calling FORTRAN
-
- This example shows how to pass variable-length strings between BASIC and
- FORTRAN using the general method explained above. The first module,
- MXSHKB.BAS, creates the strings A$ and B$, then passes their
- string-descriptor far pointers and data lengths to a FORTRAN procedure
- called ADDSTR. This procedure is contained in the file MXADSTF.FOR. ADDSTR
- transfers the data to its own work area and then concatenates the two
- strings. It then transfers the output to a BASIC variable-length string.
- Upon return, the BASIC module prints the output string.
-
- '******************************MXSHKB.BAS*******************************
- DEFINT A-Z
- 'Define non-BASIC procedures.
- DECLARE FUNCTION AddString$(SEG s1$,BYVAL s1length,SEG s2$,BYVAL s2length)
-
-
- 'Create the data.
- A$ = "To be or not to be;"
- B$ = " that is the question."
-
- 'Use Non-BASIC function to add two BASIC far strings.
- C$ = AddString(A$, LEN(A$), B$, LEN(B$))
-
- 'Print the result on the screen.
- PRINT C$
- C ********************
- ADDSTRING *********************
- C This program is in file MXADSTF.FOR
- C Declare interface to Stringassign subprogram. The pointer fields are
- C declared INTEGER*4, so that different types of far pointers can be
- C passed without conflict. The INTEGER*4 fields are essentially generic
- C pointers. [VALUE] must be specified, or FORTRAN will pass pointers to
- C pointers. INTEGER*2 also passed by [VALUE], to be consistent with
- C declaration of StringAssign.
- C
- INTERFACE TO SUBROUTINE STRASG [ALIAS:'STRINGASSIGN'] (S,SL,D,DL)
- INTEGER*4 S [VALUE]
- INTEGER*2 SL [VALUE]
- INTEGER*4 D [VALUE]
- INTEGER*2 DL [VALUE]
- END
- C
- C Declare heading of Addstring function in the same way as above: the
- C pointer fields are INTEGER*4
- C
- INTEGER*2 FUNCTION ADDSTR [ALIAS:'ADDSTRING'] (S1,S1LEN,S2,S2LEN)
- INTEGER*4 S1 [VALUE]
- INTEGER*2 S1LEN [VALUE]
- INTEGER*4 S2 [VALUE]
- INTEGER*2 S2LEN [VALUE]
- C
- C Local parameters TS1, TS2, and BIGSTR are temporary strings. STRDES is
- C a four-byte object into which Stringassign will put BASIC string
- C descriptor.
- C
- CHARACTER*50 TS1, TS2
- CHARACTER*100 BIGSTR
- INTEGER*4 STRDES
-
- TS1 = " "
- TS2 = " "
- STRDES = 0
-
- C
- C Use the LOCFAR function to take the far address of data. LOCFAR returns
- C a value of type INTEGER*4.
- C
- CALL STRASG (S1, 0, LOCFAR(TS1), S1LEN)
- CALL STRASG (S2, 0, LOCFAR(TS2), S2LEN)
- BIGSTR = TS1(1:S1LEN) // TS2(1:S2LEN)
- CALL STRASG (LOCFAR(BIGSTR), S1LEN+S2LEN, LOCFAR(STRDES), 0)
- ADDSTR = LOC(STRDES)
- RETURN
- END
-
- Instead of returning a string as the output of a function, you can also pass
- a string back as a subroutine parameter. In fact, to pass back more than one
- string, you must use this method. To do so, make these changes to the
- preceding code:
-
- 1. Declare the subroutine as a BASIC SUB procedure with the output
- parameter included in the calling list:
-
- 2. Call the subprogram with output argument C$:
-
- 3. In the FORTRAN module, change the FUNCTION declaration to a
- SUBROUTINE declaration, in which the subroutine accepts an additional
- parameter not passed by value:
-
- 4. Change the line of code that sets the following return value:
-
- ADDSTR = LOC(STRDES) Into a statement that sets the value of the following
- output string:
-
- OUTS = STRDES
- In some cases, you may want to return several strings. To do so, simply add
- additional INTEGER*4 parameters to your procedure declaration.
-
-
- FORTRAN Calling BASIC
-
- This example shows how to pass variable-length strings between FORTRAN and
- BASIC using the general method explained previously. The first module is the
- MXSHKF.FOR file. For proper initialization, however, all mixed-language
- programs involving BASIC must start in BASIC. So startup begins in a second
- module, MXADSTB.BAS, which then calls the FORTRAN procedure SHAKES.
-
- The SHAKES procedure creates the strings STR1 and STR2. For each string it
- also creates a data block that contains the length and a near pointer to the
- data. The elements of the data block are then passed to the BASIC AddString
- procedure by reference, along with a similar data block for output. The
- AddString procedure transfers the data to its own work area and then
- concatenates the two strings. It then transfers the output to the FORTRAN
- fixed-length string STR3. Upon return, the FORTRAN module prints the output
- string.
-
- C *********************** SHAKESPEARE ****************
- C This program is in file MXSHKF.FOR
- C Declare interface to BASIC routine ADDSTRING.
- C All parameters must be passed NEAR, for compatibility with BASIC's
- C conventions.
- C
-
- INTERFACE TO SUBROUTINE ADDSTR[ALIAS:'ADDSTRING']
- * (S1,L1,S2,L2,S3,L3)
- INTEGER*2 S1 [NEAR]
- INTEGER*2 L1 [NEAR]
- INTEGER*2 S2 [NEAR]
- INTEGER*2 L2 [NEAR]
- INTEGER*2 S3 [NEAR]
- INTEGER*2 L3 [NEAR]
- END
- C
- C Declare subroutine SHAKESPEARE, which declares two strings, calls
- C BASIC subroutine ADDSTRING, and prints the result.
- C
- SUBROUTINE SHAKES [ALIAS:'SHAKESPEARE']
- CHARACTER*50 STR1, STR2
- CHARACTER*100 STR3
- INTEGER*2 STRLEN1, STRLEN2, STRLEN3
- INTEGER*2 TMP1, TMP2, TMP3
-
- C
- C The subroutine uses FORTRAN LEN_TRIM function, which returns the
- C length of string, excluding trailing blanks. (All FORTRAN strings
- C are initialized to blanks.)
- C
- STR1 = 'To be or not to be;'
- STRLEN1 = LEN_TRIM(STR1)
- STR2 = ' that is the question.'
- STRLEN2 = LEN_TRIM(STR2)
- TMP1 = LOC(STR1)
- TMP2 = LOC(STR2)
- TMP3 = LOC(STR3)
- CALL ADDSTR (TMP1, STRLEN1, TMP2, STRLEN2, TMP3, STRLEN3)
- WRITE (*,*) STR3
- END
-
- '*************************MXADSTRB.BAS***************************
- DEFINT A-Z
-
- 'Start program in BASIC for proper initialization.
- ' Define external and internal procedures.
- DECLARE SUB shakespeare ()
- DECLARE SUB StringAssign(BYVAL srcsegment,BYVAL srcoffset,_
- BYVAL srclen,BYVAL destsegment,_
- BYVAL destoffset,BYVAL destlen)
- DECLARE SUB addstring (instrg1off,instrg1len,instrg2off,_
- instrg2len,outstrgoff,outstrglen)
- DECLARE SUB StringRelease (s$)
-
- 'Go to main routine in second language
- CALL shakespeare
-
- 'The non-BASIC program calls this SUB to add the two strings together
- SUB addstring (instrg1off,instrg1len,instrg2off,instrg2len,_
- outstrgoff,outstrglen)
-
- ' Create variable-length strings and transfer non-BASIC fixed strings
- ' to them. Use VARSEG() to compute the segment of the strings
- ' returned from the other language--this is the DGROUP segment,
- ' and all string descriptors are found in this segment (even
- ' though the far string itself is elsewhere).
-
- CALL StringAssign(VARSEG(a$), instrg1off, instrg1len, VARSEG(a$),_
- VARPTR(a$), 0)
- CALL StringAssign(VARSEG(b$), instrg2off, instrg2len, VARSEG(b$),_
- VARPTR(b$), 0)
-
- ' Process the strings--in this case, add them.
- c$ = a$ + b$
-
- ' Calculate the new output length.
- outstrglen = LEN(c$)
-
- ' Transfer string output to a non-BASIC fixed-length string.
- CALL StringAssign(VARSEG(c$), VARPTR(c$), 0, VARSEG(c$), outstrgoff,_
- outstrglen)
- END SUB
-
-
- BASIC Calling Pascal
-
- This example shows how to pass variable-length strings between BASIC and
- Pascal using the general method explained previously. The first module,
- MXSHKB.BAS, creates the strings A$ and B$, then passes their
- string-descriptor far pointers and data lengths to a Pascal procedure called
- ADDSTRING. This procedure is contained in the file MXADSTP.PAS. ADDSTRING
- transfers the data to its own work area and then concatenates the two
- strings. It then transfers the output to a BASIC variable-length string.
- Upon return, the BASIC module prints the output string.
-
- '******************************MXSHKB.BAS*******************************
- DEFINT A-Z
- 'Define non-basic procedures.
- DECLARE FUNCTION AddString$(SEG s1$,BYVAL s1length,SEG s2$,_
- BYVAL s2length)
-
- 'Create the data.
- A$ = "To be or not to be;"
- B$ = " that is the question."
-
- 'Use Non-BASIC function to add two BASIC far strings.
- C$ = AddString(A$, LEN(A$), B$, LEN(B$))
-
- 'Print the result on the screen.
- PRINT C$
- {
- **********************ADDSTRING ***********************
- This program is in file MXADSTP.PAS }
-
- { Module MXADSTP--takes address and lengths of two BASIC
- strings, concatenates, and creates a BASIC string descriptor. }
- MODULE MAXADSTP;
- { Declare type ADSCHAR for all pointer types. For ease of programming,
- all address variables in this module are considered pointers to
- characters, and all strings and string descriptors are considered
- arrays of characters. Also, declare the BASIC string descriptor
- type as a simple array of four characters. }
-
- TYPE
- ADSCHAR = ADS OF CHAR;
- ADRCHAR = ADR OF CHAR;
- STRDESC = ARRAY[0..3] OF CHAR;
- VAR
- MYDESC : STRDESC;
- { Interface to procedure BASIC routine StringAssign. If source
- string is a fixed-length string, S points to string data and SL
- gives length. If source string is a BASIC variable-length string,
- S points to a BASIC string descriptor and SL is 0. Similarly for
- destination string, D and DL. }
- PROCEDURE STRINGASSIGN (S:ADSCHAR; SL:INTEGER;
- D:ADSCHAR; DL:INTEGER ); EXTERN;
-
- FUNCTION ADDSTRING (S1:ADSCHAR; S1LEN:INTEGER;
- S2:ADSCHAR; S2LEN:INTEGER) : ADRCHAR;
-
- VAR
- BIGSTR : ARRAY[0..99] OF CHAR;
- { Execute function by copying S1 to the array BIGSTR, appending S2
- to the end, and then copying combined data to the string descriptor. }
-
- BEGIN
- STRINGASSIGN (S1, 0, ADS BIGSTR[0], S1LEN);
- STRINGASSIGN (S2, 0, ADS BIGSTR[S1LEN], S2LEN);
- STRINGASSIGN (ADS BIGSTR[0], S1LEN+S2LEN, ADS MYDESC[0], 0);
- ADDSTRING := ADR MYDESC;
- END; { End Addstring function,}
- END. {End module.}
-
-
- Instead of returning a string as the output of a function, you can also
- pass a string back as a procedure parameter. In fact, to pass back more than
- one string, you must use this method. To do so, make the following changes
- to the preceding code:
-
- 1. Declare the procedure as a BASIC SUB procedure with the output
- parameter included in the calling list:
-
- 2. Then call the subprogram with output argument C$:
-
- 3. In the Pascal module, change the EXTERN declaration from a function to
- a procedure, in which the procedure accepts an additional VAR
- parameter:
-
- 4. Change the line of code that sets the following return value:
-
-
- ADDSTRING:=MYDESC; Into a statement that sets the value of the following
- output string:
-
-
- OUTSTR:=MYDESC;
- In some cases, you may want to return several strings. To do so, simply add
- additional VAR parameters to your procedure declaration. Each formal
- argument (parameter) should be of type STRDESC. You can use another name for
- this type, but remember to define this type or some equivalent type
- (ARRAY[0..3] OF CHAR) at the beginning of your Pascal module.
-
-
- Pascal Calling BASIC
-
- This example shows how to pass variable-length strings between Pascal and
- BASIC using the general method explained previously. The first module is the
- MXSHKP.PAS file. For proper initialization, however, all mixed-language
- programs involving BASIC must start in BASIC. So startup begins in a second
- module, MXADSTB.BAS, which then calls the Pascal procedure shakespeare.
-
- The shakespeare procedure creates the strings STR1 and STR2. For each string
- it also creates a data block that contains the length and a near pointer to
- the data. The elements of the data block are then passed to the BASIC
- procedure AddString by reference, along with a similar data block for
- output. The AddString procedure transfers the data to its own work area and
- then concatenates the two strings. It then transfers the output to the
- Pascal fixed-length string STR3. Upon return, the Pascal module prints the
- output string.
-
- {
- ************************ SHAKESPEARE ******************
- This program is in file MXSHKP.PAS }
-
- MODULE MPAS;
- TYPE
- ADRCHAR = ADR OF CHAR;
- VAR
- S1, S2, S3 : LSTRING (100);
- S1LEN, S2LEN, S3LEN : INTEGER;
- TMP1, TMP2, TMP3 : ADRCHAR;
- { Declare interface to procedure ADDSTRING, which concatenates first
- two strings passed and places the result in the third string
- passed. }
- PROCEDURE ADDSTRING (VAR TMP1:ADRCHAR; VAR STR1LEN:INTEGER;
- VAR TMP2:ADRCHAR; VAR STR2LEN:INTEGER;
- VAR TMP3:ADRCHAR; VAR STR3LEN:INTEGER ); EXTERN;
-
- { Procedure Shakespeare declares two strings, calls BASIC procedure
- AddString to concatenate them, then prints results. With LSTRING
- type, note that element 0 contains length byte. String data starts
- with element 1. }
- PROCEDURE SHAKESPEARE;
- BEGIN
- S1:='To be or not to be;';
- S1LEN:=ORD(S1[0]);
- S2:=' that is the question.';
- S2LEN:=ORD(S2[0]);
- TMP1:=ADR(S1[1]);
- TMP2:=ADR(S2[1]);
- TMP3:=ADR(S3[1]);
- ADDSTRING (TMP1, S1LEN, TMP2, S2LEN, TMP3, S3LEN);
- S3[0]:=CHR(S3LEN);
- WRITELN(S3);
- END;
- END.
-
- '*************************MXADSTRB.BAS***************************
- DEFINT A-Z
-
- 'Start program in BASIC for proper initialization.
- ' Define external and internal procedures.
- DECLARE SUB shakespeare ()
- DECLARE SUB StringAssign(BYVAL srcsegment,BYVAL srcoffset,_
- BYVAL srclen,BYVAL destsegment,_
- BYVAL destoffset,BYVAL destlen)
- DECLARE SUB addstring (instrg1off,instrg1len,instrg2off,_
- instrg2len,outstrgoff,outstrglen)
- DECLARE SUB StringRelease (s$)
-
- 'Go to main routine in second language
- CALL shakespeare
-
- 'The non-BASIC program calls this SUB to add the two strings together
- SUB addstring (instrg1off,instrg1len,instrg2off,instrg2len,_
- outstrgoff,outstrglen)
-
- ' Create variable-length strings and transfer non-BASIC fixed strings
- ' to them. Use VARSEG() to compute the segment of the strings
- ' returned from the other language--this is the DGROUP segment,
- ' and all string descriptors are found in this segment (even
- ' though the far string itself is elsewhere).
-
- CALL StringAssign(VARSEG(a$), instrg1off, instrg1len, VARSEG(a$),_
- VARPTR(a$), 0)
- CALL StringAssign(VARSEG(b$), instrg2off, instrg2len, VARSEG(b$),_
- VARPTR(b$), 0)
-
- ' Process the strings--in this case, add them.
- c$ = a$ + b$
-
- ' Calculate the new output length.
- outstrglen = LEN(c$)
-
- ' Transfer string output to a non-BASIC fixed-length string.
- CALL StringAssign(VARSEG(c$), VARPTR(c$), 0, VARSEG(c$), outstrgoff,_
- outstrglen)
- END SUB
-
-
- Passing Strings in QBX
-
- If you have a BASIC module that calls an external procedure, as in the
- preceding examples, you may want to debug the BASIC code in QBX. If you are
- using any of the string-processing routines, you'll need to make a Quick
- library that contains the routines. Here's how to do this:
-
- 1. Compile the external procedure.
-
- 2. Link the object file with the necessary libraries plus the QBXQLB.LIB
- library. Use the /Q option. For instance, for the first C example:
-
- 3. Load the new Quick library when starting up QBX by using the /L option
- as shown here:
-
-
- QBX /L MXADSTC.QLB
- If you just want to test the new string routines in QBX, without calling an
- external procedure, load the QBX.QLB Quick library when starting QBX.
-
-
- Passing Variable-Length String Arrays
-
- To manage variable-length string arrays, BASIC creates a data block in
- DGROUP containing a series of variable-length string descriptors -- one for
- each array element. The descriptors begin at address of the first element in
- the array, for example the first descriptor in a three dimensional
- zero-based array named A$() would be A$(0,0,0). The descriptors are in
- column-major order, where the rightmost dimension changes first. Note,
- however, that if you compile with the /R option the descriptors will be in
- row-major form. For examples of this, see the Microsoft Mixed-Language
- Programming Guide.
-
- Using the StringAssign routine, a non-BASIC language can copy a BASIC
- variable-length string array into its own workspace, modify any data element
- (even change its length), and copy the changed array back to BASIC.
-
- Assume, for example, that A$() is a one-dimensional BASIC string array that
- contains these elements indexed with numbers 1 to 10:
-
- A,BB,CCC,DDDD,EEEEE,FFFFFF,GGGGGGG,HHHHHHHH,IIIIIIIII,JJJJJJJJJJ
-
- The elements need to be changed to:
-
- jjjjjjjjjj,iiiiiiiii,hhhhhhhh,ggggggg,ffffff,eeeee,dddd,ccc,bb,a
-
- To accomplish this, BASIC could call a MASM procedure, passing it the
- address of the first string descriptor in the array:
-
- DECLARE SUB ChangeArray(S$)
- CALL ChangeArray(A$(1))
-
-
- The array transfer is accomplished by:
-
- .modelmedium,basic
-
- .data
- arraydw100 dup(0);Create space for 10 element array
- .code
- changearray proc uses si di, arraydescriptor: near ptr ;pointer to array
- extrnstringassign:proc;declare BASIC callback
- movcx, 10 ;number of transfers
- movsi, arraydescriptor;first source
- leadi, array ;first destination
- transferin:pushcx ;preserve cx during callback
-
- pushds;far pointer to source--
- pushsi
- xorax,ax ;a variable-length string
- pushax
- pushds;far pointer to destination--
- pushdi;a fixed-length string
- movax, 10 ;10 bytes long
- pushax
- callstringassign;go transfer one string
- popcx;restore cx
- addsi, 4;update pointers
- adddi,10
- looptransferin;last transfer?
-
- ;Now, change the data to lower case
-
- movcx,100
- leabx, array
- more:cmpbyte ptr[bx], 0
- jzskip
- addbyte ptr[bx], 32
- skip:incbx
- loopmore
-
- ; and send it back out, last element first.
-
- movcx, 10;number of transfers
- leasi, array + 90 ;first source--the last element
- movdi, arraydescriptor;first destination
- transferout:pushcx;preserve cx during call
-
- pushds;far pointer to source--
- pushsi
- push cx;a fixed-length string
- pushds;far pointer to destination--
- pushdi;a variable-length
- xorax,ax;string.
- pushax
- callstringassign;go transfer one string
- popcx;restore variables
- subsi, 10;update pointers
- adddi, 4
- looptransferout;last transfer?
-
- ret
-
- changearrayendp
- end
-
- Passing Fixed-Length Strings
-
- If you want to pass BASIC fixed-length string data to and from another
- language, you can also use the StringAssign routine. This works in spite of
- the fact that fixed-length strings are illegal as parameters in BASIC
- procedures. In other words, this is impossible:
-
- DECLARE FUNCTION PassFixed AS STRING * 10 (FixedString1 AS STRING * 10)
-
- You can, however, achieve the same result by using fixed-string arguments
- and variable-string parameters:
-
- ' Declare an external function.
- DECLARE FUNCTION PassFixed$(FixedString1$)
- DIM InputString AS STRING * 10, OutputString AS STRING * 20
- OutputString = PassFixed(InputString)
-
- When BASIC makes the call, it creates the variable-length string
- FixedString1$ and copies the data from the fixed string into it. It pushes
- the address of the FixedString1$ descriptor (remember, this is a
- variable-length string) onto the stack.
-
-
- From here on, processing is the same as for the preceding examples. The
- called function, before returning, creates a 4-byte string descriptor filled
- with zeros. It uses StringAssign to transfer its output data to this newly
- created variable-length string. The address of the string's descriptor is
- placed in AX and the return is made.
-
- When the equal operator (=) is executed in the last code statement, BASIC
- assigns the data from the returned variable-length string to the the
- fixed-length string OutputString.
-
-
- Getting Online Help
-
- Online Help is available in QBX for mixed-language programming. For help
- with making an external call, see the CALL, CALLS, and DECLARE non-BASIC
- Statement categories in the Help Keyword Index. More information can be
- found in the Mixed-Language Programming section listed in the Help Table of
- Contents.
-
- Sample code in the online Help screens can be copied and pasted to a file.
- All copied BASIC code samples will execute within QBX. Any of the code
- samples can also be compiled, linked into executable files or Quick
- libraries, or made into object-module libraries. See Chapters 18, "Using
- LINK and LIB" and 19, "Creating and Using Quick Libraries," for more
- information.
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 14: OS/2 Programming
- ────────────────────────────────────────────────────────────────────────────
-
- Microsoft BASIC enables you to create programs for the OS/2 protected-mode
- environment as well as the real-mode (DOS) environment. This chapter
- explains how OS/2 protected-mode programs differ from BASIC programs written
- for DOS. You'll learn how to write, compile, and link programs that run
- under OS/2, as well as the following:
-
- ■ What libraries and include files you'll need.
-
- ■ How to call OS/2 functions in your program.
-
- ■ Limitations for BASIC programs.
-
- ■ Language changes for protected-mode-only programs.
-
- ■ How to prepare your programs for debugging.
-
-
-
- Creating Real or Protected-Mode Programs
-
- With BASIC you can create protected-mode programs or real-mode programs. You
- cannot create bound programs -- programs that run in real and protected
- modes. You also cannot bind a BASIC executable file after linking.
-
- To create an OS/2 program, BASIC provides the QuickBASIC Extended (QBX)
- programming environment.
-
- QBX can be run only in real mode, although you can create OS/2 programs that
- run in real or protected mode from QBX. The QBX environment contains
- context-sensitive online Help.
-
- While this chapter assumes you are writing, compiling, and linking from QBX
- you can, if you wish, invoke the BASIC Compiler (BC) and the LINK utility
- separately from the command line.
-
-
- Editing Source Code
-
- To edit source code, you can use the editing facilities built into QBX. Use
- the F1 key to access online Help for information about using specific
- editing features and commands.
-
- Except where noted, you may write your program using the BASIC statements
- and functions described in the BASIC Language Reference. There are certain
- BASIC statements and functions, however, that behave differently or require
- extra caution in protected mode. These are described in the following
- sections.
-
-
- Language Changes for Protected Mode
-
- This section describes specific BASIC statements and functions that behave
- differently or require extra caution in protected mode. The most significant
- differences between real mode and protected mode concern memory management.
- Others concern BASIC statements that directly access the machine's hardware,
- an activity that is not always appropriate in a protected environment. Table
- 14.1 lists and explains the statements and functions that are changed for
- protected mode in Microsoft BASIC.
-
-
-
- Making OS/2 Calls in Your Program
-
- Microsoft BASIC allows you to make direct calls to OS/2 functions when
- running in protected mode. When invoking an OS/2 function, it is necessary
- to use the syntax for calling a BASIC function, even if you are not
- interested in the error code or other information that might be returned by
- the OS/2 function.
-
- For example, the following program fragment invokes the OS/2 function
- DOSBEEP:
-
- ' Include the file BSEDOSPC.BI
- REM $INCLUDE: 'bsedospc.bi'
- ' Invoke the DOSBEEP function
- x = DOSBEEP(100, 200)
-
- OS/2 Include Files
-
- To provide support for both OS/2 function calls and type definitions for
- data structures used by OS/2 functions, Microsoft BASIC provides several
- OS/2 include files. You can insert include files into your program with a
- $INCLUDE metacommand.
-
- Microsoft BASIC provides the following OS/2 include files:
-
- BSEDOSFL.BI: Device drivers, file management
- BSESUBMO.BI: Mouse
- BSEDOSPE.BI: National language, resource management,
- module management, date/time and timer, memory management,
- information segments
-
- These include files provide support only for OS/2 functions that are
- appropriate for BASIC. Many of the omitted functions involve multiple thread
- capabilities, while others duplicate existing BASIC support, such as
- keyboard and screen I/O. Because these include files provide a standard
- interface to OS/2 functions, you should avoid changing their contents
- unnecessarily.
-
- The process of calling OS/2 functions requires certain data types that are
- not intrinsic to BASIC. Because of this, the include files listed in the
- preceding table use standard methods for simulating those types. How various
- data types are simulated in the OS/2 include files for BASIC is described in
- the following sections.
-
-
- Unsigned Values
-
- Unsigned values are not intrinsic to BASIC. The signed version of the given
- type is used instead.
-
-
- Pointers in User-Defined Types
-
- In cases where OS/2 requires a pointer as a field of a user-defined type,
- the include files use the type ADDRESS, which is defined at the beginning of
- the include file BSEDOSPC.BI. You can fill in the fields of this type with
- VARSEG and SADD in the case of a variable-length string, or VARSEG and
- VARPTR in the case of other data objects.
-
-
- Far Character Pointers in Function Parameters
-
- The far character pointer for OS/2 functions (far char *) is simulated with
- two parameters: a segment and an offset. Note that both of these values are
- integers. Thus, if the original declaration would have been DOSXYZ( far char
- * ), it is declared in this form:
-
- DOSXYZ( BYVAL S1s AS INTEGER, BYVAL S1o AS INTEGER )
-
- You would call this function with a statement in the following form, where
- FixedLen is a fixed-length string:
-
- DOSXYZ( VARSEG(FixedLen), VARPTR(FixedLen) )
-
- You can call the function with a statement in the following form if VarLen
- is a variable-length string:
-
- DOSXYZ( VARSEG(VarLen), SADD(VarLen) )
-
- Pointer to a Function in a Function Parameter
-
- The pointer to a function is simulated with two parameters, just as in the
- case of a far character pointer. The first integer represents the segment
- and the second integer represents the offset (see the preceding section).
- BASIC itself has no means of finding the location of a function; however, if
- you find that location with a language procedure written in another
- language, the address can be used in an OS/2 function call from BASIC.
-
-
- Character in a Function Parameter
-
- A character in a function parameter is simulated with an integer. This
- method is safe because parameters are always passed as words. Thus, a
- character is extended to an integer before being passed in other languages.
-
- Example
-
- The following example prompts you for a file or set of files you want
- information on, and then uses the DosFindFirst and DosFindNext API
- functions to retrieve the information.
-
-
- ' This is an OS/2 protect mode program: compile with the /Lp switch
-
- CONST TRUE = -1
- CONST FALSE = 0
- ' $INCLUDE: 'bsedosfl.bi'
-
- DEFINT A-Z
-
- COLOR 15, 1
- DIM buffer AS FILEFINDBUF
- DIM Filelist(255) AS FILEFINDBUF
- DIM reserved AS LONG
-
- CLS
-
- PRINT "Test of DOSFINDFIRST..."
-
- DO
- PRINT
- INPUT "Enter the Filename(s) : "; flname$
- flname$ = flname$ + CHR$(0)
-
- counter = 0
- atr = 0 + 2 + 4 + 16 'normal + hidden + system + subdirectory
- dirh = 1
- searchcount = 255
- bufflen = 36
- X = DosFindFirst%(VARSEG(flname$) SADD(flname$),dirh,_
- atr,buffer,bufflen,searchcount,reserved)
- IF (X = 0) THEN
- DO
- counter = counter + 1
- Filelist(counter) = buffer
-
- ' clear out buffer for call to DosFindNext
- buffer.achName = STRING$(13, 32) 'assign blanks
- buffer.fdateLastWrite = 0
- buffer.ftimeLastWrite = 0
- LOOP WHILE (DosFindNext%(dirh, buffer, bufflen, searchcount) = 0)
- ELSE
- PRINT "No MATCH was found"
- END
- END IF
-
-
- PRINT : PRINT counter; " matching files found:": PRINT
- FOR t = 1 TO counter
- PRINT USING "###"; t; SPC(2);
- PRINT Filelist(t).achName
- NEXT t
-
- PRINT : INPUT "Repeat (y/n)"; y$
-
- LOOP WHILE UCASE$(LEFT$(y$, 1)) = "Y"
-
- Note
-
- This program should be compiled using near strings; to use far strings, use
- the SSEG and SADD functions instead to return the segment and offset of
- the filename you want. For more information about compiling programs that
- use far strings, see Chapter 11, "Advanced String Storage."
-
-
- Creating Dynamic-Link Libraries
-
- You cannot create dynamic-link libraries from BASIC modules created with
- Microsoft BASIC, but a protected-mode BASIC program can invoke routines
- contained in a dynamic-link library.
-
- BASIC run-time and extended-run-time modules can be dynamic-link libraries.
- And user-created routines embedded in a protected-mode, extended run-time
- module are dynamically linked.
-
-
- Creating Multiple Threads
-
- A protected-mode BASIC program can call an external routine or execute a
- process that creates multiple threads. However, because Microsoft BASIC
- run-time routines are not re-entrant, you cannot create multiple threads
- from within a protected-mode BASIC program. For the same reason, threads
- created by external routines cannot call BASIC routines.
-
-
- Making Memory References
-
- Several BASIC statements and functions refer directly to addresses in the
- machine's physical memory. These include CALL ABSOLUTE, DEF SEG, PEEK,
- POKE, BLOAD, BSAVE, VARPTR, and VARSEG.
-
- When using these statements in protected mode, you should take care not to
- refer to an illegal memory address. The selector portion of the address in
- question must refer to a memory segment for which your process has
- appropriate read and/or write permission. In addition, the segment must be
- large enough to contain the address referenced by the offset and the size of
- the object being accessed.
-
- If your program ignores these requirements, it may trigger a protection
- exception by the operating system or the BASIC error message Permission
- denied.
-
-
- The default DEF SEG segment is safely addressable. Values returned by
- VARPTR and VARSEG are valid; and you can safely access BASIC variables and
- arrays, provided you do not exceed the size of valid BASIC objects.
-
-
- Using Graphics
-
- In protected mode, all graphics operations are limited to BASIC screen modes
- 1 and 2. Screen modes 3 and 7-13 are supported only in real mode. Microsoft
- BASIC does not support multiple screen pages in protected mode.
-
-
- Using Music, Sound, and Devices
-
- Except for the BEEP statement, no music or sound statements are available
- in protected mode (you cannot use SOUND or PLAY). However, you can call
- the OS/2 function DOSBEEP.
-
- You cannot use the light pen, joystick, and joystick triggers in protected
- mode.
-
-
- Creating Extended Run-Time Modules
-
- You can create extended run-time modules that can be used in real and
- protected mode. The program BUILDRTM.EXE is a bound program, so it can be
- run in either real or protected mode. As with the compiler, BUILDRTM creates
- an extended run-time module suitable for the environment you are in at the
- time. You can override the default environment by specifying either the /LP
- (protected mode) or /LR (real mode) compiler option in QBX.
-
- To avoid errors, it is important that all modules in a given application
- share the same target environment (real or protected mode).
-
- For information about the standard run-time modules and libraries, see
- "Using Standard Run-Time Modules and Libraries" later in this chapter.
-
-
- Compiling OS/2 Programs
-
- After writing your BASIC program, you can compile it from QBX. From QBX,
- choose Make EXE from the Run menu. For protected-mode programs, make sure to
- specify the /LP option to LINK.
-
- Unless you specify otherwise, the compiler creates an object file suitable
- for the environment in which it was created. If you run the compiler under
- DOS or real mode, it automatically creates an object file suitable for DOS
- and OS/2 real mode. Likewise, if you run the compiler in protected mode, it
- creates a protected-mode object file.
-
-
- You can override the default environment by using one of the options listed
- in Table 14.2.
-
- If you supply the /Lp option, the compiler creates a protected-mode object
- file no matter which environment you are in at the time. If you supply the
-
- /Lr option, the compiler creates a real-mode (DOS-compatible) object file.
-
-
- Linking OS/2 Programs
-
- Before linking OS/2 programs, you should read Chapter 18, "Using LINK and
- LIB." That chapter contains information about module definition files and
- import libraries, which are needed if your program makes calls to
- dynamic-link libraries.
-
- From the QBX environment, your program is automatically linked with the
- proper libraries when you choose the Make EXE option from the Run menu. By
- default, LINK uses the libraries created by the BASIC Setup program. These
- libraries use the following naming convention:
-
- BCL70 float string mode.LIB
-
- For example, if you specified the emulator floating-point option, near
- strings, and real-mode-only options during setup, your program would link,
- by default, with the BCL70ENR.LIB library. From QBX, you can change the
- default library by changing options in the Create EXE dialog box.
-
- For information about LINK options you can use, see Chapter 18, "Using LINK
- and LIB."
-
-
- Using Standard Run-Time Modules and Libraries
-
- The BASIC Setup program automatically creates a run-time module and run-time
- module library that match the operating environment and floating-point
- method you specify. Run-time modules use the following naming convention:
-
- Protected-mode run-time moduleBRT70 float string P.DLL Run-time libraryBRT70
- float string mode.LIB
- The possible values for each variable are the same as shown in the preceding
- section.
-
- If you create programs for more than one operating mode, you must make sure
- that the appropriate run-time module library is available when linking, and
- the appropriate run-time module is available at run time. This can be done
- by setting the LIB and PATH environment variables to the directories where
- your libraries and run time modules are kept, or by moving them into
- appropriate directories where they can be found when run.
-
-
- LINK Options for Real and Protected Modes
-
- The following options for the LINK utility can only be used when linking
- real-mode programs:
-
- ■ /CPARMAXALLOC ■ /DSALLOCATE
-
- ■ /HIGH ■ /NOGROUPASSOCIATION
-
- ■ /OVERLAYINTERRUPT
- The following options can only be used when linking protected-mode programs:
-
- ■ /ALIGNMENT: size ■ /WARNFIXUP
-
-
- For descriptions of these and other LINK options, see Chapter 18, "Using
- LINK and LIB."
-
-
- Debugging OS/2 Programs
-
- Microsoft BASIC provides the CodeView debugger to help you debug your OS/2
- program. Two versions of CodeView are supplied: CVP.EXE, for debugging under
- protected mode and CV.EXE, for debugging under real mode.
-
- To prepare files for use with CodeView, you must specify the /Zi option of
- the compiler and the /CO option of LINK. From QBX, choose the CodeView
- Information option in the Make EXE File dialog box.
-
-
- Running BASIC Programs Under OS/2
-
- When you run a BASIC program in OS/2 protected mode, the system needs to
- find one or more dynamic-link libraries (.DLL). If a needed dynamic-link
- library cannot be found at run time, the system displays an error message.
- When searching for a dynamic-link library, OS/2 looks in the directories
- specified by the LIBPATH configuration command in your CONFIG.SYS file. For
- more about OS/2 configuration commands, see Part 2 in the Microsoft
- Operating System/2 User's Guide.
-
- In OS/2 protected mode, it is possible to run a BASIC program as a detached
- program (using the DETACH command). A detached program, however, cannot
- perform console input or output while it is detached. If input or output is
- attempted (including key trapping), a Device unavailable error message is
- generated. If a detached program causes an error, the error message appears
- in an OS/2 pop-up window.
-
- See the Microsoft Operating System/2 User's Guide for more information
- about the DETACH command.
-
-
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 15: Optimizing Program Size and Speed
-
- Improved code generation and BASIC run-time management as well as
- higher-capacity development tools make it possible to write substantially
- larger BASIC programs, that can then be compiled into smaller and faster
- executables than previously possible.
-
- This chapter consists of programming hints and technical information to help
- you write faster, more efficient BASIC programs. The first half focuses on
- making the most of available random-access memory (RAM) and ways to reduce
- the size of programs in memory and on disk. The second half of this chapter,
- beginning with the section "Compiling Programs for Speed," is a series of
- tips to help you improve the performance of BASIC programs.
-
-
- Size and Capacity
-
- While there is no substitute for good program design, new features like far
- strings, overlays, stub files, and improved code generation can help
- significantly reduce executable size and increase data capacity.
-
- There are three major areas of BASIC size and capacity issues: BASIC
- variable space, BASIC program size, and the size of the compiled executable
- file on disk. An understanding of how BASIC programs allocate and use memory
- will help you to manage variable and program space to your advantage.
-
-
- BASIC Memory Use
-
- Under the DOS and OS/2 operating systems, RAM stores the resident portion of
- any BASIC program that is currently running, the various constants and data
- needed by the program, the variable space, and any other information needed
- by the computer while the program is running.
-
- RAM used by a BASIC program is divided into two categories: near memory and
- far memory. Near memory and far memory each contains a "heap," which is an
- area of memory used to store dynamic variables. "Near memory," also referred
- to as "DGROUP," is the single segment of memory (maximum size of 64K) that
- includes, but is not limited to, the near heap (where near strings and
- variables are stored), the stack, and state information about the BASIC
- run-time. "Far memory" is the multiple-segment area of memory outside of
- DGROUP that includes, but is not limited to, the BASIC program (run-time and
- generated code) and the far heap (where dynamic arrays and far strings are
- stored).
-
- DOS and OS/2 manage
- memory in fundamentally different ways. In DOS, application programs use
- physical addresses to directly access specific memory locations. In
- contrast, OS/2 supports virtual addressing. "Virtual addressing" is an
- advanced system of memory management whereby the operating system maintains
- an address space larger than physical memory by swapping data to and from
- disk (and within memory) as needed, while mapping address references in
- application programs onto the physical addresses of the actual memory
- locations. Under DOS, the upper boundary of standard memory is 640K. Under
- OS/2, program size is realistically limited only by available disk space.
-
-
- The memory map in Figure A.1 shows the high-level memory organization for a
- BASIC program. The diagram shows the different areas of memory in their
- correct relative positions for a stand-alone (compiled with /O) BASIC
- program running under DOS or OS/2 real mode. A program compiled without /O
- under DOS loads the run-time module in far memory (above DGROUP), not next
- to the generated code as pictured in Figure 15.1. Also, all references to
- strings in the diagram refer only to variable-length strings, since
- fixed-length strings are managed in memory the same way fixed-length data
- types (such as numerics) are managed.
-
- There are a few fundamental things you can do to maximize capacity in
- BASIC under DOS. If you are working with a large amount of code in a
- program, or if you want more far memory to be available for far string
- or dynamic array data, overlays will allow different module groups in
- your program to share the same memory space. Microsoft BASIC supports up
- to 64 overlays. Each overlay may be up to 256K for a maximum total of over
- 15 megabytes of compiled code. See the section "Overlays" later in this
- chapter and Chapter 18, "Using LINK and LIB," for more information about
- overlays.
-
-
- If DGROUP (near memory) is constraining, compiling with the far string
- option (/Fs) will place variable-length string data in the far heap, leaving
- more room in DGROUP for other data. See Chapter 11, "Advanced String
- Storage," for more information on using strings.
-
- The FRE() function can give you information about exactly how much memory
- is available in a particular area of memory. FRE(-2) returns the amount of
- stack space not yet used. FRE(-1) returns the amount of available far
- memory. FRE() can also be used to determine the amount of string space
- available for near and far strings. See the BASIC Language Reference or
- online Help for a complete description of the FRE() function.
-
-
- Variable-Length String Storage
-
- Microsoft BASIC gives you two options for storing variable-length string
- data and string array data: near string storage in DGROUP and far string
- storage in the far heap. You can specify which option you want to use by
- compiling with or without the far string option (/Fs). The only data type
- affected by the /Fs option is the variable-length string data type. Table
- 15.1 shows where variables and arrays are stored in memory based on the
- storage option you choose. This information can help you make better use of
- the space available in memory, and it can help you make speed/size and
- capacity tradeoffs in your BASIC code.
-
-
- Note
-
- When you compile from within QBX and a Quick library is loaded, the default
- compile option is far string (/Fs). When you compile from the command line,
- near string storage is the default, as it was in all previous versions of
- BASIC. To use far string storage instead, add the /Fs option to the BASIC
- Compiler (BC) command line.
-
- Since the QBX environment treats all strings as far strings, a program that
- uses near pointers to string data cannot run or be debugged in the QBX
- environment. However, the program may still work when compiled using the
- near string compiler option, and can be debugged with CodeView. On the other
- hand, far pointers will always work in QBX and in a program compiled with
- far strings. For detailed information about string storage, see Chapter 11,
- "Advanced String Storage."
-
-
- String Array Storage
-
- A string array has three parts in memory: the array descriptor, the array of
- string descriptors, and the string data. The array descriptor is always
- stored in DGROUP. Each element in the array of string descriptors is stored
- in DGROUP and contains the length and location in memory of the string data.
- The string data resides in the near heap if near string storage is specified
- at compile time, or in far heap if far string storage is specified at
- compile time. The 4-byte string descriptor for each variable-length string
- resides in DGROUP regardless of which string option (near or far) is used.
- Therefore, even with far strings, it is possible to run out of DGROUP by
- filling it with string descriptors. When this happens, the only remedy is to
- reduce the number of elements in the array.
-
- BASIC string arrays can be either static or dynamic. A "static string array"
- is an array of variable length strings whose descriptors reside in a
- permanently allocated array in DGROUP. This array of descriptors is fixed
- when compiled and cannot be altered while a program is running.
-
- A "dynamic string array" is a string array whose array of descriptors can be
- defined or changed during run-time with BASIC's DIM, REDIM, or ERASE
- statements. Dynamic arrays that are declared local to a procedure are
- de-allocated when control leaves the procedure. As with static string
- arrays, dynamic string array descriptors also reside in DGROUP, but they may
- change in number and/or location during execution of a BASIC program.
-
- The location of the string data itself is independent of the static or
- dynamic status of a string array, and depends solely on whether the near or
- far string option is chosen when compiling.
-
-
- Numeric Array Storage
-
- Unlike string arrays, numeric arrays have a fixed amount of data associated
- with each element. All of the following information about numeric arrays is
- equally applicable to arrays of fixed-length strings or arrays of
- user-defined types, since they also have a fixed amount of data associated
- with each element in the array.
-
- A numeric array has two parts: an array descriptor and the numeric
- array data itself. The array descriptor contains information about the
- array including its type, dimensions, and the location of its data in
- memory. Array descriptors are always stored in DGROUP. The data in a
- numeric array may be stored entirely in DGROUP or entirely in the far
- heap, as shown in Table 15.1.
-
- As with string arrays, numeric arrays may be static or dynamic. A static
- numeric array has a fixed number of elements that are allocated when the
- program is compiled, while a dynamic numeric array is allocated and can be
- changed when the program is running. The compiler blocks out portions of
- DGROUP to contain all static array data, and this DGROUP space cannot be
- reclaimed while the program is running. Dynamic arrays are more flexible.
- They can be declared with a variable argument to the DIM statement,
- re-sized using the REDIM statement, and de-allocated altogether using the
- ERASE statement. Any space allocated for local dynamic arrays is reclaimed
- when BASIC leaves a particular procedure.
-
- Certain tradeoffs are involved in the choice between static and dynamic
- numeric arrays. As described previously, static arrays are unchanging and
- thus consume a constant amount of memory. Dynamic arrays, however, will
- reclaim memory space after an array is erased or redimensioned to be
- smaller, thus allowing the same far heap space to be used for different
- purposes at different times in the same program. Static arrays provide the
- advantage of faster referencing and fixed locations in memory (helpful for
- mixed-language programming and quick look-ups).
-
-
- Example
-
- In the following example, A and B start out the same size, but A can grow or
- shrink during program execution; if A is no longer needed, the space can be
- reclaimed:
-
- Index = 0
- DIM A (Index) AS INTEGER' A is dynamic,
- DIM B (10) AS SINGLE' B is static,
- Index = 20
- REDIM A (Index) AS INTEGER' A's size can change,
- .
- .
- .
- ERASE A' Or disappear.
-
- Huge Dynamic Array Storage
-
- Huge dynamic arrays allow you to create and store in memory arrays that
- contain more than 64K of data (64K is the limitation of standard dynamic
- arrays). Huge arrays are invoked by using the /Ah option when starting QBX
- or by using the /Ah option when compiling a program from the command line.
-
- Huge arrays are limited to 128K unless the individual element size of each
- record divides evenly into 64K. In other words, the number of bytes in each
- element must be an integer power of two in order for an array of these
- elements to be larger than 128K, as shown by the following example:
-
- TYPE ElementType
- a AS INTEGER' 2 bytes
- b AS LONG' 4 bytes
- c AS SINGLE' 4 bytes
- d AS DOUBLE' 8 bytes
- e AS CURRENCY' 8 bytes
- f AS STRING * 6' 6 bytes
- END TYPE'Total of 32 bytes in ElementType
- MaxElement% = 5000
- DIM HugeArray(1 to MaxElement%) AS ElementType
-
- The ElementType defined in the preceding example is a total of 32 bytes per
- element, so it will divide evenly into 64K and will enable BASIC to create
- the 160,000 byte array -- assuming the /Ah option on QBX or BC was used, and
- there is sufficient far heap available). However, if the fixed-length string
- F is changed to either 5 or 7 bytes, a Subscript out of range error will
- result.
-
-
- Note
-
- When compiling from within QBX, the /Ah compile option is automatically set
- only if you started QBX with the /Ah option. This can be changed only by
- exiting QBX and restarting it with the desired option.
-
- Using the /Ah option in QBX or when compiling forces all dynamic arrays in
- your program to be huge. Huge arrays have slower access times and require
- more memory than similar standard dynamic arrays.
-
-
- Tips on Conserving Data Space
-
- The following are some tips on coding style that will help to conserve data
- space:
-
- ■ Use constants.
-
- ■ Normal numeric variables in BASIC programs reside in DGROUP. However,
- when a program is compiled, constant values can be folded directly
- into the generated code, so no additional DGROUP is needed to support
- these constants. If a value does not need to change in a program,
- constants can frequently make a program execute faster and will use
- less memory than variables.
-
- ■ Use local variables in SUB procedures.
-
- ■ Local variables within a SUB procedure occupy DGROUP space only while
- the program is running within that SUB procedure. BASIC keeps local
- variables on the stack, so that when the program returns from a
- procedure using local variables, the local stack variables are
- discarded and the stack is returned to the state it was in before the
- procedure was invoked. This is different from static variables, that
- occupy DGROUP space for the entire duration of the program.
-
-
- ■ Trade file I/O buffer size for string space.
-
- ■ File I/O buffers reside in the same area of memory as variable-length
- strings. If a program is compiled with /Fs, then the file I/O buffer
- will reside in far memory. Otherwise, the file I/O buffer will reside
- in DGROUP. The size of the file I/O buffer is defined by the LEN=
- argument of the OPEN statement, with the default buffer for
- sequential file I/O equal to 512 bytes (default for random file I/O is
- 128 bytes). A larger buffer generally requires fewer disk accesses and
- results in a faster executing program; but a smaller file I/O buffer
- takes up less memory in either DGROUP or the far heap, depending on
- the string option chosen when compiled.
-
-
-
- Controlling Program Size
-
- BASIC programs consist of the BASIC run-time routines and code generated by
- the compiler. Knowing how to manage both of these will help you to keep
- program size down leaving more room for other data in memory and on disk.
-
- The BASIC run-time is the set of routines that support most of BASIC's
- underlying functionality. Since most compiled programs do not need all of
- the functionality available in the BASIC run-time routines, these routines
- are divided into many individual object modules and are included as
- necessary in the user's final program. The extent to which these routines
- are divided into individually accessible pieces is called "granularity."
-
- The BASIC run-time routines included in any given program can be minimized
- either through writing code that does not use certain BASIC functionality,
- or through the use of "stub" files. This will result in smaller executables
- in memory and on disk, when compiling stand-alone programs.
-
-
- Using Stub Files
-
- Stub files are the special object files shipped with Microsoft BASIC that
- block certain pieces of the BASIC run-time from being included in the final
- executable file when the program is linked. To understand how stub files
- work, it is necessary to understand what happens during the link step of the
- build process.
-
- When a BASIC source file is compiled into an object file, the object file
- contains many unresolved references. These references are simply calls into
- the BASIC run-time or other libraries on which the main BASIC program is
- depending, but are not present in the object file. During linking, the
- Microsoft Segmented-Executable Linker (LINK) matches up these calls to
- external procedures with the procedures themselves. LINK can be thought of
- as the matchmaker between the requests for certain functionality and the
- procedures that provide that functionality.
-
- In order to resolve these calls to external procedures, LINK first
- searches every object module that is in the object field of the LINK
- command. Then, if there are any remaining unresolved references, LINK
- searches the files in the library field of the LINK command. The main
- difference between the object field and the library field, besides the
- order in which they are searched, is that every module found in the
- object field will be linked into the final executable file, while only
- those modules that contain procedures necessary to resolve references will
- be linked in from the library field. Therefore, if you specify a module
- in the object field that resolves the references that would otherwise be
- resolved by a module in the library field, you can block the module in
- the library field from being included in the final executable file.
-
-
- Stub files accomplish that goal. Stub files contain dummy procedures, or
- "stubs," that have the same names as the BASIC run-time routines targeted
- for exclusion. The only functionality that exists in most of these dummy
- procedures is the ability to return a Feature Stubbed Out error message.
- Some stub files, however, contain a smaller, limited version of the
- functionality (as in SMALLERR and NOEDIT). Trading full-functionality
- routines for reduced or removed functionality routines reduces executable
- size.
-
- Stub files can be used either when the stand-alone library or run-time
- module is created with BUILDRTM (see Chapter 21, "Building Custom Run-Time
- Modules") or the Setup program (see Getting Started ), or they can be used
- individually with each program on a case-by-case basis. Stub files are only
- of value on a case-by-case basis when compiling stand-alone executables
- (compiling with the /O option).
-
- It is important to remember to use the /NOE option when linking with stub
- files. This option causes LINK to ignore conflicts when a public symbol has
- been redefined, and instructs LINK to use only the specified object files
- (as opposed to using predefined dictionaries to identify libraries which
- will resolve references, as would happen if stub files were not being used).
-
- The following example builds a program called MYPROG.BAS into an executable
- file and excludes support for COM and LPT I/O:
-
- BC /O MYPROG.BAS;
- LINK /NOE MYPROG.OBJ NOCOM.OBJ NOLPT.OBJ;
-
- If you are certain all of your programs won't need some piece of
- functionality for which a stub file is provided, removing the functionality
- during setup is a way to reduce every program's size and thus increase room
- for other code or data competing for far memory space.
-
- Note
-
- Functionality that is excluded from the BASIC run-time modules during setup
- using stub files will not be available in any program, so these choices
- should be made cautiously. If you accidentally exclude needed functionality
- from the BASIC libraries during setup, run the Setup program again to add
- functionality back into the run-time module. See Getting Started for more
- information.
-
- Table 15.2 lists the stub files that are shipped with Microsoft BASIC.
-
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- File Description
- ────────────────────────────────────────────────────────────────────────────
- NOFLTIN.OBJ Allows a program to contain INPUT,
- VAL, and READ statements without
- support for parsing floating point
- numbers that would require the
- floating point math package to be
- included. If a program is linked
- with this stub file, all numbers
- recognized by INPUT, VAL, and READ
- must be legal long integers.
-
- NOEDIT.OBJ Reduces functionality of the
- editor provided with the INPUT
- statement to support only Enter
- and Backspace keys (no Home, End,
- etc.).
- File Description
- ────────────────────────────────────────────────────────────────────────────
- etc.).
-
- NOCOM.OBJ Removes support for COM device I/O.
- COM support is included by LINK if
- an OPEN statement is used with a
- string variable in place of the
- file or device name, as in OPEN A$
- FOR OUTPUT AS #1 or if a string
- constant starting with COMn is
- used with an OPEN statement.
-
- NOLPT.OBJ Removes support for LPT device I/O.
- LPT support is included by LINK if
- an OPEN statement is used with a
- string variable in place of the
- file or device name, as in OPEN A$
- FOR OUTPUT AS #1 or if a string
- constant starting with LPTn: is
- used with an OPEN statement. Using
- File Description
- ────────────────────────────────────────────────────────────────────────────
- used with an OPEN statement. Using
- this stub file also removes
- run-time support for screen
- printing using Ctrl+PrtSc.
-
- NOEVENT.OBJ Removes support for event trapping.
- This stub file is only effective
- if linked with the run-time
- module; it has no effect when
- linked into stand-alone
- executables.
-
- NOEMS.OBJ Prevents a program linked for
- overlays from using Expanded
- Memory Specification (EMS);
- instead, the program will be
- forced to swap to disk.
-
- OVLDOS21.OBJ Required in order for a program
- File Description
- ────────────────────────────────────────────────────────────────────────────
- OVLDOS21.OBJ Required in order for a program
- linked for overlays to work under
- DOS 2.1. Does not reduce the size
- of the executable.
-
- NOISAM.OBJ Removes ISAM functionality from
- BASIC run-time modules. This stub
- file is not useful when creating
- stand-alone executable files.
-
- SMALLERR.OBJ Reduces length of run-time error
- messages.
-
- 87.LIB Removes software coprocessor
- emulation, so that an 8087-family
- math coprocessor must be present
- in order for the program to
- perform any floating-point
- calculations, if floating-point
- File Description
- ────────────────────────────────────────────────────────────────────────────
- calculations, if floating-point
- statements are used in the program.
-
-
-
-
-
- ╓┌───────────────────────────────────────┌───────────────────────────────────╖
- File Description
- ────────────────────────────────────────────────────────────────────────────
- NOTRNEMm.LIB1 Removes support for any command
- using transcendental operation
- including: LOG, SQR SIN, COS, TAN,
- ATN, EXD, ^, CIRCLE statements
- with a start and /or stop angle,
- DRAW statements with A or T
- commands.
-
- TSCNIOsm.OBJ1,2 These stub files limit the program
- File Description
- ────────────────────────────────────────────────────────────────────────────
- TSCNIOsm.OBJ1,2 These stub files limit the program
- to text-only screen I/O with no
- support for special treatment of
- control characters. Each TSCNIOsm
- .OBJ stub file is a superset of
- all the graphics-related stub
- files that follow.
-
- NOGRAPH.OBJ Removes all support for graphics
- statements and non-zero SCREEN
- modes. This stub file is a
- superset of all the following
- graphic SCREEN mode stub files.
-
- NOCGA.OBJ Removes all support for CGA
- graphics SCREEN modes 1 and 2.
-
- NOHERC.OBJ Removes all support for Hercules
- graphics SCREEN mode 3.
- File Description
- ────────────────────────────────────────────────────────────────────────────
- graphics SCREEN mode 3.
-
- NOOGA.OBJ Removes all support for Olivetti
- graphics SCREEN mode 4.
-
- NOEGA.OBJ Removes all support for EGA
- graphics SCREEN modes 7-10..
-
- NOVGA.OBJ Removes all support for VGA
- graphics SCREEN modes 11-13.
-
-
-
-
-
-
- 2 s can be either N or F for near or far strings.
- Exploiting Run-Time Granularity with Code Style
-
- Compiled BASIC programs will automatically leave out pieces of the run-time
- that LINK can determine are unnecessary. Since the run-time is pulled into a
- program in discrete pieces, understanding how to avoid pulling in large
- chunks of run-time support when they are not completely necessary can result
- in significantly smaller executable files, especially with smaller programs.
-
-
- Avoid Accidental Use of Floating-Point Math
-
- Depending on which math option is specified when compiling (/FPa for
- alternate math or /FPi for coprocessor/emulation), inadvertent inclusion of
- a floating-point math package can add over 10K to program size. Here are
- some tips to help you avoid pulling in a floating-point math package when it
- is not needed:
-
- ■ Use integers.
-
- ■ Start each program with DEFINT A-Z, or be sure to use integer
- variables (terminated with %) wherever possible. This will help you
- avoid the floating-point math package and speed up your program. Since
- the default data type in BASIC is SINGLE (4-byte floating-point
- real), people frequently make the mistake of using the SINGLE data
- type where integers would actually better suit their needs.
-
- ■ Use the integer division operator ( \ ) whenever doing integer
- division.
-
- ■ This division operator will not cause the floating-point math support
- to be pulled in if the math pack is not needed elsewhere in the
- program. Using the regular division operator ( / ) always pulls in the
- floating-point package, even when used with integers.
-
- ■ Avoid BASIC statements and functions that use the floating-point
- package.
-
- ■ Some of the BASIC statements and functions that pull in the floating
- point package are obvious, such as SIN, COS, TAN, ATN, LOG, and
- EXP. The not-so-obvious BASIC functions that use the floating-point
- package are VAL, WINDOW, DRAW, TIMER, RANDOM, INPUT, READ,
- PMAP, POINT, PRINT USING, and SOUND.
-
-
-
- Use Constants in SCREEN Statements
-
- Using a variable as an argument to the SCREEN statement will cause support
- for all graphic screen modes to be pulled in, unless stub files are being
- used, whereas a SCREEN statement with a constant only pulls in the support
- necessary for the specified screen mode.
-
-
- Minimizing Generated Code
-
- As a BASIC program gets longer, the compiled executable file becomes
- increasingly dominated by generated code. Since run-time library routines
- are included only once in any given program, the larger a program becomes,
- the more it benefits from the BASIC strategy of using run-time library
- routines for much of its functionality. The converse is also true -- the
- shorter a program is, the more its size is dominated by the run-time
- routines, making generated code size less important. However, if a program
- is compiled without the /O option, no run-time library routines are
- contained in the executable file, so the marginal space taken up on disk is
- dominated by code generation even for short BASIC programs.
-
- The following are some
- tips to help you keep your programs small by minimizing generated code:
-
-
- ■ Use procedures.
-
- ■ Designing a BASIC program to efficiently re-use code for repetitive
- operations will not only make your code smaller but it will also make
- it more understandable, easier to maintain and debug, and easier to
- use in other programs.
-
- ■ Beware of event trapping.
-
- ■ While it takes relatively little BASIC code to set up event trapping,
- the resultant compiler-generated code will be disproportionately
- large, since the compiler must generate tests for the specified event
- between each label or statement. Event-trapping code can be more
- precisely controlled with EVENT ON and EVENT OFF statements, which
- allow you to specify exactly where within a module event-trapping code
- is generated.
-
- ■ Compile modules that use ON ERROR RESUME statements separately.
-
- ■ BASIC does not provide statements to turn error trapping on and off.
- Compiling with the /X option (to support ON ERROR RESUME statements)
- generates 4 bytes of code per BASIC statement in a module compiled
- with /X. These 4 bytes contain the location to which the program
- should return if an error occurs at any particular time. To get the
- smallest size and best performance in a program with such error
- trapping, use local error handling and have all those procedures with
- local error handling in a separate module compiled with /X. This will
- prevent the performance degradations associated with /X from affecting
- those pieces of your program that do not require error handling.
-
-
-
- Overlays
-
- Modular programs that use overlays can execute in substantially less memory
- than programs that do not use overlays. The key to successful program design
- for overlays is minimizing the performance penalty inherent in swapping
- modules in and out of memory. This can be accomplished by designing the
- program in groups of interconnected modules that will primarily call each
- other, with only infrequent calls outside the group that would cause another
- overlay to be swapped back into memory.
-
- In general, event-handling routines and general-purpose routines should go
- in code which is not overlaid, unless events are expected to be rare. See
- Chapter 18, "Using LINK and LIB," for more information about using overlays
- in BASIC.
-
-
- Minimizing Executable File Size
-
- Compiling without /O is the easiest single thing which can be done to
- minimize the size of BASIC executables on disk. The resultant executable
- programs do not contain the BASIC run-time routines. Rather, these programs
- access the same run-time routines that exist in a single BASIC run-time file
- on disk (one of the files named BRT70 mso, where mso stand for the math,
- string, and operating mode options). You can customize these run-time
- modules by adding modules of your own routines with the BUILDRTM utility.
- See Chapter 21, "Building Custom Run-Time Modules," for more information on
- how to use this feature.
-
- The primary tradeoff of compiling programs that require the presence of a
- BRT70 msofile is that the program will not work as a stand-alone,
- single-file executable. The ultimate end-user of the program must always
- have a copy of the appropriate BRT70 mso file present in order for the
- program to run. Other tradeoffs made in working with run-time modules are
- that these programs use more memory (since the entire run-time gets loaded
- into memory) and program initialization is slower.
-
- If the program is meant to be stand-alone and is compiled with /O, then all
- of the BASIC run-time management tips in the section "Controlling Program
- Size" earlier in this chapter are equally valuable in reducing the size of
- executables on disk. Minimizing generated code will also result in
- proportionately smaller executable files.
-
- LINK provides some options such as /EXEPACK, /PACKCODE, and
- /FARCALLTRANSLATION that help to compress the size of the executable and
- improve executable speed in some cases. See Chapter 18, "Using LINK and
- LIB," for more information on these and other LINK options.
-
-
- Compiling Programs for Speed
-
- This section and the rest of the sections in this chapter discuss ways to
- improve the performance of your BASIC programs so they execute in the
- shortest amount of time possible.
-
-
- Compiling for the Target System
-
- Knowing the hardware configuration of the system on which the program will
- ultimately run makes it possible to compile a program optimally for speed on
- that particular machine.
-
- The /G2 compiler option causes the compiler to generate code that takes
- advantage of the expanded instruction set of the Intel 80286 microprocessor.
- If the target system is definitely an IBM AT or compatible 286-based
- machine, the /G2 option will make compiled BASIC programs smaller and
- faster. PCs with backward-compatible microprocessors that support the 286
- instruction set (i.e. 386 and 486) will also run programs compiled with /G2
- faster than programs compiled without /G2.
-
-
- Math Options
-
- Depending on whether or not the target system has an 8087-family math
- coprocessor, one of two math packages will provide the fastest possible
- floating-point math support.
-
- 80x87 Support
-
- If the target system is expected to have an 80x87-family math coprocessor,
- then compiling with the /FPi option (the default) will either precisely
- emulate the functionality of such a chip or will use the hardware directly
- to perform the desired functions. If you are absolutely certain that the
- target system will have such a coprocessor, then you may wish to LINK with
- 87.LIB (the coprocessor math package which does not provide floating-point
- operation software). If math is used, this reduces executable size by over 9
- K.
-
-
- Alternate Math
-
- If the target system is not expected to have an 80x87-family math
- coprocessor, then compiling with the /FPa option will link in the alternate
- floating-point math pack, which does not attempt to emulate the 80x87
- hardware. Alternate math is an IEEE-compatible math package optimized for
- speed and size on systems without a floating-point coprocessor. The tradeoff
- of using the alternate math package is that a small amount of precision is
- lost compared with the true 80x87 emulator. For most applications, this loss
- of precision is negligible and the alternate math package will offer better
- math speed with no undesirable side effects.
-
- Specifically, all floating-point calculations performed by alternate math
- are performed in either 24-bit or 53-bit mantissa precision for SINGLE or
- DOUBLE data types, respectively. With the 80x87/emulator, all intermediate
- results in an expression are calculated to 64-bits of mantissa precision.
-
- Simple operations involving only one calculation followed by an assignment,
- such as the following will yield the same result with either math package:
-
- A! = B! + C!
- A# = B# + C#
-
- The differences start appearing when multiple calculations are performed in
- a single expression, as in the following example:
-
- B! = 1000001!
- C! = 1!
- D! = 1!
- A! = (1000001! * B! + C!) * D! - (1000001! * B!)
-
- With the alternate math package, the intermediate results are calculated to
- 24 bits of precision. The result of this expression to 24 bits is 0. With
- the 80x87/emulator, the result is exactly 1. The preceding single-precision
- calculation could be performed without losing any bits of significance.
-
- Transcendental functions computed with the emulator are accurate to between
- 62 and 64 bits. The alternate math package is also accurate to within 2 bits
- of its normal mantissa precision for each data type in transcendental math.
- This means that the following expression could be accurate to 22 bits with
- alternate math, but with the 80x87/emulator it will usually be accurate to
- 24 bits (it would be off by 1 bit in about 1 in 238 samples):
-
- A! = SIN(1.0!)
-
- Managing Data for Speed
-
- Variable types and storage options have a significant impact on program
- execution speed. Choosing data types and storage options intelligently can
- decrease execution time.
-
-
- Constants
-
- Use constants whenever possible to save space and make programs faster.
- References to named constants are resolved when compiled and the values are
- then embedded directly into the generated code (except for string and
- floating point constants). Where constants can be used, they are almost
- universally more efficient than variables.
-
-
- Integers
-
- Integers are the fastest data type available in BASIC. Integers are smaller
- than any other numeric BASIC data type (each integer uses only 2 bytes per
- instance), and integers can be handled efficiently without run-time calls
- through compiler-generated in-line code. A good programming habit is to put
- DEFINT A-Z at the top of each module and procedure and then only use other
- data types as necessary.
-
- The following tips on using integers can help your programs run faster:
-
- ■ Loop variables should usually be integers.
-
- ■ A common error made by BASIC programmers is to just use the default
- data type ( SINGLE 4-byte floating point real) for loop variables.
- This error could slow down loop speed significantly and cause program
- size to grow by unnecessarily pulling in a floating-point math package
- if it hasn't yet been pulled in.
-
- ■ Booleans should always be integers.
-
- ■ If a variable's purpose is to either specify true or false, or some
- other discrete set of values, integers will be by far the smallest and
- fastest data type suitable for this purpose.
-
- ■ BASIC statement and function arguments should be integers wherever
- possible.
-
- ■ Using integer arguments for calls to graphics functions, for example,
- will make the code execute more quickly and will not require the use
- of the floating-point math package.
-
-
-
- Currency
-
- Microsoft BASIC supports a new data type, CURRENCY (specified with the @
- suffix), that can be thought of as an extra-long integer that has been
- shifted to accommodate fixed decimal-place fractional values. Internally,
- the CURRENCY data type is represented as an 8-byte integer that is then
- scaled by a factor of 10,000 to leave four decimal places to the right of
- the decimal point, and 15 places to the left. Its internal representation as
- an integer gives the CURRENCY data type a significant advantage in speed
- over floating point for addition and subtraction and a disadvantage in speed
- for other mathematical operations. Where fixed-decimal precision across the
- CURRENCY data type's range of values is acceptable (for example in dollar
- and cent calculations), CURRENCY should be seen as a desirable alternative
- to traditional floating point data types.
-
- Compared with binary-coded-decimal (BCD) data types, the CURRENCY data type
- has superior range for a comparable number of bytes (since BCD data type
- only uses 100 out of 256 values in every byte, while CURRENCY uses all
- 256). The CURRENCY data type is also faster in math operations such as
- addition and subtraction.
-
-
- Near Vs. Far Strings
-
- While far strings provide greater capacity, near strings are measurably
- faster for most operations, since it takes less time to access a near string
- than a far string. Depending on the program, string speed or capacity may be
- most desirable, and the choice between string packages should be made
- accordingly.
-
-
- Static Vs. Dynamic Arrays
-
- As with type of strings, the type of arrays used involves a tradeoff of
- capacity for speed. Static array referencing is always faster than
- referencing dynamic arrays, but dynamic arrays (since they exist in far
- memory) offer significantly higher capacity. Similarly, accessing a standard
- dynamic array is faster than accessing a huge array (compiled with the /Ah
- operator).
-
-
- Optimizing Control-Flow Structures for Speed
-
- BASIC provides three different levels of general purpose control-flow
- structures, each with its own advantages. They are the GOTO, GOSUB/ DEF
- FN procedures, and SUB/ FUNCTION procedures. The speed with which each of
- these can cause your program to branch off to execute different pieces of
- code is largely a function of how much information must be passed and how
- much of the current context must be maintained.
-
-
- GOTO
-
- The GOTO statement is the oldest and least structured of the BASIC
- branching controls, but is still unmatched in raw jumping speed. The simple
- mapping of the GOTO statement onto the single-instruction JMP in assembly
- language makes the GOTO statement the fastest way to go from point A to
- point B in a program. The obvious tradeoff here is that GOTO statements are
- unstructured, making code difficult to read, debug, and maintain, and are
- therefore considered poor programming style.
-
-
- GOSUB and DEF FN Subroutines
-
- GOSUB and DEF FN procedures do little more for the user than the GOTO
- statement, except to push a return address onto the stack so that after the
- procedure is finished, a return instruction is executed that returns the
- program to the statement after the assembly language CALL instruction. This
- branching construct is slower than the GOTO statement, but it adds the
- advantage of returning to the point after the branch.
-
-
- SUB and FUNCTION Procedure Calls
-
- SUB and FUNCTION procedures are the powerful, fully structured BASIC
- constructs that allow passing of parameters, local variables, recursion,
- local error handling, inter-module calls, and mixed-language programming.
- The elegance of these modular language features leads some people to argue
- that the older constructs discussed above are completely obsolete, despite
- their speed advantage in some situations. The speed of a call to a procedure
- of this type is dominated by the amount of context and parameter information
- that must be stored on the stack (called the stack frame) before the branch
- can take place. The size of this stack frame is determined by the number and
- size of parameters being passed to the procedure, the amount of
- error-trapping information to be maintained, and the code that exists within
- the procedure itself.
-
- The following tips help you improve the speed of calls:
-
- ■ Minimize parameter passing.
-
- ■ While parameter passing is generally preferred programming style
- compared to using global variables, pushing parameters on the stack
- takes time. When call speed is critical, global variables reduce frame
- size and minimize the net time spent transferring control to a
- procedure.
-
- ■ Compile with the /Ot option when appropriate.
-
- ■ Compiling with the /Ot option will improve call speed if you are not
- also compiling with the /D or /Fs option and your program does not use
- local error handling. If a SUB or FUNCTION procedure uses local
- error handling, calls a DEF FN or GOSUB procedure, or contains a
- RETURN statement, then a full stack frame is generated and compiling
- with the /Ot option will not improve call speed.
-
- ■ Avoid setting up a large stack frame.
-
- ■ A BASIC stack frame that contains a large block of information about
- the current context of the program will be saved in some situations as
- soon as a SUB or FUNCTION procedure is invoked. The following things
- require a large stack frame and should be avoided if call speed is
- critical:
-
- ■ GOSUB and DEF FN invocations from
- within procedures
-
- Module-level error handlers (if only procedure-level
- error-handling routines are used, a BASIC stack frame is required
- only when calling those procedure containing local error-handling
- routines)
-
- Run-time error checking (compiling with the /D option for debug
- information)
-
- ON... GOSUB statements within procedures
-
- Compiling with the /Fs option
-
- ■ Pass parameters by value.
-
-
- ■ When passing dynamic array elements or fixed-length strings as
- parameters to BASIC procedures, putting parentheses around the
- expression within the parameter list will cause the parameter to be
- passed by value rather than by reference, which will result in faster
- calls. Also, when calling a non-BASIC procedure from BASIC, using the
- BYVAL attribute in the parameter list of the DECLARE statement will
- cause the particular parameter to be passed by value, thus speeding up
- the call. Of course, passing parameters by value should be used only
- when the value of the parameter does not need to be changed globally
- within the procedure.
-
-
- ■ Use STATIC SUB statements to maximize call speed to SUB procedures.
-
-
- ■ Variables will then be static by default and will exist between calls
- to the SUB procedure; the compiler won't have to recreate them. The
- variables will require more space, however.
-
-
-
- Writing Faster Loops
-
- While looping is a straightforward task in BASIC, there are some things that
- can help speed up simple looping. The first thing to remember, once again,
- is always use integer loop counters. Second, remember to remove any and all
- "loop invariant code"-- code that doesn't execute differently during each
- iteration of the loop. Any operation whose result does not change through
- multiple iterations of the loop should be coded outside of the loop
- altogether. An example of removal of loop invariant code is:
-
- FOR I%=1 to LastLoop%
- A(I%)=SIN(B)
- NEXT I%
-
- The preceding example
- could be more efficiently recoded as:
-
-
- C=SIN(B)
- FOR I%=1 to LastLoop%
- A(I%)=C
- NEXT I%
-
- Optimizing Input and Output for Speed
-
- File and screen I/O are frequently the speed bottlenecks for BASIC programs,
- because of the relatively slow speed of disk access and writing to screen.
- But as with all the other areas of BASIC programming, there are ways to
- maximize the performance of these operations.
-
-
- Sequential File I/O
-
- The OPEN statement has an optional LEN= argument that can be used to
- specify the buffer size that will be used when opening a file for sequential
- file I/O. This sequential file I/O buffer resides in the same area of memory
- that contains variable-length strings, namely DGROUP if the program is
- compiled for near strings, or the far memory if the program is compiled with
- /Fs. The larger this buffer is, the smaller the number of disk accesses that
- will be necessary to perform a given quantity of file I/O with the disk. The
- tradeoff here is speed of file I/O in exchange for capacity in the area of
- memory (near or far) that contains variable length strings. The default
- sequential buffer size is 512 bytes.
-
-
- ISAM
-
- The BASIC ISAM statements and functions make structured file I/O much faster
- and simpler than using non-ISAM file I/O statements and functions and
- creating your own code in BASIC to handle structured file access. ISAM is
- optimized for working with very large numbers of records that must be
- accessed by indexes that do not necessarily correspond to the physical order
- of the records in the file. When using ISAM, however, compiling with /D can
- degrade performance. See Chapter 10, "Database Programming with ISAM," for
- more information on programming with the ISAM statements and functions.
-
-
- Printing to Screen
-
- The stub files TSCNIO sm.OBJ not only reduce executable size but also speed
- up printing characters to the screen. These stub files eliminate checking
- for special control characters when they are printed, so that all characters
- are then sent directly to the screen without filtering out and treating
- characters below ASCII 32 differently from the other characters.
-
- Sending text to the screen can be accelerated further through precise use of
- the PRINT statement. The PRINT statement comes with many automatic
- functions, among them are auto-scroll if there is no terminating semicolon.
- Putting a semicolon at the end of any PRINT statement where a line feed is
- not needed will save an unnecessary call to the lower level print routines
- to start a new line. This code change could make printing to screen twice as
- fast.
-
-
- Other Hints for Speed
-
- The following sections include some general hints to help you improve
- program execution speed.
-
-
- Event Trapping
-
- Event trapping in BASIC programs forces the compiler to generate
- event-checking code that is executed either between statements or between
- labels. This makes the code larger and slower. Precise use of EVENT ON and
- EVENT OFF statements to limit the areas affected by event trapping, or
- avoiding use of event trapping altogether when possible, will substantially
- improve speed of the resultant executable.
-
-
- Line Labels
-
- Excessive numbers of line labels limit the optimizations that can be
- performed by the compiler. Programs run faster after extraneous line labels
- have been removed. The REMLINE.BAS program that comes with Microsoft BASIC
- can automatically remove all unneeded line labels and thus help the compiler
- perform more sophisticated optimizations, resulting in faster executables.
-
-
- Buying Speed with Memory
-
- A common technique used to gain speed in calculation-intensive programs is
- to set up arrays in which the results of a particular function or
- calculation, across the expected range of input values, are stored. The
- underlying assumption is that the time needed to refer to the array is
- shorter than the time needed to perform the calculation itself, and that the
- speed advantage is worth the price of additional memory used to store the
- array.
-
- For example, a program that repeatedly requires the tangents of angles to be
- calculated might run substantially faster if builds an array of values
- TAN(x) that can be quickly referred to, rather than calculating the tangent
- each time it is required.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 16: Compiling with BC
-
- This chapter explains how to compile your BASIC program
- using the BASIC Compiler (BC). You'll learn how to invoke BC from the
- command line as well as how to use command-line options to BC.
-
- While you can compile and link your program from QBX by choosing Make EXE
- File from the Run menu, you might run BC from the command line to create an
- object file. Then you can run the LINK utility to link object modules and
- libraries to create an executable file. You may want to invoke BC and LINK
- in separate steps for the following reasons:
-
- ■ To compile a program that is too large to compile in memory within the
- QBX environment
-
- ■ To debug your program with the Microsoft CodeView debugger
-
- ■ To use a different text editor
-
- ■ To create listing files for use in debugging a stand-alone executable
- program
-
- ■ To use options not available within the QBX environment, such as
- storing arrays in row order (/R)
-
- ■ To link your program with stub files, which reduce the size of
- executable files in programs that do not use a particular BASIC
- feature
-
-
- This chapter assumes that you will be using BC from the command line. If you
- are compiling your program from QBX, use online Help to learn how to invoke
- the compiler and specify compiler options.
-
-
- New Options and Features
-
- The following options and features have been added to this release of BC:
-
- ■ The /Fs option enables you to store string data in far memory.
-
- ■ The /G2 option generates instructions specific to the 80286 memory
- chip that result in smaller, faster executable code.
-
- ■ The /Ot option optimizes the performance of procedure calls.
-
- ■ The /I x: options control memory use in ISAM applications.
-
- ■ The DOS INCLUDE environment variable enables you to determine where BC
- will look for included files without changing the $INCLUDE
- metacommand in your source file.
-
-
-
-
- Compiling with the BC Command
-
- You can compile with the BC command in either of the following ways:
-
- ■ Type all information on a single command line, using the following
- syntax:
-
- ■ BC sourcefile , objectfile , listingfile options;
-
- ■ You can let BC create default object and listing files for you by
- typing a semicolon or commas after sourcefile. BC will generate an
- object file with the same name as your source file (without its
- extension), and will append an .OBJ (object file) or .LST (listing
- file) filename extension.
-
- Type the BC command and respond to the following prompts:
-
- BC
-
- Source Filename [.BAS]:
- Object Filename [filename.OBJ]:
- Source Listing [NUL.LST]:
-
- The argument filename is the name of your source file
- without the .BAS filename extension.
-
- To accept the default filenames shown in brackets, type a carriage
- return or semicolon after the colon.
-
-
- Table 16.1 shows the input you must give on the BC command line or in
- response to each prompt.
-
-
- Specifying Filenames
-
- The BC command makes certain assumptions about the files you specify, based
- on the paths and extensions you use for the files. The following sections
- describe these assumptions and other rules for specifying filenames to the
- BC command.
-
-
- Uppercase and Lowercase Letters
-
- You can use any combination of uppercase and lowercase letters for
- filenames; the compiler accepts uppercase and lowercase letters
- interchangeably. Thus, the BC command considers the following three
- filenames to be equivalent:
-
- abcde.BAS
- ABCDE.BAS
- aBcDe.Bas
-
- Filename Extensions
-
- The name of a DOS file has two parts: the base filename, which includes
- everything up to (but not including) the period (.), and the filename
- extension, which includes the period and up to three characters following
- the period. In general, the extension identifies the type of file (for
- example, whether the file is a BASIC source file, an object file, an
- executable file, or an object-module library).
-
- BC uses the filename extensions described in the following list:
-
- .BAS BASIC source file
-
- .OBJ Object file
-
- .LST Listing file produced by BC. The extension .LST is
- the default filename extension; it will be overridden if
- you provide an extension for sourcefile.
-
-
- Paths
-
- Any filename can include a full or partial path. A full path starts with the
- drive name; a partial path has one or more directory names preceding the
- filename, but does not include a drive name.
-
- Giving a path allows you to specify files in different paths as input to the
- BC command and lets you create files on different drives or in different
- directories on the current drive.
-
- Note
-
- For files that you are creating with BC, you can give a path ending in a
- backslash. When it creates the file, BC uses the default name for the file.
-
- You can use the DOS command SET to change the INCLUDE environment variable
- determining a search path for files included in your BASIC source file. To
- do this enter a command with the following syntax before invoking BC:
-
- SET INCLUDE = path ; path...
-
- Using BC Command Options
-
- Options to the BC command consist of either a slash (/) or a dash (-)
- followed by one or more letters. (The slash and the dash can be used
- interchangeably. In this manual, forward slashes are used for options.) From
- QBX, you can modify BC options by choosing options in the Modify EXE File
- dialog box.
-
-
- Using Far Strings (/Fs)
-
-
- Using Floating-Point Options (/FPa and /FPi)
-
- Microsoft BASIC offers two main methods for handling floating-point math
- operations: in-line instructions (/FPi) or alternate math library (/FPa).
- Your choice of methods affects the speed and accuracy of floating-point
- operations, as well as the size of the executable file.
-
- Object files created with the /FPa option are not compatible with those
- created with the /FPi option. If you link files that use incompatible
- floating-point methods, BASIC detects the incompatibility and terminates
- with the error message Error during run-time initialization. When you link
- multiple-source files into a single executable file, you must ensure that
- every part of your program handles floating-point operations consistently.
-
-
- In-Line Instructions (/FPi)
-
- Specifying the /FPi option causes the compiler to create "in-line
- instructions" for use in floating-point operations. In-line instructions are
- machine-code instructions that a math coprocessor can execute. At run time,
- BASIC checks to see whether a math coprocessor is present. BASIC uses the
- coprocessor if it is present, or emulates its functions in software if it is
- not.
-
- This method of handling floating-point operations provides the fastest
- solution if you have a math coprocessor, and it offers the convenience of
- automatically checking for a coprocessor at run time. For these reasons,
- BASIC uses in-line instructions (/FPi) by default if you compile without
- choosing a floating-point option.
-
- If you choose the /FPi option, your program will link the emulator library
- (EMR.LIB or EMP.LIB), which provides a large subset of the functions of a
- math coprocessor in software. The emulator can perform basic operations to
- the same degree of accuracy as a math coprocessor. However, the emulator
- routines used for transcendental math functions differ slightly from the
- corresponding math coprocessor functions, causing a slight difference
- (usually within 2 bits) in the results of these operations when performed
- with the emulator instead of a math coprocessor.
-
-
- Alternate Math Library (/FPa)
-
- If you compile with the /FPa option, your program uses the alternate math
- library for floating-point operations. The alternate math library
- (BLIBFA.LIB or BLIBFP.LIB) is a software-only math package that uses a
- subset of the Institute of Electrical and Electronics Engineers, Inc. (IEEE)
- format numbers.
-
- This option offers the fastest math solution if your computer does not have
- a math coprocessor. It also creates a smaller executable file. However,
- using the alternate math library does sacrifice some accuracy in favor of
- speed and simplicity because infinity, "not-a-number" (NAN) values, and
- denormal numbers are not used. See Chapter 15, "Optimizing Program Size and
- Speed," for information about the performance gain and accuracy of the
- alternate math library. See Appendix B, "Data Types, Constants, Variables,
- and Arrays," for the range of values valid with alternate math.
-
- When you choose the alternate-math library, BASIC does not use a math
- coprocessor, even if one is present.
-
-
- Run-Time Modules and Floating-Point Methods
-
- Your choice of floating-point method has implications at run time for
- programs that use run-time modules and libraries. If you compile without the
- /O option, you must ensure that the appropriate run-time module is present
- at run time. (The /O option creates a stand-alone file that does not require
- the BASIC run-time module.) For each operating mode (real or protected),
- Microsoft BASIC offers several versions of the run-time module. Some
- versions use the in-line-instructions method for floating-point operations,
- while other versions use the alternate-math-library method. See Chapter 21,
- "Custom Run-Time Modules," for more information about run-time modules and
- libraries.
-
-
- Optimizing Procedure Calls (/Ot)
-
- Calls to SUB, FUCTION, and DEF FN procedures can be optimized by
- compiling with the /Ot option under certain conditions. The /Ot option
- reduces the size of the stack frame generated by certain calls, thereby
- reducing the amount of information passed to the stack when a call is made.
- Reducing the amount of information passed to the stack reduces the amount of
- time required to execute. The /Ot option will improve execution speed as
- follows:
-
- ■ SUB and FUNCTION procedures
-
- ■ A reduced stack frame is generated with /Ot if no module-level
- error-handling routines exist in the code and the /D or /Fs option is
- not used. The full stack frame is generated and no performance benefit
- results if your code uses local error handling, uses a DEF FN or
- GOSUB statement, has a return, or contains an ON event GOSUB
- statement.
-
- ■ DEF FN procedures
-
- ■ A full stack frame is generated and no benefit results if the /D, /Fs,
- /E, or /X option is used. A partial stack frame is generated if the /W
- or /V option is used. In all other cases, no stack frame is generated
- and a performance benefit results.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
-
- Chapter 17: About Linking and Libraries
-
- This chapter provides an overview of library and linking support provided
- with Microsoft BASIC. You'll learn about the types of libraries you can
- work with and how to link them into your BASIC programs. If you are
- interested in learning about how to use the LINK and LIB utilities and
- their options, see Chapter 18, "Using LINK and LIB." The Quick library,
- a special type of library used within the QBX environment, is described
- in Chapter 19, "Creating and Using Quick Libraries."
-
-
- What Is a Library?
-
- A library is an organized collection of object code; that is, a library
- contains functions and data that are already compiled (with a compiler or an
- assembler) and are ready to link with your program. The structure of a
- library supports the mass storage of common procedures -- procedures that
- can be called by a variety of programs. These common procedures, called
- "modules," can be added, deleted, changed, or copied.
-
- Libraries are typically used for one of three purposes:
-
- ■ To support high-level languages. For example, Microsoft BASIC performs
- input/output and floating-point operations by calling standard support
- routines. Because the support routines are available in a library, the
- compiler never needs to regenerate code for these routines.
-
- ■ To perform complex and specialized activities, such as financial
- functions or matrix math operations. Libraries containing such
- routines often are provided by the makers of the compilers or by
- third-party software vendors. Microsoft BASIC comes with several such
- libraries.
-
- ■ To support your own work. If you have created routines that you find
- useful for a variety of programs, you may want to put these routines
- into a library. That way, these routines do not need to be rewritten
- or recompiled. You save development time by using work you have
- already done, and disk space because you don't have to replicate
- source code.
-
-
- Note
-
- Because LIB and LINK assign special meaning to the at sign (@), it should
- never be used as the first character of a filename that is to be made into
- an executable or library file.
-
-
-
- Types of Libraries (.LIB and .QLB)
-
- Microsoft BASIC provides tools for creating two different types of
- libraries, which are identified by the following filename extensions:
-
- You can think of a Quick library as a group of procedures appended to QBX
- when the library is loaded into the environment, while an object-module
- library is a collection of independent, compiled procedures. Object-module
- libraries can be linked with a main module to create a file that is
- executable from the DOS command line.
-
- Both types of libraries are discussed in the following sections.
-
-
- Object-Module Libraries (.LIB )
-
- Object-module libraries can be linked with compiled object files to produce
- stand-alone executable programs. These libraries normally have the .LIB
- filename extension. Microsoft BASIC supplies the following types of
- object-module libraries:
-
- ■ Stand-alone libraries. These libraries allow your executable file to
- run alone, without run-time modules (see the following item).Your
- program will look for this type of library during linking if you
- compiled your program with the /O option of the BASIC Compiler (BC).
-
- ■ Run-time libraries. These libraries are used in conjunction with
- special modules called run-time modules. Your program will look for
- this type of library during linking if you have not used the /O option
- of BC when you compiled your program. Run-time modules and libraries
- are discussed in greater detail in Chapter 21, "Building Custom
- Run-Time Modules."
-
- ■ Add-on libraries and BASIC toolbox files. Microsoft BASIC supplies two
- add-on libraries: a financial function library and a date/time
- library. These are object-module libraries (.LIB) that contain
- special-purpose routines.
-
- ■ BASIC toolbox files are BASIC source files (.BAS) containing mouse,
- menu, window, graphics, and support routines. These files can be
- turned into stand-alone or Quick libraries. (See Chapters 18, "Using
- LINK and LIB" and 19, "Creating and Using Quick Libraries," for
- details about creating libraries.)
-
-
- The Setup program automatically creates stand-alone and run-time
- libraries during the setup operation. The exact libraries created
- depend upon which math package, mode (real or protected), and string
- support options you specify.
-
-
- You can also create your own custom library or turn BASIC source code
- modules into a library. To do this, separately compile source code modules
- that you want in the library, then use LIB to combine those object modules
- into one library. LIB also lets you add, delete, replace, copy, and move
- modules in the library as you choose. The QBX environment automates this
- process if you select the Make Library command from the Run menu.
-
-
- Stand-Alone Libraries
-
- Stand-alone libraries use the following naming convention:
-
- BCL70 float string mode.LIB
-
- The possible values for each variable are shown in Table 17.1.
-
- For example, if you specify the emulator floating-point math, far
- strings, and real mode options during setup, your program would search
- for and link with the BCL70EFR.LIB stand-alone library. From within QBX,
- you can change the library used for linking your program by changing
- options in the Make EXE dialog box.
-
- When you compile with the /O option of BC, an object file requiring a
- stand-alone library is produced. If you then run LINK, the proper
- stand-alone library is linked with your program to produce a stand-alone
- executable file. In the QBX environment, your program is automatically
- linked with a stand-alone library if you select the Stand-Alone EXE option
- from the Make EXE File dialog box.
-
- Stand-alone programs require more disk space than those requiring the run-tim
- variables listed in COMMON statements as well as open files are not
- preserved when a CHAIN statement transfers control to another program.
- Stand-alone programs do have the following advantages, however:
-
-
- ■ Stand-alone programs always require less memory than their run-time
- equivalents.
-
- ■ Execution of the program does not require that the run-time module be
- on the disk when the program is run. This is important when you write
- programs that users will copy, since an inexperienced user may not
- know to copy the run-time module as well.
-
-
-
- BASIC Run-Time Libraries
-
- You may choose to link with a run-time library instead of with a stand-alone
- library. If you compile and then link your program with a run-time library,
- the resulting executable file can only be run in the presence of a run-time
- module. This module contains code that implements the BASIC language.
-
- Using a run-time module and library provides the following advantages:
-
- ■ The executable file is much smaller. If several programs are kept on
- the disk, considerable space is saved.
-
- ■ Unnamed COMMON variables and open files are preserved across CHAIN
- statements. This can be valuable in systems of programs that use
- shared data.
-
- ■ Run-time modules reside in memory, so they do not need to be reloaded
- for each program in a system of chained programs.
-
-
- Run-time modules use the following naming conventions for real and protected
- modes and run-time libraries:
-
- BRT70 float string mode.EXE (Real mode)
-
- BRT70 float string mode.DLL (Protected mode)
-
- Run-time libraries use the following naming convention:
-
- BRT70 float string mode.LIB
-
- The possible values for each variable are the same as for stand-alone
- libraries (see Table 17.1). For example, if you specify the emulator
- math, far strings, and real mode options during the setup operation,
- your program would use the BRT70EFR.EXE run-time module and the
- BRT70EFR.LIB run-time library.
-
-
- In addition to the standard run-time module, Microsoft BASIC also lets
- you embed your own routines into the run-time module to create a custom
- run-time module. To do this, you write and compile your source modules;
- create a file called an export list to define object files, routines,
- and libraries you wish to add to the run-time module; invoke the BUILDRTM
- utility to create the custom run-time module and support files; and,
- finally, link the object files for your application with the support
- files for your custom run-time module. These steps are described in
- detail in Chapter 21, "Building Custom Run-Time Modules."
-
-
- Add-On Libraries
-
- Microsoft BASIC provides several additional libraries. These libraries
- provide additional support for various types of functions that you may want
- to include in your BASIC programs.
-
-
- BASIC Toolbox Files
-
- BASIC toolbox files are BASIC source files that you can use to build
- object-module or Quick libraries.
-
- After compiling these files into object files, you can use LIB to
- create an object-module library or LINK to create a Quick library.
- See Chapter 18, "Using LINK and LIB," for details on how to create an
- object-module library or Chapter 19, "Creating and Using Quick
- Libraries," for details on how to create a Quick library.
-
-
-
- Quick Libraries (.QLB)
-
- Microsoft BASIC supports another type of library called a Quick library.
- This type of library can only be used in the QBX environment -- it can't be
- linked with object modules to create an executable file. Quick libraries
- normally have the .QLB filename extension.
-
- Quick libraries contain one or more procedures that can be loaded and made
- available to your BASIC programs in the QBX environment. A Quick library can
- contain procedures written in BASIC or other Microsoft languages such as C.
- Procedures in a Quick library behave like QBX's own statements. Just like
- BASIC statements, Quick library procedures can be executed directly from the
- Immediate window or called from your BASIC program. This makes it easy to
- test them before using them in other programs.
-
-
- Advantages of Quick Libraries
-
- There are several reasons why you might want to use Quick libraries. Quick
- libraries facilitate program development and maintenance. As development
- progresses on a project and modules become stable components of your
- program, you can add them to a Quick library, then set aside the source
- files for the original modules until you want to improve or maintain those
- source files. Thereafter you can load the library along with QBX, and your
- program has instant access to all procedures in the library.
-
- If you develop programs with others, Quick libraries make it easy to
- update a pool of common procedures. If you wish to offer a library of
- original procedures for commercial distribution, all QBX programmers
- will be able to use them immediately to enhance their own work. You
- could leave your custom Quick library on a bulletin board for others
- to try before purchasing. Because Quick libraries contain no source
- code and can only be used within the QBX programming environment, your
- proprietary interests are protected while your marketing goals are
- advanced.
-
- BASIC procedures within Quick libraries represent code compiled with the
- command-line compiler. These procedures share significant characteristics
- with executable files. For example, executable files and Quick libraries
- perform floating-point arithmetic faster than the same calculations
- performed within the QBX environment.
-
-
- Note
-
- Quick libraries have the same function as user libraries in QuickBASIC
- versions 2.0 and 3.0. However, you cannot load a user library as a Quick
- library. You must recreate the library from the original source code, as
- described in the following section.
-
-
- Making Quick Libraries
-
- If your source modules are written in BASIC, you can create Quick libraries
- from either QBX or the command line. However, if you have source modules
- written in languages besides BASIC, you must create the Quick library from
- the command line.
-
- From QBX, load desired BASIC source modules into the environment and choose
- the Make Library option from the Run menu to build the library. QBX
- automatically compiles your BASIC source modules, then creates a Quick
- library (.QLB) and a parallel object-module library (.LIB) as shown in
- Figure 17.1.
-
- To make a Quick library from the command line, you must compile all
- source modules using the appropriate compiler (or assembler), then invoke
- LINK to create the Quick library. You should also create a parallel
- object-module library using the LIB.
-
- Since Quick libraries are managed by LINK, you can't manipulate objects in
- it as with object-module libraries. To remove, add, or modify modules in a
- Quick library, you must work with its parallel object-module library.
-
- Instructions for creating, modifying, and using Quick libraries are found in
- Chapter 19, "Creating and Using Quick Libraries."
-
-
- Overview of Linking
-
- Linking is the process of combining libraries and other object files to
- create an executable file or a Quick library. To do this, Microsoft BASIC
- provides the LINK utility. To create a Quick library, LINK must be invoked
- with the /Q (Quick library) option. To create an executable file, LINK is
- invoked without the /Q option.
-
- LINK can be invoked from either QBX or from the command line. From QBX, LINK
- is invoked automatically when you select either the Make EXE or Make Library
- option from the Run menu. From the command line, LINK is invoked by typing
- LINK on the command line followed by command-line arguments and options.
-
-
- Using LINK to Create Executable Files
-
- LINK can be used to create an executable file from object modules and
- libraries. You can write source modules in any standard Microsoft language
- such as BASIC, C, or MASM. Then compile the programs using the appropriate
- compiler to produce an object file (.OBJ). Finally, use LINK to combine the
- object modules with appropriate libraries.
-
- For example, say you have a written a BASIC program ONE.BAS, which calls a C
- program named TWO.C and an assembly language program named THREE.ASM. You
- would first compile each program separately, and then link the resulting
- object files to produce an executable file:
-
- The file COMBO.EXE can be run from the DOS command prompt. LINK also
- supports other features such as producing a map file and reading information
- from a module definition file (OS/2 programs). For details on using LINK,
- see Chapter 18, "Using LINK and LIB."
-
-
- Using LINK to Create Quick Libraries
-
- If you specify the /Q option, the LINK will create a Quick library instead
- of an executable file. You use object modules such as compiled source code
- or libraries as input to LINK.
-
-
- For example, the files ONE.BAS, TWO.C, and THREE.ASM could be compiled
- and then linked along with the library OLD.LIB to create a Quick library
- by specifying the /Q option when you invoke LINK:
-
- For more information about creating Quick libraries, see Chapter 19,
- "Creating and Using Quick Libraries."
-
-
- Linking with Stub Files
-
- Microsoft BASIC provides several special-purpose object files with which you
- can link to minimize the size of your executable file. These files, called
- "stub files," cause LINK to exclude (or in some cases, include a smaller
- version of) code that would normally be placed in your executable file.
-
- For example, if your program does not need editor support, you could link
- your program with the NOEDIT.OBJ stub file. LINK would then include code
- that replaces the editor used with the INPUT and LINE INPUT statements.
- Keep in mind that this process is appropriate only for programs compiled
- with the /O option of BC or when creating a custom run-time module.
-
- The stub files supplied with Microsoft BASIC and how to link with them are
- discussed in Chapter 18, "Using LINK and LIB."
-
-
- Linking to Create Overlays
-
- You can direct LINK to create an overlaid version of a program. In an
- overlaid version of a program, specified parts of the program (known as
- "overlays") are loaded only if and when they are needed. These parts share
- the same space in memory. Only code is overlaid; data is never overlaid.
- Programs that use overlays usually require less memory, but they run more
- slowly because of the time needed to read and reread the code from disk or
- Expanded Memory Specification (EMS) into conventional memory. Specifying
- overlays can be useful if you have compiled a program that is too large to
- load into memory. You can have only those portions of code loaded into
- memory that are currently needed. Overlays are discussed in Chapters 15,
- "Optimizing Program Size and Speed," and 18, "Using LINK and LIB."
-
-
- ────────────────────────────────────────────────────────────────────────────
-
- Chapter 18: Using LINK and LIB
-
- This chapter describes the syntax and usage of the Microsoft Library
- Manager (LIB) and the Microsoft Segmented-Executable Linker (LINK).
-
- The syntax to invoke each utility is provided, as well as descriptions
- of command-line arguments and options. For an overview of linking and
- libraries, see Chapter 17, "About Linking and Libraries."
-
-
- Invoking and Using LIB
-
- The Microsoft Library Manager (LIB) helps you create and maintain
- object-module libraries. An object-module library is a collection of
- separately compiled or assembled object files combined into a single file.
- Object-module libraries provide a convenient source of commonly used
- routines. A program that calls library routines is linked with the library
- to produce the executable file. Only modules containing the necessary
- routines, not all library modules, are linked into the executable file.
-
- Library files are usually identified by their .LIB extension, although other
- extensions are allowed. In addition to accepting DOS object files and
- library files, LIB can read the contents of 286 XENIX archives and
- Intel-style libraries and combine their contents with DOS libraries.
-
- You can use LIB for the following tasks:
-
- ■ Create a new library file.
-
- ■ Add object files or the contents of a library to an existing library.
-
- ■ Delete library modules.
-
- ■ Replace library modules.
-
- ■ Copy library modules to object files.
-
-
- While the Microsoft BASIC Setup program creates libraries during
- installation, you will need to run LIB if you want to create new libraries
- (for example, from BASIC object modules) or if you want to modify the
- contents of an existing library. This lets you create custom libraries
- containing only those routines that you want.
-
- To invoke LIB, type the LIB command on the DOS command line. You can specify
- the input required in one of three ways:
-
- ■ Type it on the command line after the LIB command.
-
- ■ Respond to prompts.
-
- ■ Specify a file containing responses to prompts (called a "response
- file").
-
-
- This section describeshow to specify input to LIB on the command line. To
- use prompts or a response file to specify input, see the sections "LIB
- Prompts" or "LIB Response File" later in this chapter.
-
- The command-line syntax for LIB is as follows:
-
- LIB oldlibrary [options%]@AE@% [commands%]@AE@%,
- [listfile], [newlibrary] ;
-
-
- The individual fields are discussed in greater detail in the sections that
- follow.
-
- Type a semicolon (;) after any field except the oldlibrary field to tell
- LIB to use the default responses for the remaining fields. The semicolon
- should be the last character on the command line. Typing a semicolon after
- the oldlibrary field causes LIB to perform a consistency check on the
- library -- no other action is performed. LIB displays any consistency errors
- it finds and returns to the operating-system level (see the section
- "Consistency Check" later in this chapter for more information).
-
- You can terminate the library session at any time and return to the
- operating system by pressing Ctrl+C.
-
-
- Old Library File
-
- Use the oldlibrary field to specify the name of the library to be created,
- modified, or operated upon. LIB assumes that the filename extension is .LIB,
- so if your library file has the .LIB extension, you can omit it. Otherwise,
- include the extension. You must give LIB the path of a library file if it is
- in another directory or on another disk.
-
- There is no default for the oldlibrary field. This field is required and
- LIB issues an error message if you do not give a filename. If the library
- you name does not exist, LIB displays the following prompt:
-
- Library does not exist. Create? (y/n)
-
- Type Y to create the library file, or N to terminate the session. This
- message does not appear if a command, a comma, or a semicolon immediately
- follows the library name.
-
-
- Consistency Check
-
- If you type a library name and follow it immediately with a semicolon (;),
- LIB only performs a consistency check on the given library. A consistency
- check tells you whether all the modules in the library are in usable form.
- No changes are made to the library. It usually is not necessary to perform
- consistency checks because LIB automatically checks object files for
- consistency before adding them to the library. LIB prints a message if it
- finds an invalid object module; no message appears if all modules are
- intact.
-
-
- Creating a Library File
-
- To create a new library file, give the name of the library file you want to
- create in the oldlibrary field of the command line (or at the "Library
- name" prompt). LIB supplies the .LIB extension, if needed.
-
- If the name of the new library file is the same as the name of an existing
- library file, LIB assumes that you want to change the existing file. If the
- name of the new library file is the same as the name of a file that is not a
- library, LIB issues an error message.
-
- When you give the name of a file that does not currently exist, LIB displays
- the following prompt:
-
- Library does not exist. Create? (y/n)
-
- Type Y to create the file, or N to terminate the library session. This
- message does not appear if the name is followed immediately by a command, a
- comma, or a semicolon.
-
- You can specify a page size for the library by specifying the /PA: number
- option when you create the library. The default page size is 16 bytes.
-
- Once you have given the name of the new library file, you can insert object
- modules into the library by using the add-command symbol (+) (described
- under "Commands" later in this chapter).
-
- Examples
-
- The following example causes LIB to perform a consistency check of the
- library file GRAPHIC.LIB:
-
- LIB GRAPHIC;
-
- The following example tells LIB to perform a consistency check of the
- library file GRAPHIC.LIB and to create SYMBOLS.LST, a
- cross-reference-listing file:
-
- LIB GRAPHIC ,SYMBOLS.LST;
-
- Options
-
- Specify options on the command line following the required library
- filename and preceding any commands.
-
-
- Ignoring Case of Symbols (/I)
-
- The /I option tells LIB to ignore case when comparing symbols, which is the
- default. Use this option when you are combining a library that is case
- sensitive (was created with the /NOI option) with others that are not case
- sensitive. The resulting library will not be case sensitive. The /NOI option
- is described later in this section.
-
-
- No Extended Dictionary (/NOE)
-
- The /NOE option tells LIB not to generate an extended dictionary. The
- extended dictionary is an extra part of the library that helps LINK process
- libraries faster.
-
- Use the /NOE option if you get the error message Insufficient memory or No
- more virtual memory, or if the extended dictionary causes problems with LINK
- (that is, if you receive the message Symbol multiply defined). For more
- information on how LINK uses the extended dictionary, see the description of
- the /NOE option for LINK.
-
-
- Using Case-Sensitive Symbols (/NOI)
-
- The /NOI option tells LIB not to ignore case when comparing symbols; that
- is, /NOI makes LIB case sensitive. By default, LIB ignores case. Using this
- option allows symbols that are the same except for case, such as Spline and
- SPLINE, to be put in the same library.
-
- Note that when you create a library with the /NOI option, LIB "marks" the
- library internally to indicate that /NOI is in effect. Earlier versions of
- LIB did not mark libraries in this way. If you combine multiple libraries
- and any one of them is marked /NOI, then /NOI is assumed to be in effect for
- the output library.
-
-
-
- Specifying Page Size (/PA:number)
-
- The /PA option specifies the library page size of a new library or changes
- the library page size of an existing library. The number specifies the new
- page size. It must be an integer value representing a power of two between
- the values 16 and 32,768.
-
- A library's page size affects the alignment of modules stored in the
- library. Modules in the library are always aligned to start at a position
- that is a multiple of the page size (in bytes) from the beginning of the
- file. The default page size for a new library is 16 bytes; for an existing
- library, the default is its current page size. Because of the indexing
- technique used by LIB, a library with a large page size can hold more
- modules than a library with a smaller page size. For each module in the
- library, however, an average of number / 2 bytes of storage space is
- wasted. In most cases, a small page size is advantageous; you should use a
- small page size unless you need to put a very large number of modules in a
- library.
-
- Another consequence of the indexing technique is that the page size
- determines the maximum possible size of the library file. Specifically, this
- limit is number * 65,536. For example, /PA:16 means that the library file
- must be smaller than 1 megabyte (16 * 65,536 bytes).
-
-
- Commands
-
- LIB can perform a number of library-management functions, including creating
- a library file, adding an object file as a module to a library, deleting a
- module from a library, replacing a module in the library file, copying a
- module to a separate object file, and moving a module out of a library and
- into an object file.
-
- The commands field allows you to specify the command symbols for
- manipulating modules. In this field, type a command symbol followed
- immediately by a module name or the name of an object file. The command
- symbols are the following:
-
- + Adds an object file or library to the library.
- - Deletes a module from the library.
- - + Replaces a module in the library.
- * Copies a module from the library to an object file.
- -* Moves a module (copies the module and then deletes it).
-
- Each of these commands is described in the following sections. Note
- that LIB does not process commands in left-to-right order; it uses its
- own precedence rules for processing (described in the next section).
-
- You can specify more than one operation in the commands field,
- in any order. LIB makes no changes to oldlibrary if you
- leave this field blank.
-
-
-
- Order of Processing
-
- For each library session, LIB reads and interprets commands in the following
- order. It determines whether a new library is being created or an existing
- library is being examined or modified.
-
- 1. LIB processes any deletion and move commands.
-
- LIB does not actually delete modules from the existing file. Instead,
- it marks the selected modules for deletion, creates a new library
- file, and copies only the modules not marked for deletion into the
- new library file.
-
- 2. LIB processes any addition commands.
-
- Like deletions, additions are not performed on the original library
- file. Instead, the additional modules are appended to the new library
- file. (If there were no deletion or move commands, a new library file
- would be created in the addition stage by copying the original library
- file.)
-
-
- How LIB Processes Commands
-
- As LIB carries out these commands, it reads the object modules in the
- library, checks them for validity, and gathers the information necessary to
- build a library index and a listing file. When you link a library with other
- object files, LINK uses the library index to search the library.
-
- LIB never makes changes to the original library; it copies the library and
- makes changes to the copy. Therefore, if you press Ctrl+C to terminate the
- session, you do not lose your original library. Because of this, when you
- run LIB, you must make sure your disk has enough space for the original
- library file and the copy.
-
- Once an object file is incorporated into a library, it becomes an "object
- module." An object file has a full path, including a drive designation,
- directory path, and filename extension (usually .OBJ) object modules have
- only a name. For example, B:\RUN\SORT.OBJ is an object filename, while SORT
- is an object module name.
-
-
- Add Command (+)
-
- Use the add-command symbol (+) to add an object module to a library. Give
- the name of the object file to be added, without the .OBJ extension,
- immediately following the plus sign.
-
- LIB uses the base name of the object file as the name of the object module
- in the library. For example, if the object file B:\CURSOR.OBJ is added to a
- library file, the name of the corresponding object module is CURSOR.
-
- Object modules are always added to the end of a library file.
-
- You can also use the
- plus sign to combine two libraries. When you give a library name following
- the plus sign, a copy of the contents of that library is added to the
- library file being modified. You must include the .LIB extension when you
- give a library filename. Otherwise, LIB uses the default .OBJ extension when
- it looks for the file. If both libraries contain a module with the same
- name, LIB ignores the second module of that name. For information on
- replacing modules, see the description of the replace-command symbol (- +)
- found later in this chapter.
-
-
- LIB adds the modules of the library to the end of the library being changed.
- Note that the added library still exists as an independent library because
- LIB copies the modules without deleting them.
-
- In addition to allowing DOS libraries as input, LIB also accepts 286 XENIX
- archives and Intel-format libraries. Therefore, you can use LIB to convert
- libraries from either of these formats to the DOS format.
-
-
- Examples
-
- The following example uses the add-command symbol (+) to instruct LIB to add
- the file STAR to the library GRAPHIC.LIB:
-
- LIB GRAPHIC +STAR;
- The semicolon at the end of the preceding command line causes LIB to use the
- default responses for the remaining fields. As a result, no listing file is
- created and the original library file is renamed GRAPHIC.BAK. The modified
- library is GRAPHIC.LIB.
-
- The following example adds the file FLASH.OBJ to the library MAINLIB.LIB:
-
- LIB MAINLIB +FLASH;
- The following example adds the contents of the library TRIG.LIB to the
- library MATH.LIB. The library TRIG.LIB is unchanged after this command is
- executed.
-
- LIB MATH +TRIG.LIB;
-
- Delete Command (-)
-
- Use the delete-command symbol (-) to delete an object module from a library.
- After the minus sign, give the name of the module to be deleted. Module
- names do not have paths or extensions. The contents of the deleted library
- are copied to another file having the same filename except with a .BAK
- extension.
-
-
- Example
-
- The following example deletes the module FLASH from the library MAINLIB.LIB:
-
- LIB MAINLIB -FLASH;
-
- Replace Command (- +)
-
- Use the replace-command symbol (- +) to replace a module in a library.
- Following the symbol, give the name of the module to be replaced. Module
- names do not have paths or extensions.
-
- To replace a module,
- LIB first deletes the existing module, then appends an object file that has
- the same name as the module. The object file is assumed to have the .OBJ
- extension and to reside in the current directory; if not, give the object
- filename with an explicit extension or path.
-
-
- Example
-
- This command replaces the module FLASH in the MAINLIB.LIB library with the
- contents of FLASH.OBJ from the current directory. Upon completion of this
- command, the file FLASH.OBJ still exists and the FLASH module is updated in
- MAINLIB.LIB.
-
- LIB MAINLIB -+FLASH;
-
- Copy Command (*)
-
- Use the copy-command symbol (*) followed by a module name to copy a module
- from the library into an object file of the same name. The module remains in
- the library. When LIB copies the module to an object file, it adds the .OBJ
- extension to the module name and places the file in the current directory.
-
- Example
-
- The following example copies the module FLASH from the MAINLIB.LIB library
- to a file called FLASH.OBJ in the current directory. Upon completion of this
- command, MAINLIB.LIB still contains the module FLASH.
-
- LIB MAINLIB *FLASH;
-
- Move Command (-*)
-
- Use the move-command symbol (-*) to move an object module from the library
- file to an object file. This operation is equivalent to copying the module
- to an object file, then deleting the module from the library.
-
- Example
-
- The following example moves the module FLASH from the MAINLIB.LIB library to
- a file called FLASH.OBJ in the current directory. Upon completion of this
- command, MAINLIB.LIB no longer contains the module FLASH.
-
- LIB MAINLIB -*FLASH;
-
- This following example instructs LIB to move the module JUNK from the
- library GRAPHIC.LIB to an object file named JUNK.OBJ. The module JUNK is
- removed from the library in the process.
-
- LIB GRAPHIC -*JUNK *STAR, ,SHOW
-
- In the preceding example, the module STAR is copied from the library to an
- object file named STAR.OBJ; the module remains in the library. No
- cross-reference listing file is produced. The revised library is named
- SHOW.LIB. It contains all the modules in GRAPHIC.LIB except JUNK, which was
- removed by using the move-command symbol (-*). The original library,
- GRAPHIC.LIB, remains unchanged.
-
-
- Cross-Reference Listing File
-
- The listfile field allows you to specify a filename for a cross-reference
- listing file. You can give the listing file any name and any extension. To
- create it outside your current directory, supply a path. Note that LIB does
- not assume any defaults for this field on the command line. If you do not
- specify a name for the file, the file is not created.
-
- A cross-reference listing file contains the following two lists:
-
- ■ An alphabetical list of all public symbols in the library.
-
- ■ Each symbol name is followed by the name of the module in which it is
- defined. The following example output shows that the public symbol ADD
- is contained in the module junk and the public symbols CALC, MAKE, and
- ROLL are contained in the module dice:
-
-
- ADD...............junk CALC..............dice
- MAKE..............dice ROLL..............dice
-
- ■ A list of the modules in the library.
-
- ■ Under each module name is an alphabetical listing of the public
- symbols defined in that module. The following example output shows
- that the module dice contains the public symbols CALC, MAKE, and ROLL
- and the module junk contains the public symbol ADD:
-
-
- dice Offset: 00000010H Code and data size: 621H
-
- CALC MAKE ROLL
-
- junk Offset: 00000bc0H Code and data size: 118H
-
- ADD
-
-
-
- New Library
-
- If you specify a name in the newlibrary field, LIB gives this name to the
- modified library it creates. This optional field is only used if you specify
- commands to change the library.
-
- If you leave this field blank, the original library is renamed with a .BAK
- extension and the modified library receives the original name.
-
-
- LIB Prompts
-
- If you type LIB at the DOS command line, the library manager prompts you for
- the input it needs by displaying the following four messages, one at a time:
-
- Library name:
- Operations:
- List file:
- Output library:
-
- The input for each prompt corresponds to each field of the LIB command.
- See the previous sections for descriptions of each LIB command field.
-
-
- LIB waits for you to respond to each prompt before printing the next prompt.
- If you notice that you have entered an incorrect response to a previous
- prompt, press Ctrl+C to exit LIB and begin again.
-
-
- Extending Lines
-
- If you have many operations to perform during a library session, use the
- ampersand symbol (&) to extend the operations line. Type the ampersand
- symbol after the name of an object module or object file; do not put the
- ampersand between a command symbol and a name.
-
- The ampersand causes LIB to display the Operations prompt again, allowing
- you to specify more operations.
-
-
- Default Responses
-
- Press the Enter key to choose the default response for the current prompt.
- Type a semicolon (;) and press Enter after any response except "Library
- name" to select default responses for all remaining prompts.
-
- The following list shows the defaults for LIB prompts:
-
- Operations No operation; no change to library file
- List file NUL; no listing file is produced
- Output library The current library name
-
-
- LIB Response File
-
- Using a response file lets you conduct the library session without typing
- responses to prompts at the keyboard. To run LIB with a response file, you
- must first create the response file. Then type the following at the DOS
- command line:
-
- LIB @ responsefile
-
- The responsefile is the name of a response file. Specify a
- path if the response file is not in the current directory.
-
- You can also enter @ responsefile at any position on a command line or after
- any of the prompts. The input from the response file is treated exactly as
- if it had been entered on a command line or after the prompts. A new-line
- character in the response file is treated the same as pressing the Enter key
- in response to a prompt.
-
- A response file uses one text line for each prompt. Responses must appear
- in the same order as the command prompts appear. Use command symbols
- in the response file the same way you would use responses typed on the
- keyboard. You can type an ampersand (&) at the end of the response to
- the Operations prompt, for instance, and continue typing operations on
- the next line.
-
- When you run LIB with a response file, the prompts are displayed with the
- responses from the response file. If the response file does not contain
- responses for all the prompts, LIB uses the default responses.
-
- Example
-
- Assume that a response file named RESPONSE in the directory B:\PROJ contains
- the following lines:
-
- GRAPHIC
- +CIRCLE+WAVE-WAVE*FLASH
- GRAPHIC.LST
-
- If you invoke LIB with the following command line, then LIB deletes the
- module WAVE from the library GRAPHIC.LIB, copies the module FLASH into an
- object file named FLASH.OBJ, appends the object files CIRCLE.OBJ and
- WAVE.OBJ as the last two modules in the library, and creates a
- cross-reference listing file named GRAPHIC.LST.
-
- LIB @B:\PROJ\RESPONSE
-
-
-
- Invoking and Using LINK
-
- This section describes how to use the Microsoft Segmented-Executable Linker
- (LINK). LINK allows you to link object files with appropriate libraries to
- create an executable file or Quick library.
-
- You can invoke LINK in several ways. If you are working in the QBX
- environment and have selected the Make EXE file option from the Run menu,
- LINK is automatically called after your program is compiled by BASIC. If you
- are compiling and linking your program from the command line, you must
- invoke LINK separately after compiling your program with BC. You can also
- invoke LINK and specify LINK options from within a MAKEFILE (described in
- Chapter 20, "Using NMAKE").
-
- Regardless of how you invoke LINK, you may press Ctrl+C at any time to
- terminate a LINK operation and exit to the operating system.
-
- You can specify the input required for the LINK command in one of three
- ways:
-
- ■ By placing it on the command line.
-
- ■ By responding to prompts.
-
- ■ By specifying a file containing responses to prompts. This type of
- file is known as a "response file."
-
-
- This section describes how to invoke link from the command line. For
- information about responding to prompts or using a response file, see
- the sections "LINK Prompts" and "LINK Response File" later in this chapter.
-
-
- The command-line syntax for the LINK command is as follows:
-
- LINK
- [options%]@AE@% objfiles[, [exefile] [, [mapfile%]@AE@%[, [libraries]
- [, [deffile]] ] ] ] ] [;]
-
- A module-definition file is needed only for OS/2 protected mode and
- Microsoft Windows programs (not compatible with BASIC programs); however,
- this prompt is still issued when you are linking DOS programs.
-
- The command line fields are described fully in the sections that follow.
-
- A comma must separate each command-line field from the next. You may omit
- the text from any field (except the required objfiles), but you must
- include the comma. A semicolon may end the command line after any field,
- causing LINK to use defaults for the remaining fields.
-
- The following example causes LINK to load and link the object modules
- SPELL.OBJ, TEXT.OBJ, DICT.OBJ, and THES.OBJ, and to search for unresolved
- references in the library XLIB.LIB as well as in the default library created
- during setup.
-
- LINK SPELL+TEXT+DICT+THES, ,SPELLIST, XLIB.LIB;
- By default, the executable file produced by LINK is named SPELL.EXE. LINK
- also produces a map file, SPELLIST.MAP. The semicolon at the end of the line
- tells NMAKE to accept the default module-definition file (NUL.DEF).
-
- The following example produces a map file named SPELL.MAP because a comma
- appears as a placeholder for the map file specified on the command line:
-
- LINK SPELL,,;
-
- The following example does not produce a map file because commas do not
- appear as placeholders for the map file specified:
-
- LINK SPELL,;
- LINK SPELL;
-
- The following example causes LINK to link the three files MAIN.OBJ,
- GETDATA.OBJ, and PRINTIT.OBJ into an executable file. A map file named
- MAIN.MAP is also produced:
-
- LINK MAIN+GETDATA+PRINTIT, , MAIN;
-
-
- Default Filename Extensions
-
- You can use any combination of uppercase and lowercase letters for the
- filenames you specify on the LINK command line or give in response to the
- LINK command prompts.
-
- You can override the default extension for a particular command-line
- field or prompt by specifying a different extension. To enter a filename
- that has no extension, type the name followed by a period.
-
-
-
- Choosing Defaults
-
- If you include a comma (to indicate where a field would be) but do not put a
- filename before the comma, then LINK selects the default for that field.
- However, if you use a comma to include the mapfile field (but do not
- include a name), then LINK creates a map file. This file has the same base
- name as the executable file. Use NUL for the map filename if you do not want
- to produce a map file.
-
- You can also select default responses by using a semicolon (;). The
- semicolon tells LINK to use the defaults for all remaining fields. Anything
- after the semicolon is ignored. If you do not give all filenames on the
- command line or if you do not end the command line with a semicolon, LINK
- prompts you for the files you omitted. Descriptions of these prompts are
- given in the following section.
-
- If you do not specify a drive or directory for a file, LINK assumes that the
- file is on the current drive and directory. If you want LINK to create files
- in a location other than the current drive and directory, you must specify
- the new drive and directory for each such file on the command line.
-
-
- LINK Options
-
- The linkoptions field contains command-line options to LINK. You may
- specify command-line options after any field, but before the comma that
- terminates the field. You do not have to give any options when you run LINK.
- See the sections that follow for individual descriptions of command-line
- link options.
-
- This section explains how to use LINK options to specify and control the
- tasks performed by LINK. When you use the LINK command line to invoke
- LINK, you may put options at the end of the line or after individual
- fields on the line. Options, however, must immediately precede the comma
- that separates each field from the next.
-
- If you respond to the individual prompts for the LINK command, you may
- specify LINK options at the end of any response. When you use more than one
- option, you can either group the options at the end of a single response or
- distribute the options among several responses. Every option must begin with
- the slash character (/) or a dash (-), even if other options precede it on
- the same line.
-
- In a response file, options may appear on a line by themselves or after
- individual response lines.
-
-
- Abbreviations
-
- Because LINK options are named according to their functions, some of their
- names are quite long. You can abbreviate the options to save space and
- effort. Be sure that your abbreviation is unique so that LINK can determine
- which option you want. The minimum legal abbreviation for each option is
- indicated in the description of that option listed in the sections that
- follow.
-
- Abbreviations must begin with the first letter of the name and must be
- continuous through the last letter typed. No spaces or transpositions are
- allowed. Options may be entered in uppercase or lowercase letters.
-
-
- Numeric Arguments
-
- Some LINK options take numeric arguments. A numeric argument can be any of
- the following:
-
- ■ A decimal number from 0 to 65,535.
-
- ■ An octal number from 00 to 0177777. A number is interpreted as octal
- if it starts with 0. For example, the number "10" is interpreted as a
- decimal number, but the number "010" is interpreted as an octal
- number, equivalent to 8 in decimal.
-
- ■ A hexadecimal number from 0X0 to 0XFFFF. A number is interpreted as
- hexadecimal if it starts with 0X. For example, "0X10" is a hexadecimal
- number, equivalent to 16 in decimal.
-
-
-
- LINK Environment Variable
-
- You can use the LINK environment variable to cause certain options to be
- used each time you link. LINK checks the environment variable for options if
- the variable exists.
-
- LINK expects to find options listed in the variable exactly as you would
- type them on the command line. It does not accept any other arguments; for
- instance, including filenames in the environment variable causes the error
- message Unrecognized option name.
-
- Each time you link, you can specify other options in addition to those
- in the LINK environment variable. If you enter the same option on the
- command line and in the environment variable, LINK ignores the redundant
- option. If the options conflict, however, the command-line option overrides
- the effect of the environment-variable option. For example, the
- command-line option /SE:512 cancels the effect of the environment-variable
- option /SE:256.
-
-
- Note
-
- Unless you override it on the command line, the only way to prevent an
- option in the environment variable from being used is to reset the
- environment variable itself.
-
-
- Examples
-
- In the following example, the file TEST.OBJ is linked with the options /NOI,
- /SE:256, and /CO:
-
- SET LINK=/NOI /SE:256 /CO
- LINK TEST;
-
- In the next example, the file PROG.OBJ is then linked with the option /NOD,
- in addition to /NOI, /SE:256, and /CO (note that the second /CO option is
- ignored):
-
- LINK /NOD /CO PROG;
-
-
- Valid LINK Options
-
- LINK provides many options that can be used to link programs written in
- several Microsoft languages. While most are valid for BASIC programs,
- several should not be used. Table 18.1 lists those options that are valid
- for BASIC programs.
-
-
-
- Invalid LINK Options
-
- Not all options of the LINK command are suitable for use with BASIC
- programs. Table 18.2 lists options that do not have an effect or that should
- not be used with BASIC programs.
-
-
- Aligning Segment Data (/A:size)
-
- This option directs LINK to align segment data in the executable file along
- with the boundaries specified by size. The size argument must be a power
- of two. For example, /A:16 indicates an alignment boundary of 16 bytes. The
- default alignment for OS/2 application and dynamic-link segments is 512.
- This option is used for linking Windows applications or protected-mode
- programs.
-
-
- Running in Batch Mode (/BA)
-
- By default, LINK prompts you for a new path whenever it cannot find a
- library that it has been directed to use. It also prompts you if it cannot
- find an object file that it expects to find on a removable disk. If you use
- the /BA option, however, LINK does not prompt you for any libraries or
- object files that it cannot find. Instead, LINK generates an error or
- warning message, if appropriate. In addition, when you use /BA, LINK does
- not display its copyright banner, nor does it echo commands from response
- files. This option does not prevent LINK from prompting for command-line
- arguments. You can prevent such prompting only by using a semicolon on the
- command line or in a response file.
-
- Using this option may result in unresolved external references. It is
- intended primarily for use with batch or NMAKE files that link many
- executable files with a single command and to prevent LINK operation from
- halting.
-
-
- Note
-
- In earlier versions of LINK, the /BATCH option was abbreviated to /B.
-
-
- Preparing for Debugging (/CO)
-
- The /CO option is used to prepare for debugging with the Microsoft CodeView
- window-oriented debugger. This option tells LINK to prepare a special
- executable file containing symbolic data and line-number information.
-
- Object files linked with the /CO option must first be compiled with the /Zi
- option, which is described in Chapter 16, "Compiling With BC."
-
- You can run this executable file outside the CodeView debugger; the extra
- data in the file is ignored. To keep file size to a minimum, however, use
- the special-format-executable file only for debugging; then you can link a
- separate version without the /CO option after the program is debugged.
-
-
- Ordering Segments (/DO)
-
- The /DO option forces a special ordering on segments. This option is
- automatically enabled by a special object-module record in Microsoft BASIC
- libraries. If you are linking to one of these libraries, then you do not
- need to specify this option.This option is also enabled by assembly modules
- that use the MASM directive .DOSSEG.
-
- The /DO option forces
- segments to be ordered as follows:
-
-
- 1. All segments with a class name ending in CODE
-
- 2. All other segments outside DGROUP
-
- 3. DGROUP segments, in the following order:
-
- a. Any segments of class BEGDATA (this class name
- reserved for Microsoft use)
-
- b. Any segments not of class BEGDATA, BSS, or
- STACK
-
- c. Segments of class BSS
-
- d. Segments of class STACK
-
-
- When the /DO option is in effect LINK initializes two special variables as
- follows:
-
- _edata = DGROUP : BSS
- _end = DGROUP : STACK
-
- The variables _edata and _end have special meanings for the Microsoft C and
- FORTRAN compilers, so it is not wise to give these names to your own program
- variables. Assembly modules can reference these variables but should not
- change them.
-
-
- Packing Executable Files (/E)
-
- The /E option directs LINK to remove sequences of repeated bytes (typically
- null characters) and to optimize the load-time-relocation table before
- creating the executable file. (The load-time-relocation table is a table of
- references, relative to the start of the program. Each reference changes
- when the executable image is loaded into memory and an actual address for
- the entry point is assigned.)
-
- Executable files linked with this option may be smaller, and thus load
- faster, than files linked without this option. Programs with many load-time
- relocations (about 500 or more) and long streams of repeated characters are
- usually shorter if packed. The /E option, however, does not always save a
- significant amount of disk space and sometimes may increase file size. LINK
- notifies you if the packed file is larger than the unpacked file.
-
-
- Optimizing Far Calls (/F)
-
- The /F option directs LINK to optimize far calls to procedures that lie in
- the same segment as the caller. Using the /F option may result in slightly
- faster code and smaller executable-file size. It should be used with the
- /PAC option for significant results. By default, the /F option is off.
-
- For example, a medium- or large-model program may include a machine
- instruction that makes a far call to a procedure in the same segment.
- Because the instruction and the procedure it calls have the same segment
- address, only a near call is truly necessary. A near-call instruction does
- not require an entry in the relocation table as does a far-call instruction.
- In this situation, use of /F (together with /PAC) would result in a smaller
- executable file because the relocation table is smaller. Such files load
- faster.
-
- When /F has been specified, LINK optimizes code by removing the
- following instruction:
-
- call FAR label
-
- LINK then substitutes the sequence:
-
- nop
- push cs
- call NEAR label
-
- Upon execution, the called procedure still returns with a far-return
- instruction. Because the code segment and the near address are on the stack,
- however, the far return is executed correctly. The nop (no-op) instruction
- appears so that exactly 5 bytes replace the 5-byte far-call instruction;
- LINK may in some cases place nop at the beginning of the sequence.
-
- The /F option has no effect on programs that make only near calls. Of the
- high-level Microsoft languages, only small- and compact-model C programs use
- near calls.
-
-
- Note
-
- There is a small risk involved with the /F option: LINK may mistakenly
- translate a byte in a code segment that happens to have the far-call opcode
- (9A hexadecimal). If a program linked with /F inexplicably fails, then you
- may want to try linking with this option off. Object modules produced by
- Microsoft high-level languages, however, should be safe from this problem
- because relatively little immediate data is stored in code segments.
-
- In general, assembly language programs are also relatively safe for use with
- the /F option, as long as they do not involve advanced system-level code,
- such as might be found in operating systems or interrupt handlers.
-
-
- Viewing the Options List (/HE)
-
- The /HE option causes LINK to display a list of its options on the screen.
- This gives you a convenient reminder of the options.
-
- When you use this option, LINK ignores any other input you give and does not
- create an executable file.
-
-
- Preparing for Incremental Linking (/INC)
-
- The /INC option prepares a file for subsequent linking with ILINK. The use
- of this option produces a .SYM file and an .ILK file, each containing extra
- information needed by ILINK. Note that this option is not compatible with
- the /E option.
-
-
- Displaying LINK Process Information (/INF)
-
- The /INF option tells LINK to display information about the linking process,
- including the phase of linking and the names of the object files being
- linked. This option is useful if you want to determine the locations of the
- object files being linked and the order in which they are linked.
-
- Output from this option is sent to the standard error output.
-
- Example
-
- The following is a sample of LINK output when the /INF option is specified
- on the LINK command line:
-
- **** PARSE DEFINITIONS FILE ****
- **** PASS ONE ****
- HELLO.OBJ(HELLO.OBJ)
- **** LIBRARY SEARCH ****
- BRT70ENR.LIB(..\rt\rtmdata.asm)
- BRT70ENR.LIB(..\rt\rtmload.asm)
- BRT70ENR.LIB(..\rt\rmessage.asm)
- BRT70ENR.LIB(fixups.ASM)
- BRT70ENR.LIB(..\rt\rtmint1.asm)
- BRT70ENR.LIB(C:\TEMP\B6.)
- **** ASSIGN ADDRESSES ****
- **** PASS TWO ****
- BRT70ENR.LIB(..\rt\rtmdata.asm)
- BRT70ENR.LIB(..\rt\rtmload.asm)
- BRT70ENR.LIB(..\rt\rmessage.asm)
- BRT70ENR.LIB(fixups.ASM)
- BRT70ENR.LIB(..\rt\rtmint1.asm)
- BRT70ENR.LIB(C:\TEMP\B6.)
- **** WRITING EXECUTABLE ****
- Segments 36
- Groups 1
- Bytes in symbol table 10546
-
-
- Including Line Numbers in the Map File (/LI)
-
- You can include the line numbers and associated addresses of your source
- program in the map file by using the /LI option. This option is primarily
- useful if you will be debugging with the SYMDEB debugger included with
- earlier releases of Microsoft language products.
-
- Ordinarily the map file does not contain line numbers. To produce a map file
- with line numbers, you must give LINK an object file (or files) with
- line-number information. (The /Zd and /Zi options of the compiler direct the
- compiler to include line numbers in the object file.) If you give LINK an
- object file without line-number information, the /LI option has no effect
-
- The /LI option forces LINK to create a map file even if you did not
- explicitly tell LINK to create a map file. By default, the file is
- given the same base name as the executable file plus the extension .MAP.
- You can override the default name by specifying a new map file on the
- LINK command line or in response to the "List File" prompt.
-
-
- Listing Public Symbols (/M)
-
- You can list all public (global) symbols defined in the object file(s) by
- using the /M option. When you invoke LINK with the /M option, the map file
- contains a list of all the symbols sorted by name and a list of all the
- symbols sorted by address. LINK sorts the maximum number of symbols that can
- be sorted in available memory. If you do not use this option, the map file
- contains only a list of segments.
-
- When you use this option, the default for the mapfile field or "List File"
- prompt response is no longer NUL. Instead, the default is a name that
- combines the base name of the executable file with a .MAP extension. You may
- still specify NUL in the mapfile field (which indicates that no map file is
- to be generated); if you do, the /M option has no effect.
-
-
- Ignoring Default Libraries (/NOD:filename)
-
- The /NOD option tells LINK not to search any library specified in the
- object file to resolve external references. If you specify filename, then
- LINK searches all libraries specified in the object file except for
- filename.
-
- In general, higher-level language programs do not work correctly without a
- standard library. Therefore, if you use the /NOD option, you should
- explicitly specify the name of a standard library in the libraries field.
-
-
- Ignoring Extended Dictionary (/NOE)
-
- The /NOE option prevents LINK from searching the extended dictionary, which
- is an internal list of symbol locations that LINK maintains. Normally, LINK
- consults this list to speed up library searches. The effect of the /NOE
- option is to slow down LINK. You often need this option when a library
- symbol is redefined. Use /NOE if LINK issues the following error message:
-
- symbol name multiply defined
-
-
- Disabling Far-Call Optimization (/NOF)
-
- This option is normally not necessary because far-call optimization
- (translation) is turned off by default. However, if an environment variable
- such as LINK turns on far-call translation automatically, you can use /NOF
- to turn far-call translation off again.
-
-
- Preserving Case Sensitivity (/NOI)
-
- By default, LINK treats uppercase letters and lowercase letters as
- equivalent. Thus, ABC, abc, and Abc are considered the same name. When you
- use the /NOI option, LINK distinguishes between uppercase letters and
- lowercase letters, and considers ABC, abc, and Abc to be three separate
- names. Because names in Microsoft BASIC are not case sensitive, this option
- can have minimal importance. You should not use the /NOI option when linking
- a protected-mode custom run-time module, or protected-mode program without
- the /O option.
-
-
- Suppressing the Sign-On Logo (/NOL)
-
- This option prevents the LINK sign-on banner from being displayed.
-
-
- Ordering Segments Without Inserting Null Bytes (/NON)
-
- This option directs LINK to arrange segments in the same order as they are
- arranged by the /DO option. The only difference is that the /DO option
- inserts 16 null bytes at the beginning of the _TEXT segment (if it is
- defined), whereas /NON does not insert these extra bytes.
-
-
- Disabling Segment Packing (/NOP)
-
- This option is normally not necessary because code-segment packing is turned
- off by default. However, if an environment variable such as LINK turns on
- code-segment packing automatically, you can use /NOP to turn segment packing
- off again.
-
-
- Setting the Overlay Interrupt (/O:number)
-
- By default, the interrupt number used for passing control to overlays is 63
- (3F hexadecimal). The /O option allows you to select a different interrupt
- number.
-
- The number can be a decimal number from 0 to 255, an octal number from
- octal 0 to octal 0377, or a hexadecimal number from hexadecimal 0 to
- hexadecimal FF. Numbers that conflict with DOS interrupts can be used;
- however, their use is not advised.
-
- In general, you should not use /O with programs. The exception to this
- guideline would be a program that uses overlays and spawns another program
- that also uses overlays. In this case, each program should use a separate
- overlay-interrupt number, meaning that at least one of the programs should
- be compiled with /O.
-
-
- Packing Contiguous Segments (/PAC:number)
-
- The /PAC option affects code segments only in medium- and large-model
- programs. It is intended to be used with the /F option. It is not necessary
- to understand the details of the /PAC option in order to use it. You only
- need to know that this option, used in conjunction with /F, produces
- slightly faster and more compact code. The packing of code segments provides
- more opportunities for far-call optimization, which is enabled with /F. The
- /PAC option is off by default and can always be turned off with the /NOP
- option.
-
- The /PAC option directs LINK to group neighboring code segments. Segments
- in the same group are assigned the same segment address; offset addresses
- are adjusted upward accordingly. In other words, all items have the
- correct physical address whether the /PAC option is used or not. However,
- /PAC changes segment and offset addresses so that all items in a group
- share the same segment address.
-
- The number field specifies the maximum size of groups formed by /PAC. LINK
- stops adding segments to a group as soon as it cannot add another segment
- without exceeding number. At that point, LINK starts forming a new group.
- The default for number is 65,530.
-
- Do not use this option if you have overlays. In addition, the /PAC option
- should not be used with assembly programs that make assumptions about the
- relative order of code segments. For example, the following assembly code
- attempts to calculate the distance between CSEG1 and CSEG2. This code would
- produce incorrect results when used with /PAC because /PAC causes the two
- segments to share the same segment address. Therefore, the procedure would
- always return 0.
-
- CSEG1 SEGMENT PARA PUBLIC 'CODE'
- .
- .
- .
- CSEG1 ENDS
-
- CSEG2 SEGMENT PARA PUBLIC 'CODE'
- ASSUME cs:CSEG2
-
- ; Return the length of CSEG1 in AX.
-
- codsize PROC NEAR
- mov ax,CSEG2 ; Load para address of CSEG2
- sub ax,CSEG1 ; Load para address of CSEG1
- mov cx,4 ; Load count, and convert
- shl ax,cl ; distance from paragraphs
- ; to bytes
- codsize ENDP
-
- CSEG2 ENDS
-
-
- Packing Contiguous Data Segments (/PACKD)
-
- This option only affects code segments in medium- and large-model programs
- and is safe with all Microsoft high-level language compilers. It behaves
- exactly like the /PAC option except that is applies to data segments, not
- code segments. LINK recognizes data segments as any segment definition with
- a class name that does not end in CODE. The adjacent data segment
- definitions are combined into the same physical segment up to the given
- limit. The default limit is 65,536.
-
-
- Padding Code Segments (/PADC:padsize)
-
- The /PADC option causes LINK to add filler bytes to the end of each code
- module for subsequent linking with ILINK. The option is followed by a colon
- and the number of bytes to add (a decimal radix is assumed, but you can
- specify octal or hexadecimal numbers by using a C-language prefix). Thus,
- the following adds an additional 256 bytes to each module:
-
- /PADCODE:256
-
- The default size for code-module padding is 0 bytes. To use this option, you
- must also specify the /INC option.
-
-
- Note
-
- Code padding is usually not necessary for large- and medium-model programs,
- but is recommended for small, compact, and mixed-memory model programs, and
- for assembly language programs in which code segments are grouped.
-
-
- Padding Data Segments (/PADD:padsize)
-
- The /PADD option performs a function similar to the /PADC option, except
- that it specifies padding for data segments (or data modules, if the program
- uses the small- or medium-memory model). The default size for data-segment
- padding is 16 bytes. To use the /PADD option, you must also specify the /INC
- option.
-
-
- Note
-
- If you specify too large a value for padsize, you may exceed the 64K
- limitation on the size of the default data segment.
-
-
- Pausing During Linking (/PAU)
-
- The /PAU option tells LINK to pause before it writes the executable file to
- disk. This option is useful on machines without hard disks, where you might
- want to create the executable file on a new disk. Without the /PAU option,
- LINK performs the linking session from beginning to end without stopping.
-
- If you specify the /PAU option, LINK displays the following message before
- it creates the file:
-
- About to generate .EXE file
- Change diskette in drive letter and press <ENTER>
-
- The letter corresponds to the current drive. LINK resumes
- processing when you press Enter.
-
-
- Note
-
- Do not remove the disk that will receive the listing file or the disk used
- for the temporary file.
-
- Depending on how much memory is available, LINK may create a temporary disk
- file during processing, as described in the section "LINK Memory
- Requirements," later in this chapter and display the following message:
-
- Temporary file tempfile has been created.
- Do not change diskette in drive, letter
-
- If the file is created on the disk you plan to swap, press Ctrl+C to
- terminate the LINK session. Rearrange your files so that the temporary file
- and the executable file can be written to the same disk, then try linking
- again.
-
-
- Specifying OS/2 Window Type (/PM:type)
-
- The /PM option specifies the type of Presentation Manager window that the
- application can be run in. The argument type can be one of
- the following:
-
- PM Presentation Manager application. The application uses the
- Presentation Manager API and must be executed in the Presen-
- tation Manager environment. Not valid for BASIC programs.
-
- VIO Presentation Manager-compatible application. This application
- can run in the Presentation Manager environment from a VIO
- window, or it can be run in a separate screen group. An
- application can be of this type if it uses the proper subset
- of OS/2 video, keyboard, and mouse functions supported in the
- Presentation Manager API.
-
- VIO applications written in BASIC are valid with the following
- restrictions: the application cannot support event handling or
- graphics. You can link your program with the NOEVENT.OBJ and
- NOGRAPH.OBJ stub files to remove these features.
-
- NOVIO Application is not compatible with the Presentation Manager
- and must operate in a separate screen group (default). Valid
- for BASIC programs.
-
- This option can be used in place of the WINDOAPI, WINDOWCOMPAT,
- and NOTWINDOWCOMPAT keywords in the module-definition file.
-
-
- Creating a Quick Library (/Q)
-
- When you use this option, LINK will create a Quick library that can be used
- from within the QBX environment. For instructions on how to create a Quick
- library, see Chapter 19, "Creating and Using Quick Libraries."
-
-
- Setting Maximum Number of Segments (/SE:number)
-
- The /SE option controls the number of segments that LINK allows a program to
- have. The default is 128, but you can set number to any value (decimal,
- octal, or hexadecimal) in the range 1-3072 (decimal).
-
- For each segment, LINK must allocate some space to keep track of segment
- information. By using a relatively low segment limit as a default (128),
- LINK is able to link faster and allocate less storage space.
-
- When you set the segment limit higher than 128, LINK allocates additional
- space for segment information. This option allows you to raise the segment
- limit for programs with a large number of segments. For programs with fewer
- than 128 segments, you can keep the storage requirements of LINK at the
- lowest level possible by setting number to reflect the actual number of
- segments in the program. If the number of segments allocated is too high for
- the amount of memory available to LINK, LINK issues the following error
- message:
-
- segment limit set too high
-
- If this occurs, link the object files again, specifying a lower segment
- limit.
-
-
- Issuing Fixup Warnings (/W)
-
- This option directs LINK to issue a warning for each segment-relative fixup
- of location type offset, such that the segment is contained within a group
- but is not at the beginning of the group. LINK will include the displacement
- of the segment from the group in determining the final value of the fixup,
- unlike DOS executable files. Use this option when linking protected-mode
- programs or Windows applications.
-
- To use this option with BASIC programs, you must also specify the /NOP
- option. This option should not be used to link custom run-time modules.
-
-
- Object Files
-
- The objfiles field allows you to specify the names of the object files you
- are linking. At least one object filename is required. A space or plus sign
- (+) must separate each pair of object filenames. LINK automatically supplies
- the .OBJ extension when you give a filename without an extension. If your
- object file has a different extension or if it appears in another directory
- or on another disk, you must give the full name--including the extension and
- path--for the file to be found. If LINK cannot find a given object file, and
- the drive associated with the object file is a removable-disk drive, then
- LINK displays a message and waits for you to change disks.
-
- You may also specify one or more libraries in the objfiles
- field. To enter a library in this field, make sure that you include the
- .LIB extension; otherwise, LINK assumes the .OBJ extension. Libraries
- entered in this field are called "load libraries" as opposed to regular
- libraries. LINK automatically links every object module in a load library;
- it does not search for unresolved external references first. The effect
- of entering a load library is exactly the same as if you had entered the
- names of all the library's object modules in the objfiles field.
- This feature is useful if you are developing software using many modules
- and wish to avoid typing the name of each module on the LINK command line.
-
-
- Executable File
-
- The exefile field allows you to specify the name of the executable file. If
- the filename you give does not have an extension, LINK automatically adds
- .EXE as the extension (or .QLB if /Q is specified). You can give any
- filename you like; however, if you are specifying an extension, you should
- always use .EXE because the operating system expects executable files to
- have either this extension or the .COM extension.
-
-
- Map File
-
- The mapfile field allows you to specify the name of the map file if you are
- creating one. To include public symbols and their addresses in the map file,
- specify the /M option on the LINK command line.
-
- If you specify a map filename without an extension, LINK automatically adds
- a .MAP extension. LINK creates the map file in the current working directory
- unless you specify a path for the map file.
-
-
- Libraries
-
- The libraries field allows you to specify the name of one or more libraries
- that you want linked with the object file(s). When LINK finds the name of a
- library in this field, it treats the library as a "regular library" and
- links only those object modules needed to resolve external references.
-
- Each time you compile a source file for a high-level language, the compiler
- places the name of one or more libraries in the object file that it creates;
- LINK automatically searches for a library with this name (see the next
- section, "How LINK Searches for Libraries"). Because of this, you need not
- supply library names on the LINK command line unless you want to search
- libraries other than the default libraries or search for libraries in
- different locations.
-
- When you link your program with a library, LINK pulls any library modules
- that your program references into your executable file. If the library
- modules have external references to other library modules, your program is
- linked with those other library modules as well.
-
-
-
- How LINK Searches for Libraries
-
- LINK searches for libraries that are specified in either of the following
- ways:
-
- ■ In the libraries field on the command line or in response to the
- "Libraries" prompt.
-
- ■ By an object module. BC writes the name of a default library in each
- object module it creates.
-
-
- Note
-
- The material in the following sections does not apply to libraries that LINK
- finds in the objfiles field, on the command line or in response to the
- "Object Modules" prompt. Those libraries are treated simply as a series of
- object files, and LINK does not conduct extensive searches in such cases.
-
-
- Library Name with Path
-
- If the library name includes a path, LINK searches only that directory for
- the library. Libraries specified by object modules (that is, default
- libraries) normally do not include a path.
-
-
- Library Name Without Path
-
- If the library name does not include a path, LINK searches the following
- locations, in the order shown, to find the library file:
-
- 1. The current directory.
-
- 2. Any paths or drive names that you give on the command line or type in
- response to the "Libraries" prompt, in the order in which they appear.
-
- 3. The locations given by the LIB environment variable.
-
-
- Because object files created by BC contain the names of all the standard
- libraries you need, you are not required to specify a library on the LINK
- command line or in response to the LINK "Libraries" prompt unless you want
- to do one of the following:
-
- ■ Add the names of additional libraries to be searched.
-
- ■ Search for libraries in different locations.
-
- ■ Override the use of one or more default libraries.
-
-
- For example, if you have developed your own libraries, you might want to
- include one or more of them as additional libraries when you link them.
-
-
- Searching Additional Libraries
-
- You can tell LINK to search additional libraries by specifying one or more
- library files on the command line or in response to the "Libraries" prompt.
- LINK searches these libraries in the order you specify before it searches
- default libraries.
-
- LINK automatically supplies the .LIB extension if you omit it from a
- library filename. If you want to link a library file that has a different
- extension, be sure to specify the extension.
-
- For example, suppose that you want LINK to search the NEWLIBV3.LIB library
- before it searches the default library BCL70AFR.LIB. You would type LINK to
- start LINK, and then respond to the prompts as follows:
-
- Object Modules .OBJ: SPELL TEXT DICT THES
- Run File SPELL.EXE:
- List File NUL.MAP:
- Libraries .LIB: C:\TESTLIB\NEWLIBV3
-
- This example links four object modules to create an executable filename
- SPELL.EXE. LINK searches NEWLIBV3.LIB before searching BCL70AFR.LIB to
- resolve references. To locate NEWLIBV3.LIB and the default libraries,
- LINK searches the current working directory, then the C:\TESTLIB\
- directory, and finally the locations given by the LIB environment variable.
-
-
- Searching Different Locations for Libraries
-
- You can tell LINK to search additional locations for libraries by giving a
- drive name or path in the libraries field on the command line or in
- response to the "Libraries" prompt. You can specify up to 32 additional
- paths. If you give more than 32 paths, LINK ignores the additional paths
- without displaying an error message.
-
-
- Overriding Libraries Named in Object Files
-
- If you do not want to link with the library whose name is included in the
- object file, you can give the name of a different library instead. You might
- need to specify a different library name in the following cases:
-
- ■ You assigned a "custom" name to a standard library when you set up
- your libraries.
-
- ■ You want to link with a library that supports a different math package
- than the math package you gave on the compiler command line (or the
- default).
-
-
- If you specify a new library name on the LINK command line, LINK searches
- the new library to resolve external references before it searches the
- library specified in the object file.
-
- If you want LINK to ignore the library whose name is included in the object
- file, you must use the /NOD option. This option tells LINK to ignore the
- default-library information that is encoded in the object files created by
- high-level language compilers. Use this option with caution; for more
- information, see the section "Ignoring Default Libraries (/NOD:filename)"
- earlier in this chapter.
-
-
-
- Module-Definition File
-
- The deffile field allows you to specify the name of a module-definition
- file (OS/2 protected-mode or Microsoft Windows programs only). Leave this
- field blank if you are linking a real mode program. The use of a
- module-definition file is optional for applications, but is required for
- dynamic-link libraries.
-
-
- LINK Prompts
-
- If you want LINK to prompt you for input, start LINK by typing the following
- at the system prompt:
-
- LINK
-
- LINK also displays prompts if you type an incomplete command line that
- does not end with a semicolon or if a response file (described in the
- section "LINK Response File" later in this chapter) is missing any required
- responses.
-
-
- LINK prompts you for the input it needs by displaying the following lines,
- one at a time. The items in square brackets are the defaults LINK applies if
- you press Enter in response to the prompt. (You must supply at least one
- object filename for the "Object Modules" prompt.) LINK waits for you to
- respond to each prompt before it displays the next one.
-
- Object Modules [.OBJ]:
- Run File basename.[EXE]:
- List File [NUL.MAP]:
- Libraries [.LIB]:
- Definitions File [NUL.DEF]:
-
- Note that the default for the "Run File" prompt is the base name of the
- first object file with the .EXE extension. To select the default response
- to the current prompt, press the Enter key without giving a filename.
- The next prompt appears.
-
- To select default responses to the current prompt and all remaining prompts,
- type a semicolon (;) and press Enter. After you type a semicolon, you cannot
- respond to any of the remaining prompts for that link session. This saves
- time when you want the default responses. Note, however, that you cannot
- enter only a semicolon in response to the "Object Modules" prompt because
- there is no default response for that prompt; LINK requires the name of at
- least one object file.
-
-
- LINK Response File
-
- A response file contains responses to the LINK prompts. The responses must
- be in the same order as the LINK prompts discussed in the previous section.
- Each new response must appear on a new line or must begin with a comma;
- however, you can extend long responses across more than one line by typing a
- plus sign (+) as the last character of each incomplete line. You may give
- options at the end of any response or place them on one or more separate
- lines.
-
- LINK treats the input from the response file just as if you had entered it
- in response to prompts or on a command line. It treats any new-line
- character in the response file as if you had pressed Enter in response to a
- prompt or included a comma in a command line. For compatibility with OS/2
- versions of LINK, it is recommended that all LINK response files end with a
- semicolon after the last line.
-
- To use LINK with a response file, create the response file, then type the
- following command:
-
- LINK @ responsefile
-
- Here responsefile specifies the name or path of the
- response file for LINK. You can also enter the name of a response file,
- preceded by an "at" sign (@), after any LINK command prompt or at any
- position in the LINK command line; in this case, the response file completes
- the remaining input.
-
-
- Options and Command Characters
-
- You can use options and command characters in the response file in the same
- way as you would use them in responses you type at the keyboard. For
- example, if you type a semicolon on the line of the response file
- corresponding to the "Run File" prompt, LINK uses the default responses for
- the executable file and for the remaining prompts.
-
-
- Prompts
-
- When you enter the LINK command with a response file, each LINK prompt is
- displayed on your screen with the corresponding response from your response
- file. If the response file does not include a line with a filename,
- semicolon, or carriage return for each prompt, LINK displays the appropriate
- prompt and waits for you to enter a response. When you type an acceptable
- response, LINK continues.
-
-
- Example
-
- Assume that the following response file is named SPELL.LNK:
-
- SPELL+TEXT+DICT+THES /PAUSE /MAP
- SPELL
- SPELLIST
- XLIB.LIB;
-
- You can type the following command to run LINK and tell it to use the
- responses in SPELL.LNK:
-
- LINK @SPELL.LNK
-
- The response file tells LINK to load the four object files SPELL, TEXT,
- DICT, and THES. LINK produces an executable file named SPELL.EXE and a map
- file named SPELLIST.MAP. The /PAU option tells LINK to pause before it
- produces the executable file so that you can swap disks, if necessary. The
- /M option tells LINK to include public symbols and addresses in the map
- file. LINK also links any needed routines from the library file XLIB.LIB.
- The semicolon is included after the library name for compatibility with the
- OS/2 version of LINK.
-
-
- LINK Operation
-
- LINK performs the following steps to combine object modules and produce an
- executable file:
-
- 1. Reads the object modules submitted.
-
- 2. Searches the given libraries, if necessary, to resolve external
- references.
-
- 3. Assigns addresses to segments.
-
- 4. Assigns addresses to public symbols.
-
- 5. Reads code and data in the segments.
-
- 6. Reads all relocation references in object modules.
-
- 7. Performs fixups.
-
- 8. Produces an executable file (executable image and relocation
- information).
-
-
- Steps 5, 6, and 7 are performed concurrently: in other words, LINK moves
- back and forth between these steps before it progresses to step 8.
-
- The "executable image" contains the code and data that constitute the
- executable file. The "relocation information" is a list of references,
- relative to the start of the program. The references change when the
- executable image is loaded into memory and an actual address for the entry
- point is assigned.
-
- The following sections explain the process LINK uses to concatenate segments
- and resolve references to items in memory.
-
-
- Alignment of Segments
-
- LINK uses a segment's alignment type to set the starting address for the
- segment. The alignment types are BYTE, WORD, PARA, and PAGE. These
- correspond to starting addresses at byte, word, paragraph, and page
- boundaries, representing addresses that are multiples of 1, 2, 16, and 256,
- respectively. The default alignment is PARA.
-
- When LINK encounters a segment, it checks the alignment type before copying
- the segment to the executable file. If the alignment is WORD, PARA, or PAGE,
- LINK checks the executable image to see if the last byte copied ends on the
- appropriate boundary. If not, LINK pads the image with null bytes.
-
-
- Frame Number
-
- LINK computes a starting address for each segment in the program. The
- starting address is based on the segment's alignment and the sizes of the
- segments already copied to the executable file (as described in the previous
- section). The starting address consists of an offset and a canonical frame
- number. The "canonical frame number" specifies the address of the first
- paragraph in memory that contains one or more bytes of the segment. (A
- paragraph is 16 bytes of memory; therefore, to compute a physical location
- in memory, multiply the frame number by 16 and add the offset.) The offset
- is the number of bytes from the start of the paragraph to the first byte in
- the segment. For BYTE and WORD alignments, the offset may be nonzero. The
- offset is always zero for PARA and PAGE alignments. (An offset of zero means
- that the physical location is an exact multiple of 16.).
-
- You can find the frame number for each segment in the map file created by
- LINK. The first four digits of the segment's start address give the frame
- number in hexadecimal. For example, a start address of 0C0A6 indicates the
- frame number 0C0A.
-
-
- Order of Segments
-
- LINK copies segments to the executable file in the same order that it
- encounters them in the object files. This order is maintained throughout the
- program unless LINK encounters two or more segments that have the same class
- name. Segments having identical segment names are copied as a contiguous
- block to the executable file.
-
- The /DO option may change the way in which segments are ordered.
-
-
- Combined Segments
-
- LINK uses combine types to determine whether two or more segments that share
- the same segment name should be combined into one large segment. The valid
- combine types are PUBLIC, STACK, COMMON, and PRIVATE.
-
- If a segment has combine type PUBLIC, LINK automatically combines it with
- any other segments that have the same name and belong to the same class.
- When LINK combines segments, it ensures that the segments are contiguous
- and that all addresses in the segments can be accessed using an offset
- from the same frame address. The result is the same as if the segment
- were defined as a whole in the source file.
-
- LINK preserves each individual segment's alignment type. This means that
- even though the segments belong to a single, large segment, the code and
- data in the segments do not lose their original alignment. If the combined
- segments exceed 64K, LINK displays an error message.
-
- If a segment has combine type STACK, LINK carries out the same combine
- operation as for PUBLIC segments. The only exception is that STACK segments
- cause LINK to copy an initial stack-pointer value to the executable file.
- This stack-pointer value is the offset to the end of the first stack segment
- (or combined stack segment) encountered.
-
- If a segment has combine type COMMON, LINK automatically combines it with
- any other segments that have the same name and belong to the same class.
- When LINK combines COMMON segments, however, it places the start of each
- segment at the same address, creating a series of overlapping segments. The
- result is a single segment no larger than the largest segment combined.
-
- A segment has combine type PRIVATE only if no explicit combine type is
- defined for it in the source file. LINK does not combine private segments.
-
-
- Groups
-
- Groups allow segments to be addressed relative to the same frame address.
- When LINK encounters a group, it adjusts all memory references to items in
- the group so that they are relative to the same frame address.
-
- Segments in a group do not have to be contiguous, belong to the same class,
- or have the same combine type. The only requirement is that all segments in
- the group fit within 64K.
-
- Groups do not affect the order in which the segments are loaded. Unless you
- use class names and enter object files in the right order, there is no
- guarantee that the segments will be contiguous. In fact, LINK may place
- segments that do not belong to the group in the same 64K of memory. LINK
- does not explicitly check whether all the segments in a group fit within 64K
- of memory; however, LINK is likely to encounter a fixup-overflow error if
- they do not.
-
-
- Fixups
-
- Once LINK knows the starting address of each segment in the program and has
- established all segment combinations and groups, LINK can "fix up" any
- unresolved references to labels and variables. To fix up unresolved
- references, LINK computes the appropriate offset and segment address and
- replaces the temporary values generated by the assembler with the new
- values.
-
- LINK carries out fixups for the types of references shown in Table 18.3.
-
- The size of the value to be computed depends on the type of reference.
- If LINK discovers an error in the anticipated size of a reference, it
- displays a fixup-overflow message. This can happen, for example, if a
- program attempts to use a 16-bit offset to reach an instruction which
- is more than 64K away. It can also occur if all segments in a group do
- not fit within a single 64K block of memory.
-
-
-
- LINK Memory Requirements
-
- LINK uses available memory for the link session. If the files to be linked
- create an output file that exceeds available memory, LINK creates a
- temporary disk file to serve as memory. This temporary file is handled in
- one of the following ways, depending on the DOS version
-
- ■ For the purpose of creating a temporary file, LINK uses the directory
- specified by the TMP environment variable. If the TMP variable is set
- to C:\TEMPDIR, for example, then LINK puts the temporary file in
- C:\TEMPDIR.
-
- ■ If there is no TMP environment variable or if the directory specified
- by TMP does not exist, then LINK puts the temporary file in the
- current directory.
-
- ■ If LINK is running on DOS version 3.0 or later, it uses a DOS system
- call to create a temporary file with a unique name in the
- temporary-file directory.
-
- ■ If LINK is running on a version of DOS prior to 3.0, it creates a
- temporary file named VM.TMP.
-
-
- When LINK creates a temporary disk file, you see the message
-
- Temporary file tempfile has been created. Do not change diskette in drive,
-
- letter.In the preceding message, tempfile is ".\" followed by either VM.TMP
- or a name generated by the system, and letter is the drive containing the
- temporary file.
-
- If you are running on a floppy-disk system, the Do not change diskette
- message appears. After this message appears, do not remove the disk from the
- specified drive until the LINK session ends. If you remove the disk, the
- operation of LINK is unpredictable, and you may see the following message:
-
- unexpected end-of-file on scratch file
-
- If this happens, rerun the LINK operation. The temporary file created by
- LINK is a working file only. LINK deletes it at the end of the operation.
-
-
- Note
-
- Do not give any of your own files the name VM.TMP. LINK displays an error
- message if it encounters an existing file with this name.
-
-
- Linking Stub Files
-
- Microsoft BASIC provides several special-purpose object files called "stub
- files" that you can use to minimize the size of your executable file in
- cases where your program does not use a particular BASIC feature or where
- special support is needed. By linking these files, you can make LINK exclude
- (or in some cases, include smaller versions of) code that it would otherwise
- place in your executable file automatically. Keep in mind that this process
- is appropriate only for programs compiled with the /O option.
-
- You can also link stub files with custom run-time modules. Include the
- name of the file under the # OBJECTS directive in the export-file list
- for BUILDRTM. Table 18.4 lists the stub files included with Microsoft
- BASIC.
-
-
-
- Stub files (including the .LIB files listed) are specified in the objfiles
- field of LINK. You must supply the /NOE (/NOEXTDICTIONARY) option when
- linking any of the stub files. It is permissible to link more than one stub
- file at once. For instance, the following LINK command is appropriate for a
- program that requires no communications or printer support:
-
- LINK /NOE NOLPT+NOCOM+MYPROG.OBJ,MYPROG.EXE;
-
- This command links NOLPT.OBJ and NOCOM.OBJ to the user-created object file
- MYPROG.OBJ, producing the executable file MYPROG.EXE.
-
-
- Linking with Overlays
-
- You can direct LINK to create an overlaid version of a program. In an
- overlaid version of a program, specified parts of the program (known as
- "overlays") are loaded only if and when they are needed. These parts share
- the same space in memory. Only code is overlaid; data is never overlaid.
- Programs that use overlays usually require less memory, but they run more
- slowly because of the time needed to read and reread the code from disk into
- memory.
-
- You specify overlays by enclosing them in parentheses in the list of object
- files that you submit to LINK. Each module in parentheses represents one
- overlay. For example, you could give the following object-file list in the
- objfiles field of the LINK command line:
-
- A + (B+C) + (E+F) + G + (I)
-
- In this example, the modules (B+C), (E+F), and (I) are overlays. The
- remaining modules, and any drawn from the run-time libraries, constitute the
- resident part (or root) of your program. Overlays are loaded into the same
- region of memory, so only one can be resident at a time. Duplicate names in
- different overlays are not supported, so each module can appear only once in
- a program.
-
- LINK replaces calls from the root to an overlay, and calls from an overlay
- to another overlay, with an interrupt (followed by the module identifier and
- offset). By default, the interrupt number is 63 (3F hexadecimal). You can
- use the /O option of the LINK command to change the interrupt number.
-
- The CodeView debugger is compatible with overlaid modules. In fact, in
- the case of large programs, you may need to use overlays to leave
- sufficient room for the debugger to operate. When you link overlaid code
- using the /CO option, you will receive an error message Multiple code
- segments in module of overlaid code. This is normal.
-
- Care should be taken to compile each module in the program with compatible
- options. This means, for example, that all modules must be compiled with the
- same floating-point options.
-
-
- Using Expanded Memory
-
- If expanded memory is present in your computer, overlays are loaded from
- expanded memory; otherwise, overlays are loaded from disk. You can specify
- that overlays only be loaded from disk by linking your program with the
- NOEMS.OBJ stub file.
-
- If your program uses overlays from Expanded Memory Specification (EMS), and
- if it contains a routine that changes the state of EMS (for example, an
- assembly language routine that shells out to another program), you must
- restore the state of EMS before returning to the overlaid code. To do this,
- call the B_OVREMAP routine. This routine restores EMS to the state that
- existed before the routine that changed the state was called, and insures
- that overlays are loaded from EMS correctly. B_OVREMAP has no effect if
- overlays are not used or if overlays are not loaded from EMS.
-
-
- Restrictions on Overlays
-
- The following restrictions apply to using overlays in Microsoft BASIC:
-
- ■ Each Microsoft BASIC overlay cannot be larger than 256K. There is a
- maximum of 64 overlays per program.
-
- ■ Overlays should not be specified as the first object module on the
- LINK command line (the first object module must be a part of the
- program that is not overlaid).
-
- ■ When you create an overlaid version of a program, make sure that each
- module contained in the program is compiled with compatible options.
-
- ■ You cannot use the /PACKCODE option when linking a program that uses
- overlays.
-
- ■ You can overlay only modules to which control is transferred and
- returned by a standard 8086 long (32-bit) call/return instruction.
- Also, LINK does not produce overlay modules that can be called
- indirectly through function pointers. When a function is called
- through a pointer, the called function must be in the same overlay or
- root.
-
-
-
- Overlay-Manager Prompts
-
- The overlay manager is part of the language's run-time library. If you
- specify overlays during linking, the code for the overlay manager is
- automatically linked with the other modules of your program. Even with
- overlays, LINK produces only one .EXE file. At run time, the overlay manager
- opens the .EXE file each time it needs to extract new overlay modules. The
- overlay manager first searches for the file in the current directory; then,
- if it does not find the file, the manager searches the directories listed in
- the PATH environment variable. When it finds the file, the overlay manager
- extracts the overlay modules specified by the root program. If the overlay
- manager cannot find an overlay file when needed, it prompts you for the
- filename.
-
- For example, assume that an executable program named PAYROLL.EXE uses
- overlays and does not exist in either the current directory or the
- directories specified by PATH. If your program does not contain expanded
- memory, when you run PAYROLL.EXE (by entering a complete path), the overlay
- manager displays the following message when it attempts to load overlay
- files:
-
- Cannot find PAYROLL.EXE
- Please enter new program spec:
-
- You can then enter the drive or directory, or both, where PAYROLL.EXE is
- located. For example, if the file is located in directory \EMPLOYEE\DATA\ on
- drive B, you could enter B:\EMPLOYEE\DATA\ or simply \EMPLOYEE\DATA\ if the
- current drive is B.
-
- If you later remove the disk in drive B and the overlay manager needs to
- access the overlay again, it does not find PAYROLL.EXE and displays the
- following message:
-
- Please insert diskette containing B:\EMPLOYEE\DATA\PAYROLL.EXE
- and strike any key when ready.
-
- After reading the overlay file from the disk, the overlay manager displays
- the following message:
-
- Please restore the original diskette.
- Strike any key when ready.
-
- Execution of the program then continues.
-
- ────────────────────────────────────────────────────────────────────────────
-
- Chapter 19: Creating and Using Quick Libraries
-
- This chapter describes how to create and use Quick libraries. You'll
- learn how to do the following:
-
- ■ Make libraries from within the QBX environment and from the command
- line.
-
- ■ Make a Quick library that contains routines from an existing Quick
- library.
-
- ■ Load a Quick library when running a QBX program.
-
- ■ View the contents of a Quick library.
-
-
- Also, specific examples of how to create Quick libraries from different
- types of source code modules are presented and the last section of this
- chapter provides programming information specific to Quick libraries.
-
- For an overview of Quick libraries and reasons why you might want to use
- them, see Chapter 17, "About Linking and Libraries."
-
-
- The Supplied Library (QBX.QLB)
-
- Microsoft BASIC supplies a default Quick library named QBX.QLB. If you
- invoke QBX with the /L option, but do not supply a Quick library name, QBX
- automatically loads the library QBX.QLB, included with the QBX package. This
- file contains three routines, INTERRUPT, INT86OLD, and ABSOLUTE, that
- provide software-interrupt support for system-service calls and support for
- CALL ABSOLUTE. QBX.QLB is also necessary for creating certain other Quick
- libraries (see the section "Mouse, Menu, and Window Libraries" later in this
- chapter).
-
- You must load QBX.QLB (or another library into which INTERRUPT, INT86OLD,
- and ABSOLUTE have been incorporated) from the command line when you invoke
- QBX in order to use the routines from QBX.QLB. If you wish to use these
- routines along with other routines that you have placed in libraries, make a
- copy of the QBX.QLB library and use it as a basis for building a library
- containing all the routines you need.
-
- A parallel object-module library, QBX.LIB, is also supplied for use outside
- of the QBX environment.
-
-
- Files Needed to Create a Quick Library
-
- To create a Quick library, make sure that the following files are in
- the current working directory or accessible to QBX through the
- appropriate DOS environment variables:
-
- QBX.EXE Directs the process of creating a Quick Library. If
- you are working only with QBX modules, you can do
- everything in one step from within the QBX environment.
-
- BC.EXE Creates object files from source code.
-
- LINK.EXE Links object files.
-
- LIB.EXE Manages object-module libraries of object modules.
-
-