Dec 222017
Basic Wizard's Library v3.0. More than 500 routines for MS BASIC compilers: BCD and fraction math, .BMP read/write, millisecond timer, telecomm, graphics from Herc to SVGA and Epson to HP printer, equation solver. By Tom H

Full Description of File

BASIC Wizard's Library v3.0 for Microsoft
BASIC compilers: QB, PDS, VB-DOS. BCD math,
telecomm, graphics from Herc to SVGA incl.
VESA and dot matrix to laser printer, read
and write .BMP images, mSec timers, equation
solver, virtual windows. By Tom Hanlin.

File BASWIZ30.ZIP from The Programmer’s Corner in
Category BASIC Language
Basic Wizard’s Library v3.0. More than 500 routines for MS BASIC compilers: BCD and fraction math, .BMP read/write, millisecond timer, telecomm, graphics from Herc to SVGA and Epson to HP printer, equation solver. By Tom H
File Name File Size Zip Size Zip Type
ADDFILE.BAT 1643 535 deflated
ANSI.BAS 6374 1584 deflated
BASWIZ.DOC 154587 50448 deflated
BASWIZ.INF 81125 10664 deflated
BASWIZ.NEW 13418 5360 deflated
BIBLIO.TXT 4892 2342 deflated
BW$FAR.LIB 48389 17152 deflated
BW$MAIN.LIB 39597 15602 deflated
BW$NEAR.LIB 45829 16485 deflated
CALC.BAS 2188 668 deflated
CATALOG.TXT 13064 5318 deflated
CREATE.BAT 425 129 deflated
DESC.SDI 59 59 stored
FDEMO.BAS 2526 910 deflated
FILE_ID.DIZ 263 206 deflated
GDEMO.BAS 8714 2238 deflated
GDEMO.BMP 38102 30518 deflated
GDEMO.PCX 1063 489 deflated
LIBRARY.TXT 3430 1599 deflated
LIB_BI.DOC 24608 8920 deflated
MIRRORS.BAS 2156 657 deflated
ORDER.FRM 3131 952 deflated
PACKING.LST 2073 798 deflated
PDEMO.BAS 2217 672 deflated
QUESTION.TXT 7009 2969 deflated
ROSES.BAS 1512 539 deflated
TERM.BAS 10416 2644 deflated
TPCREAD.ME 199 165 deflated
VESAINFO.BAS 2104 666 deflated
WDEMO.BAS 9479 2434 deflated
WDEMO.DAT 1055 607 deflated

Download File BASWIZ30.ZIP Here

Contents of the BASWIZ.DOC file

BasWiz Copyright (c) 1990-1994 Thomas G. Hanlin III
The BASIC Wizard's Library, version 3.0

Use of LibWiz is strongly recommended for creating the BasWiz
library, due to the number of routines involved.

This is BasWiz, a general-purpose library with hundreds of
routines for use with Microsoft BASIC compilers: QuickBasic,
Bascom/PDS, and Visual BASIC for DOS.

The BasWiz collection is copyrighted. It may be distributed only
under the following conditions:

All BasWiz files must be distributed together as a unit in
unmodified form. No files may be left out or added.

own computer, but I will not assume responsibility for any
problems which BasWiz may cause you.

It is expected that if you find BasWiz useful, you will register
your copy. You may not use BasWiz routines in programs intended
for distribution unless you have registered. See the ORDER.FRM
file for registration details.

To create a BasWiz library using LibWiz, you will need a
complete set of .OBJ files for all BasWiz routines. Create a
fresh subdirectory and extract the BASIC source code from the
BW$BAS archive. Compile them using a DOS command like so:

FOR %x IN (*.BAS) DO BC %x /o;

Add the /FS switch (before the /o) if you wish to use far
strings with the PDS compiler. This is required for using BasWiz
in the QBX editor/environment.

Extract the .OBJ files from BW$MAIN.LIB into the same location,
using the UNLIB utility that comes with LibWiz.

Now you must extract the string routines by using UNLIB on the
appropriate string library: BW$NEAR.LIB for near strings or
BW$FAR for far strings. For QuickBasic, use near strings. For
Visual Basic, use far strings. For PDS, choose one or the other
(use far strings if you use the QBX editor/environment).

After these three steps-- compiling the BASIC files with your
compiler, extracting the main .OBJ files, and extracting the
appropriate string .OBJ files-- you have a complete set of .OBJs
for BasWiz. Use LibWiz to create a custom subset of BasWiz
that's tailored to your needs.

Table of Contents page 2

Overview and Legal Info ................................... 1

BCD Math .................................................. 3

Expression Evaluator ...................................... 7

Extensions to BASIC's math ................................ 8

Far Strings .............................................. 11

File Handling ............................................ 13

Fractions ................................................ 21

General Routines ...................................... 22
VESA Info Routines .................................... 32
Text-mode Routines .................................... 34
Dual Monitor Routines ................................. 35
Printer Routines ...................................... 36
A Little Geometry ..................................... 37
Equations, Etc ........................................ 41

Memory Management and Pointers ........................... 44

Quick Time (millisecond timer) ........................... 48

Telecommunications ....................................... 50

Virtual Windowing System ................................. 56

Other Routines ........................................... 70

Miscellaneous Notes ...................................... 71

Error Codes .............................................. 74

Troubleshooting .......................................... 76

History & Philosophy ..................................... 79

Using BasWiz with P.D.Q. or QBTiny ....................... 81

Credits .................................................. 82

BCD Math page 3

Some of you may not have heard of BCD math, or at least not have
more than a passing acquaintance with the subject. BCD (short
for Binary-Coded Decimal) is a way of encoding numbers. It
differs from the normal method of handling numbers in several
respects. On the down side, BCD math is much slower than normal
math and the numbers take up more memory. However, the benefits
may far outweigh these disadvantages, depending on your
application: BCD math is absolutely precise within your desired
specifications, and you can make a BCD number as large as you
need. If your applications don't require great range or
precision out of numbers, normal BASIC math is probably the best
choice. For scientific applications, accounting, engineering and
other demanding tasks, though, BCD may be just the thing you

