Volume 1, Number 3 May 22, 1990 ************************************************** * * * QBNews * * * * International QuickBASIC Electronic * * Newsleter * * * * Dedicated to promoting QuickBASIC around * * the world * * * ************************************************** The QBNews is an electronic newsletter published by Clearware Computing. It can be freely distributed providing NO CHARGE is charged for distribution. The QBNews is copyrighted in full by Clearware Computing. The authors hold the copyright to their individual articles. All program code appearing in QBNews is released into the public domain. You may do what you wish with the code except copyright it. QBNews must be distributed whole and unmodified. You can write The QBNews at: The QBNews P.O. Box 507 Sandy Hook, CT 06482 Copyright (c) 1989 by Clearware Computing. The QBNews Page i Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- T A B L E O F C O N T E N T S 1. From the Editors Desk From the Editor .............................................. 1 2. Mail Bag QBNews in Europe ............................................. 2 3. Beginners Corner BASIC Menuing and Graphics by Ryan Snodgrass ................. 4 4. Who ya gonna call? CALL INTERRUPT Directory Swapping by Hector Plasmic ......................... 6 5. The Tool Shed P-Screen+ and P-Screen Professional by David Cleary .......... 8 Index Manager - B-Tree indexing for QB by David Cleary ....... 10 6. Product Announcements P-Screen, P-Screen Professional, P~F Presents ................ 12 7. Under The Hood Fast File I/O in QuickBASIC by Ethan Winer ................... 16 8. Power Programming How to Make a Self-Cloning Exe in QuickBASIC by Larry Stone .. 19 Programming UEVENT by Jim Mack ............................... 23 9. Some Assembly Required Assembler Programming for QuickBASIC by Tom Hanlin ........... 26 10. And I Heard it Through the Grapevine Exerpts from the QUIK_BAS echo ............................... 29 11. Swap Shop Screen Scrolling with Call Interrupt ........................ 33 Getting the Day of the Week with Call Interrupt ............. 35 Yes/No Response DEF FN ...................................... 37 A Replacement for INPUT with MUCH MORE programmer control. .. 39 Windowing Routines with Shading ............................. 41 12. Input Past End Get the QBNews on Disk ...................................... 42 Contacting the QBNews ....................................... 43 The QBNews Page ii Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- F r o m t h e E d i t o r s D e s k ---------------------------------------------------------------------- From the Editor Welcome to the third issue of The QBNews. I know it has been a while coming but I hope it was worth the wait. There are some big changes in store for the news. I have realized that putting out an issue every 2 months is impractical. Therefore, The QBNews will be published quarterly. Expect issues around these dates: May 30 August 30 November 30 February 28 I have also received alot of requests for the news on disk. You can now receive the QBNews on disk. Information on this service is available in the back of this issue. The QBNews is distributed through SDS on Fidonet. It seems that SDS is not as far reaching as I had thought. In order that everybody knows that the can get the news reasonably close to them, I would like to set up distribution hubs around the country. I am looking for BBS's in these areas who would like to be QBNews hubs: Baltimore/Washington, Charlotte NC, Atlanta, Orlando, Memphis, New Orleans, Chicago, Cincinnati, Minneapolis, Kansas City MO, Dallas, Denver, Phoenix, Los Angeles, San Francisco, and Portland. I do require a few things in order to be a hub. First, first time callers must be granted limited download privledges. Second, you must accept file requests from Point systems. Last, you must be willing to send the news down the line to the next hub. If you meet these conditions and are interested in being a hub, write me. I want to hear from you. After the first issue, I received quite a bit of feedback. I wasn't too happy with that issue. I thought the second issue was really good. I didn't receive as much feedback on it as the first though. It was kind of disappointing. Let me hear your complaints, suggestions, coding tips, or whatever. It always helps to know people are interested. Thanks and enjoy. David Cleary The QBNews Page 1 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- M a i l B a g ---------------------------------------------------------------------- Hello David, Today I've picked up 2 QBNews files and I've read them both. Most interesting stuff! Just the electronic magazine I need! Coming from the TRS80 Model I computer on which I've learned basic and wrote my own BBS and videotext systems in basic (the system is still online) I've entered the PC community and started with RBBS-PC which is, as you would know, almost completely written in QuickBasic. I've purchased the package a few years ago and developed a few administrative applications in it for a friend his firm. At the moment, due to lack of time, I'm only do a little debugging and modifying RBBS-PC. I recently discovered a bug (well, I think it is a bug) which I will give you here: DEFINT A-Z :' so every variable is by default integer Single precision! = 3600 * Hour The maximum value of an integer is 32767 and when you expect "Hour" can have a maximum value of 24, then the receiving variable must be a single precision variable 'cause by example 20 * 3600 = 72000. So the construction above looks good but isn't. QuickBasic will evaluate "3600 * Hour" as an integer expression 'cause both variables are integers. The result will be an overflow error. I've tested the same construction on my TRS80 and on the PC in GWBASIC and they both work fine!! The solution for this problem is very simple. The technique is called typecasting. Single precision! = 3600.0 * Hour :' 3600.0 is of type single precision When you enter this in the QB environment, QB will change 3600.0 in 3600! which is the same. Some undocumented META statements, nice to know: ' $LINESIZE:132 ' $PAGE ' $TITLE: 'put the main title here' ' $SUBTITLE: 'put the sub-title here' Anyway, I hope this is some contribution to the magazine. From now on I will put the QBNews files in my two BBS systems and will upload them to some other systems here in Holland. I hope more QBNews will come from the other side of the ocean. Please respond through Fidonet if you receive this message. Where I living, well, in a city called Amsterdam, that's in Holland or The Netherlands and that is in Europe ('cause many Americans don't know what's on the other side of the ocean ;-) The QBNews Page 2 Volume 1, Number 3 May 22, 1990 Cu, Kim Kodde (2:500/41.1433) Holland Hello David, A very good initiative! I give my full support for whit it's worth. I am not a professional programmer and I have no education in this area, but I have been programming in BASIC for some years for fun and during that time gone from an VIC64 to an AMSTRAD 6128 to an Artech 286/AT (and IBM ps 20). I started out with QB 2.00 a couple of years ago but recently upgraded to 4.50. It is a marvelous difference. I have also included an extract from a program I have written for my work. I am a journalist and the program is used to store things we are to check or cover in the future. Certain things come back regularly on certain weekdays no matter which date it is. I therefore needed a simple routine to check which weekday a certain date is so when the agenda for that date is printed out the correct items depending on the weekday is included. I found my answer in interrupt 33, function 2a00. The low part of register AX returns a number for the weekday. 0 for Sunday to 6 for Saturday. This is very good for the future. If you are interested in checking which day you were born for example you need something else because the computer only covers dates from the early 80-ies. I have seen programs in genealogy- packages which covers this part. Johan Lindgren Sundsvall, Sweden I would be interested in hearing from you. Please write with comments, suggestions, complaints, or whatever you feel like talking about. Send it to: The QBNews P.O. Box 507 Sandy Hook, CT 06482 The QBNews Page 3 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- B e g i n n e r s C o r n e r ---------------------------------------------------------------------- BASIC Menuing and Graphics by Ryan Snodgrass This section of QB News is dedicated to the readers who are novices to Quick BASIC. In this issue we will talk about creating and editing simple menu programs and about some of the elementary commands used in making computerized graphics. The first thing we talk about is making simple menus. The first step you take in making a menu is to plan what selections you will have. In our example we will use the following selections: DIR, make the computer beep, and quit. The next step is to plan whether to use numbers, letters, or both. In our example we will use both numbers and letters. The following is an example program; you may edit it as much as you like. Menu: PRINT "(1) - DIR" 'Shows the first selection (DIR) PRINT "(2) - BEEP" 'Shows the second selection (BEEP) PRINT "(Q) - QUIT" 'Shows the third selection (QUIT) MenuInput: A$=INPUT$(1) 'Waits for the input from the keyboard IF A$="1" THEN GOTO Selection1 'See if one was pressed IF A$="2" THEN GOTO Selection2 'See if two was pressed IF A$="Q" OR A$="q" THEN CLS:END 'See if Q was pressed GOTO MenuInput 'Go back and wait for another key Selection1: SHELL"DIR" 'Do a DIR of the current directory GOTO Menu 'Redisplay the menu Selection2: BEEP 'Produce a beep GOTO Menu 'Redisplay the menu -+* To edit selections on this menu do the following: *+- 1) Change the menu listing by replacing one of the selections by another (i.e. PRINT "(1) - DIR" replace with the following: PRINT "(1) - DIR/P") 2) Change the selection commands (i.e. change the Selection1 commands to: Selection2: SHELL"DIR/P" GOTO Menu -+* To add selections to the menu do the following: *+- 1) After the PRINT "(2) - BEEP" add PRINT "(3) - Your Selection" 2) After IF A$="2" THEN... add IF A$="3" THEN GOTO Selection3 3) After the GOTO Menu on Selection2 add: Selection3: The QBNews Page 4 Volume 1, Number 3 May 22, 1990 'Your command GOTO Menu The next subject we will discuss is elementary commands for creating graphics screens. A graphics screen is represented by a number of pixels or points on the screen, which is referred to as the resolution of your screen. The first step we must do is select a screen number which corresponds to your resolution or any other resolution you can use. The following is a list of screen number and their attributes: +-----------+------------------+-------------+------------------+ |Screen #: | # of colors: | Resolution: | Width of Screen:| +-----------+------------------+-------------+------------------+ | 1 (CGA) | 4 out of 16 | 320x200 | 40 Characters | | 2 (CGA) | 2 out of 2 | 640x200 | 80 Characters | | 7 (EGA) | 16 out of 64 | 320x200 | 40 Characters | | 8 (EGA) | 16 out of 64 | 640x200 | 80 Characters | | 9 (EGA) | 16 out of 64 | 640x350 | 80 Characters | | 10 (MONO) | 9 grays | 640x350 | 80 Characters | | 11 (VGA) | 2 out of 2 | 640x480 | 80 Characters | | 12 (VGA) | 16 out of 256000| 640x480 | 80 Characters | +-----------+------------------+-------------+------------------+ The next step is to type: SCREEN (and then the type of graphics you want). The first and easiest command is LINE. The usage is LINE (X,Y)-(X,Y),Color (i.e. LINE (100,100)-(200,200),1 would draw a line from 100,100 to 200,200 using the color 1). Using that in an example program would be as follows: SCREEN 1 LINE(100,100)-(200,200),1 END You can also draw an open rectangle by using LINE (X,Y)- (X,Y),Color,B or a solid rectangle by using LINE (X,Y)-(X,Y),Color,BF. The next command we will go into is the DRAW command. The usage is DRAW "(Attributes)" (i.e. DRAW "U4 R4 D4L4"). The attributes are as follows: C#=Color Number, U#=Up a number of pixels, D#=Down a number of pixels, L#=Left a number of pixels, R#=Right a number of pixels, E#=Up and to the right a number of pixels, F#=Down and to the right a number of pixels, G#=Down and left a number of pixels, H#=Up and left a number of pixels, M X,Y=Moves to a certain pixel (i.e. DRAW "M 100,100"). Our example program draws a 3-D box: SCREEN 1 DRAW "M 100,100 C1 U10 R10 D10 L10 H10 U10 F10 R10 H10 L10" END If you have any suggestions as to what you would like to see in this column, please send them to the QBNews at the address in the back. The QBNews Page 5 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- W h o y a g o n n a c a l l ? C A L L I N T E R R U P T ---------------------------------------------------------------------- Directory Swapping by Hector Plasmic You've all seen professional programs that let you Shell to DOS and always return you to the proper drive and subdirectory when you Exit. It's not too hard to implement something similar for your own QuickBASIC programs. QB doesn't have all the functions you need to do it, but DOS can lend a hand via Interrupt. What we need to do is handle all SHELL calls from a common point (Sub). In this Sub, we'll determine the current drive and subdirectory, perform the SHELL, then restore the old drive and subdirectory before Exitting the Sub. Interrupt 21h function 19h returns the number of the current disk drive in register .al as a number (0=A, 1=B, etc.). Interrupt 21h function 47h copies the current directory of the drive # in .dl (0=default, 1=A, 2=B, etc.) as an ASCIIZ (null terminated) string into a buffer pointed to by .ds:.si. The pathname does not include the drive identifier or a leading backslash. An error can occur if you use an invalid drive specification; this is not likely since we are using the default drive, but if you are using this function to attempt to read the default directory on a drive that may not exist, check the carry flag (IF OutReg.FLAGS AND 1 THEN...oops!). .ax will contain 0Fh (invalid drive spec) if the error occurs. Finally, Interrupt 21h function 0Eh sets the default drive to the drive number passed in register .dx (0=A, 1=B, etc.).This completes the list of DOS functions we'll need to perform our switching. What follows is some sample code to show a practical application of these interrupt functions: DEFINT A-Z TYPE RegType2 AX AS INTEGER BX AS INTEGER CX AS INTEGER DX AS INTEGER BP AS INTEGER SI AS INTEGER DI AS INTEGER Flags AS INTEGER DS AS INTEGER ES AS INTEGER END TYPE 'You must link with QB.LIB (QB.QLB) to use Interrupt functions DECLARE SUB InterruptX (Intr%, InReg AS RegType2, OutReg AS RegType2) The QBNews Page 6 Volume 1, Number 3 May 22, 1990 DECLARE SUB DoShell (Before$, Args$, After$) DoShell "Type EXIT to return", "", "Welcome back" 'Just to test it END SUB DoShell (Before$, Args$, After$)'Declare some stuff to use DIM InReg AS RegType2 DIM OutReg AS RegType2 DIM CurrentDrive AS INTEGER DIM CurrentDir AS STRING * 64 'Get current disk drive InReg.AX = &H19 * 256 InterruptX &H21, InReg, OutReg CurrentDrive = OutReg.AX MOD 256 'Get current directory InReg.AX = &H47 * 256 InReg.DX = CurrentDrive + 1 'Note adding one to drive for this, or 'could use 0 for default drive InReg.DS = VARSEG(CurrentDir) InReg.SI = VARPTR(CurrentDir) InterruptX &H21, InReg, OutReg 'Do the shell IF Before$ <> "" THEN CLS : PRINT Before$ 'Optional SHELL Args$ IF After$ <> "" THEN CLS : PRINT After$ 'Optional 'Change to old disk drive InReg.AX = &HE * 256 InReg.DX = CurrentDrive InterruptX &H21, InReg, OutReg '(InReg.AX MOD 256 is the # of logical drives in the 'system if anyone is interested) 'Change to old subdirectory (Could use Int &H21 func &H3B instead) ToDir$ = CHR$(ASC("A") + CurrentDrive) + ":\" + LEFT$(CurrentDir,_ INSTR(CurrentDir, CHR$(0))) CHDIR ToDir$ END SUB The QBNews Page 7 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- T h e T o o l S h e d ---------------------------------------------------------------------- P-Screen+ and P-Screen Professional by David Cleary *** IMPORTANT *** It took me a while to write this review. As you read it, you will see I have two major complaints concerning these programs. They are lack of mouse support and an easier user interface. These problems have been corrected in the latest release. Please see the product announcement for the new version in this issue. A program's user interface can be the difference between success and failure. You could have the best program in the world but if people don't like using it, it won't succeed. The user interface is also the part of a program I hate doing the most. I know what I like in a user interface but I have a hard time translating it into my programs. I also find the coding of user interfaces very tedious. So, in this issue, I will review a tool that helps you design user interfaces quickly and easily. The product is called P-Screen and is from Pro~Formance. P- Screen+ is a shareware program while P-Screen Professional is commercial. This program combines a screen drawing utility, a screen storage and display utility, and the Professional version adds a QuickBASIC 3 and 4 code generator and a variety of user input routines. These programs allows you to create screens and forms, store them in libraries, and access them from your QuickBASIC programs. We will examine these features individually. As a screen generator, I feel that P-Screen has one major flaw. It lacks mouse support. Personally, I like mice and I don't like having to navigate the screen with the cursor keys. When you start up P-Screen Professional, you get a nice title screen that tells you your options and waits for you to press a key. After you press a key, you are left with a blank screen and a "What do I do now?" look on your face. Pressing F1 for help shows you 2 screens of commands but that is all. To learn how to use P-Screen, you will need a copy of the printed documentation next to you. After you start using the commands and learn what they do, P- Screen is a pleasure to use. It allows you to draw your screens and store them in either ASCII or a compressed format. P-Screen also allows you to load screens saved in QB's BSAVE format but won't let you save them that way. If P-Screen had mouse support and better on line help, it would be one of the best screen designers available. Without it though, it still is very good. Next comes the screen storage and display utility. P-Screen stores screens on disk in a compressed screen format. The savings in size is very noticeable the more screens you have in a library. P- Screen then has two routines that load the screens into an array and display them. They are written in Assembler and are very fast. These routines allow you to display full or partial screens anywhere you The QBNews Page 8 Volume 1, Number 3 May 22, 1990 want. I couldn't find anything to complain about. They are nicely done and will add a professional look to your programs. Now to what I think is the best part of P-Screen. The Professional version comes with a form generator. P-Screen Professional comes with the ability to create forms and it generates code that handles user input on these forms. See the program Index.Bas for an example of the code P-Screen Professional generated for a small index card screen I created. The user input routine is nicely done in Assembler and includes a variety of formatting options and masks. Some of these include Date, Phone Number, Zip Code, Social Sec. Number, and Currency. With these options, your program makes sure that the user types in only valid keys and in the format you want. The code that P- Screen Professional generates is then inserted into your program. This allows you to create professional looking documents with very little effort. I am very impressed with these capabilities in P-Screen Professional. Two shareware programs you should be aware of are P-Screen and P~F Presents. P-Screen is the screen drawing and library utility portion of P-Screen Professional. Gone are the code generator and form utilities and the ability to display partial screens. My advice to you is to get the shareware version and try out the screen generator. If you like using it to create your screens, then you should certainly buy the professional version. You won't be disappointed. P~F Presents is a screen presentation system. It lets you take screens created with P-Screen and create slide shows out of them. This is great for presentations or prototyping applications before you get down to writing code. With it, you can create your whole user interface and let the users give you there comments on it BEFORE you write any code. This saves a lot of headaches from having to rewrite your programs when your views on how the program should operate are different from the intended users. P-Screen Professional is available from: Pro~Formance 132 Alpine Terrace San Francisco, CA 94117 (415) 863-0530 The cost is $49 plus $3 shipping and handling. You also can download the shareware version of P-Screen and P~F Presents from Peter Tiffany's BBS, (415) 458-6404 or on CompuServe in the IBMPro and IBMApp areas. The filenames are PSCRN35.ZIP and PFPRES35.ZIP. The QBNews Page 9 Volume 1, Number 3 May 22, 1990 Index Manager - B-Tree indexing for QB by David Cleary Databases are an important part of computers and computer programming. QuickBASIC has built in capabilities that make it easy to write a simple database. The problem is, as your database gets larger and larger, it takes longer to find the information you need. That is where Index Manager helps out. Index Manager allows you to build single user ISAM (Indexed Sequential Access Method) databases using a B+ Tree index. A B+ Tree is a data structure that allows you to find information quickly without searching something from start to finish. All Index Manager does is handle the index of your database allowing you to handle your datafile as you please. All you do is to associate a unique "Key" with each record and Index Manager does the rest. This key could be a name or customer number or what have you. The only thing you have to be sure of is that each record in your database has it's own unique key. The specifics of Index Manager are as follows: 1. Create an indexed-access file using the key of your choice. 2. Read any record on you indexed-access files by specifying it's key. 3. Browse through your indexed-access file by specifying a partial key. 4. Read your indexed-access file sequentially sorted by key either forward or backward. 5. Work with up to ten indexed-access files at the same time. All of Index Manager's functions are incorporated into one call making it very easy to use. It is also written in assembly language making it extremely fast and small. It only adds 5k of code size to your QB programs. It utilizes a large cache buffer to cut down on disk accesses when searching for a key. This makes it very fast but also introduces one of it's drawbacks. It uses 24k of string space for it's buffers. This makes string space kind of tight on large applications. I would like to see some sort of variable buffer allocation so you could reclaim the string space while sacrificing some speed. Although Index Manager is very easy to use and comes with examples showing how to use every function, it doesn't teach you database principles. If you know nothing of how to set up good databases, you could find yourself making errors that could cause your program to not be as useful as it can. The example programs show you how the functions work but they are not real world applications. I would like to see some examples of things like a small phone directory, ect. that will help someone not familiar with database programming go in the right direction. Aside from the string space problem and the lack of good database examples, I love this program. I received my copy of Index Manager The QBNews Page 10 Volume 1, Number 3 May 22, 1990 free for the purpose of doing this review. At the time, I had never written a database program before. As luck would have it, a database programming application had just come up where I work. I decided that I would give Index Manager a try in helping me with this project. The project turned out so well that my company decided to pay for this copy. Index Manager is for those people who just want to add fast indexing to their QB programs. It is an alternative to going out and spending more money on products like DB-Lib or B-Trieve when you don't need all the functions of these products. Index Manager is also alot easier to use than those products and allows you to handle your datafiles anyway you choose. Index Manager costs $59 and is compatible with QB 2 to 4.5 and Bascom 6. A Basic 7 PDS version that supports far strings is in the works. You can get Index Manager by contacting: CDP Consultants 1700 Circo del Cielo Drive El Cajon, CA 90202 619-440-6482 The QBNews Page 11 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- P r o d u c t A n n o u n c e m e n t s ---------------------------------------------------------------------- P-Screen, P-Screen Professional, P~F Presents Rob W. Smetana (415) 863 - 0530 Pro~Formance 132 Alpine Terrace San Francisco Ca 94117 Program Name Cost Summary --------------------- ---- ----------------------------------- P-Screen "Plus" $ 29 Screen Design, Display, Library system for QuickBASIC 3.0 - 4.x. P-Screen "Professional" $ 49 PS Pro writes programs for you! and includes other CALLable routines. P~F Presents $ 49/$ 79 Presentation system to display P-Screen's "text" screens -or- BSAVEd "graphics." See below for many "uses" for presentations. P-Screen "Pro" AND Enhanced versions of BOTH. P~F Presents ($79 ver.) $110 NOTE: P-Screen and P~F Presents are "shareware." Registered versions cost 1/2 to 2/3 LESS than commercial examples. Yet they're finely tuned, professional quality programs that'll save you enormous amounts of time, and give your programs a professional appearance -- f-a-s-t, easily. P-Screen and P-Screen Professional are among the most sophisti- cated "screen management systems" available. They were written BY Quick- BASIC programmers FOR QuickBASIC programmers (we support QB 3.0-4.x). P-Screen "+" and P-Screen Pro share most features. These are described immediately below. Later we'll explain features that PS Pro adds. DESIGNING help screens, menus, data entry screens, etc. is a snap. * Use a mouse or fast keyboard "hot keys" (eg., alt-B = Box). * Choose options using hot keys or pull down menus. * Many design options: Boxes, lines, "auto-joining" of lines and boxes, text, Big! Font text (tm), clipboard, paint, center, copy, move, erase, WalkAbout, re-color, repeat, add any Ascii character, view in Monochrome, UnDo, and much more. LOAD screens for editing from Screen Libraries, ASCII or BSAVE files. * A RAM-resident program is included letting you: 1) "Capture" screens from other applications (to later load and edit); and 2) Save ANY text screen in BSAVE format when you need them. SAVE screens to Screen Libraries, ASCII or "Com" files. * Screens saved to libraries or Com files are COMPRESSED, saving disk space and RAM. Libraries store 1-100 screens; libraries "index" screens, letting us display or edit them fast. The QBNews Page 12 Volume 1, Number 3 May 22, 1990 * Com screens are "executable!" Run them from batch files or DOS for instant screens -- complete with color, lines, shading, etc. DISPLAY screens from your programs fast and easily -- just CALL two routines we include. OTHER FEATURES * Blazing speed! Assembler language routines plus "indexed" screen libraries mean your programs display screens f-a-s-t! 10-20 screens per second is typical. We've seen 75 per second! - We've eliminated the disadvantages of storing screens on disk. - Screen libraries let you keep "screen text" OUT of your programs, preserving string space and memory. And you can edit screens WITHOUT changing your programs. - And there are NO RAM-resident screen loaders to worry about! * Big! Font (tm) lets you easily add large-character messages. We include several Big! Fonts. You can create your own Big! Fonts, or customize them "on the fly." * Supports 25, 43 or 50 line screen modes. ================================================================= P-Screen Professional ($ 49) ================================================================= P-Screen Professional (PS Pro) has all the features of P-Screen described above, but also saves you enormous amounts of time by: * Writing your QuickBASIC 4.x programs for you! * Including several other subprograms you can use in ANY Quick- BASIC 4.x program you write (most also work with QB 3.0). In short, you focus on what your programs "look like." Once you have your screen designed, PS Pro can write your BASIC code. Just add any routines you need for printing or database manage- ment. Then compile your programs and away you go. PS Pro writes your data entry programs for you! Just "mark" a "field" on your screen and tell PS Pro what "type" of field it is. * You can create fields with are "editable," and fields which are "calculated." For calculated fields, enter ANY Quick- BASIC formula, and PS Pro will handle the calculations for you and print their results. * And you can link "help screens" to each field if you like. * Once you've "formatted" each field on your screen just press a key. PS Pro writes your program in about 2-3 seconds! PS Pro lets you choose from among 13 field types. * Choose a field type and PS Pro formats editing, printing, The QBNews Page 13 Volume 1, Number 3 May 22, 1990 and displaying the results of "calculated fields" for you. * Choose field types from among: string, upper case, proper name, date, phone number, Zip Code, Social Security number, numeric string, integer, long integer, single precision, currency and double precision. PS Pro comes with several subroutines you can use in any program. These include: rsMinput, OneKey, Mask/StripMask, FormatUsing, ProperName, rsQprint, Exists, and StripTail. Use them to: * Handle ALL user input (text, extended keys, printer codes). Get a single key, or a full line of text. rsMinput offers the full array of editing features, includes UnDo, AND offers "masked" input for easy, accurate editing of fields like Phone Numbers [(...) ...-....] or Zip Codes [.....-....]. * Format text and numbers for easy editing or printing * Print with assembler speed * Convert all lower case text to "proper name" format * Determine if files exist (before you try to open them) ================================================================= Product Announcement: P~F PRESENTS ================================================================= P~F Presents (PFP) is our desktop presentation system. It dis- plays screens from P-Screen's screen libraries. It also displays BSAVED "graphics" screens. Text, graphics or both; it's your call. Presentations (or slide shows) can include, among other things: * Full- or Sub- screen displays. * Menus, in 3 different styles. * Display and sound effects. * Special options including: "If x Goto," "Gosub", "Loop," "Pause," "MakeMono" (to display colored screens on monochrome monitors). * Timed slides (which pause as long as you want between slides), -or- slides which wait for the viewer to press a key. And you can create self-running presentations, or run 'em yourself. Why would a programmer be interested in a presentation system? For many of the same reasons programmers like Dan Bricklin's DEMO program (at about 1/4 the cost)! Plus, P~F Presents offers many other opportunities as well. Consider these: PROGRAM PROTOTYPES * You can create "working prototypes" of programs in minutes or just a few hours. * Since presentations can include menus (3 types), display effects, sound effects, etc. your prototypes can have the "look and feel" of your actual program. The QBNews Page 14 Volume 1, Number 3 May 22, 1990 * Letting your clients, customers or potential users see how your program will look and feel (BEFORE you've written a line of code): 1) Helps you "beta test" your ideas; 2) Helps eliminate time- consuming re-writes; and, 3) Speeds up program development. * And creating "mock ups" helps you think through a program's logic before you write any code. PROGRAM: DEMOS, MARKETING TOOLS, TUTORIALS * You can easily and quickly create demos, marketing tools or program tutorials -- using many of the SAME SCREENS your programs will display. As an example, we recently used PFP to create, in 2 hours, a working prototype of a new program. We had it back to a prospective client the day after we first talked to him. It had the complete "look & feel" of the program he wanted. And creating it helped us think through our program design. The QBNews Page 15 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- U n d e r T h e H o o d ---------------------------------------------------------------------- Fast File I/O in QuickBASIC by Ethan Winer [EDITOR'S NOTE] This article first appeared in the March 1990 issue of Programmer's Journal. Back issues can be ordered by calling 1-800-234-0386. Without doubt, one of the slowest operations a program can perform is saving and loading disk data files. In many cases, this is dictated by the physical access delay of the disk device, and the time required to transfer data based on its rotation speed. One exception, however, is when many reads or writes must be performed on small pieces of data. For example, it is quite common to save or load an entire numeric array. In the case of an integer array that contains, say, ten thousand elements, that many individual calls to the DOS file I/O services will be needed. Even though DOS is written in assembly language, it still takes a fair amount of time to process each read or write request. One obvious solution is to process the file operation as a single large read or write. Indeed, I have written assembly language routines to do just that for use in company's QuickPak Professional add-on library product. But it is also possible to call QuickBASIC's internal PUT and GET routines directly. By bypassing the QuickBASIC compiler and its syntax checking, you can coerce it to read and write up to 64K of data in a single operation. Larger files can be accommodated by processing the file in pieces. The trick is to determine the names of these routines, and the number and type of parameters they expect to receive. QuickBASIC versions 4.0 and later contain four different internal routines for reading and writing binary data. Two of these are meant for reading data from a file, with one using the current DOS SEEK location and the other accepting a long integer SEEK argument. Similarly, there are two separate routines for writing data to disk. Most of QuickBASIC's internal routines begin with the characters "B$", which are illegal in a subroutine name. Fortunately, the ALIAS keyword allows you to declare a procedure with two different names -- the name you will use when calling it from the program, and the actual name that is made public for the linker. When Microsoft introduced inter-language calling capabilities in QuickBASIC version 4.00, it needed a way to allow access to routines written in C. These routines always start with an underscore character, which is also illegal as a QuickBASIC procedure name. The example program shown in Figure 1 declares the four internal routines as follows: BigSave writes data using the current DOS file pointer position, and BigSaveS expects a SEEK argument. Likewise, BigLoad reads from the current file position, and BigLoadS requires an offset to SEEK to before reading. All four of these routines require the parameters to be passed "by value", as opposed to "by address" The QBNews Page 16 Volume 1, Number 3 May 22, 1990 which is BASIC's usual method of passing parameters. This results in code that is both faster and smaller, because an extra level of indirection is avoided. That is, the routines can obtain the values directly from the stack, rather than having to first determine an address, and then go to that address for the actual value. Even though BYVAL and SEG *look* like they would result in additional code being added to a program, they are really just directives to the compiler. Before any of these routines may be called, you must open the file to be read or written for BINARY operation. Then, the first parameter that each routine expects is the BASIC file number that was used to open the file. The address parameter is passed as a SEG value, which means that both a segment and offset are required. Notice that a file may be loaded to or saved from any area of memory, by replacing [SEG Address] with [BYVAL Segment, BYVAL Address]. When SEG is used as part of a CALL statement, the "value" of the variable's segment is pushed on the stack, followed by the value of its address. Substituting two separate arguments "by value" is functionally the same thing as far as the routines are concerned. Also notice that the internal routine names are not available within the QuickBASIC editing environment. Therefore, this example program must be compiled to disk before it may be tested. In my own informal tests, I have found this technique to be as much as ten times faster than reading or writing individual array elements using a BASIC FOR/NEXT loop. The actual savings will of course depend on the number of elements being processed and their length in bytes. Unfortunately, this method cannot be used with QuickBASIC string arrays, because they are not kept in consecutive memory locations. However, numeric arrays may be accommodated, as well as any fixed-length or user-defined TYPE array. It is important to understand that when manipulating a fixed- length string array, the SEG operator must not be used. Whenever a fixed-length string or array element is used as an argument to an external routine, QuickBASIC first makes a copy of it into a regular string variable. Then, the address of the copy is passed instead. Since the address of a copy of an array element has no relevance to the address of the actual array, we must use a different approach. In fact, there are two possible solutions. One is to create a TYPE definition that is comprised solely of a fixed-length string portion. Although the example below assumes a string length of twenty characters, you would of course use whatever is appropriate for your program. TYPE FLen S AS STRING * 20 END TYPE DIM Array(1 TO 10000) AS FLen The second solution is to use a combination of BYVAL VARSEG and BYVAL VARPTR, to pass the segment and address of the starting array The QBNews Page 17 Volume 1, Number 3 May 22, 1990 element directly. When QuickBASIC sees VARSEG or VARPTR, it realizes that you do in fact want the actual segment and address of the specified array element. Thus, you would use the following syntax when calling BigSave to save a fixed-length string array: CALL BigSave(FileNumber, BYVAL VARSEG(Array(First)), BYVAL _ VARPTR(Array(First)), NumBytes) One final note concerns saving or loading more than 32767 bytes. QuickBASIC does not support unsigned integers, so you must instead use an equivalent negative value. This is quite easy to determine, by simply subtracting 65536 from the required number of bytes. It is a common trick to avoid negative numbers when calling assembly language routines by instead substituting a long integer number or variable. However, that will not work in this case, because two extra bytes will be pushed onto the stack by the use of BYVAL. Therefore, it is essential that you specify the correct type of parameters when calling these routines. ********************************************************************** Ethan Winer is the president of Crescent Software, and the author of QuickPak Professional and P.D.Q. He can be reached by calling Cresent Software at (203) 846-2500. ********************************************************************** [EDITOR'S NOTE] Source code for this article is contained in FASTFILE.ZIP. The QBNews Page 18 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- P o w e r P r o g r a m m i n g ---------------------------------------------------------------------- How to Make a Self-Cloning Exe in QuickBASIC by Larry Stone [EDITOR'S NOTE] All extended ASCII codes have been replaced in the following article. Have you ever had the need to create a program that holds a password away from prying eyes of others? Or, maybe you have discovered that writing shareware rewards your ego by making your name familiar to the PC world, but doesn't reward your pocket-book because most people will register their programs when they get around to it, which, often-times, is never. Or, maybe you just wish to write a program that holds it's configuration without having to create a configuration file. One of the easiest methods to accomplish the above listed tasks is to create an EXE file that "clones" information to itself. The trick is to create a "recognizable" area inside of the EXE itself that can be quickly read and modified. What makes this such an easy trick? Well, have you ever used Vernon D. Buerg's LIST utility to list your EXE's? If you do, you will notice that towards the end of the program, every string that you have defined within your program is CLEARLY VISIBLE! What you are viewing is the EXE's token definition area. When you compile and link your programs, a token is defined for every string used. For example, your program might have the code, Strike$ = "Strike any key". When you list the EXE, it may show something like, 0TStrike any key. The symbols 0T would be the programs marker to the definition, "Strike any key". Under no circumstance do you want to change this marker because really weird results could ensue. However, you can create a string that contains a marker that is exclusive to your use, i.e., Special$ = "<*!@#%>This is my special string" In the above example, <*!@#%> then becomes your special marker to the data that immediately follows. Then, all your program has to do is to look for your special marker and modify the next 25 characters as needed! CAUTION! Never, never, never try to reserve a data area for cloning by defining a string as: Special$ = "<*!@#%>" + SPACE$(25) Special$ = "<*!@#%> " Both of the above examples will *NOT* create the 25 character data area desired because BC will optimize the SPACE$(25) as a two byte token! Let's assume that we need to build a program that needs to hold a The QBNews Page 19 Volume 1, Number 3 May 22, 1990 user-defined, sixteen character password and a token that determines whether the EXE is shareware (limited in scope) or registered. Let's further say that when a registration is received by you, the author, you then mail that person a key which redefines the definition of a token so that features not available to the shareware user now become available. Let's further state that your program is going to hold configuration information within a 13 byte string. The first thing to do is to create a unique string inside of the program itself, as well as, two or three variables shared within the program. At the top of the program, do something like the following: DIM SHARED StoredData$, BytesToData& Now, if your program does not "clone" configuration information or passwords, then BytesToData& does not need to be SHARED. Rather, in this case, only the information itself (Shareware/registration key) needs to be shared. However, for this discussion, we're going for the entire pie. Next, someplace within your initialization subprogram (or in your main module), you should place code such as the following: SpotForKey$ = "<%*@#!>123456789012345678901234567890" Now, because your program needs to read itself, modify itself and, at the same time, hold part of the original string as your special marker, we need to do the following: tempKey$ = LEFT$(SpotForKey$, 6) In this way, no matter what we do to the following 30 character spaces, <%*@#!> will always remain our special marker that the program looks for. Let's build the routine that reads in this data. '--------------------- Get Special Data Routine --------------------- DIM SHARED StoredData$, BytesToData& SpotForKey$ = "<%*@#!>123456789012345678901234567890" tempKey$ = LEFT$(SpotForKey$, 6) 'Our special marker. 'The Bytes% variable is the number of bytes to read.If your program is 'less than 16000 bytes then this routine adjusts accordingly. Also, if 'a 16K byte "GET" cuts the marker field in two then you need to change 'it to another value, such as 15550. Bytes% = 16000 BytesToData& = 0 portion& = 1 countToKey% = 0 StoredDataLen% = 30 The QBNews Page 20 Volume 1, Number 3 May 22, 1990 OPEN MyProg$ FOR BINARY AS #1 FiSize& = LOF(1) 'Get the size of the file DO WHILE NOT EOF(1) IF Bytes% > FiSize& THEN Bytes% = FiSize& IF Bytes% + portion& > FiSize& THEN Bytes% = FiSize& - portion& A$ = INPUT$(Bytes%, 1) 'Read Bytes% number of characters. countToKey% = INSTR(A$, tempKey$) 'Look for our special marker. IF countToKey% THEN 'If we found it then process it. BytesToData& = portion& + countToKey% + 5 'Get past our marker. SEEK #1, BytesToData& 'Get the data from the file. StoredData$ = INPUT$(StoredDataLen%, 1) EXIT DO 'We found it so out of the DO LOOP END IF portion& = Bytes% + portion& 'Determine where the next SEEK is. 'If we're within 800 bytes of the end of the EXE then we are past the 'token definition area of the QB program. In this case, we're done. IF portion& >= FiSize& - 800 THEN EXIT DO 'Move pointer to the next 16000 byte block to read from the file. SEEK #1, portion& LOOP CLOSE #1 '--------------------- End Special Data Routine --------------------- Now, whenever our program needs to look for a password, a registration key value, or it's configuration information, it need only do the following: Password$ = LEFT$(StoredData$, 16) RegisValue = VAL(MID$(StoredData$, 17, 1)) IF RegisValue = 7 THEN PRINT "Shareware Edition" ELSE PRINT "Registered Edition" END IF ConfigData$ = MID$(StoredData$, 18) To prove this works, snip out the special routine and add the following statement at the end of the routine: PRINT StoredData$ Next, compile and link it, then run it. You will see the above string printed (you will also notice just how fast QB's INSTR function really is! - It's blazingly fast! - Couple this with BINARY access and you'll discover that load time is not appreciably degredated). Don't forget to define MyProg$ as something or you'll get a nasty error message! Okay, okay, so how do we write new information to our special The QBNews Page 21 Volume 1, Number 3 May 22, 1990 data area? Simple - just do the following: Password$ = "Lawrence Stone " RegisValue$ = "0" ConfigData$ = "Config Area 1" OPEN MyProg$ FOR BINARY AS #1 PUT #1, BytesToData&, Password$ 'To write the new password. RegisValue$ = "0" PUT #1, BytesToData& + 16, RegisValue$'To create a registered version. PUT #1, BytesToData& + 17, ConfigData$ 'To clone configuration data. CLOSE #1 Now, re-run your compiled program and have these values print to the monitor. Notice how easy it was to change the data? If you are going to have an external "key" program that "turns on" the registered version then it needs to simply read in the data using the same special marker that we created as a marker to search for. Also, you might wish to make another small program that converts your pre- defined password and configuration space to spaces. Otherwise, you need to run your program before you distribute it so that you can change the password (which is equal to "1234567890123456") to something like SPACE$(16). In other words, nullify the temporary string used by our program that forced BC to give us the data space we needed in the first place. Now, for demonstration purposes, we have used the default "7" for indicating that the program is shareware and "0" for registered. I would recommend that you reserve at least 8 character spaces for this field because then you can create unique codes for every key and every user. In this way, your program can look for the key in both itself and within the key program as well. This would also offer one more level of safeguards for you. One final word: Any casual hacker can use LIST to find your password if you leave it in it's native ASCII. You should consider a routine that converts the appearance of your data so that it looks like the rest of the binary code. Routines can be as simple as taking each character in the strings and adding 100 to their ASCII value for writing, then, subtracting 100 from their ASCII value for reading, to some truely cryptive procedure, depending on how sensitive you want the information contained therein to remain. ********************************************************************** Larry Stone is President of LSRGroup and is involved in writing software for marine and aquatic research. He can be reached at LSRGroup, P.O. Box 5715, Charleston, OR 97420, or in care of this newsletter. ********************************************************************** [EDITOR'S NOTE] The file CLONE.BAS contains a slightly modified version of Larry's code above. I have modified it to make it easier to add to your program. It is contained in CLONE.ZIP. The QBNews Page 22 Volume 1, Number 3 May 22, 1990 In Search of the Elusive UEVENT by Jim Mack QB allows you to trap a number of different "events", such as the TIMER tick, the arrival of a byte in a COM buffer, the press of a specific KEY, and so on. It does so by adding extra code after each statement (/V) or each line (/W) which checks the state of flags associated with enabled events. Special handlers deal with the actual interrupt and set these internal flags as appropriate. This is a three-stage process: first, an interrupt (an event) occurs and is handled quickly by the QB runtime, which sets the flag and variables associated with the event. This happens behind your program's back, as it were. Second, when the current line or statement completes, QB checks the flags to see if any active events (those for which you have executed "ON xxx GOSUB" and "xxx ON" commands) have occurred. Third, on discovering such a condition QB executes the GOSUB code you wrote to deal with it. The only area where UEVENT differs from events like KEY is in the first part of the first step. In defining a UEVENT, _you_ take responsibility for dealing with the interrupt, and for notifying the QB runtime that such an event has occurred. From that point on, the action is exactly the same. The difference between invoking a GOSUB via SetUEvent (or any trap) and calling it directly is that when you invoke it, it's executed only when QB gets around to it, and only if UEVENT ON is currently in effect. A side effect of this is that you may "lose" events if more than one occurs between occasions when QB checks its internal flags. This whole business of interrupts can be broken down in several ways: shared vs. exclusive vs. chained, or software vs. hardware, and so on. The code packages included here give examples of two common combinations. You can trigger a UEvent in QB with no interrupt at all, by just saying "CALL SetUEvent". In MASM, declaring SetUEvent as an EXTRN far procedure lets you do the same thing: CALL SetUEvent. In C, you'd declare "setuevent" as a void far external function and then reference "setuevent()" to cause your QB handler to be invoked. Simple... and practically useless by itself. You need to combine this with a software or hardware interrupt. >> "Software interrupts" are really misnamed: they have more in >> common with a subroutine call than with a hardware interrupt. >> Since they occur under direct program control, there's nothing >> unexpected or asynchronous about them. They do however use >> the same table of vectors that the hardware interrupts use. A small step up is the exclusive "true" software interrupt. This involves taking over an unused interrupt vector, writing a tiny MASM routine which intercepts INTs directed at this vector and performs a CALL SetUEvent. There's no reason to take this extra step unless you're working with a canned other-language program which must use a The QBNews Page 23 Volume 1, Number 3 May 22, 1990 pre-defined INT to access your code. If you're using DOS 3.x, this can be done in exactly the same manner as the "chained" software interrupt described below, since what you're chaining onto is a pre-defined Dismiss This Interrupt routine. >> A "vector" in this context is a memory location reserved by >> the computer as a pointer: it contains the address of a routine >> intended to service an interrupt. There are 255 such vectors in >> the PC, occupying the memory from 0000:0000 through 0000:03FF. >> Eight of these (sixteen in the AT) are reserved for use by the >> hardware Interrupt ReQuest lines, or IRQs. When an enabled >> interrupt occurs, the PC stops what it's doing and executes the >> routine whose address is stored in the appropriate vector. Next most complicated is the chained software interrupt. One example of an existing software interrupt is the BIOS disk service, which uses INT 13H. If you wanted your handler to be invoked whenever disk activity occurred, you'd chain onto this interrupt vector and monitor the registers using MASM. When an event of interest occurred, you'd "CALL SetUEvent" to notify QB. In any case, you'd pass the interrupt along to the original INT 13H handler. Closely related to this is the chained hardware interrupt. The setup is exactly the same: hook the interrupt vector, monitor the registers, etc. All other details are taken care of by an existing handler. The code in CHNEVENT.BAS is an example of a chained handler which will work for any hardware or software interrupt. The assumption is that you're just monitoring existing events (and sometimes activating SetUEvent), but not touching any hardware. In the example we monitor INT 9, the keyboard interrupt, but you can monitor almost any of the256 vectors by replacing "9" with the appropriate number. Try an experiment: replace the INKEY loop with a LINE INPUT statement. If you can explain what happens, you've grasped the essentials of QBevent handling. >> "Hooking" a vector means only that you store the address of your >> own service routine in the vector. To facilitate cleanup, it's >> usual to first retrieve and store the existing contents of the >> vector so that they can be replaced on exit. If you're "chaining" >> onto this vector, then you'll also use that original address when >> your routine is finished, by performing a JMP directly to it. >> Since this can happen to several routines in sequence, it's easy >> to see why it's known as chaining. The next step up in complexity (and it's a pretty big step) is the exclusive hardware interrupt. Here, you're responsible for all of the nitty-gritty of the PC hardware, in addition to any details associated with the hardware device. You must program the 8259A Programmable Interrupt Controller to allow interrupts on your IRQ level, then issue a command to clear the PIC when you service an interrupt. These must be done in MASM, as your QB event handler will not be executed in a timely fashion and so cannot be relied on to take care of these high-speed events. The code in EVENTHDW.ASM shows how to deal with an event occurring on an arbitrary IRQ line (determined at install time), but because we aren't dealing with a real device The QBNews Page 24 Volume 1, Number 3 May 22, 1990 here, the specific instructions for the interrupting hardware can only be hinted at. >> Each hardware IRQ line is intimately tied to a vector: in the >> case of the lower IRQs (0-7) the INT number (the vector number) is >> simply the IRQ number plus 8. That's why the KB interrupt, which >> uses IRQ 1, is vectored through INT 9. Slightly more complicated is the shared hardware interrupt. In order for two hardware devices to share an IRQ line, there must be away to determine which device requested service by interrupting. An example of sharing an interrupt might be COM1 and COM3, which both use IRQ4 and hence INT 0CH. When an interrupt occurs on IRQ4, the COM3 service routine gains control and examines a register in the UART it's responsible for to see if that UART caused the interrupt. If it didn't, control is passed to the COM1 service routine. I haven't included a specific example of adding a shared handler, but if you need one and can't figure it out from the code shown, you can contact me and I'll try to help. In addition to the above, whenever you take over an interrupt vector you must somehow put things back in order when your program terminates. At a minimum this means restoring the original contents of the vector; for hardware interrupts, you must also restore the 8259A Interrupt Mask Register bit to the state in which you found it. To make this process a bit more automatic, QB includes the B_OnExit routine. Any running BC program takes over a number of interrupt vectors for its own use (for example, BC math functions invoke INT 04H whenever an overflow occurs) which must be restored on any exit, normal or abnormal. BC and QB provide B_OnExit as an extension of this internal cleanup. You still must write the code to do the actual restoring of vectors, etc., but BC can call that code automatically on *any* exit, even an error crash, if you "register "your routine via B_OnExit. Each of the included code packages uses B_OnExit in this way. ********************************************************************** Jim Mack is a programmer specializing in real-time systems for the entertainment industry. He can be reached via CIS ID 76630,2012 on the MSSYS forum in the BASIC or MASM sections, or at Editing Services Co., PO Box 599, Plymouth MI 48170, (313) 459-4618 ********************************************************************** [EDITORS NOTE] For some reason, the program CHNEVENT.BAS will cause my computer to crash when run in the QB 4.5 enviroment. This is not the case when run in the QBX (BC7) enviroment. Caution is advised if you try to run this in the enviroment. Source code for this article is contained in UEVENT.ZIP. The QBNews Page 25 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- S o m e A s s e m b l y R e q u i r e d ---------------------------------------------------------------------- Assembler Programming for QuickBASIC by Tom Hanlin Perhaps you already know a little bit about programming in assembly language, or perhaps you've never given it much thought. After all, it's supposed to be frighteningly difficult to write assembler programs, and you've already got QuickBASIC anyway. Well, it's true that there's a lot of work involved in writing large programs in assembly language. If you keep to the small stuff, however, there's actually very little to it. One of the easiest and most rewarding uses for assembly language lies in writing routines that can be called from a higher-level language like QuickBASIC. This can give you entirely new capabilities or make your existing programs smaller and faster. By mixing the capabilities of a low-level language like assembler with a high-level language like QuickBASIC, you can gain a lot of flexibility when you need it, without sacrificing the ease of use of good ol' BASIC. Microsoft's documentation on mixed-language programming is rather daunting. It's not exactly clear and the examples never seem to cover quite what you had in mind. Once you understand a few simple rules, though, you'll see that adding assembler routines to your QuickBASIC programs can be readily accomplished. I'm going to assume you have some notion of how to program in both QuickBASIC and assembly language, since explaining an entire language would be a bit more than a single article could cover! With that in mind, let's take a look at the basic rules of writing assembly routines for QuickBASIC and follow that up with the code for a working routine. The first thing you need to know is which registers you can use. The answer is, "all of them." However, certain registers must be preserved for BASIC, so if you use them, you must restore their original values before returning to the main BASIC program. The registers that must be preserved are SI, DI, BP, and DS. You also need to preserve the stack (SS and SP) and direction flag. The direction flag must always be "forward" when you exit, so if you change it using the "STD" instruction, be sure to restore it using the "CLD" instruction. Believe it or not, that's most of what you need to know right there. The other important thing to know is how to pass parameters to or from the assembler routine. I'll keep it simple by assuming you use the standard convention, which is "pass by reference", rather than "pass by value", which has to be explicitly declared. What do I mean by "pass by reference?" I mean that, instead of getting the actual value of a parameter, your routine gets the address of that parameter and has to look up the value. It's useful to have the address of the parameter, since that means you can return a value The QBNews Page 26 Volume 1, Number 3 May 22, 1990 by storing it in the parameter's address. Integral numbers are stored simply. For integers, the address you are given points directly to the integer (a word). If you use long integers, the address points to the long integer (a doubleword). Strings are stored in a slightly more complex fashion. The address you are given points to a "string descriptor". This descriptor is composed of two words, with the first giving the length of the string and the second the address of the string. You are not allowed to change the length or address of the string in your assembler routine, although you may change the string itself. I won't go into single precision or double precision numbers, because they are rather tricky to handle in assembler. I won't go into arrays, TYPEd values, or fixed-length strings here either, to keep it reasonably brief. Perhaps in a later article... Parameters are passed on the stack, starting at offset 6 (six) for the -last- parameter and going up by two as you move towards the first parameter. Finally, you need to end your routine with a special "RET" opcode that will clean the parameters off the stack for QuickBASIC. The RET must have a number after it which is twice the number of parameters passed to the routine. Clear as mud? Well, perhaps the example routine will help show what I'm talking about. The DOSVER.ASM file contains the source code. To assemble it just type: ASM DOSVER; (where "ASM" is the name of your assembler: MASM, TASM, or OPTASM) Convert the resulting DOSVER.OBJ file to a library so you can easily use it both from the command line and QB environment: LIB DOSVER; (this creates DOSVER.LIB) LINK DOSVER/Q,,NUL,BQLB45; (this creates DOSVER.QLB) If you are using QuickBASIC 4.0, change the BQLB45 to BQLB40. If you are using QuickBASIC 4.0a or 4.0b, change it to BQLB41. You can now use the DOSVER routine from the QB environment by specifying the library name when you start QuickBASIC: QB /L DOSVER Use of the DECLARE statement is optional, but it will help catch any errors you might make when calling DOSVER. Use this: DECLARE SUB DOSVER(VERSION$, MAJORV%, MINORV%) Before calling the routine, you must set the VERSION$ string to at least four characters, since the routine is not allowed to change the length of the string. Call the routine like this: VERSION$ = SPACE$(4) CALL DOSVER(VERSION$, MAJORV%, MINORV%) Typical results will be "3.11" for VERSION$, 3 for MAJORV%, and 11 for MINORV%. The QBNews Page 27 Volume 1, Number 3 May 22, 1990 In later articles, if there is any interest in it, I'll explain how to handle arrays, TYPEd variables, and fixed-length strings, and also how to pass values back from functions rather than using subprograms. ********************************************************************** Tom Hanlin is the author of the very popular ADVBAS library for QuickBASIC. His new shareware library is called BASWIZ. He can be reached through the QUIK_BAS echo on Fidonet or in care of this newsletter. ********************************************************************** [EDITOR'S NOTE] The archive ASMREQ.ZIP contains the assembler source code plus an assembled .OBJ file for the routine contained in the article. The QBNews Page 28 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- A n d I H e a r d i t T h r o u g h t h e G r a p e v i n e ---------------------------------------------------------------------- From: Mike Sinak To: All Subj: PEEK & POKE I am just beginning to study DOS and BIOS calls and don't understand how to do it at all. Does anybody have any info on the PEEK and POKE usage? Like if you want to peek and poke to turn caps lock on and off, how in the world do you know what address to peek and poke? I'm lost! I would like to learn more about this stuff and get some kind of comprehensive list of peeks and pokes. Also I learned that at DEF SEG 0 you can turn on and off caps lock by POKE &H0417, PEEK &H0417 OR &H40 (to turn on) and POKE &H0417, PEEK &H0417 AND &HBF (to turn off) Now how in the world did they know that? The AND and OR statments are supposed to be turning on and off bit 6 of the &H0417 address. How did they know these particular numbers turn on and off bit 6? Better yet, how did they know that bit 6 had to be turned on and off at this particular address to turn caps lock on and off. Is there a pattern? Obviously my ignorance is being displayed. Go ahead and laugh but I would sure like to know more about this. ANY HELP? From: Robin Hunziker @ 965/1 To: Mike Sinak Subj: Re: PEEK & POKE Perhaps your best bet for figuring out how DOS operates is by purchasing the following book: Advanced MSDOS Programming 2nd Edition Ray Duncan Microsoft Press ISBN 1-55615-157-8 For example, on page 582 the book discusses exactly your area of question in easy-to-understand tabular format. It discusses how to call Int 16H Function 02H to "get keyboard flags". Although the front cover states that it is "The Microsoft guide for Assembly Language and C programmers", it is very relevant to much of the discussion in this echo. The QBNews Page 29 Volume 1, Number 3 May 22, 1990 From: David Martin To: Mike Sinak Subj: Peek & Poke A better way to set upper and lower case is the following: Lower Case: POKE 1047, PEEK(1047) AND 191 Upper Case: POKE 1047, PEEK(1047) OR 64 Lots of stuff can be done with poke such as disable the CTRL-BREAK, turn number lock key on and off, read hardware configurements, read size of RAM. The list goes on and on. If you want a list of the commands let me know. These commands must be preceded by: DEF SEG = 0. Let me know what kind of stuff you are working with so I can give you the commands. From: Tom Hanlin To: Mike Sinak Subj: Re: PEEK & POKE In order to understand bit numbering, you need to convert the number to binary. Bits are numbered from right to left, with the lowest bit being number zero. So: &H40 = 64 = 0100,0000b (note that bit 6 is turned on) &HBF =191 = 1011,1111b (note that all bits except 6 are turned on) when you OR a number with another number, the result contains all of the bits that were turned on in the first number OR in the second number. When you AND two numbers, the result contains all of the bits that were turned on in the first number AND in the second number. So, to turn on a specific bit, you OR with a number that has only that bit turned on (which leaves the other bits in the original number alone). To turn a specific bit off, you AND with a number that has only that bit turned off (which leaves the other bits in the original number alone). See? Knowing which memory locations have special purposes, such as keeping track of Caps Lock and Num Lock, comes from (mostly) IBM's listing of the system BIOS. Many reference books contain the information in a decoded format, so you can use it without having to understand the BIOS at all. If you can find a copy, COMPUTE!'s "Mapping the IBM PC and PCjr" is an excellent guide to this sort of thing, although there are many others. You may also want something like Norton's guide to assembly language, to give you some idea about converting numbers between hex, decimal and binary, and how PC memory mapping works. Things like... DEF SEG=0:PRINT PEEK(&H417) is the same as DEF SEG=&H40:PRINT PEEK(&H17) From: David Martin To: Mike Sinak Subj: Peek & Poke The QBNews Page 30 Volume 1, Number 3 May 22, 1990 The following list is some things that can be done with peek & poke. KEYBOARD KEYS: PEEK (1047) AND 8: value 8 if ALT is pressed. 4: CTRL 2: 2 LEFT SHIFT. 1: 1 RIGHT SHIFT. 3: 3 NIETHER SHIFT. PEEK (1048) AND 4: 4 PRINT SCREEN KEY. PEEK (1047) AND 64 0 if in LOWER CASE. 64 UPPER CASE. POKE 1047, PEEK(1047) AND 191 specify LOWER CASE. OR 64 specify UPPER CASE. FOR J=0 TO 3:POKE (108+J),PEEK(112+J):NEXT: DISABLE CTRL-BREAK CONFIGURATION OF COMPUTER: (PEEK(1041) AND 192)/64: number of PRINTER ADAPTERS. (PEEK(1040) AND 1)*(1+PEEK(1040)/64 number of DISK DRIVES. (PEEK(1041) AND 14)/2 number of RS232 ports. PEEK(1043)+256*PEEK(1044) size of RAM. NUMBER LOCK KEY: PEEK(1047) AND 32 0 if OFF, 32 if ON. POKE 1047,PEEK(1047) AND 223: turn NUM LOCK key OFF. 32: turn NUM LOCK key ON. Alot of these commands are abreviated. If nothing appears below a line just type in the line on top of what you want along with what is changed. Got it? Let me know if you have any questions. Glad to help! All lines must be preceded by: DEF SEG =0 (Only one DEF SEG = 0 at the beginning of the program) From: Ronny Ong To: Mike Sinak Subj: Re: PEEK & POKE Mike, I also have Duncan's book and find it useful, but I do happen to use a number of languages including assembler, C, and QuickBASIC. Let me suggest "DOS Programmer's Reference" (2nd Edition), by Terry Dettmann, Revised by Jim Kyle. It is published by Que ISBN 0-88022-458-4. List price is $27.95 U.S. This book contains roughly the same type of information as Duncan's, but it has specific QuickBASIC 4.xx examples, so it's probably easier to use for the QuickBASIC programmer with less experience in assembler and C. From: Ronny Ong To: Mike Sinak Subj: Re: Peek & Poke Hi, Mike. You might try getting a general intro/computers type of book and brushing up on binary numbers. All digital computers of today do everything in binary, or base 2. In others words, computers are simply made up of billions of tiny on/off switches. "On" represents the binary digit (or "bit" for short) of "1" and "off" represents "0." The QBNews Page 31 Volume 1, Number 3 May 22, 1990 When you see "POKE xxx, PEEK(xxx) AND 64," here's what happens (I've simplified this slightly): Let's say that PEEK(xxx) currently contains the byte value 98. In binary, that's 01100010. The decimal number 64 is 01000000 in binary. The "AND" operator means to produce a result which has a "1" in every position which is "1" in both of the operands and has a "0" for all other positions. 01100010 <--- 98 01000000 <--- 64 -------- 01000000 <--- Result of 64, to be POKE'd back into memory location xxx The end result is that all bits were turned "off" (set to 0) except that one bit which corresponded to 64. So, essentially, the AND 64 "stuff" you see is manipulating individual on/off switches inside the computer. A good book will walk you through conversion between binary and decimal. Also, you'll see that hexadecimal ("hex") numbers which you've probably come across, are shorthand forms of binary. Good luck! [EDITOR'S NOTE] The purpose of this conference is to discuss Microsoft QuickBASIC and related applications and utilities. SysOps looking for a Group Mail or EchoMail link into QUIK_BAS should contact the Alliance node known as 520/323, the FidoNet node known as 107/323, or the Good Egg Net Node known as 9230/323, or simply 1-201-247-8252 for information on local feeds. The QBNews Page 32 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- S w a p S h o p ---------------------------------------------------------------------- ***** Screen Scrolling with Call Interrupt DECLARE SUB scroll.ner (rader!, left!, up!, right!, down!) DECLARE SUB scroll.up (rader!, left!, up!, right!, down!) 'To demonstrate the use of interrupt 10H ah=06 'To scroll any part of the screen up or down 'QB must be invoked with the /l switch. In QB.BI the RegType is 'defined and the INTERRUPT-Call set up. 'By Johan Lindgren (BBS:+46 60115371) '$INCLUDE: 'QB.BI' DIM SHARED inregs AS RegType, outregs AS RegType '--------------------------------------------------------------------- ' Fill the screen with numbers to indicate where scrolling takes place '--------------------------------------------------------------------- FOR i = 1 TO 24 FOR j = 1 TO 40 PRINT USING "##"; i; NEXT j NEXT i '---------------------------------------------------------- ' Do one scroll up first. Give values and call the sub '---------------------------------------------------------- rader = 2: up = 5: left = 5: down = 10: right = 20 scroll.up rader, left, up, right, down '------------------------------------------------------------ ' Do a scroll down. Give values and call the sub '------------------------------------------------------------ rader = 3: up = 10: left = 35: down = 15: right = 70 scroll.ner rader, left, up, right, down 'END of demo. SUB scroll.ner (rader, left, up, right, down) '----------------------------------------------------------- The QBNews Page 33 Volume 1, Number 3 May 22, 1990 'First put up and down in right hexadecimal postion 'Then enter the values into the registers and call interrupt '------------------------------------------------------------ up = up * 256 down = down * 256 inregs.ax = &H700 + rader inregs.cx = up + left inregs.dx = down + right CALL INTERRUPT(16, inregs, outregs) END SUB SUB scroll.up (rader, left, up, right, down) '----------------------------------------------------------- 'First put up and down in right hexadecimal postion 'Then enter the values into the registers and call interrupt '------------------------------------------------------------ up = up * 256 down = down * 256 inregs.ax = &H600 + rader inregs.cx = up + left inregs.dx = down + right CALL INTERRUPT(16, inregs, outregs) END SUB The QBNews Page 34 Volume 1, Number 3 May 22, 1990 ***** Getting the Day of the Week with Call Interrupt DECLARE FUNCTION dag$ (nr!) ' This is just an example to illustrate the use of an interrupt ' to get the day of any date. ' By Johan Lindgren (+46 60-121497 (BBS System)) '$INCLUDE: 'QB.BI' DIM inregs AS RegType, outregs AS RegType inregs.ax = &H2A00 spardat$ = DATE$ CLS PRINT PRINT "This is just to show the function of interrupt 33,function 2a" PRINT "It works well for our time and the future." PRINT "If you need this function for old dates, you have to use" PRINT "something else." ON ERROR GOTO fel.datum 'In case you enter a bad date main: svar$ = " " DO UNTIL svar$ = "" INPUT "Input the date to check in the format MM-DD-YEAR"; svar$ IF svar$ <> "" THEN DATE$ = svar$ 'Set the date we want to check 'Call the interrupt to get the day CALL Interrupt (33, inregs, outregs) dagnr = outregs.ax MOD 256 'Extract the daynumber returned DATE$ = spardat$ 'Restore proper date SELECT CASE dagnr CASE 0 dag$ = "Sunday" CASE 1 dag$ = "Monday" CASE 2 dag$ = "Tuesday" CASE 3 dag$ = "Wednesday" CASE 4 dag$ = "Thursday" CASE 5 dag$ = "Friday" CASE 6 dag$ = "Saturday END SELECT PRINT "The day of the week for ";svar$;" is ";dag$ The QBNews Page 35 Volume 1, Number 3 May 22, 1990 PRINT END IF LOOP CLS END fel.datum: RESUME main The QBNews Page 36 Volume 1, Number 3 May 22, 1990 ***** A YES/NO response DEF FN ' TO: DAVID CLEARY 76510,1725 ' FROM: BEN HARTLEY 70033,2612 ' SUBJECT: For QBNews -- CHOOSE.BAS -- (FUNCTION) '=================================================================== 'Dear David, ' ' Liked your newsletter, which I downloaded from Exec-PC BBS. ' I'm not what you might call an experienced programmer, but the ' enclosed function might be of interest. ' There are LOTS of routines around that allow selection of ' a simple "Yes" or "No" response. There are times, however, when ' the flow of a program is improved if the choices are something ' other than "Y" or "N". That's where this one comes in. If there ' are no more than two choices, then they can be explicitly stated. ' Whether this is any faster, or, heaven save us all, more "elegant" ' than using SELECT CASE, I haven't the foggiest. I do know that it ' works! ' You can load this entire file into your QuickBASIC editor, ' and run it. It was originally written in QB 3.0, but seems to ' run fine in QB 4.5 -- ' Hope this is something along the lines of what you're ' looking for. ' ' Ben Hartley ' Jaffrey, NH '------------------------------------------------------------------- DEF FNChoose (prompt$, Response1$, Response2$) R1$ = UCASE$(LEFT$(Response1$, 1)) R2$ = UCASE$(LEFT$(Response2$, 1)) alpha$ = R1$ + R2$ PRINT prompt$; " ("; R1$; " or "; R2$; ") " reply$ = "" charPos% = 0 WHILE charPos% = 0 reply$ = UCASE$(INKEY$) IF (reply$ <> "") THEN charPos% = INSTR(alpha$, reply$) IF (charPos% = 0) THEN BEEP END IF WEND reply$ = UCASE$(MID$(alpha$, charPos%, 1)) FNChoose = (reply$ = R1$) END DEF '------------------------------------------------------------------- COLOR 15, 1 beginhere: CLS LOCATE 10, 5 PRINT "Which option do you wish..." LOCATE 12, 7 IF FNChoose("...The First or the Second ?", "First", "Second") THEN The QBNews Page 37 Volume 1, Number 3 May 22, 1990 PRINT PRINT TAB(20); "You chose the First Option." ELSE PRINT PRINT TAB(20); "You chose the Second Option." END IF LOCATE 16, 7 IF FNChoose("Do It Again or Quit ?", "Again", "Quit") THEN GOTO beginhere END IF PRINT PRINT TAB(30); "**** All done! ****" END The QBNews Page 38 Volume 1, Number 3 May 22, 1990 ***** A Powerful Replacement for INPUT 'SYNTAX: Enput$(Row%,Col%,FLen%,Default$,Allow$,EndKeys$,ExitKey$) 'WHERE: Row% & Col% - where input to occur ' FLen% - Field Length - maximum allowable ' Default$ - Field's beginning default value ' Allow$ - Character Set allowable for input ' EndKeys$ - Keystrokes acceptable for finishing. ' SUCH: [Enter] is CHR$(13) ' [Esc] is CHR$(27) ' [F1] is CHR$(255)+CHR$(59) ' [Ctrl+PgUp] is CHR$(255)+CHR$(132) ' ... etc ' ExitKey$ - The keystroke (from above EndKeys$) ' which was struck at function end. 'SIMPLEST USE: A$ = Enput$(0,0,0,"",","",Any$) ' Will default to cursor's current row/col, length is ' autoset to right margin, allowable chars are ALL, ' only ending key is [Enter]. '==========================================> Frederick Volking FUNCTION Enput$ (Row%, Col%, FieldLen%, Default$, AllowCharsMask$_ ,EndingKeys$, ExitKey$) STATIC SHARED ScreenWidth%, BackSpaceKeyStrokes$, EmptySpaceChar$ ' verify SHARED globals are set IF ScreenWidth% = 0 THEN ScreenWidth% = 40 IF BackSpaceKeyStrokes$ = "" THEN BackSpaceKeyStrokes$ = _ CHR$(8) + CHR$(255) + CHR$(75) IF EmptySpaceChar$ = "" THEN EmptySpaceChar$ = CHR$(254) ' If not specified then supply defaults to incomming vars IF AllowCharsMask$ = "" THEN AllowCharsMask$ = CHR$(34) +_ " !#$%&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]_ ^_abcdefghijklmnopqrstuvwxyz|~" IF EndingKeys$ = "" THEN EndingKeys$ = CHR$(13) IF Row% = 0 THEN Row% = CSRLIN IF Col% = 0 THEN Col% = POS(0) IF FieldLen% = 0 THEN FieldLen% = ScreenWidth% - Col% IF LEN(Default$) > FieldLen% THEN Default$ = LEFT$(Default$_ , FieldLen%) ' define internal defaults CharsCollected% = 0 ReturnVar$ = "" ' begin main loop DO NotDone% = 1 IF CharsCollected% = 0 THEN LOCATE Row%, Col%, 0 The QBNews Page 39 Volume 1, Number 3 May 22, 1990 PRINT Default$ + STRING$(FieldLen% - LEN(Default$), _ EmptySpaceChar$); END IF IF CharsCollected% = FieldLen% THEN LOCATE Row%, Col% + CharsCollected% - 1, 1 ELSE LOCATE Row%, Col% + CharsCollected%, 1 END IF DO KeyStroke$ = INKEY$ LOOP WHILE KeyStroke$ = "" IF LEN(KeyStroke$) = 2 THEN KeyStroke$ = CHR$(255) + _ RIGHT$(KeyStroke$, 1) IF INSTR(AllowCharsMask$, KeyStroke$) > 0 THEN IF CharsCollected% < FieldLen% THEN IF CharsCollected% = 0 THEN PRINT STRING$(FieldLen%, EmptySpaceChar$); LOCATE Row%, Col%, 0 END IF ReturnVar$ = ReturnVar$ + KeyStroke$ CharsCollected% = CharsCollected% + 1 PRINT KeyStroke$; END IF ELSE IF INSTR(BackSpaceKeyStrokes$, KeyStroke$) > 0 THEN IF CharsCollected% > 1 THEN CharsCollected% = CharsCollected% - 1 LOCATE Row%, Col% + CharsCollected%, 0 PRINT EmptySpaceChar$; LOCATE Row%, Col% + CharsCollected%, 1 ReturnVar$ = LEFT$(ReturnVar$, CharsCollected%) ELSE CharsCollected% = 0 ReturnVar$ = Default$ END IF ELSE IF INSTR(EndingKeys$, KeyStroke$) > 0 THEN ExitKeys$ = KeyStroke$ NotDone% = 0 ELSE SOUND 500, 1 END IF END IF END IF LOOP WHILE NotDone% Enput$ = ReturnVar$ END FUNCTION The QBNews Page 40 Volume 1, Number 3 May 22, 1990 ***** Windowing Routines with Shading 'FRAME.BAS test program 'Lawrence Stone, 1990 ' 'Purpose: Demonstrate the versitility of the Frame subprogram. 'Note: I built this routine just for the fun of it. I designed it to 'be easy to use and versitile. This demonstration program simply makes 'six calls to the frame subprogram-the subprogram does the rest. DECLARE SUB Frame (TopRow%, leftCol%, botRow%, rightCol%, boxType%,_ boxFg%, boxBg%, filFg%, filBg%, fillChar%, shadow%, header$) COLOR 12, 4 'Set a fancy color FOR A% = 1 TO 25 LOCATE A%, 1 PRINT STRING$(80, 206); 'Draw a complex background NEXT 'Test the Frame subprogram Frame 5, 20, 21, 75, 2, 15, 3, 9, 7, 178, -1, "This is a Test" 'Overlay the 1st box with a smaller 2nd box Frame 4, 3, 10, 36, 1, 15, 6, 6, 0, 219, -1, "This is Test 2" 'What the heck, let's draw another box Frame 13, 33, 22, 70, 3, 14, 2, 15, 3, 221, -1, "This is Test 3" 'What the heck, draw box #4 without a header Frame 15, 5, 19, 65, 4, 13, 0, 0, 7, 32, -1, "" LOCATE 17,14:PRINT "Box #4 has a shadow but doesn't have a Header." 'What the heck, draw box #5 without a shadow Frame 8, 40, 10, 73, 4, 11, 0, 11, 0, 32, 0, "" LOCATE 9, 43: PRINT "Box #5 doesn't have a Shadow!" 'What the heck, draw a frame around it all Frame 1, 1, 25, 80, 2, 15, 0, 0, 0, 32, 0, _ "Box #6 Frames Without Clearing" COLOR 7, 0 'Restore color before ending A$ = INPUT$(1) 'Give a keystroke to end this test. END 'We're out of here! [EDITOR'S NOTE] Due to the formatting of the FRAME subprogram, it is not printed here. You can find the sub program in the file FRAME.BAS The QBNews Page 41 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- I n p u t P a s t E n d ---------------------------------------------------------------------- Due to demand, you can now get the QBNews mailed to you on disk for a nominal charge. The rates are as follows: United States $3.00 North America (outside US) $4.00 Outside US $6.00 Please add $1.00 if you want it on 3.5" media. Also, you can receive it in either ZIP, PAK, or LHZ format. If you don't specify, you will receive it in ZIP format. The first time you request the QBNews, you will receive all available issues. That way you won't miss any. And of course, you can always download it free from Treasure Island. First time callers are granted limited download privlidges so you can get it. Treasure Island is 9600 HST, is at FIDO address 1:141/730, and its phone is 203-791-8532. To request your copy on disk, send US funds to: The QBNews P.O. Box 507 Sandy Hook, CT 06482 Remember to specify which archive format you want. The QBNews Page 42 Volume 1, Number 3 May 22, 1990 WE NEED AUTHORS! If you are interested in writing for the QBNews, you can contact me at the address below. I can also be reached on Compuserve as 76510,1725 or on Prodigy as HSRW18A. If you are submitting articles, I ask that they be ASCII text with no more than 70 characters per line. As far as reviews go, I am pretty well set so what I really want is code. You can write me at: The QBNews P.O. Box 507 Sandy Hook, CT 06482 David Cleary The QBNews Page 43