The BCD math routines provided by BasWiz allow numbers of up to
255 digits long (the sign counts as a digit, but the decimal
point doesn't). You may set the decimal point to any position
you like, as long as there is at least one digit position to the
left of the decimal.

Since QuickBasic doesn't support BCD numbers directly, we store
the BCD numbers in strings. The results are not in text format
and won't mean much if displayed. A conversion routine allows
you to change a BCD number to a text string in any of a variety
of formats.

Note that the BCD math handler doesn't yet track
overflow/underflow error conditions. If you anticipate that this
may be a problem, it would be a good idea to screen your input
or to make the BCD range large enough to avoid these errors.

Let's start off by examining the routine which allows you to set
the BCD range:

BCDSetSize LeftDigits%, RightDigits%

The parameters specify the maximum number of digits to the left
and to the right of the decimal point. There must be at least
one digit on the left, and the total number of digits must be
less than 255. The BCD strings will have a length that's one
larger than the total number of digits, to account for the sign
of the number. The decimal point is implicit and doesn't take up
any extra space.

It is assumed that you will only use one size of BCD number in
your program-- there are no provisions for handling mixed-length
BCD numbers. Of course, you could manage that yourself with a
little extra work, if it seems like a useful capability. If you
don't use BCDSetSize, the default size of the BCD numbers will
be 32 (20 to the left, 11 to the right, 1 for the sign).

BCD Math page 4

You can get the current size settings as well:

BCDGetSize LeftDigits%, RightDigits%

Of course, before doing any BCD calculations, you must have some
BCD numbers! The BCDSet routine takes a number in text string
form and converts it to BCD:

TextSt$ = "1234567890.50"
Nr$ = BCDSet$(TextSt$)

If your numbers are stored as actual numbers, you can convert
them to a text string with BASIC's STR$ function, then to BCD.
Leading spaces are ignored:

Nr$ = BCDSet$(STR$(AnyNum#))

BCD numbers can also be converted back to text strings, of
course. You may specify how many digits to the right of the
decimal to keep (the number will be truncated, not rounded). If
the RightDigits% is positive, trailing zeros will be kept; if
negative, trailing zeros will be removed. There are also various
formatting options which may be used. Here's how it works:

TextSt$ = BCDFormat$(Nr$, HowToFormat%, RightDigits%)

The HowToFormat% value may be any combination of the following
(just add the numbers of the desired formats together):

0 plain number
1 use commas to separate thousands, etc
2 start number with a dollar sign
4 put the sign on the right side of the number
8 use a plus sign if the number is not negative

BCD Math page 5

The BCD math functions are pretty much self-explanatory, so I'll
keep the descriptions brief. Here are the single-parameter

Result$ = BCDAbs$(Nr$) ' absolute value
Result$ = BCDCos$(Nr$) ' cosine function
Result$ = BCDCot$(Nr$) ' cotangent function
Result$ = BCDCsc$(Nr$) ' cosecant function
Result$ = BCDDeg2Rad$(Nr$) ' convert degrees to radians
e$ = BCDe$ ' the constant "e"
Result$ = BCDFact$(N%) ' factorial
Result$ = BCDFrac$(Nr$) ' return the fractional part
Result$ = BCDInt$(Nr$) ' return the integer part
Result$ = BCDNeg$(Nr$) ' negate a number
pi$ = BCDpi$ ' the constant "pi"
Result$ = BCDRad2Deg$(Nr$) ' convert radians to degrees
Result$ = BCDSec$(Nr$) ' secant function
Result% = BCDSgn%(Nr$) ' signum function
Result$ = BCDSin$(Nr$) ' sine function
Result$ = BCDSqr$(Nr$) ' square root
Result$ = BCDTan$(Nr$) ' tangent function

Notes on the single-parameter functions:

The signum function returns an integer based on the sign of
the BCD number:

-1 if the BCD number is negative
0 if the BCD number is zero
1 if the BCD number is positive

BCDpi$ is accurate to the maximum level afforded by the BCD
functions. BCDe$ is accurate to as many as 115 decimal places.
The actual accuracy, of course, depends on the size of BCD
numbers you've chosen.

The trigonometric functions (cos, sin, tan, sec, csc, cot)
expect angles in radians. BCDDeg2Rad and BCDRad2Deg will allow
you to convert back and forth between radians and degrees.

BCD Math page 6

Here is a list of the two-parameter functions:

Result$ = BCDAdd$(Nr1$, Nr2$) ' Nr1 + Nr2

Result$ = BCDSub$(Nr1$, Nr2$) ' Nr1 - Nr2

Result$ = BCDMul$(Nr1$, Nr2$) ' Nr1 * Nr2

Result$ = BCDDiv$(Nr1$, Nr2$) ' Nr1 / Nr2

Result$ = BCDPower$(Nr$, Power%) ' Nr ^ Power

Result% = BCDCompare%(Nr1$, Nr2$) ' compare two numbers

The comparison function returns an integer which reflects how
the two numbers compare to each other:

-1 Nr1 < Nr2
0 Nr1 = Nr2
1 Nr1 > Nr2

Expression Evaluator page 7

The expression evaluator allows you to find the result of an
expression contained in a string. Normal algebraic precedence is
used, e.g. 4+3*5 evaluates to 19. The usual numeric operators
(*, /, +, -, ^) are supported (multiply, divide, add, subtract,
and raise to a power). Use of negative numbers is just fine, of
course. Parentheses for overriding the default order of
operations are also supported.

You may use either double asterisk ("**") or caret ("^") symbols
to indicate exponentiation.

The constant PI is recognized, as are the following functions:
ABS absolute value INT integer
ACOS inverse cosine LOG natural log
ASIN inverse sine SIN sine
ATAN inverse tangent SQR square root
COS cosine TAN tangent
FRAC fraction

Functions should not be nested.

Trig functions expect angles in radians.

To evaluate an expression, you pass it to the evaluator as a
string. You will get back either an error code or a single-
precision result. Try this example to see how the expression
evaluator works:

INPUT "Expression? "; Expr$
Evaluate Expr$, Result!, ErrCode%
IF ErrCode% THEN
PRINT "Invalid expression. Error = "; ErrCode%
PRINT "Result: "; Result!

An expression evaluator adds convenience to any program that
needs to accept numbers. Why make someone reach for a calculator
when number crunching is what a computer does best?

Extensions to BASIC's math page 8

For the most part, the math routines in this library is designed
to provide alternatives to the math routines that are built into
BASIC. Still, BASIC's own math support is quite adequate for
many purposes, so there's no sense in ignoring it. Here are some
functions which improve on BASIC's math. I'll list the single-
precision functions on this page, and double-precision on the

Result! = ArcCosHS!(Nr!) ' inverse hyperbolic cosine
Result! = ArcCosS!(Nr!) ' inverse cosine (1 >= Nr >= -1)
Result! = ArcCotS!(Nr!) ' inverse cotangent
Result! = ArcCotSH!(Nr!) ' inverse hyperbolic cotangent
Result! = ArcCscHS!(Nr!) ' inverse hyperbolic cosecant
Result! = ArcCscS!(Nr!) ' inverse cosecant
Result! = ArcSecHS!(Nr!) ' inverse hyperbolic secant
Result! = ArcSecS!(Nr!) ' inverse secant
Result! = ArcSinHS!(Nr!) ' inverse hyperbolic sine
Result! = ArcSinS!(Nr!) ' inverse sine (1 >= Nr >= -1)
Result! = ArcTanHS!(Nr!) ' inverse hyperbolic tangent
Result! = Cent2Fahr!(Nr!) ' centigrade to Fahrenheit
Result! = CosHS!(Nr!) ' hyperbolic cosine
Result! = CotHS!(Nr!) ' hyperbolic cotangent
Result! = CotS!(Nr!) ' cotangent
Result! = CscHS!(Nr!) ' hyperbolic cosecant
Result! = CscS!(Nr!) ' cosecant
Result! = Deg2RadS!(Nr!) ' convert degrees to radians
e! = eS! ' the constant "e"
Result! = ErfS!(Nr!) ' error function
Result! = FactS!(Nr%) ' factorial
Result! = Fahr2Cent!(Nr!) ' Fahrenheit to centigrade
Result! = Kg2Pound!(Nr!) ' convert kilograms to pounds
Pi! = PiS! ' the constant "pi"
Result! = Pound2Kg!(Nr!) ' convert pounds to kilograms
Result! = Rad2DegS!(Nr!) ' convert radians to degrees
Result! = SecHS!(Nr!) ' hyperbolic secant
Result! = SecS!(Nr!) ' secant
Result! = SinHS!(Nr!) ' hyperbolic sine
Result! = TanHS!(Nr!) ' hyperbolic tangent

Extensions to BASIC's math page 9

Result# = ArcCosD#(Nr#) ' inverse cosine (1 >= Nr >= -1)
Result# = ArcCosHD#(Nr#) ' inverse hyperbolic cosine
Result# = ArcCotD#(Nr#) ' inverse cotangent
Result# = ArcCotHD#(Nr#) ' inverse hyperbolic cotangent
Result# = ArcCscD#(Nr#) ' inverse cosecant
Result# = ArcCscHD#(Nr#) ' inverse hyperbolic cosecant
Result# = ArcSecD#(Nr#) ' inverse secant
Result# = ArcSecHD#(Nr#) ' inverse hyperbolic secant
Result# = ArcSinD#(Nr#) ' inverse sine (1 >= Nr >= -1)
Result# = ArcSinHD#(Nr#) ' inverse hyperbolic sine
Result# = ArcTanHD#(Nr#) ' inverse hyperbolic tangent
Result# = CosHD#(Nr#) ' hyperbolic cosine
Result# = CotD#(Nr#) ' cotangent
Result# = CotHD#(Nr#) ' hyperbolic cotangent
Result# = CscD#(Nr#) ' cosecant
Result# = CscHD#(Nr#) ' hyperbolic cosecant
Result# = Deg2RadD#(Nr#) ' convert degrees to radians
e# = eD# ' the constant "e"
Result# = ErfD#(Nr#) ' error function
Result# = FactD#(Nr%) ' factorial
Pi# = PiD# ' the constant "pi"
Result# = Rad2DegD#(Nr#) ' convert radians to degrees
Result# = SecD#(Nr#) ' secant
Result# = SecHD#(Nr#) ' hyperbolic secant
Result# = SinHD#(Nr#) ' hyperbolic sine
Result# = TanHD#(Nr#) ' hyperbolic tangent

Extensions to BASIC's math page 10

Result% = GCDI%(Nr1%, Nr2%) ' greatest common denominator
Result% = Power2I%(Nr%) ' raise 2 to a specified power
Result% = ROLI%(Nr%, Count%) ' bit-rotate left
Result% = RORI%(Nr%, Count%) ' bit-rotate right
Result% = SHLI%(Nr%, Count%) ' bit-shift left
Result% = SHRI%(Nr%, Count%) ' bit-shift right
Result% = VALI%(St$) ' integer VAL function

Result& = GCDL&(Nr1&, Nr2&) ' greatest common denominator
Result& = Power2L&(Nr%) ' raise 2 to a specified power

Like BASIC's trig functions, these trig functions expect the
angle to be in radians. Conversion functions are provided in
case you prefer degrees.

Note that there is no ArcTanS! or ArcTanD# function for the
simple reason that BASIC supplies an ATN function.

Constants are expressed to the maximum precision available.

The Power2I% and Power2L& functions are vastly quicker than the
equivalent BASIC formulas. If powers of two are useful to you,
try these functions!

If you are not familiar with variable postfix symbols, here's a
brief summary:

Symbol Meaning Range (very approximate)
------ -------- ------------------------
% integer +- 32767
& long integer +- 2 * 10^9
! single precision +- 1 * 10^38 (7-digit prec.)
# double precision +- 1 * 10^308 (15-digit prec.)
$ string [0 to 32767 characters]

See your BASIC manual or QuickBasic's online help for further

Far Strings page 11

One of the best things about BASIC is its support for
variable-length strings. Few other languages support such
dynamically-allocated strings and they're a terrifically
efficient way of using memory. At least, they would be, except
for one minor limitation... in every version of QuickBasic and
BASCOM (except for the new and expensive BASCOM 7.0
"Professional Development System"), string space is limited to a
mere 50K-60K bytes. As if this weren't trouble enough, this
space is also shared with a number of other things. Running out
of string space is a common and painful problem.

Anyway, it used to be. The BasWiz library comes with an
assortment of routines and functions which allow you to keep
variable-length strings outside of BASIC's tiny string area.
Currently, you may have up to 65,535 far strings of up to 255
characters each, subject to available memory. Either normal
system memory or expanded memory may be used.

Using far strings works almost the same way as using normal
strings. Rather than referring to a far string with a string
variable name, however, you refer to it with an integer variable
called a "handle". To create a new far string, you use a handle
of zero. A new handle will be returned to you which will
identify that string for future reference.

Before you use any far strings, you must initialize the far
string handler. When you are done using far strings, you must
terminate the far string handler. Normally, each of these
actions will take place only once in your program: you
initialize at the beginning and terminate at the end.

NOTE: The BasWiz far string handler does not support PDS or
VB/DOS far strings! If you are using Microsoft far strings, you
can't use BasWiz far strings.

Far Strings page 12

A working example of far string use is provided in FDEMO.BAS.
Somewhat simplified code is given below as an overview.

DIM Text%(1 TO 5000) ' array for string handles
FSInit 0 ' init far string handler
TextLines = 0
LINE INPUT#1, TextRow$
Handle% = 0 ' zero to create new string
FSSet Handle%, TextRow$ ' set far string
TextLines% = TextLines% + 1
Text(TextLines%) = Handle% ' save far string handle
FOR Row% = 1 TO TextLines%
PRINT FSGet$(Text%(Row%)) ' display a far string
FSDone ' close far string handler

If you wanted to change an existing far string, you would
specify its existing handle for FSSet. The handle of zero is
used only to create new far strings, rather in the manner of
using a new variable for the first time.

Note the 0 after the FSInit call. That specifies that main
system memory is to be used. If you would prefer to use EMS, use
a 1. If you specify EMS and none is available, BasWiz will fall
back to conventional memory.

File Handling page 13

The file handling capabilities of BASIC were improved quite a
bit as of QuickBasic 4.0. A binary mode was added and it became
possible to use structured (TYPE) variables instead of the
awkward FIELD-based random access handling. Even today, however,
BASIC file handling is inefficient for many tasks. It requires
error trapping to avoid problems like open floppy drive doors
and cannot transfer information in large quantities at a time.

The BasWiz routines provide additional flexibility and power.
They allow you to access files at as low or high a level as you
wish. Here are some of the features of BasWiz file handling:

- File sharing is automatically used if the DOS version is
high enough, (DOS 3.0 or later) providing effortless
network compatibility.
- Critical errors, like other errors, are detected at any
point you find convenient via a single function call.
- Optional input buffers speed up reading from files.
- Up to 32K of data may be read or written at one time.
- Files can be flushed to disk to avoid loss due to power
outages, etc.

Files are not considered to be strongly moded by BasWiz,
although there are a few limitations on how you can deal with
text files as opposed to other kinds of files. Reads and writes
normally take place sequentially, like the INPUT and OUTPUT
modes allowed by BASIC. However, you can also do random access
by moving the file pointer to anywhere in the file, just as with
the RANDOM and BINARY modes allowed by BASIC. These routines
place no arbitrary limitations on the programmer.

As with BASIC, files are referred to by a number after they are
opened for access. Unlike BASIC, the number is returned to you
when the file is successfully opened, rather than being
specified by you when you open the file. This means that you
never have to worry about a file number already being in use.
We'll refer to the file number as a "file handle" from now on.

File Handling page 14

Before doing anything else, you must initialize the file
handling routines. This is typically done only once, at the
beginning of your program. The FInit routine needs to know the
number of files you want to deal with. This can be up to 15
files, or possibly up to 50 if you are using DOS 3.3 or higher.

FInit MaxFiles%, ErrCode%

A file is opened for access like so:

FOpen File$, FMode$, BufferLen%, Handle%, ErrCode%

You pass the File$, FMode$, and BufferLen%. The Handle% and
ErrCode% are returned to you. The BufferLen% valueis the length
of the buffer desired for input. This must be zero if you want
to write to the file. The filename is passed in File$, naturally
enough. There is a choice of various modes for FMode$ and these
can be combined to some extent:

A Append to file used to add to an existing file
C Create file creates a new file
R Read access allows reading (input) from a file
T Text mode file allows text-mode input from a file
W Write access allows writing (output) to a file

For the most part, the combinations are self-explanatory. For
instance, it would be reasonable to open a file for read and
write, for create and write, for append and write, or for read
and text. Text files always require a buffer. If you request
text access without specifying a buffer, a buffer of 512 bytes
will be provided for you. If you request "create" access without
additional parameters, the file will be opened for write by

You may not use a buffer if you want to write to a file. This
includes text files, which always use a buffer, as well as
binary files. However, writing may be done to a text-type file
if the file was not opened in text mode. We'll see how that
works presently.

When you are done using a particular file, you can close it,
just as in ordinary BASIC:

FClose Handle%

Before your program ends, you should terminate the file handler.
This will close any open files as well as concluding use of the
file routines:


File Handling page 15

That covers the basic set-up routines: initialize, open, close,
and terminate. Of more interest are the routines which actually
deal with the file itself. These provide assorted read/write
services, the ability to get or set the file read/write pointer,
size, time, and date, and the ability to get or set the error
code for a specific file, among other things. Let's take a look
at the error handler first.

The FInit and FOpen routines return an error code directly,
since you need to know immediately if these have failed. The
other file routines do not return a direct error code, however.
In order to discover whether an error has occurred, you use the
FGetError% function. This will return an error of zero if there
was no error, or a specific error code (listed at the end of
this manual) if some problem occurred. The error code will
remain the same until you reset it using FError. The FError
service also allows you to test your error handler by forcing
specific error codes even when everything is fine.

PRINT "Error code: "; FGetError%(Handle%)
FError Handle%, 0 ' clear the error code

It is recommended that you check for errors after any file
routine is used if there is a chance that your program will be
executed on a floppy disk. These are particularly prone to user
errors (like leaving the drive door open) or running out of
space. If your program will only run on a hard drive, you may
not need to check as frequently. It's your choice. Note that the
error code is not cleared automatically-- use FError to reset
the error code to zero if you determine that it wasn't a serious

Down to the nitty-gritty... we've seen how to open and close a
file, how to check operations for errors, and so forth. So how
do we actually manipulate the file? There are assorted
alternatives, depending on how you want to deal with the file:
text reads, text writes, byte-oriented reads and writes, and
block reads and writes, not to mention handling the time, date,
size, and read/write pointer. We'll start off with the routines
which read from a file.

If you opened the file for text access, you must want to read
the file a line at a time. Each line is assumed to be less than
256 characters and delimited by a carriage return and linefeed
(, or ^M^J, in normal notation). In that case, you
should use the FReadLn$ function:

St$ = FReadLn$(Handle%)

File Handling page 16

A simple program to display a text file directly on the screen
might look something like this in BASIC:


The same program using BasWiz would look something like this:

FInit 5, ErrCode%
FOpen COMMAND$, "RT", 0, Handle%, ErrCode%
PRINT FReadLn$(Handle%)

In either case, we're accepting a command-line parameter which
specifies the name of the file. In the BasWiz example, note the
use of the FEOF% function, which tells whether we've gone past
the end of the file. This works like the EOF function in BASIC.

There are two ways of reading from binary files. You can get the
results as a string of a specified (maximum) length:

St$ = FRead$(Handle%, Bytes%)

In plain BASIC, the same thing might be expressed this way:

St$ = INPUT$(Bytes%, FileNumber%)

The other way of reading from a binary file has no equivalent in
BASIC. It allows you to read in up to 32K bytes at a time,
directly into an array or TYPEd variable. You can read the
information into anything that doesn't contain normal strings
(the fixed-length string type can be used, though):

Segm% = VARSEG(Array(0))
Offs% = VARPTR(Array(0))
FBlockRead Handle%, Segm%, Offs%, Bytes%

That would read the specified number of bytes into Array(),
starting at array element zero.

File Handling page 17

You can use any data type, whether single variable or array, as
long as it is not a variable length string. In other words, Vbl$
and Vbl$(0) would not work. If you want to use a string with the
block read, it must be a fixed-length string. For example:

DIM Vbl AS STRING * 1024
Segm% = VARSEG(Vbl)
Offs% = VARPTR(Vbl)
FBlockRead Handle%, Segm%, Offs%, Bytes%

It's a good idea to calculate the Segment and Offset values each
time. These tell FBlockRead where to store the information it
reads. BASIC may move the variables around in memory, so VARSEG
and VARPTR should be used just before FBlockRead to ensure that
they return current information.

The file output commands are similar. File output can only be
done if there is no input buffer. This means that you can't use
file output if the file was opened in text mode, either, since
text mode always requires an input buffer. It is possible to do
text output on a file that was opened in binary mode, however.
The limitation just means that you can't open a file for both
reading and writing if you use a buffer (or text mode).

To output (write) a string to a file, use this:

FWrite Handle%, St$

This is like the plain BASIC statement:

PRINT #FileNumber%, St$;

If you would like the string to be terminated by a carriage
return and linefeed, use this instead:

FWriteLn Handle%, St$

This is like the plain BASIC statement:

PRINT #FileNumber%, St$

In BASIC, the difference between the two writes is controlled by
whether you put a semicolon at the end. With BasWiz, different
routines are used instead. FWrite is like PRINT with a semicolon
and FWriteLn is like PRINT without a semicolon.

File Handling page 18

As well as simple string output, you can also output TYPEd
variables and even entire arrays. This type of output has no
corresponding BASIC instruction, although it's somewhat similar
to the file PUT statement. Up to 32K can be output at a time:

Segm% = VARSEG(Array(0))
Offs% = VARPTR(Array(0))
FBlockWrite Handle%, Segm%, Offs%, Bytes%

If you haven't already read the section on FBlockRead, go back a
page and review it. The same comments apply for FBlockRead: it
can handle fixed-length strings but not old-style strings, and
VARSEG/VARPTR should immediately precede the block I/O, among
other things.

Normally, reads and writes take place sequentially. If you want
to move to a specific spot in the file, though, that's easy. You
can do it in text mode or binary mode, whether or not you have a
buffer, giving you additional flexibility over the usual BASIC
file handling. Set the location for the next read or write, so:

FLocate Handle%, Position&

The Position& specified will be where the next read or write
takes place. It starts at one and (since it's specified as a
LONG integer) can go up to however many bytes are in the file.
If you want a record position rather than a byte position, you
can do that too. Just convert the record number to a byte
number, like so:

Position& = (RecordNumber& - 1&) * RecordLength& + 1&

If you do not want to maintain RecordNumber and RecordLength as
LONG integers, convert them to such by using the CLNG() function
on them before doing the calculation. Otherwise you may get an
overflow error in the calculation, since QuickBasic will assume
that the result will be an integer.

You can get the current position of the file read/write pointer

Position& = FGetLocate&(Handle%)

Let's see... we've examined initialization and termination,
opening and closing, reading and writing, and manipulating the
file read/write pointer. What else could there be? Well, how
about checking the size of a file and getting or setting the
file time and date? Why, sure! The "get" routines are pretty
well self-explanatory:

FileSize& = FGetSize&(Handle%)
FileTime$ = FGetTime$(Handle%)
FileDate$ = FGetDate$(Handle%)

File Handling page 19

Setting the time and date is equally easy. This should be done
just before you close the file with FClose or FDone. You may use
any date and time delimiters you choose. If a field is left
blank, the appropriate value from the current time or date will
be used. Years may be specified in four-digit or two-digit
format. Two-digit years will be assumed to be in the 20th
century ("90" == "1990"). Careful there! Your program should
allow four-digit dates to be used or disaster will strike when
the year 2000 rolls around. The 21st century is closer than you

FTime Handle%, FileTime$
FDate Handle%, FileDate$

There's just one more file routine. It allows you to "flush" a
file to disk. This insures that the file has been properly
updated to the current point, so nothing will be lost if there
is a power outage or similar problem. If you do not use the
"flush" routine, data may be lost if the program terminates
unexpectedly. Note that use of FFlush requires that a free file
handle be available, before DOS 4.0.

FFlush Handle%

That's it for the BasWiz file handler. As a quick review, let's
run through the available routines, then try a couple of example
programs. You might also wish to examine the WDEMO.BAS program,
which also makes use of the file routines.

FInit initialize the file handler
FDone end the file handler and close any open files

FOpen open a file for access (like OPEN)
FClose close a file (like CLOSE)

FRead$ read a string from a binary file (like INPUT$)
FReadLn$ read a string from a text file (like LINE INPUT)
FBlockRead read an item from a binary file

FWrite write a string to a binary file
FWriteLn write a string with a to a binary file
FBlockWrite write an item to a binary file

FLocate set the read/write pointer to a given position
FTime set the time stamp
FDate set the date stamp
FError set the error code

FGetLocate& get the read/write pointer
FGetTime$ get the time stamp
FGetDate$ get the date stamp
FGetError get the error code

FFlush flush to disk (makes sure file is updated)
FGetSize& get size
FEOF see if the end of the file has been reached

File Handling page 20

So much for theory. Let's try something practical. A common
problem is copying one file to another. We'll limit this to text
files, so we can do it in both plain BASIC and with BasWiz.
Although BasWiz can handle any type of file readily, BASIC has
problems in efficiently handling variable-length binary files.
So, we'll do this first in BASIC, then BasWiz, for text files.

In BASIC, a text-file copying program might look like this:

INPUT "File to copy"; FromFile$
INPUT "Copy file to"; ToFile$
PRINT#2, St$

With BasWiz, the same program would look more like this:

INPUT "File to copy"; FromFile$
INPUT "Copy file to"; ToFile$
FInit 15, ErrCode%
FOpen FromFile$, "RT", 1024, FromHandle%, ErrCode%
FOpen ToFile$, "CW", 0, ToHandle%, ErrCode%
FileTime$ = FGetTime$(FromHandle%)
FileDate$ = FGetDate$(FromHandle%)
WHILE NOT FEOF%(FromHandle%)
WriteLn ToHandle%, ReadLn$(FromHandle%)
FTime ToHandle%, FileTime$
FDate ToHandle%, FileDate$

You might have noticed that the BasWiz version of the program is
a bit longer than the plain BASIC version. It has a number of
advantages, however. It's faster, produces smaller code under
ordinary circumstances, and preserves the date and time of the
original file in the copied file. Unlike some of the BASIC
compilers, the BasWiz routines do not automatically add a ^Z to
the end of text files, so the BasWiz example will not alter the
original file.

Fractions page 21

Using BCD allows you to represent numbers with excellent
precision, but at a fairly large cost in speed. Another way to
represent numbers with good precision is to use fractions.
Fractions can represent numbers far more accurately than BCD,
but can be handled much more quickly. There are limitations, of
course, but by now you've guessed that's always true!

Each fraction is represented by BasWiz as an 8-byte string. The
numerator (top part of the fraction) may be anywhere from
-999,999,999 to 999,999,999. The denominator (the bottom part)
may be from 1 to 999,999,999. This allows handling a fairly wide
range of numbers exactly.

Fractions can be converted to or from numeric text strings in
any of three formats: real number (e.g., "1.5"), plain fraction
(e.g., "3/2"), or whole number and fraction (e.g., "1 1/2").
Internally, the numbers are stored as a plain fraction, reduced
to the smallest fraction possible which means the same thing
(for instance, "5/10" will be reduced to "1/2").

To convert a numeric text string into a fraction, do this:

Nr$ = FracSet$(NumSt$)

To convert a fraction into a numeric text string, try this:

NumSt$ = FracFormat$(Nr$, HowToFormat%)

The formatting options are:

0 convert to plain fraction
1 convert to whole number and fraction
2 convert to decimal number

Here is a list of the other functions available:

Result$ = FracAbs$(Nr$) ' absolute value
Result$ = FracAdd$(Nr1$, Nr2$) ' Nr1 + Nr2
Result% = FracCompare%(Nr1$, Nr2$) ' compare two fractions
Result$ = FracDiv$(Nr1$, Nr2$) ' Nr1 / Nr2
Result$ = FracMul$(Nr1$, Nr2$) ' Nr1 * Nr2
Result$ = FracNeg$(Nr$) ' - Nr
Result% = FracSgn%(Nr$) ' signum function
Result$ = FracSub$(Nr1$, Nr2$) ' Nr1 - Nr2

Fractions are automatically reduced to allow the greatest
possible range. Note that little range-checking is done at this
point, so you may wish to screen any input to keep it

Result FracSgn FracCompare
-1 negative # 1st < 2nd
0 # is zero 1st = 2nd
1 positive # 1st > 2nd

Graphics: General Routines page 22

These routines are designed to work with specific graphics
modes, so your program will only include those routines which
apply to the modes you use. These modes are supported:

SCREEN Card Graph. Res Colors Text Res. Notes
====== ==== ========== ====== ============= =====
0 any varies 16 varies *0
1 CGA 320 x 200 4 40 x 25
2 CGA 640 x 200 2 80 x 25
3 HGA 720 x 348 2 90 x 43 *1
7 EGA 320 x 200 16 40 x 25
8 EGA 640 x 200 16 80 x 25
9 EGA 640 x 350 16 80 x 25/43
10 EGA 640 x 350 4 80 x 25/43 mono
11 VGA 640 x 480 2 80 x 30/60
12 VGA 640 x 480 16 80 x 30/60
13 VGA 320 x 200 256 40 x 25
N0 VGA 360 x 480 256 45 x 30 *2
N1 VGA 320 x 400 256 40 x 25 *2
N2 480 x 640 2 60 x 80/45/40 *3
N4 any 80 x 50 2 6 x 10 *4
N5 SVGA 256 *5
N6 MDA 80 x 25 --- 25 x 80 *6

The number of rows of text available depends on the font size:
8 x 8, 8 x 14, or 8 x 16.

*0 This is actually for text mode, not graphics mode.

*1 The BasWiz Hercules routines don't need Microsoft's QBHERC
TSR to be loaded. This may confuse the BASIC editor. In that
case, use BC.EXE to compile the program directly.

*2 This non-standard VGA mode works on most ordinary VGAs.

*3 This works with Epson-compatible dot matrix printers and
HP-compatible laser printers. The results may be previewed
on a VGA. See "Printer Routines".

*4 This actually provides graphics in text mode for any display
adapter. 80x25 text remains available via PRINT.

*5 This mode provides support for high-resolution 256-color
on SuperVGAs based on the popular Tseng ET4000 chips.

*6 This mode provides support for the monochrome monitor of a
dual-monitor system. It works when the mono display is the
(theoretically) "inactive" display.

*7 This mode provides support for the VESA graphics standard,
which is common on SuperVGAs. See "VESA Info Routines".

Graphics: General Routines page 23

Compatibility: An EGA can display CGA modes. A VGA can display
EGA and CGA modes. An MCGA can display CGA modes and two VGA
modes: SCREEN 11 and SCREEN 13. See "Miscellaneous Notes" for
additional information.

The routine for a specific mode is indicated by a prefix of "G",
followed by the mode number, and then the routine name. For
example, if you wished to plot a point in SCREEN 2 mode, you
would use:

G2Plot X%, Y%

Many of these routines correspond with existing BASIC
instructions. However, they are smaller and usually faster by
22% - 64%. See "Miscellaneous Notes" for notes on the
differences between BASIC and the BasWiz routines.

The smaller size may not be noticeable if you use the SCREEN
statement, since that causes BASIC to link in some of its own
graphics routines. If you intend to use only BasWiz routines for
graphics, you can avoid that by using the G#Mode command instead

G#Mode Graphics% ' 0 for SCREEN 0, else SCREEN #

If you're using the mode N5 routines, you'll need to initialize
them before setting the mode. This is done by specifying the
BIOS mode number and the screen resolution:

GN5Init BIOSMode%, PixelsWide%, PixelsHigh%

The mode GV routines need to be initialized before you set the
mode and shut down before your program terminates. Also, you
specify the actual VESA mode number when setting the mode.

GGVInit ' before setting the mode
GGVMode VESAmode% ' to set a VESA mode
GGVDone ' when done using VESA modes

One difference between BASIC and BasWiz is that, instead of each
"draw" command requiring a color parameter as in BASIC, the
BasWiz library provides a separate color command:

G#Color Foreground%, Background%

The "foreground" color is used by all graphics routines. The
background color is used by the G#Cls routine. Both foreground
and background colors are used in the G#Write and G#WriteLn

Graphics: General Routines page 24

Here is a list of the corresponding routines, first BASIC, then
BasWiz (replace the "#" with the appropriate mode number):

' get the color of a specified point
colour% = POINT(x%, y%)
colour% = G#GetPel(x%, y%)

' set the color of a specified point
PSET (x%, y%), colour%
G#Color colour%, backgnd% : G#Plot x%, y%

' draw a line of a specified color
LINE (x1%, y1%) - (x2%, y2%), colour%
G#Color colour%, backgnd% : G#Line x1%, y1%, x2%, y2%

' draw a box frame of a specified color
LINE (x1%, y1%) - (x2%, y2%), colour%, B
G#Color colour%, backgnd% : G#Box x1%, y1%, x2%, y2%, 0

' draw a box of a specified color and fill it in
LINE (x1%, y1%) - (x2%, y2%), colour%, BF
G#Color colour%, backgnd% : G#Box x1%, y1%, x2%, y2%, 1

' clear the screen and home the cursor

' get the current cursor position
Row% = CSRLIN: Column% = POS(0)
G#GetLocate Row%, Column%

' set the current cursor position
LOCATE Row%, Column%
G#Locate Row%, Column%

' display a string without a carriage return and linefeed
G#Write St$

' display a string with a carriage return and linefeed
G#WriteLn St$

Note that BasWiz, unlike BASIC, allows both foreground and
background colors for text in graphics mode. It also displays
text substantially faster than BASIC. See the "Miscellaneous
Notes" section for information on other differences in text

Graphics: General Routines page 25

If you need to print a number rather than a string, just use the
BASIC function STR$ to convert it. If you don't want a leading
space, use this approach:

St$ = LTRIM$(STR$(Number))

The BasWiz library has other routines which have no BASIC
equivalent. One allows you to get the current colors:

G#GetColor Foreground%, Background%

Sometimes the normal text services seem unduly limited. Text is
displayed only at specific character positions, so it may not
align properly with a graph, for instance. Text is also of only
one specific size. These are limitations which make the normal
text routines very fast, but for times when you need something a
little bit more fancy, try:

G#Banner St$, X%, Y%, Xmul%, Ymul%

You may display the string starting at any graphics position.
The Xmul% and Ymul% values are multipliers, specifying how many
times larger than normal each character should be. Use Xmul% = 1
and Ymul% = 1 for normal-sized characters. What "normal" means
depends on size of the font in use.

Since G#Banner "draws" the text onto the screen, it is a bit
slower than the normal text services. It also uses only the
foreground color, so the letters go right on top of anything
that was previously there. Use G#Box to clear the area
beforehand if this is a problem for you.

The G#Banner routine supports several fonts. The larger fonts
provide a more precise character set but leave you with less
room on the screen. You may choose from these fonts:

Font Number Font Size (width x height)
0 8 x 8 --- default
1 8 x 14
2 8 x 16

Select a font like so:

BFont FontNr%

If you want to find out what the current font is, can do:

FontNr% = GetBFont

Besides looking more elegant, the larger fonts are easier to
read. They will also suffer less from being increased in size,
although some deterioration is inevitable when magnifying these
kinds of fonts.

Graphics: General Routines page 26

The G#Banner routines accept CHR$(0) - CHR$(127). No control
code interpretation is done. All codes are displayed directly to
the screen.

Circles and ellipses can be drawn with the Ellipse routine. This
is similar to the BASIC CIRCLE statement. You specify the center
of the ellipse (X,Y), plus the X and Y radius values:

G#Ellipse CenterX%, CenterY%, XRadius%, YRadius%

A circle is an ellipse with a constant radius. So, to draw a
circle, just set both radius values to the same value.

As well as the usual points, lines, and ellipses, BasWiz also
allows you to draw polygons: triangles, squares, pentagons,
hexagons, all the way up to full circles!

G#Polygon X%, Y%, Radius%, Vertices%, Angle!

The X% and Y% values represent the coordinates of the center of
the polygon. The Radius% is the radius of the polygon (as if you
were fitting it into a circle). Vertices% is the number of
angles (also the number of sides) for the polygon to have.
Angle! specifies the rotation of the polygon, and is specified
in radians. See "A Little Geometry" for more information.

Another routine is designed to manipulate a GET/PUT image. Given
an image in array Original%() and a blank array of the same
dimensions called Flipped%(), this routine copies the original
image to the new array as a mirror image about the horizontal
axis. This is the same as the image you'd see if you turned your
monitor upside-down: the resulting image is upside-down and

G#MirrorH Original%(), Flipped%()

Don't forget to make the Flipped%() array the same DIM size as
the original, or the picture will overflow into main memory,
probably causing disaster!

Note that G#MirrorH will only work properly on images with byte
alignment. This means that the width of the image must be evenly
divisible by four if SCREEN 1 is used, or evenly divisible by
eight if SCREEN 2 is used. EGA modes are not yet supported for
this routine.

There are more routines that work only with SCREEN 2. One allows
you to load a MacPaint-type image ("ReadMac" or .MAC files) into
an array which can then be PUT onto the screen:

G2LoadMAC FileName$, Image%(), StartRow%

Graphics: General Routines page 27

Note that a full .MAC picture is 576x720, which won't fit on the
screen, so the image will be truncated to 576x200. You may
specify a starting row within the .MAC image, StartRow%, which
may be 0-521, allowing the entire picture to be loaded in
several parts.

The Image%() must be dimensioned with 7202 elements:

DIM Array(1 TO 7202) AS INTEGER

If you don't give an extension in the FileName$, an extension of
".MAC" will be used. There is no checking to see if the file
actually exists, so you may wish to do this beforehand.

There is no way of knowing whether a .MAC picture is supposed to
be black on white or white on black. If the image doesn't look
right when you PUT using PSET, you can switch it around by using
PUT with PRESET instead.

PC PaintBrush (.PCX) pictures can also be loaded. These images
can be of various sizes, so you need to dimension a dynamic
array for them:


The array will be set to the correct size by the loader. It goes
like this:

G2LoadPCX FileName$, Image%(), ErrCode%

If you don't give an extension in the FileName$, an extension of
".PCX" will be used. You may wish to check to see if the file
exists beforehand. Possible errors are as follows:

-1 File is not in PCX format
1 Image is too large for this screen mode
2 Image won't work in this screen mode (too many planes)

Two new routines are replacements for the GET and PUT image
statements in BASIC. They are on the slow side, but if you don't
intend to use them for animation, they will serve to save some
memory. There are also GN5Get and GN5Put routines for use with
256-color SuperVGA modes.

G2Get X1%, Y1%, X2%, Y2%, Image()

Note the DIMensioning of a dynamic array. The G2Get routine will
set the array to the appropriate size to hold the image.

Graphics: General Routines page 28

The PUT replacement assumes that you intend to PSET the image.
It doesn't allow for other display modes yet:

G2Put X%, Y%, Image()

See "Miscellaneous Notes" for more information on using GET/PUT
images. Note that SCREEN 13 is also supported, via G13Get and

Windows 256-color bitmaps may displayed in, or written from, any
of the VGA or SVGA modes which support 256 colors. This includes
modes 13, GV, N0, N1, and N5. Since BasWiz file handling is
employed, you must be sure to initialize the file handler with
FInit before using these routines and close the file handler
with FDone before your program terminates. The graphics mode
must be set beforehand. When reading a BMP, the palette will be
initialized according to the settings in the .BMP file.

G#ShowBMP FileName$, X%, Y%, ErrCode%

G#MakeBMP FileName$, X1%, Y1%, X2%, Y2%, ErrCode%

A bitmap can only be displayed if the screen resolution is
sufficient to hold the entire image. You can read the .BMP
information using the following routine:

GetInfoBMP FileName$, Wide%, High%, Colors%, ErrCode%

A positive error code indicates a DOS error code, which is a
problem in reading the file. Negative error codes are one of the

-1 not a valid .BMP file
-2 color format not supported
-3 compression type not supported
-4 incorrect file size
-5 unreasonable image size
-6 invalid (X,Y) origin specified for G#ShowBMP

Graphics: General Routines page 29

The COLOR statement in SCREEN 1 is anomalous. It doesn't really
control color at all, which is why QuickBasic proper doesn't
support colored text in this (or any graphics) mode. Instead, it
is used for controlling the background/border color and palette.
Since BasWiz -does- support a true G1COLOR routine, there are
different routines which allow you to change the palette and
border colors. To change the background (and border) color, use:

G1Border Colour%

There are two palette routines. Why two? Well, QuickBasic
supports two CGA palettes. One of the routines works like
QuickBasic and can be used on any CGA, EGA or VGA display (as
long as it's in CGA mode). The other routine gives you a wider
choice of palettes, but will only work on true CGAs (and some
EGA or VGA systems that have been "locked" into CGA mode).

Here's the QuickBasic-style two-palette routine for any

G1PaletteA PaletteNr%

The PaletteNr% may be as follows:

0 (bright) Green, Red, Yellow
1 Cyan, Violet, White

The more flexible six-palette routine (for CGA only) works like

G1PaletteB PaletteNr%

Palettes are as follows:

0 Green, Red, Brown 4 (bright) Green, Red, Yellow
1 Cyan, Violet, White 5 (bright) Cyan, Violet, White
2 Cyan, Red, White 6 (bright) Cyan, Red, White

Graphics: General Routines page 30

The EGA has a number of features which work in all its modes, so
rather than giving them screen mode prefixes, they are simply
named with an "E". These routines allow you to get or set the
palette, get or set the border color, and determine whether the
higher background colors should be displayed as bright colors or
as blinking.

To get a palette color value, use:

Colour% = EGetPalette(ColorNumber%)

To set the color value, use:

EPalette ColorNumber%, Colour%

To get the border color:

Colour% = EGetBorder%

You can probably guess how to set the border color:

EBorder Colour%

Finally, the blink vs. intensity. Actually, this is designed for
text mode; I'm not sure whether it has any function in graphics
modes. The text-mode default is for blinking to be turned on.
With BASIC, you add 16 to the foreground color to make it blink.
That's a little weird, since the "blink" attribute is actually a
part of the background color, but that's how BASIC views it. You
can tell the EGA to turn off blinking, in which case adding 16
to the foreground color makes the background color intense. This
doubles the number of available background colors.

EBlink Blink%

Use -1 for blinking (default), or 0 to turn off blinking.

Like the EGA, the VGA has a number of features which work in all
its modes. Again, rather than giving them screen mode prefixes,
we simply name them with a "V". The current routines allow you
to get or set the palette colors.

To get a palette color value, use:

VGetPalette ColorNumber%, Red%, Green%, Blue%

To set the color value, use:

VPalette ColorNumber%, Red%, Green%, Blue%

Graphics: General Routines page 31

As you've probably noticed, this doesn't work the same way as
the QuickBasic PALETTE statement. Rather than using a formula to
calculate a single LONG color value, like QuickBasic, the BasWiz
library allows you to specify the color in a more meaningful
way. The Red%, Green%, and Blue% parameters each hold an
intensity value (0-63). By mixing these three, you can get an
immense variety of shades-- over 250,000 combinations in all.

If you need to keep track of the intensities in your program,
I'd suggest the following TYPE definition:


If space is more important than speed, you can compress that to
half the size by using STRING * 1 instead of INTEGER. In that
case, you will need to use the CHR$ and ASC functions to convert
between string and integer values.

VESA Info Routines page 32

As IBM's influence decreased in the microcomputer world, there
came to be a great deal of chaos associated with new graphics
standards-- or, more precisely, the lack thereof. With each
manufacturer merrily creating a new SVGA interface, it became
very difficult to find software which actually supported the
particular SuperVGA you purchased. The exciting capabilities of
the new adapters, often as not, turned out to be worthless
advertising promises. Eventually, the VESA graphics standard was
created in order to help resolve this problem.

Most SuperVGAs today offer VESA support, either built into the
adapter's ROM BIOS, or as an optional TSR or driver. BasWiz
allows you to see if VESA support is available, and if so, what
video modes may be used. The best approach to using VESA is to
offer the user a choice of the modes that VESA reports as
available, since the monitor may well not support all of the
modes that the adapter is capable of handling.

Quite honestly, VESA is not much of a standard. It offers the
barest minimum needed to access the capabilities of a display,
and not always even that. The standard allows the manufacturer
to leave out normal BIOS support for extended video modes,
though it does recommend that they be supported. BasWiz expects
a bit more than such sketchy minimalist compliance with VESA.
Most importantly, BasWiz must be able to access the display
through the normal BIOS routines, in conjunction with
appropriate VESA mode handling. If you believe your SVGA
provides VESA support, but the routines to get mode information
do not return any valid modes, perhaps your implementation of
VESA lacks this key feature. Check with the manufacturer. Among
the things VESA doesn't support, by the way, is modes with more
than 256 colors-- so BasWiz can't either. Sorry.

One thing the VESA standard does provide is a great deal of
information about the available video modes-- their mode
numbers, graphics and text resolutions, number of colors, and so
forth. You may access this information through BasWiz whether or
not you intend to actually use VESA graphics in your program--
the routines do not need GGVInit to be initialized. Note that
VESA supports both extended graphics and text modes. The BasWiz
VESA routines currently support only VESA graphics, which is
referred to as mode GV. VESA text modes, when implemented, will
become mode TV. As the VESA information routines do not
specifically refer to either a text or graphics mode, there is
no mode number; instead, the routines simply have a prefix of

The key routine for all of this allows you to see whether VESA
support is available, and which version of VESA:

VesaVersion MajorV%, MinorV%

The MajorV% value is the major version number, and MinorV% the
minor version number. If VESA support is not available, both of
these numbers will be zero.

VESA Info Routines page 33

Since VESA is intended to support a wide variety of video modes,
the mode numbers and specifications are not built into the
standard as such. Instead, they are part of the VESA driver
which supports your particular video card. You can ask the
driver which modes are available and what they are like. BasWiz
treats this in a way similar to the way DOS lets you seek for
files: with a "find first" request to initialize the routines
and search for the first item, followed by any number of "find
next" requests to find subsequent items.

The "find first" and "find next" routines return a mode number.
If the mode number is -1 (negative one), you have reached the
end of the mode list. Otherwise, it's a video mode number, and
you can get additional information about the mode with an
assortment of other functions. This is pretty straightforward,
so I won't go into detail here. See VESAINFO.BAS for an example
program which uses all of these functions to tell you about all
available VESA modes on your display.

VMode% = VesaFindFirst% ' find first mode
VMode% = VesaFindNext% ' find subsequent mode

These provide results in pixels for graphics modes, or in
characters for text modes. Note that BasWiz does not yet support
use of VESA text modes.

XSize% = VesaScrWidth% ' screen width
YSize% = VesaScrHeight% ' screen height

These describe the size of the character matrix in pixels (use
for figuring text rows & cols in graphics modes). Note that
BasWiz requires VesaChrWidth% = 8 for printing, which is the
usual setting in graphics modes.

ChWidth% = VesaChrWidth% ' character width
ChHeight% = VesaChrHeight% ' character height

BasWiz supports no more than 256 colors, as neither VESA nor the
standard BIOS functions were designed for more. If someone can
send me info on how to handle more colors, I'll see what I can

Colors& = VesaColors& ' number of colors

These two return boolean values: -1 if true, 0 if false.

Mono% = VesaIsMono% ' if mode is monochrome
Text% = VesaIsText% ' if it's a text mode

Scrolling in VESA modes is extremely slow. Avoid if possible.

Graphics: Text-mode Routines page 34

It may seem odd to lump text-mode handling in with graphics
mode. It seemed like the most logical approach, however. There
is certainly some value in having graphics-type capabilities for
text mode. The ability to draw lines and boxes, use banner-style
text, and so forth can be handy. So, for the folks who don't
need all the power of the virtual windowing system, I've added
text-mode support into the "graphics" routines.

There are some quirks to these routines, since text mode doesn't
work the same way as graphics mode. For one thing, each "pixel"
is actually an entire character. The default pixel is a solid
block character, CHR$(219). You can change this, however:

G0SetBlock Ch% ' set ASCII code (use ASC(Ch$))
Ch% = G0GetBlock% ' get ASCII code

Since a pixel consists of a character with both foreground and
background colors, the "get pixel" routine has been altered to

G0GetPel X%, Y%, Ch%, Fore%, Back%

Finally, let's consider the "set mode" command. If you pass it a
zero, the current mode will be used as-is. This is useful in
case you've already set up a desired mode.

Any other mode number will be assumed to be a BIOS video mode
which should be set. If you feel like initializing the screen
mode for some reason, it may be useful to know that 3 is the
normal color mode (for CGA, EGA, VGA, etc) and 7 is the normal
mono mode (for MDA and Hercules). These provide 80x25 text. If
you wish to take advantage of 43-row EGA or 50-row VGA text
modes, you must set them up in advance (using the BASIC
statements SCREEN and WIDTH) before calling G0Mode with a zero.

If you have a SuperVGA or other adapter which supports unusual
text modes, you can use the mode command to switch to the
appropriate mode. On my Boca SuperVGA, for example, mode &H26
provides 80x60 text, and mode &H22 provides 132x44. The G0
routines are designed to support any text resolution up to
255x255, provided that the video BIOS properly updates the
appropriate memory locations when a mode set is done. This
should be true for any special EGA or VGA-based text modes.

G0Mode ModeNr%

Graphics: Dual Monitor Routines page 35

The N6 mode support dual monitors. To use this mode, you must
make the color monitor the "active" display, so it can be
handled with the usual BASIC or BasWiz display routines. The N6
routines are designed to work with a monochrome monitor only
when it is the (supposedly) "inactive" display in a dual monitor
system. The normal BASIC, BIOS, and DOS routines are designed
with the idea that only one monitor is active at any given time,
so we let them think what they like, and bypass 'em with a bit
of fancy footwork.

These routines are designed for monochrome adapters. Either a
plain MDA or a Hercules mono graphics adapter will do.

The notes on SetBlock and GetPel from the explanation of mode 0
on the previous page apply. In this case, of course, they're
GN6SetBlock and GN6GetPel, but the functionality is the same.

In addition, there are two new routines which allow you to get
and set the cursor size. The cursor size is defined in scan
lines, which may range from 0 (invisible) to 11 (large block):

GN6CursorSize ScanLines%
ScanLines% = GN6GetCursorSize%

Graphics: Printer Routines page 36

The BasWiz printer routines allow you to work with a printer
using the same convenient methods you'd use on a screen. The
image is created with the usual G# routines (using mode N2), but
the results are kept in a buffer in memory (about 37K bytes)
rather than being displayed directly. The image can be previewed
on a VGA or printed out at your convenience, to any printer or
even a file. The results will take up a single printer page,
assuming the usual 8.5" x 11" paper is used.

Printing a finished page works like this:

GN2Print Device$ ' for Epson-type dot matrix printers
GN2PrintL Device$ ' for HP-type laser printers

The Device$ variable should be set to the name of the device:

LPT1 parallel printer on port 1 (PRN also works)
LPT2 parallel printer on port 2
LPT3 parallel printer on port 3
COM1 serial printer on port 1 (AUX also works)
COM2 serial printer on port 2

Instead of using a device name, you can also use a file name, to
store the results for later printing. Output is done using BASIC
file handling, so it would be a good idea to provide an ON ERROR
GOTO trap in case of problems. The FREEFILE function is used, so
you don't have to worry about conflicts with any file numbers
which may be in use by your program.

Getting a page layout just right can consume a lot of paper.
Fortunately, there's a "preview" routine that allows you to
display the results on a VGA. The display will be sideways,
allowing the whole page to be seen at once. This will exactly
match the printed output in N2 mode. Here's how it works:

G11Mode 1 ' set SCREEN 11 (VGA 640x480 x2)
GN2Display ' display the page
DO ' wait for a key to be pressed
G11Mode 0 ' set SCREEN 0 (text mode)

The GN2Write and GN2WriteLn printer routines are unlike the
display versions of the same routines in that they don't scroll.
These routines only handle one page at a time.

Before using GN2Write or GN2WriteLn routines, you must choose a
font with GN2Font. These are the same fonts as used with

0 8 x 8 80 text rows
1 8 x 14 45 text rows
2 8 x 16 40 text rows

The current font can be retrieved with GN2GetFont%. The result
will be meaningless if the font was never set with GN2Font.

Graphics: A Little Geometry page 37

The increasing capabilities of computer graphics systems has
left many of us in the dust. It's great to be able to run
dazzling applications or to doodle with a "paint" program, but
many of us find it difficult to design appealing images of our
own. Becoming an artist is perhaps a bit more than most of us
are willing to take on! It is important to remember, however,
that computers are wonderful number-crunchers. With a little
application of plane geometry, you can have the computer take on
much of the work for you-- and after all, isn't that why we have
computers in the first place?

A complete review of plane geometry is a bit beyond the scope of
this text. However, I'm going to run through some of the things
I think you'll find most useful. I'd also like to suggest that
you might dig out your old textbooks or rummage through your
local used book store. It may have seemed like a dry subject at
the time, but when you can watch the results growing on your
computer screen, you will have a much better idea of how
geometry can be useful to you-- and it can be surprisingly fun,

In geometry talk, a "point" doesn't have any actual size. In our
case, we want to apply geometry to physical reality, namely the
computer screen. As far as we're concerned, a "point" will be an
individual graphics dot, also called a "pel" or "pixel" (for
"picture element"). We can safely dispense with such formalities
for our applications, for the most part.

The most important thing about a point is that it has a
location! Ok, that may not seem staggering, but it happens that
there are a number of ways of specifying that location. The most
common method is called the Cartesian coordinate system. It is
based on a pair of numbers: X, which represents the distance
along a horizontal line, and Y, which represents the distance
along a vertical line. Consider the CGA in SCREEN 2, for
instance. It has a coordinate system where X can be 0 - 639 and
Y can be 0 - 199. The points are mapped on kind of an invisible

The Cartesian coordinate system makes it easy to visualize how a
given point relates to other points on the same plane (or
screen). It is particularly useful for drawing lines. Horizontal
and vertical lines become a cinch: just change the X value to
draw horizontally, or the Y value to draw vertically. Squares
and rectangles (or boxes) can be formed by a combination of such
lines. You can define an area of the screen in terms of an
imaginary box (as GET and PUT do) with nice, clean boundaries.
When we get to diagonal lines, it's a bit more of a nuisance,
but still easy enough with the proper formula. That means we can
do triangles too. Curves are worse... when it comes to even a
simple circle or ellipse, the calculations start to get on the
messy side. For things like that, though, there is an

Graphics: A Little Geometry page 38

Another way of describing the location of a point is by Polar
coordinates. In Cartesian coordinates, the location is specified
by its horizontal and vertical distances from the "origin" or
reference point, (0,0). In Polar coordinates, the location is
specified by its distance and angle from the origin. Think of it
as following a map: Cartesian coordinates tell you how many
blocks down and how many blocks over the point is, whereas Polar
coordinates tell you in which direction the point is and how far
away it is "as the crow flies".

The Polar coordinate system is great for describing many kinds
of curves, much better than Cartesian. For example, a circle is
defined as all of the points at a given (fixed) distance from a
center point. Polar coordinates include both a distance and an
angle, and we've already got the distance, so all we need to do
is plot points at all of the angles on a circle. Technically,
there is an infinite number of angles, but since our points
don't follow the mathematical definition (they have a size), we
don't have to worry about that.

Let me digress for a moment to talk about angles. In BASIC,
angles are specified in "radians". People more often use
"degrees". Fortunately, it isn't hard to convert from one to the
other. Both may be visualized on a circle. In radians, the sum
of the angles in a circle is twice pi. In degrees, the sum of
the angles is 360. That's something like this:

90 deg, 1/2 * pi rad
/ | \
/ | \
180 degrees |___ . ___| 0 deg, 0 rad; or...
pi radians | | 360 deg, 2 * pi rad
\ | /
\ | /
270 deg, 3/2 * pi rad

Ok, so that's a grotesquely ugly circle! Hopefully it shows the
important thing, though. Angles start at zero on the extreme
right and get larger as they work around counter-clockwise. The
places marked on the "circle" are places where lines drawn
horizontally and vertically through the center intersect the
outside of the circle. These serve as a useful reference point,
especially in that they help show how the angles can be
construed from a Cartesian viewpoint.

So much for angles. I'll go into conversion formulae, the value
of pi, and other good junk a bit later on. Right now, let's get
back to our discussion of Polar coordinates.

Graphics: A Little Geometry page 39

I've explained how the Polar system makes it easy to draw a
circle. Since you can vary the range of angles, it's equally
simple to draw an arc. If you wanted to make a pie chart, you
might want to join the ends of the arcs to the center of the
circle, in which case you'd keep the angle constant (at the ends
of the arc) and plot by changing the distance from zero to the
radius. Circles are also handy for drawing equilateral
polygons... you know, shapes with sides of equal length:
triangle, square, pentagon, hexagon, etc. In this case, the best
features of the Cartesian and Polar systems can be joined to
accomplish something that would be difficult in either alone.

The starting point for these polygons is the circle. Imagine
that the polygon is inside a circle, with the vertices (pointy
ends, that is, wherever the sides meet) touching the edge of the
circle. These are equilateral polygons, so all of the sides and
angles are the same size. Each of the vertices touches the
circle, and each does it at exactly the same distance from each
other along the arc of the circle. All of this detail isn't
precisely necessary, but I hope it makes the reasoning a bit
more clear!

The circle can be considered as being divided by the polygon
into a number of arcs that corresponds to the number of vertices
(and sides) the polygon has. Think of a triangle inside a
circle, with the tips all touching the circle. If you ignore the
area inside the triangle, you will see that the circle is
divided into three equal arcs. The same property is true of any
equilateral polygon. As a matter of fact, as the number of
vertices goes up, the circle is partitioned into more, but
smaller, arcs... so that a polygon with a large enough number of
vertices is effectively a circle itself!

Anyway, the important thing is the equal partitioning. We know
how many angles, be they degrees or radians, are in a circle. To
get the points of a polygon, then... well, we already know the
"distance" part: that's the same as the radius. The angles can
be calculated by dividing the angles in the whole circle by the
number of vertices in the desired polygon. Trying that case with
the triangle, assuming a radius of 20 (why not), and measuring
in degrees, that would give us the Polar points (20, 0), (20,
120), (20, 240). To make this a triangle, we need to connect the
points using lines, which is easy in Cartesian coordinates.
Since the computer likes Cartesian anyway, we just convert the
Polar coordinates to Cartesian, draw the lines, and viola!

Graphics: A Little Geometry page 40

That's essentially the method used by the G#Polygon routines.
It's very simple in practice, but I haven't seen it elsewhere...
probably because people forget about the Polar coordinate
system, which is what makes it all come together. Polar
coordinates also have simple equations for figures that look
like daisies, hearts, and other unusual things. See "Equations,
Etc" and ROSES.BAS for more information.

On a side note, the Cartesian system isn't used by all
computers, although it's the most common. Cartesian coordinates
are the standard for what is called "raster" displays. The Polar
coordinate system is used on "vector" displays. One example of a
vector display that you may have seen is the old Asteroids video
arcade game. Vector displays tend to be used for drawing
"framework" pictures where the image must be very sharp (unlike
in raster images, the diagonal lines aren't jagged, since
there's no raster "grid").

In this section, I'm going to list a number of equations and so
forth. Some of them will be useful to you in experimenting with
Polar coordinates. Some of them provide formulae for things that
are already in BasWiz, but which you might like to understand
better. Some of them are just for the heck of it... note that
not all of this information may be complete enough for you to
just use without understanding it.

One problem is... if you try to draw a circle, for instance, it
will come out looking squashed in most SCREEN modes. Remember we
said our points, unlike mathematical points, have a size? In
most graphics modes, the points are effectively wider than they
are high, so a real circle looks like an ellipse.

Another problem is that these equations are based on an origin
of (0,0) which is assumed to be at the center of the plane. In
our case, (0,0) is at the upper right edge, which also makes the
Y axis (vertical values) effectively upside-down. This isn't
necessarily a problem, but sometimes it is! Adding appropriate
offsets to the plotted X and Y coordinates often fixes it. In
the case of Y, you may need to subtract the value from the
maximum Y value to make it appear right-side-up.

The displayed form of these equations may contain "holes",
usually again because the points have a size, and/or since we
try to use integer math to speed things up. If the screen had
infinite resolution, this would not be a problem... meanwhile
(!), getting around such problems takes fiddlin'.

Graphics: Equations, Etc page 41

There are other problems, mostly due to forcing these
simplified-universe theoretical equations into practical use.
It's a lot easier to shoehorn in these simple equations than to
use more accurate mathematical descriptions, though... a -lot-
easier. So a few minor quirks can be ignored!

With those disclaimers, here's the scoop on some handy

Polar coordinates may be expressed as (R, A), where R is
radius or distance from the origin, and A is the angle.

Cartesian coordinates may be expressed as (X, Y), where X is
the distance along the horizontal axis and Y is the distance
along the vertical axis.

Polar coordinates can be converted to Cartesian coordinates
like so:
X = R * COS(A): Y = R * SIN(A)

Angles may be expressed in radians or degrees. BASIC prefers
radians. Radians are based on PI, with 2 * PI radians in a
circle. There are 360 degrees in a circle. Angles increase
counter-clockwise from a 3:00 clock position, which is the
starting (zero) angle. Angles can wrap around: 720 degrees is
the same as 360 degrees or 0 degrees, just as 3:00 am is at
the same clock position as 3:00 pm.

Angles may be converted between degrees and radians, so:
radians = degrees * PI / 180
degrees = radians * 180 / PI

The value PI is approximately 3.14159265358979. For most
graphics purposes, a simple 3.141593 should do quite nicely.
The true value of PI is an irrational number (the decimal
part repeats forever, as near as anyone can tell). It has
been calculated out to millions of decimal points by people
with a scientific bent (and/or nothing better to do)!

Graphics: Equations, Etc page 42

Line Drawing:

One of the convenient ways of expressing the formula of a
line (Cartesian coordinates) is:
Y = M * X + B

Given the starting and ending points for the line, M (the
slope, essentially meaning the gradient, of the line) can be
determined by:
M = (Y2 - Y1) / (X2 - X1)

The B value is called the Y-intercept, and indicates where
the line intersects with the Y-axis. Given the ingredients
above, you can calculate that as:
B = Y1 - M * X1

With this much figured out, you can use the original formula
to calculate the appropriate Y values, given a FOR X = X1 TO
X2 sort of arrangement. If the slope is steep, however, this
will result in holes in the line. In that case, it will be
smoother to recalculate the formula in terms of the X value
and run along FOR Y = Y1 TO Y2... in that case, restate it
X = (Y - B) / M

Keep an eye on conditions where X1 = X2 or Y1 = Y2! In those
cases, you've got a vertical or horizontal line. Implement
those cases by simple loops to improve speed and to avoid
dividing by zero.

Circle Drawing:

The Cartesian formula gets messy, especially due to certain
aspects of the display that are not accounted for (mainly
that pixels, unlike theoretical points, have a size and shape
which is usually rectangular). The Polar formula is trivial,
though. The radius should be specified to the circle routine,
along with the center point. Do a FOR ANGLE! = 0 TO 2 * PI!
STEP 0.5, converting the resulting (Radius, Angle)
coordinates to Cartesian, then adding the center (X,Y) as an
offset to the result. The appropriate STEP value for the loop
may be determined by trial and error. Smaller values make
better circles but take more time. Larger values may leave
"holes" in the circle.

Graphics: Equations, Etc page 43

Spiral Drawing:

If you use Polar coordinates, this is easy. Just treat it
like a circle, but decrease the radius as you go along to
spiral in... or increase the radius as you go along if you
prefer to spiral out.

Polygon Drawing:

I've already discussed that, so I'll leave it as an
exercise-- or, just see the BasWiz source code! The polygon
routines are primarily written in BASIC.

Flower Drawing:

This sort of thing would be rather difficult to do using
strictly Cartesian methods, but with Polar coordinates, no
problem. Here we calculate the radius based on the angle,
using something like:

FOR Angle! = 0 TO PI! * 2 STEP .01

(a low STEP value is a good idea). The radius is calculated
like so:

Radius! = TotalRadius! * COS(Petals! * Angle!)

The Petals! value specifies how many petals the flower should
have. If it is odd, the exact number of petals will be
generated; if even, twice that number will be generated.

These figures are technically called "roses", although they
more resemble daisies. Try the ROSES.BAS program to see how
they look.

Other Drawing:

Experiment! There are all sorts of interesting things you can
do with the Polar coordinate system in particular. Dig up
those old Geometry texts or see if your Calculus books review
it. If you've kept well away from math, try your local
library or used book store.

Memory Management and Pointers page 44

On the whole, BASIC is easily a match for any other language, as
far as general-purpose programming goes. There is one major
lack, however-- a set of valuable features that is supported by
most other languages, but was inexplicably left out of BASIC.
Perhaps Microsoft felt it was too advanced and dangerous for a
so-called "beginner's" language. In truth, using pointers and
memory management takes a little understanding of what you're
doing-- the compiler can't protect you from all of your
mistakes. However, they can be extraordinarily useful for many
things, so I have added these capabilities to BasWiz.

A "pointer" is essentially just the address of an item. It is
useful in two respects: it allows you to pass just the pointer,
rather than the whole item (be it a TYPEd variable, normal
variable, entire array, or whatever) to a subprogram. This is
faster and more memory-efficient than the alternatives.
Secondly, a pointer combined with memory management allows you
to allocate and deallocate memory "on the fly", in just the
amount you need. You don't have to worry about DIMensioning an
array too large or too small, or even with how large each
element of the array should be, for example. You can determine
that when your program -runs-, rather than at compile time, and
set up your data structures accordingly. You can also create a
large variety of data structures, such as trees and linked
lists, which would be difficult and cumbersome to emulate using
BASIC alone.

The BasWiz memory/pointer routines allow you to allocate and
deallocate memory; fill, copy or move a block of memory; get or
put a single character according to a pointer; and convert back
and forth between a segment/offset address and a pointer.

Pointers are kept in LONG integers, using an absolute memory
addressing scheme. This means that you can manipulate pointers
just like any ordinary LONG integer, e.g. to move to the next
memory address, just add one. Since you can convert from a
segment/offset address to a pointer and you can copy information
from one pointer to another, you can move information back and
forth between allocated memory and a TYPEd variable, numeric
variable, or array. You can even do things like set a pointer to
screen memory and transfer the screen into a variable or vice
versa! Or implement your own "far string" routines, hierarchical
evaluations, or any number of other things. Pointers are
incredibly powerful!

Memory Management and Pointers page 45

Note that there are different ways of representing the same
segment/offset address, but only one absolute pointer
representation. If you need to compare two addresses, using
pointers is terrific. However, it's good to keep in mind that an
segment/offset address may -appear- to change if you convert it
to a pointer and then back to a segment/offset address. When you
convert from a pointer to a segment and offset, the segment will
be maximized and the offset will be minimized. So, for example,
0040:001C will turn into 0041:000C.

Although the byte count for these routines is handled through a
LONG integer, the routines handle a maximum of 65,520 bytes at a
time. In other words, a pointer can only access a bit less than
64K at a time. If I get enough requests to extend this range, I
will do so. Meantime, that's the limit!

There are two routines which take care of memory management.
These allow you to allocate or deallocate memory. Note that if
you allocate too much memory, QuickBasic won't have any memory
to work with! Use the BASIC function "SETMEM" to see how much
memory is available before going hog-wild.

You can allocate memory like so:

MAllocate Bytes&, Ptr&, ErrCode%

If there isn't enough memory available, an error code will be
returned. Otherwise, Ptr& will point to the allocated memory.
Memory is allocated in chunks of 16 bytes, so there may be some
memory wasted if you choose a number of bytes that isn't evenly
divisible by 16.

When you are finished with that memory, you can free it up by

MDeallocate Ptr&, ErrCode%

An error code will be returned if Ptr& doesn't point to
previously allocated memory.

In the best of all possible worlds, there would be a third
routine which would allow you to reallocate or resize a block of
memory. However, due to certain peculiarities of QuickBasic, I
was unable to implement that. You can simulate such a thing by
allocating a new area of memory of the desired size, moving an
appropriate amount of information from the old block to the new,
and finally deallocating the old block.

Memory Management and Pointers page 46

Once you've allocated memory, you can move any sort of
information in or out of it except normal strings-- fixed-length
strings, TYPEd values, arrays, or numeric values. To do that,
you use BASIC's VARSEG and VARPTR functions on the variable.
Convert the resulting segment/offset address to a pointer:

TSeg% = VARSEG(Variable)
TOfs% = VARPTR(Variable)
VariablePtr& = MJoinPtr&(TSeg%, TOfs%)

Moving the information from one pointer to another is like so:

MMove FromPtr&, ToPtr&, Bytes&

For STRING or TYPEd values, you can get the number of bytes via
the LEN function. For numeric values, the following applies:

Type Bytes per value
======= ===============

The "memory move" (MMove) routine is good for more than just
transferring information between a variable and allocated
memory, of course. Pointers can refer to any part of memory. For
instance, CGA display memory starts at segment &HB800, offset 0,
and goes on for 4000 bytes in text mode. That gives a pointer of
&HB8000. You can transfer from the screen to a variable or vice
versa. For that matter, you can scroll the screen up, down,
left, or right by using the appropriate pointers. Add two to the
pointer to move it to the next character or 160 to move it to
the next row. Pointers have all kinds of applications! You don't
need to worry about overlapping memory-- if the two pointers,
combined with the bytes to move, overlap at some point, why, the
MMove routine takes care of that for you. It avoids pointer
conflicts. MMove is a very efficient memory copying routine.

Suppose you've got a pointer and would like to convert it back
to the segment/offset address that BASIC understands. That's no

MSplitPtr Ptr&, TSeg%, TOfs%

Memory Management and Pointers page 47

You might also want to fill an area of memory with a specified
byte value, perhaps making freshly-allocated memory zeroes, for

MFill Ptr&, Value%, Bytes&

Finally, there may be occasions when you might want to transfer
a single character. Rather than going through putting the
character into a STRING*1, getting the VARSEG/VARPTR, and using
MJoinPtr&, there is a simpler way:

MPutChr Ptr&, Ch$
Ch$ = MGetChr$(Ptr&)

Hopefully, this will give you some ideas to start with. There
are many, many possible uses for such capabilities. Pointers and
memory management used to be the only real way in which BASIC
could be considered inferior to other popular languages-- that
is no more!

QuickBasic may move its arrays around in memory! Don't expect
the address of an array to remain constant while your program
is running. Be sure to get the VARSEG/VARPTR for arrays any
time you're not sure they're in the same location. Among the
things which can cause arrays to move are use of DIM, REDIM,
or ERASE, and SHELL. I'm not sure if anything else may cause
the arrays to move, so be cautious!

Quick Timer page 48

On occasion, it's convenient to be able to time something
precisely, or delay for a very small interval. The standard PC
timer runs at about 18.2 ticks per second, which is enough for
some purposes. For other tasks, it can be like timing a race
with an hourglass. Fortunately, we can do a lot better.

The Quick Timer unit provides four countdown timers which work
with millisecond resolution. This is not compatible with BASIC
programs that use BEEP, PLAY, or SOUND statements, since BASIC
reprograms the system timer to handle sound effects. A set of
replacement sound routines provides similar capabilities,
though, so this will not present a problem unless you need music
to be played in the background. The current BasWiz sound handler
only plays in the foreground.

Before using any of the Quick Timer routines, you must install
and initialize the fast timer, like so:


It is *IMPORTANT* to shut down the fast timer before your
program exits! Otherwise, the computer will certainly crash in
short order. The shutdown may be done thusly:


Once the fast timer is installed, there are four countdown
channels available. You can set any of the channels to a given
value (0-32767), and it will count down to zero at 1000 ticks
per second. Channel 0 is used internally by several of the Quick
Timer routines, but channels 1-3 are entirely free for your use.
The "countdown timer" approach allows you to maintain timing in
the background while your program does something else.

SetTimer Channel%, MilliSeconds%
MilliSeconds% = GetTimer%(Channel%)

If your aim is to time something, set a channel to 32767 when it
starts, and get the channel value when it ends. Subtract the
final channel value from 32767 to get the number of milliseconds
taken by the event. You will not be able to time events that
last for more than some 32 seconds, since that is the maximum
delay the timer can handle.

If you just want to delay for a bit, without doing anything
else, it will be more convenient to use the QTDelay routine.

QTDelay MilliSeconds%

Note that QTDelay uses channel 0. It simply sets the channel to
the desired delay and waits until the channel has counted down
to zero.

Quick Timer page 49

Since our Quick Timer reprograms the same timer than BASIC wants
to reprogram for sound handling, it is important to avoid the
use of any BASIC sound statements: BEEP, PLAY, or SOUND. BasWiz
provides replacements for these statements. Although the BasWiz
routines don't support background music processing, they can be
much smaller than the originals, especially if your program
doesn't use floating point math (which SOUND and PLAY normally
bring in, whether the rest of the program needs it or not).

All of the replacement sound routines make use of countdown
channel 0. You should generally avoid use of channel 0 by your
program to avoid possible conflicts.

The BEEP statement can be simply replaced:


The SOUND statement takes a little more work to replace. Whereas
SOUND uses a sound time based on 18.2 ticks per second, QTSound
is based on milliseconds. When converting from SOUND to QTSound,
you must multiply the time by 55 to compensate.

QTSound Frequency%, MilliSeconds%

The PLAY statement works just like you'd expect, although "MB"
(play music in the background) is ignored, since that capability
is not supported. Note that QTPlay will also ignore any errors
in the music command string-- this can be handy for interpreting
"ANSI" music, which may contain garbage. See the ANSI.BAS file,
which contains a "BBS ANSI" interpreter.

QTPlay MusicCommand$

After playing a sequence of music commands, such as a song, you
may wish to reset to the original default music settings. This
is done like so:


Remember, these BasWiz music routines are based on the Quick
Timer. You must install the Quick Timer with QTInit before they
will function. Don't forget to shut down with QTDone when you're

Telecommunications page 50

BASIC is unusual among languages in that it comes complete with
built-in telecommunications support. Unfortunately, that support
is somewhat crude. Amongst other problems, it turns off the DTR
when the program SHELLs or ends, making it difficult to write
doors for BBSes or good terminal programs. It also requires use
of the /E switch for error trapping, since it generates errors
when line noise is encountered, and doesn't provide much
control. It doesn't even support COM3 and COM4, which have been
available for years.

BasWiz rectifies these troubles. It allows comprehensive control
over communications, includes COM3 and COM4, and doesn't require
error trapping. It won't fiddle with the DTR unless you tell it
to do so. The one limitation is that you may use only a single
comm port at a time.

Before you can use communications, you must initialize the
communications handler. If you didn't have BasWiz, you would
probably use something like:

OPEN "COM1:2400,N,8,1,RS,CS,DS" AS #1

With BasWiz, you do not have to set the speed, parity, and so
forth. Communications will proceed with whatever the current
settings are, unless you choose to specify your own settings.
When you initialize the comm handler, you specify only the port
number (1-4) and the size of the input and output buffers
(1-32,767 bytes):

TCInit Port, InSize, OutSize, ErrCode

The size you choose for the buffers should be guided by how your
program will use communications. Generally, a small output
buffer of 128 bytes will be quite adequate. You may wish to
expand it up to 1,500 bytes or so if you expect to write file
transfer protocols. For the input buffer, you will want perhaps
512 bytes for normal use. For file transfer protocols, perhaps
1,500 bytes would be better. If a high baud rate is used, or for
some other reason you might not be emptying the buffer
frequently, you may wish to expand the input buffer size to
4,000 bytes or more.

When you are done with the telecomm routines, you must shut them
down. In BASIC, this would look something like:


With the BasWiz routines, though, you would use this instead:


Telecommunications page 51

The BasWiz "TCDone" does not drop the DTR, unlike BASIC's
"CLOSE". This means that the modem will not automatically be
told to hang up. With BasWiz, you have complete control over the
DTR with the TCDTR routine. Use a value of zero to drop the DTR
or nonzero to raise the DTR:


You may set the speed of the comm port to any baud rate from
1-65,535. If you will be dealing with comm programs that were
not written using BasWiz, you may wish to restrict that to the
more common rates: 300, 1200, 2400, 4800, 9600, 19200, 38400,
and 57600.

TCSpeed Baud&

The parity, word length, and stop bits can also be specified.
You may use 1-2 stop bits, 6-8 bit words, and parity settings of
None, Even, Odd, Mark, or Space. Nearly all BBSes use settings
of None, 8 bit words, and 1 stop bit, although you will
sometimes see Even, 7 bit words, and 1 stop bit. The other
capabilities are provided for dealing with mainframes and other
systems which may require unusual communications parameters.

When specifying parity, only the first character in the string
is used, and uppercase/lowercase distinctions are ignored. Thus,
using either "none" or "N" would specify that no parity is to be

TCParms Parity$, WordLength, StopBits

If your program needs to be aware of when a carrier is present,
it can check the carrier detect signal from the modem with the
TCCarrier function. This function returns zero if no carrier is

PRINT "Carrier detected"
PRINT "No carrier"

Telecommunications page 52

Suppose, though, that you need to know immediately when someone
has dropped the carrier? It wouldn't be too convenient to have
to spot TCCarrier functions all over your program! In that case,
try the "ON TIMER" facility provided by BASIC for keeping an eye
on things. It will enable you to check the carrier at specified
intervals and act accordingly. Here's a brief framework for
writing such code:

ON TIMER(30) GOSUB CarrierCheck
' ...your program goes here...
IF TCCarrier THEN ' if the carrier is present...
RETURN ' ...simply resume where we left off
ELSE ' otherwise...
RETURN Restart ' ...return to the "Restart" label

To get a character from the comm port, use the TCInkey$

ch$ = TCInkey$

To send a string to the comm port, use TCWrite:

TCWrite St$

If you are dealing strictly with text, you may want to have a
carriage return and a linefeed added to the end of the string.
No problem:

TCWriteLn St$

Note that the length of the output buffer affects how the
TCWrite and TCWriteLn routines work. They don't actually send
string directly to the comm port. Instead, they put the string
into the output buffer, and it gets sent to the comm port
whenever the comm port is ready. If there is not enough room in
the output buffer for the whole string, the TCWrite/TCWriteLn
routines are forced to wait until enough space has been cleared
for the string. This can delay your program. You can often avoid
this delay simply by making the output buffer larger.

If you'd like to know how many bytes are waiting in the input
buffer or output buffer, there are functions which will tell

PRINT "Bytes in input buffer:"; TCInStat
PRINT "Bytes in output buffer:"; TCOutStat

Telecommunications page 53

If you would like to clear the buffers for some reason, you can
do that too. The following routines clear the buffers,
discarding anything which was waiting in them:


Finally, there is a routine which allows you to handle ANSI
codes in a window. Besides the IBM semi-ANSI display code
subset, mock-ANSI music is allowed. This routine is designed as
a subroutine that you can access via GOSUB, since there are a
number of variables that the routine needs to maintain that
would be a nuisance to pass as parameters, and QuickBasic
unfortunately can't handle SUBs in $INCLUDE files (so SHARED
won't work). To use it, either include ANSI.BAS directly in your
code, or use:


Set St$ to the string to process, set Win% to the handle of the
window to which to display, and set Music% to zero if you don't
want sounds or -1 if you do want sounds. Then:


Note that the virtual screen tied to the window must be at least
an 80 column by 25 row screen, since ANSI expects that size. You
are also advised to have an ON ERROR trap if you use ANSIprint
with Music% = -1, just in case a "bad" music sequence slips
through and makes BASIC unhappy. Check for ERR = 5 (Illegal
Function Call). I may add a music handler later to avoid this.

To get some idea of how these routines all tie together in
practice, see the TERM.BAS example program. It provides a simple
"dumb terminal" program to demonstrate the BasWiz comm handler.
Various command-line switches are allowed:

/43 use 43-line mode (EGA and VGA only)
/COM2 use COM2
/COM3 use COM3
/COM4 use COM4
/300 use 300 bps
/1200 use 1200 bps
/9600 use 9600 bps
/14400 use 14400 bps
/38400 use 38400 bps
/57600 use 57600 bps
/QUIET ignore "ANSI" music

By default, the TERM.BAS program will use COM1 at 2400 baud with
no parity, 8 bit words and 1 stop bit. You can exit the program
by pressing Alt-X.

Telecommunications page 54

If you're using a fast modem (9600 bps or greater), you should
turn on hardware flow control for reliable communications:

TCFlowCtl -1

The Xmodem file transfer protocol is currently supported for
sending files only. It automatically handles any of the usual
variants on the Xmodem protocol: 128-byte or 1024-byte blocks,
plus checksum or CRC error detection. In other words, it is
compatible with Xmodem (checksum), Xmodem CRC, and Xmodem-1K
(single-file Ymodem-like variant).

There are only two routines which must be used to transfer a
file. The first is called once to initialize the transfer. The
second is called repeatedly until the transfer is finished or
aborted. Complete status information is returned by both
routines. You can ignore most of this information or display it
any way you please.

The initialization routine looks like this:

StartXmodemSend Handle, Protocol$, Baud$, MaxRec, Record,
EstTime$, ErrCode

Only the first three parameters are passed to the routine. These
are the Handle of the file that you wish to send (use FOpen to
get the handle) and the Protocol$ that you wish to use ("Xmodem"
or "Xmodem-1K"), and the current Baud$. On return, you will get
an ErrCode if the other computer did not respond, or MaxRec (the
number of blocks to be sent), Record (the current block number),
and EstTime$ (an estimate of the time required to complete the
transfer. The Protocol$ will have "CHK" or "CRC" added to it to
indicate whether checksum or CRC error detection is being used,
depending on which the receiver requested.

The secondary routine looks like this:

XmodemSend Handle, Protocol$, MaxRec, Record, ErrCount,

The ErrCode may be zero (no error), greater than zero (error
reading file), or less than zero (file transfer error,
completion or abort). See the appendix on Error Codes for
specific details. The TERM.BAS example program shows how these
routines work together in practice.

The file accessed by the Xmodem routine will remain open.
Remember to close it when the transfer is done (for whatever
reason), using the FClose routine.

Telecommunications page 55

A few notes on the ins and outs of telecommunications...

The DTR signal is frequently used to control the modem. When the
DTR is "raised" or "high", the modem knows that we're ready to
do something. When the DTR is "dropped" or "low", the modem
knows that we're not going to do anything. In most cases, this
tells it to hang up or disconnect the phone line. Some modems
may be set to ignore the DTR, in which case it will not
disconnect when the DTR is dropped. Usually this can be fixed by
changing a switch on the modem. On some modems, a short software
command may suffice.

The DTR is generally the best way to disconnect. The Hayes "ATH"
command is supposed to hang up, but it doesn't work very well.

The BasWiz comm handler makes sure the DTR is raised when TCInit
is used. It does not automatically drop the DTR when TCDone is
used, so you can keep the line connected in case another program
wants to use it. If this is not suitable, just use TCDTR to drop
the DTR. Your program must always use TCDone before it exits,
but it need only drop the DTR if you want it that way.

If you want to execute another program via SHELL, it is ok to
leave communications running as long as control will return to
your program when the SHELLed program is done. In that case, the
input buffer will continue to receive characters from the comm
port unless the SHELLed program provides its own comm support.
The output buffer will likewise continue to transmit characters
unless overruled.

The Virtual Windowing System page 56

The virtual windowing system offers pop-up and collapsing
windows, yes... but that is just a small fraction of what it
provides. When you create a window, the part that you see on the
screen may be only a view port on a much larger window, called a
virtual screen. You can make virtual screens of up to 255 rows
long or 255 columns wide. The only limitation is that any single
virtual screen must take up less than 65,520 bytes. Each virtual
screen is treated much like the normal screen display, with
simple replacements for the standard PRINT, LOCATE, and COLOR
commands. Many other commands are provided for additional
flexibility. The window on the virtual screen may be moved,
resized, or requested to display a different portion of the
virtual screen. If you like, you may choose to display a frame
and/or title around a window. When you open a new window, any
windows under it are still there and can still be updated--
nothing is ever destroyed unless you want it that way! With the
virtual windowing system, you get a tremendous amount of control
for a very little bit of work.

The current version of the virtual windowing system only allows
text mode screens to be used. All standard text modes are
supported, however. This includes 25x40 CGA screens, the
standard 25x80 screen, and longer screens such as the 43x80 EGA
screen. The virtual windowing system is designed for computers
that offer hardware-level compatibility with the IBM PC, which
includes almost all MS-DOS/PC-DOS computers in use today.


The actual screen.

This is a screen kept in memory which reflects any changes
you make to windows. Rather than making changes directly on
the actual screen, the virtual windowing system works with a
"shadow screen" for increased speed and flexibility. You
specify when to update the display from the shadow screen.
This makes changes appear very smoothly.

This is a screen kept in memory which can be treated much
like the actual screen. You may choose to make a virtual
screen any reasonable size. Every virtual screen will have a
corresponding window.

This is the part of a virtual screen which is actually
displayed. You might think of a window as a "view port" on a
virtual screen. A window may be smaller than its virtual
screen or the same size. It may have a frame or a title and
can be moved or resized.

The Virtual Windowing System page 57

Frankly, the virtual windowing system is one of those things
that's more difficult to explain than to use. It's very easy to
use, as a matter of fact, but the basic concepts will need a
little explanation. Rather than launching into a tedious and
long-winded description of the system, I'm going to take a more
tutorial approach, giving examples and explaining as I go along.
Take a look at the WDEMO.BAS program for examples.

Let's begin with the simplest possible scenario, where only a
background window is created. This looks just like a normal

Rows = 25: Columns = 80 ' define display size
WInit Rows, Columns, ErrCode ' initialize window system
IF ErrCode THEN ' stop if we couldn't...
PRINT "Insufficient memory"
Handle = 0 ' use background handle
WWriteLn Handle, "This is going on the background window."
WWriteLn Handle, "Right now, that's the full screen."
WUpdate ' update the display
WDone ' terminate window system

What we just did was to display two lines on the screen--
nothing at all fancy, but it gives you the general idea of how
things work. Let's take a closer look:

- We INCLUDE the BASWIZ.BI definition file to let BASIC know
that we'll be using the BasWiz routines.

- We define the size of the display using the integer
variables Rows and Columns (you can use any variable names
you want). If you have an EGA display and had previously
used WIDTH ,43 to go into 43x80 mode, you'd use "Rows = 43"
here, for example.

- We initialize the windowing system with WInit, telling it
how large the display is. It returns an error code if it is
unable to initialize.

- We define the Handle of the window that we want to use. The
"background window" is always available as handle zero, so
we choose "Handle = 0".

- We print two strings to the background window with WWriteLn,
which is like a PRINT without a semicolon on the end (it
moves to the next line).

- At this point, only the shadow screen has been updated.
We're ready to display the information, so we use WUpdate to
update the actual screen.

- We're all done with the program, so we end with WDone.

The Virtual Windowing System page 58

See, there's nothing to it! We initialize the screen, print to
it or whatever else we need to do, tell the windowing system to
update the display, and when the program is done, we close up

The background screen is always available. It might help to
think of it as a virtual screen that's the size of the display.
The window on this virtual screen is exactly the same size, so
the entire virtual screen is displayed. As with other virtual
screens, you can print to it without disturbing anything else.
That means you can treat the background screen the same way
regardless of whether it has other windows on top of it-- the
other windows just "cover" the background information, which
will still be there.

This leads us to the topic of creating windows. Both a virtual
screen and a window are created simultaneously-- remember, a
window is just a view port on a virtual screen. The window can
be the same size as the virtual screen, in which case the entire
virtual screen is visible (as with the background window) or it
can be smaller than the virtual screen, in which case just a
portion of the virtual screen will be visible at any one time.

A window is created like so:

' This is a partial program and can be inserted in the
' original example after the second WWriteLn statement...
VRows = 43: VColumns = 80 ' define virtual screen size
' create the window
WOpen VRows, VColumns, 1, 1, Rows, Columns, Handle, ErrCode
IF ErrCode THEN ' error if we couldn't...
PRINT "Insufficient memory available"
WDone ' (or use an error handler)

What we have done here is to create a virtual screen of 43 rows
by 80 columns. The window will be the size of the display, so if
you are in the normal 25x80 mode, only the first 25 rows of the
virtual screen will be visible. If you have an EGA or VGA in
43x80 mode, though, the entire virtual screen will be visible!
So, this window lets you treat a screen the same way regardless
of the display size.

The Handle returned is used any time you want to print to this
new window or otherwise deal with it. If you are using many
windows, you might want to keep an array of handles, to make it
easier to keep track of which is which.

The Virtual Windowing System page 59

By default, a virtual screen is created with the following

- The cursor is at (1,1), the upper left corner of the
virtual screen.
- The cursor size is 0 (invisible).
- The text color is 7,0 (white foreground on a black
- There is no title or frame.
- The window starts at (1,1) in the virtual screen, which
displays the area starting at the upper left corner of
the virtual screen.

When you create a new window, it becomes the "top" window, and
will be displayed on top of any other windows that are in the
same part of the screen. Remember, you can print to a window or
otherwise deal with it, even if it's only partially visible or
entirely covered by other windows.

Don't forget WUpdate! None of your changes are actually
displayed until WUpdate is used. You can make as many changes as
you like before calling WUpdate, which will display the results
smoothly and at lightning speed.

We've created a window which is exactly the size of the display,
but which might well be smaller than its virtual screen. Let's
assume that the normal 25x80 display is being used, in which
case our virtual screen (43x80) is larger than the window. We
can still print to the virtual screen normally, but if we print
below line 25, the results won't be displayed. What a
predicament! How do we fix this?

The window is allowed to start at any given location in the
virtual screen, so if we want to see a different portion of the
virtual screen, all we have to do is tell the window to start
somewhere else. When the window is created, it starts at the
beginning of the virtual screen, coordinate (1,1). The WView
routine allows us to change this.

In our example, we're displaying a 43x80 virtual screen in a
25x80 window. To begin with, then, rows 1-25 of the virtual
screen are visible. To make rows 2-26 of the virtual screen
visible, we simply do this:

WView Handle, 2, 1

That tells the window to start at row 2, column 1 in the virtual
screen. Sounds easy enough, doesn't it? Well, if not, don't
despair. Play with it a little until you get the hang of it.

The Virtual Windowing System page 60

You've noticed that the window doesn't need to be the same size
as the virtual screen. Suppose we don't want it the same size as
the display, either... suppose we want it in a nice box, sitting
out of the way in a corner of the display? Well, we could have
created it that way to begin with when we used WOpen. Since
we've already created it, though, let's take a look at the
routines to change the size of a window and to move it
elsewhere. The window can be made as small as 1x1 or as large as
its virtual screen, and it can be moved anywhere on the display
you want it.

Let's make the window a convenient 10 rows by 20 columns:

WSize Handle, 10, 20

And move it into the lower right corner of the display:

WPlace Handle, 12, 55

Don't forget to call WUpdate or the changes won't be visible!
Note also that we didn't really lose any text. The virtual
screen, which holds all the text, is still there. We've just
changed the size and position of the window, which is the part
of the virtual screen that we see, so less of the text (if there
is any!) is visible. If we made the window larger again, the
text in the window would expand accordingly.

If you were paying close attention, you noticed that we didn't
place the resized window flush against the corner of the
display. We left a little bit of room so we can add a frame and
a title. Let's proceed to do just that.

Window frames are displayed around the outside of a window and
will not be displayed unless there is room to do so. We have
four different types of standard frames available:

0 (no frame)
1 single lines
2 double lines
3 single horizontal lines, double vertical lines
4 single vertical lines, double horizontal lines

We must also choose the colors for the frame. It usually looks
best if the background color is the same background color as
used by the virtual screen. Let's go ahead and create a
double-line frame in bright white on black:

FType = 2: Fore = 15: Back = 0
WFrame Handle, FType, Fore, Back

The Virtual Windowing System page 61

If you'd rather not use the default frame types, there's ample
room to get creative! Frames 5-9 can be defined any way you
please. They are null by default. To create a new frame type,
you must specify the eight characters needed to make the frame:
upper left corner, upper middle columns, upper right corner,
left middle rows, right middle rows, lower left corner, lower
middle columns, and lower right corner.

| Want a plain text frame like this? |
| Use the definition string "+-+||+-+" |

The above window frame would be defined something like this:

Frame = 5
FrameInfo$ = "+-+||+-+"
WUserFrame Frame, FrameInfo$

Of course, you can choose any values you like. As always, the
names of the variables can be anything, as long as you name them
consistently within your program. You can even use constants if
you prefer:

WUserFrame 5, "+-+||+-+"

If you use a frame, you can also have a "shadow", which provides
a sort of 3-D effect. The shadow can be made up of any character
you choose, or it can be entirely transparent, in which case
anything under the shadow will change to the shadow colors. This
latter effect can be quite nice. I've found that it works best
for me when I use a dim foreground color with a black
background-- a foreground color of 8 produces wonderful effects
on machines that support it (it's "bright black", or dark gray;
some displays will show it as entirely black, though, so it may
not always work the way you want). For a transparent shadow,
select CHR$(255) as the shadow character. You can turn the
shadow off with either a null string or CHR$(0).

Shadow$ = CHR$(255) ' transparent shadow
Fore = 8: Back = 0 ' dark gray on black
WShadow Handle, Shadow$, Fore, Back

A shadow will only appear if there is also a frame, and if there
is enough space for it on the screen. Currently, there is only
one type of shadow, which appears on the right and bottom sides
of the frame. It effectively makes the frame wider and longer by
one character.

The Virtual Windowing System page 62

We can have a title regardless of whether a frame is present or
not. Like the frame, the title is displayed only if there is
enough room for it. If the window is too small to accommodate
the full title, only the part of the title that fits will be
displayed. The maximum length of a title is 70 characters.
Titles have their own colors.

Title$ = "Wonderful Window!"
Fore = 0: Back = 7
WTitle Handle, Title$, Fore, Back

To get rid of a title, just use a null title string, for

Title$ = ""

It may be convenient to set up a window that isn't always
visible-- say, for a help window, perhaps. The window could be
set up in advance, then shown whenever requested using just one

WHide Handle, Hide

You can make a window invisible by using any nonzero value for
Hide, or make it reappear by setting Hide to zero. As always,
the change will only take effect after WUpdate is used.

When WWrite or WWriteLn gets to the end of a virtual screen,
they normally scroll the "screen" up to make room for more text.
This is usually what you want, of course, but there are
occasions when it can be a nuisance. The automatic scrolling can
be turned off or restored like so:

WScroll Handle, AutoScroll

There are only a few more ways of dealing with windows
themselves. After that, I'll explain the different things you
can do with text in windows and how to get information about a
specific window or virtual screen.

If you have a lot of windows, one window may be on top of
another, obscuring part or all of the window(s) below. In order
to make sure a window is visible, all you need to do is to put
it on top, right? Hey, is this easy or what?!

WTop Handle

You may also need to "unhide" the window if you used WHide on it

The Virtual Windowing System page 63

Note that the background window will always be the background
window. You can't put handle zero, the background window, on
top. What? You say you need to do that?! Well, that's one of the
ways you can use the WCopy routine. WCopy copies one virtual
screen to another one of the same size:

WCopy FromHandle, ToHandle

You can copy the background window (or any other window) to
another window. The new window can be put on top, resized,
moved, or otherwise spindled and mutilated. The WDEMO program
uses this trick.

We've been through how to open windows, print to them, resize
them and move them around, among other things. We've seen how to
put a frame and a title on a window and pop it onto the display.
If you're a fan of flashy displays, though, you'd probably like
to be able to make a window "explode" onto the screen or
"collapse" off. It's the little details like that which make a
program visually exciting and professional-looking. I wouldn't
disappoint you by leaving something fun like that out!

Since we're using a virtual windowing system rather than just a
plain ol' ordinary window handler, there's an extra benefit.
When a window explodes or collapses, it does so complete with
its title, frame, shadow, and even its text. This adds rather
nicely to the effect.

To "explode" a window, we just set up all its parameters the way
we normally would-- open the window, add a title or frame if we
like, print any text that we want displayed, and set the screen
position. Then we use WExplode to zoom the window from a tiny
box up to its full size:

WExplode Handle

The "collapse" routine works similarly. It should be used only
when you are through with a window, because it closes the window
when it's done. The window is collapsed from its full size down
to a tiny box, then eliminated entirely:

WCollapse Handle

Note that WExplode and WCollapse automatically use WUpdate to
update the display. You do not need to use WUpdate yourself and
you should make sure that the screen is the way you want it
displayed before you call either routine.

The Virtual Windowing System page 64

The WCollapse and WExplode routines were written in BASIC, so
you can customize them just the way you want them.

That's it for the windows. We've been through all the "tricky
stuff". There are a number of useful things you can do with a
virtual screen, though, besides printing to it with WWriteLn.
Let's take a look at what we can do.

WWriteLn is fine if you want to use a "PRINT St$" sort of
operation. Suppose you don't want to move to a new line
afterward, though? In BASIC, you'd use something like "PRINT
St$;" (with a semicolon). With the virtual windowing system, you
use WWrite, which is called just like WWriteLn:

WWrite Handle, St$

There are also routines that work like CLS, COLOR and LOCATE:

WClear Handle
WColor Handle, Fore, Back
WLocate Handle, Row, Column

The WClear routine is not quite like CLS in that it does not
alter the cursor position. If you want the cursor "homed", use

Note that the coordinates for WLocate are based on the virtual
screen, not the window. If you move the cursor to a location
outside the view port provided by the window, it will disappear.
Speaking of disappearing cursors, you might have noticed that
our WLocate doesn't mimic LOCATE exactly: it doesn't provide for
controlling the cursor size. Don't panic! There's another
routine available for that:

WCursor Handle, CSize

The CSize value may range from zero (in which case the cursor
will be invisible) to the maximum size allowed by your display
adapter. This will always be at least eight.

Now, since each virtual screen is treated much like the full
display, you may be wondering what happens if the cursor is "on"
in more than one window. Does that mean multiple cursors are
displayed? Well, no. That would get a little confusing! Only the
cursor for the top window is displayed. If you put a different
window on top, the cursor for that window will be activated and
the cursor for the old top window will disappear. The virtual
windowing system remembers the cursor information for each
window, but it only actually displays the cursor for the window
that's on top.

The Virtual Windowing System page 65

In addition to the usual screen handling, the windowing system
provides a number of new capabilities which you may find very
handy. These include routines to insert and delete both
characters and rows, which is done at the current cursor
position within a selected virtual screen:

WDelChr Handle
WDelLine Handle
WInsChr Handle
WInsLine Handle

These routines can also be used for scrolling. Remember, the
display isn't updated until you use WUpdate, and then it's
updated all at once. You can use any of the routines multiple
times and the display will still be updated perfectly smoothly--
all the real work goes on behind the scenes!

Normally, the windowing system interprets control codes
according to the ASCII standard-- CHR$(7) beeps, CHR$(8) is a
backspace, and so forth. Sometimes you may want to print the
corresponding IBM graphics character instead, though... or maybe
you just don't use control codes and want a little more speed
out of the windowing system. You can turn control code handling
on or off for any individual window:

WControl Handle, DoControl

When you are done with a virtual screen and no longer need it,
you can dispose of it like so:

WClose Handle

All of the information that can be "set" can also be retrieved.
That's useful in general, of course, but it's also a great
feature for writing portable subprograms. You can create
subprograms that will work with any virtual screen, since it can
retrieve any information it needs to know about the virtual
screen or its window. That's power!

The Virtual Windowing System page 66

Here is a list of the available window information routines:

WGetColor Handle, Fore, Back
' gets the current foreground and background colors

WGetControl Handle, DoControl
' gets whether control codes are interpreted

WGetCursor Handle, CSize
' gets the cursor size

WGetFrame Handle, Frame, Fore, Back
' gets the frame type and frame colors

WGetLocate Handle, Row, Column
' gets the cursor position

WGetPlace Handle, Row, Column
' gets the starting position of a window on the display

WGetScroll Handle, AutoScroll
' gets the status of auto-scroll
' (scrolling at the end of a virtual screen)

Shadow$ = SPACE$(1)
WGetShadow Handle, Shadow$, Fore, Back
' gets the shadow character (CHR$(0) if there's no
' shadow) and colors

WGetSize Handle, Rows, Columns
' gets the size of a window

Title$ = SPACE$(70)
WGetTitle Handle, Title$, TLen, Fore, Back
Title$ = LEFT$(Title$, TLen)
' gets the title string (null if there's no title) and
' title colors

WGetTop Handle
' gets the handle of the top window

FrameInfo$ = SPACE$(8)
WGetUFrame$ Frame, FrameInfo$
' gets the specification for a given user-defined frame

WGetView Handle, Row, Column
' gets the starting position of a window within a
' virtual screen

WGetVSize Handle, Rows, Columns
' gets the size of a virtual screen

WHidden Handle, Hidden
' tells you whether a window is visible

The Virtual Windowing System page 67

As well as displaying information in a window, you will
frequently want to allow for getting input from the user. Of
course, INKEY$ will still work fine, but that's not an effective
way of handling more than single characters. The virtual window
system includes a flexible string input routine which is a lot
more powerful:

WInput Handle, Valid$, ExitCode$, ExtExitCode$,
MaxLength, St$, ExitKey$

The Valid$ variable allows you to specify a list of characters
which may be entered. If you use a null string (""), any
character will be accepted.

ExitCode$ specifies the normal keys that can be used to exit
input. You'll probably want to use a carriage return, CHR$(13),
for this most of the time. You can also specify exit on extended
key codes like arrow keys and function keys via ExtExitCode$.

MaxLength is the maximum length of the string you want. Use zero
to get the longest possible string. The length may go up to the
width of the virtual screen, minus one character. The window
will be scrolled sideways as needed to accommodate the full
length of the string.

The St$ variable is used to return the entered string, but you
can also use it to pass a default string to the routine.

ExitKey$ returns the key that was used to exit input.

A fairly strong set of editing capabilities is available through
WInput. The editing keys can be overridden by ExitCode$ or
ExtExitCode$, but by default they include support for both the
cursor keypad and WordStar:

Control-S LeftArrow move left once
Control-D RightArrow move right once
Control-V Ins insert <--> overstrike modes
Control-G Del delete current character
Control-H Backspace destructive backspace
Home move to the start of input
End move to the end of input

The Virtual Windowing System page 68

Pop-up menus have become very popular in recent years.
Fortunately, they are a natural application for virtual windows!
BasWiz provides a pop-up menuing routine which allows you to
have as many as 255 choices-- the window will be scrolled
automatically to accommodate your "pick list", with a highlight
bar indicating the current selection.

The pop-up menu routine uses a window which you've already set
up, so you can use any of the normal window options-- frames,
titles, shadows, etc. You must provide a virtual screen large
enough to hold your entire pick list; the window itself can be
any size at all.

The pick list is passed to WMenuPopUp through a string array.
You can dimension this array in any range that suits you. The
returned selection will be the relative position in the array (1
for the first item, etc); if the menu was aborted, 0 will be
returned instead.

The current window colors will be used for the "normal" colors.
You specify the desired highlight colors when calling the pop-up
menu routine.

Result = WMenuPopUp(Handle, PickList$(), HiFore, HiBack)

The mouse is not supported, since BasWiz does not yet have mouse
routines. However, scrolling can be accomplished with any of the
more common methods: up and down arrows, WordStar-type Control-E
and Control-X, or Lotus-type tab and backtab. The ESCape key can
be used to abort without choosing an option.

On exit, the menu window will remain in its final position, in
case you wish to pop up a related window next to it or something
similar. Since it's just an ordinary window, you can use WClose
or WCollapse if you prefer to get rid of it.

The WMenuPopUp routine was written in BASIC, so you will find it
easy to modify to your tastes. It was written with extra
emphasis on comments and clarity, since I know many people will
want to customize this routine!

The Virtual Windowing System page 69

There are two more routines which allow the virtual windowing
system to work on a wide variety of displays: WFixColor and

Chances are, as a software developer you have a color display.
However, there are many people out there who have monochrome
displays, whether due to preference, a low budget, or use of
notebook-style computers with mono LCD or plasma screens.
WFixColor allows you to develop your programs in color while
still supporting monochrome systems. It tells the virtual
windowing system whether to keep the colors as specified or to
translate them to their monochrome equivalents:

WFixColor Convert%

Set Convert% to zero if you want true color (default), or to any
other value if you want the colors to be translated to
monochrome. In the latter case, the translation will be done
based on the relative brightness of the foreground and
background colors. The result is guaranteed to be readable on a
monochrome system if it's readable on a color system. You should
check the results on your system to make sure that such things
as highlight bars still appear highlighted, however.

In the case of some of the older or less carefully designed CGA
cards, the high-speed displays of the virtual windowing system
can cause the display to flicker annoyingly. You can get rid of
the flicker at the expense of slowing the display:

WSnow Remove%

Set Remove% to zero if there is no problem with "snow" or
flickering (default), or to any other value if you need "snow
removal". Using snow removal will slow down the display
substantially, which may be a problem if you update (WUpdate) it

Note that you can't detect either of these cases automatically
with perfect reliability. Not all CGA cards have flicker
problems. Also, mono displays may be attached to CGA cards and
the computer won't know the difference. A VGA with a "paper
white" monitor may well think it has color, and will mostly act
like it, but some "color" combinations can be very difficult to
read. While you can self-configure the program to some extent
using the GetDisplay routine (see Other Routines), you should
also provide command-line switches so that the user can override
your settings. Microsoft generally uses "/B" to denote a
monochrome ("black and white") display, so you may want to
follow that as a standard.

Finally, by popular request, there is a routine which returns
the segment and offset of a virtual screen. This lets you do
things with a virtual screen that are not directly supported by
BasWiz. Virtual screens are laid out like normal text screens.

WGetAddress Handle, WSeg, WOfs

Other Routines page 70

There are a number of routines for which I couldn't find a
specific category.

To see how much expanded memory is available, use the GetEMS
function. It'll return zero if there is no expanded memory

PRINT "Kbytes of expanded memory:"; GetEMS

The GetDisplay routine tells what kind of display adapter is
active and whether it's hooked up to a color monitor. The only
time it can't detect the monitor type is on CGA setups (it
assumes "color"). It's a good idea to allow a "/B" switch for
your program so the user can specify if a monochrome monitor is
attached to a CGA.

GetDisplay Adapter, Mono
PRINT "Monochrome monitor"
PRINT "Color monitor"
CASE 2: PRINT "Hercules"

The ScreenSize routine returns the number of rows and columns on
the display (text modes only):

ScreenSize Rows%, Columns%

Miscellaneous Notes page 71

The virtual windowing system allows up to 16 windows to be open
at a time, including the background window, which is opened
automatically. This is subject to available memory, of course.

The far string handler allows up to 65,535 strings of up to 255
characters each, subject to available memory. When the handler
needs additional memory for string storage, it allocates more in
blocks of 16 Kbytes. If that much memory is not available, an
"out of memory" error will be generated (BASIC error number 7).
You can check the size of the available memory pool using the
SETMEM function provided by QuickBasic.

The communications handler only allows one comm port to be used
at a time.

The file handler does not allow you to combine Write mode with
Text mode or input buffering.

A certain lack of speed is inherent in BCD math, especially if
you require high precision. The division, root, and trig
routines in particular are quite slow.

The fraction routines are much faster, but they have a much
smaller range. I'll have to do some experimenting on that. It
may prove practical to use a subset of the BCD routines to
provide an extended range for fractions without an unreasonable
loss in speed.

All routines are designed to be as bomb-proof as possible. If
you pass an invalid value to a routine which does not return an
error code, it will simply ignore the value.

The EGA graphics routines are designed for use with EGAs having
at least 256K RAM on board. They will not operate properly on
old 64K EGA systems.

Image loading (.MAC and .PCX) is quite slow. The bulk of the
code is in BASIC at this point, to make it easier for me to
extend the routines to cover other graphics modes. They will be
translated to assembly later.

The G#Write and G#WriteLn services support three different
fonts: 8x8, 8x14, and 8x16. The default font is always 8x8,
providing the highest possible text density. QuickBasic, on the
other hand, allows only one font with a text density of as close
to 80x25 as possible.

Miscellaneous Notes page 72

The G#Write and G#WriteLn services interpret ASCII control
characters, i.e. CHR$(0) - CHR$(31), according to the more
standard handling used by DOS rather than the esoteric
interpretation offered by QuickBasic. This is not exactly a
limitation, but it could conceivably cause confusion if your
program happens to use these characters. The ASCII
interpretation works as follows:

Code Meaning
==== =======
7 Bell (sound a beep through the speaker)
8 Backspace (eliminate the previous character)
9 Tab (based on 8-character tab fields)
10 LineFeed (move down one line, same column)
12 FormFeed (clear the screen)
13 Return (move to the start of the row)

G#MirrorH will only work properly on images with byte alignment!
This means that the width of the image must be evenly divisible
by four if SCREEN 1 is used, or evenly divisible by eight if
SCREEN 2 is used.

The graphics routines provide little error checking and will not
do clipping (which ignores points outside the range of the
graphics mode). If you specify coordinates which don't exist,
the results will be unusual at best. Try to keep those values
within the proper range!

A very few of the graphics routines are slower than their
counterparts in QuickBasic. These are mostly drawing diagonal
lines and filling boxes. The GET/PUT replacements are quite
slow as well.

If you use PRINT in conjunction with GN4Write or GN4WriteLn, be
sure to save the cursor position before the PRINT and restore it
afterwards. BASIC and BasWiz share the same cursor position, but
each interprets it to mean something different.

The GN0 (360x480x256) and GN1 (320x400x256) routines use
nonstandard VGA modes. The GN1 routines should work on just
about any VGA, however. The GN0 routines will work on many VGAs,
but are somewhat less likely to work than the GN1 routines due
to the techniques involved.

Miscellaneous Notes page 73

The GN0Write, GN0WriteLn, GN1Write and GN1WriteLn routines are
somewhat slow in general and quite slow when it comes to
scrolling the screen.

The G1Border routine is normally used to select the background
(and border) color for SCREEN 1 mode. It can also be used in
SCREEN 2 mode, where it will change the foreground color
instead. Note that this may produce peculiar results if an EGA
or VGA is used and it isn't locked into "CGA" mode, so be
careful if your program may run on systems with displays other
than true CGAs.

Note that you can GET an image in SCREEN 1 and PUT it in SCREEN
2! It'll be shaded instead of in colors. This is a side-effect
of the CGA display format.

The first two elements of a GET/PUT array (assuming it's an
integer array) tells you the size of the image. The first
element is the width and the second is the height, in pixels.
Actually, that's not quite true. Divide the first element by 2
for the width if the image is for SCREEN 1, or by 8 if for

Error Codes page 74

The expression evaluator returns the following error codes:

0 No error, everything went fine
2 A number was expected but not found
4 Unbalanced parentheses
8 The expression string had a length of zero
9 The expression included an attempt to divide by zero

The far string handler does not return error codes. If an
invalid string handle is specified for FSSet, it will be
ignored; if for FSGet, a null string will be returned. If you
run out of memory for far strings, an "out of memory" error will
be generated (BASIC error #7). You can prevent this by checking
available memory beforehand with the SETMEM function provided by
QuickBasic. Far string space is allocated as needed in blocks of
just over 16 Kbytes, or 16,400 bytes to be exact.

The telecommunications handler returns the following error codes
for TCInit:

0 No error, everything A-Ok
1 The comm handler is already installed
2 Invalid comm port specified
3 Not enough memory available for input/output buffers

The telecommunications handler returns these error codes for
Xmodem Send:

-13 FATAL : Unsupported transfer protocol
-12 FATAL : Excessive errors
-11 FATAL : Keyboard or receiver requested CANcel
-5 WARNING : Checksum or CRC error
-1 WARNING : Time-out error (receiver didn't respond)
0 DONE : No error, transfer completed ok
>0 ERROR : File problem (see file error codes)

Error Codes page 75

The file services return the following error codes: (The
asterisk "*" is used to identify "critical errors")

0 No error
1 Invalid function number (usually invalid parameter)
2 File not found
3 Path not found
4 Too many open files
5 Access denied (probably "write to read-only file")
6 Invalid file handle
7 Memory control blocks destroyed
8 Insufficient memory (usually RAM, sometimes disk)
9 Incorrect memory pointer specified
15 Invalid drive specified
* 19 Tried to write on a write-protected disk
* 21 Drive not ready
* 23 Disk data error
* 25 Disk seek error
* 26 Unknown media type
* 27 Sector not found
* 28 Printer out of paper
* 29 Write fault
* 30 Read fault
* 31 General failure
* 32 Sharing violation
* 33 Lock violation
* 34 Invalid disk change
36 Sharing buffer overflow

A "critical error" is one that would normally give you the
dreaded prompt:

A>bort, R>etry, I>gnore, F>ail?

Such errors generally require some action on the part of the
user. For instance, they may need to close a floppy drive door
or replace the paper in a printer. If a critical error occurs on
a hard drive, it may indicate a problem in the drive hardware or
software setup. In that case, the problem may possibly be
cleared up by "CHKDSK /F", which should be executed directly
from the DOS command line (do not execute this by SHELL).

Troubleshooting page 76

QB says "subprogram not defined".

The definition file was not included. Your program must
contain the line:
before any executable code in your program. You should also
start QuickBasic with
so it knows to use the BasWiz library.

LINK says "unresolved external reference".

Did you specify BasWiz as the library when you used LINK? You
should! The BASWIZ.LIB file must be in the current directory
or along a path specified by the LIB environment variable
(like PATH, but for LIB files).

The virtual windowing system doesn't display anything.

Perhaps you left out the WUpdate routine? If so, the shadow
screen is not reflected to the actual screen and nothing will
appear. The screen also needs to be in text mode (either no
SCREEN statement or SCREEN 0). Finally, only the default
"page zero" is supported on color monitors.

The virtual windowing system causes the display to flicker on

Use the WSnow routine to get rid of it. Unfortunately, this
will slow the display down severely. You might want to
upgrade your display card!

Troubleshooting page 77

QuickBasic doesn't get along with the Hercules display

Are you using an adapter which mimics Hercules mode along
with EGA or VGA mode? QuickBasic doesn't like that, since it
thinks you'll be using EGA or VGA mode. Use the stand-alone
compiler (BC.EXE) instead of the environment (QB.EXE) and you
should be fine. You might also consider getting a separate
Herc adapter and monochrome monitor. It's possible to combine
a Hercules monochrome adapter with a CGA, EGA or VGA. This
does, however, slow down 16-bit VGAs.

QB says "out of memory" (or "range out of bounds" on a DIM or

If you're using the memory management/pointer routines,
you've probably allocated too much memory! You need to leave
some for QuickBasic. Use the SETMEM function provided by
BASIC to determine how much memory is available before
allocating memory. The amount needed by QuickBasic will
depend on your program. The primary memory-eaters are arrays
and recursive subprograms or functions.

Many of the BasWiz routines need to allocate memory,
including the virtual window manager, telecommunications
handler, and memory management system. Besides checking with
SETMEM to make sure there's memory to spare, don't forget to
check the error codes returned by these routines to make sure
they're working properly!

The cursor acts funny (appears when it shouldn't or vice

Try locking your EGA or VGA into a specific video mode using
the utility provided with your display adapter. Cursor
problems are usually related either to "auto mode detection"
or older EGAs.

Troubleshooting page 78

The BCD trig functions return weird results.

Make sure you've made room in your BCD size definition for
some digits to the left of the decimal as well as to the
right! Calculations with large numbers are needed to return
trig functions with high accuracy.

The G#MirrorH routine is -almost- working right, but the
results are truncated or wrapped to one side.

Make your GET image a tad wider. The number of pixels wide
must be evenly divisible by four in SCREEN 1, or by eight in

Using BasWiz with P.D.Q. or QBTiny page 79

Most of the BasWiz routines will work with current versions of
Crescent's P.D.Q. or my QBTiny library without modification. The
major exceptions are the expression evaluator, the BCD and
fraction math routines, and the polygon-generating graphics
routines, due to their use of floating point math.

Older versions of the P.D.Q. library do not support the SETMEM
function, which is required by many BasWiz routines. If your
version of P.D.Q. is before v2.10, contact Crescent for details
on how to upgrade to the latest version.

Note that, with some older versions of P.D.Q., it is important
to list PDQ.LIB as the last library on the command line when
LINKing. This bug has also been resolved in current versions.

Some older versions of P.D.Q. do not support dynamic string
functions. In that case, you will have to add the STATIC keyword
to all BasWiz BASIC string functions and recompile them.

QBTiny does not support dynamic arrays. You will be unable to
use any routines which require dynamic arrays with QBTiny.

Credits page 80

For some of the reference works I have used in writing BasWiz,
see the BIBLIO.TXT file.

Crescent Software provided me with a copy of P.D.Q. so I could
test for any compatibility problems between it and BasWiz.

The inverse hyperbolic trig functions are based on a set of
BASIC routines by Kerry Mitchell.

The 360x480 256-color VGA mode was made possible by John
Bridges' VGAKIT library for C. Two of the most vital low-level
routines are based directly on code from VGAKIT. If you use C,
check your local BBS for this excellent library. Last I looked,
VGAKIT50.ZIP was the current version.

The 320x400 VGA mode was made possible by Michael Abrash's
graphics articles in Programmer's Journal. Since the sad demise
of P.J., Mr. Abrash's articles can be found in another excellent
tech magazine, Dr. Dobb's Journal.

Definicon Corp very kindly released a public-domain program
called SAMPLE.C which shows how to access the 64k banks used by
extended VGA 256-color modes. This was the key to the GN5xxx
routines (the so-called "tech ref" section of my Boca SuperVGA
manual referred me to IBM's VGA docs, which would be utterly
useless in accessing these modes).

 December 22, 2017  Add comments

Leave a Reply