The QBNews Page 22 Volume 3, Number 1 March 29, 1992 ---------------------------------------------------------------------- T h e Q B N e w s P r o f e s s i o n a l L i b r a r y ---------------------------------------------------------------------- Thanks for the Memories by Robin Duffy For years QuickBASIC programmers have done some pretty incredible things when it comes to using all the resources available on a PC. Of all the things accomplished, the ability to use expanded memory was heralded as a major accomplishment (at least I thought it was!). Now QB programmers had oodles more of that precious commodity, RAM! Why, several meg of RAM could now be used by programmers without thinking, allowing them to create the most powerful of applications. Without thinking? Well, not really. Expanded memory has its drawbacks, one of which is the concept of bank switching blocks of this memory in and out of the processor's addressable space. Also there was no management of these blocks available to the programmer other than what he could muster up for himself. He had to keep track of what data was in what page and which pages were loaded where in the page frame. Without some careful advanced planning it would be easy to get things all mixed up beyond any hope of survival. Another disadvantage I saw was the difficulty in saving a huge array into this 64K page frame. A huge array by definition is larger than the page frame itself. So, one would have to save the first 64K of the array into the four available pages, swap pages, and then save another 64K chunk. I think there is a term for this sort of work around programming - kludge! Why would you want to have all this beautiful streamlined code in your program and then rely on a kludge? Well, my thoughts took a different course after I tried programming for expanded memory. I knew there was a different sort of memory native to AT machines called extended memory. Extended memory differed from expanded memory in several different ways, the most attractive of which was the fact this memory was continuous for meg after meg. The only problem with this memory was it was only available from the processor's protected mode of operation. Now, I thought, how can I get to this memory from a platform I am comfortable with? The answer was obvious, but somewhat frightening! Now enter a rather mysterious little device driver supplied with many versions of DOS named HIMEM.SYS. HIMEM.SYS was barely documented (if at all) as an extended memory manager, but precious little else could be found out about it. Now here was something that could manage extended memory for me, if only I could find out how to make this thing work! Expanded memory has this dedicated interrupt (Int 67h) that made interface simple, but HIMEM was written differently. Instead of a clean system of interrupt function calls, or even invoking a device name, HIMEM.SYS is used by calling the driver directly as a procedure call! While direct calls to device drivers can be done directly from QB with CALL ABSOLUTE, not every aftermarket runtime library supported this QB keyword. Setting up the required structures was also somewhat of a pain. The idea for an assembly The QBNews Page 23 Volume 3, Number 1 March 29, 1992 interface to HIMEM.SYS was born! HIMEM.SYS provides a variety of functions for managing extended memory, the high memory area (1M + 64K) and the upper memory area (memory between A000:0000 and 1M). It is important for user programs NOT to issue calls for managing the high and UMB memory areas if some other program is already managing those areas, especially DOS. The popularity of DOS 5.0 meant I would have to be careful which functions I chose to implement. After consideration I chose only those functions that allocate, deallocate, move, and report on extended memory. HIMEM would perform a move to and from conventional memory, so I did not require the use of the other functions. I was pleasantly surprised when I learned HIMEM uses a handle based system for managing blocks of memory instead of a more bulky system such as page frames. For each block of XMS you allocate HIMEM assigns a handle to reference the block by. This allows HIMEM to manage the actual locations any way it pleases in a transparent manner - so if I wanted to allocate a 1.5 meg block of XMS memory (certainly possible) I could reference it at anytime with a single handle. Certainly an improvement over paging! Another advantage of handles is a program can use several blocks of XMS memory of varying sizes without regard to the physical location of the memory or what's in it. Just use the handle much like you would a DOS file handle and let it go at that. There is one responsibility you as programmer must undertake when deciding to use extended memory in your program. You must make sure to release any XMS memory you allocate before your program ends its execution. This memory is not managed by DOS at all, so DOS can not automatically release XMS on program termination as it does for conventional memory. If a program exits without releasing it, the memory remains allocated until the next time the computer is booted! Although the demo program I have included does not do this, you should trap all possible means by which your program can be terminated. Included with this text is my extended memory interface for QuickBASIC, the XMS driver. When designing XMS I had considered using a system of pointers to emulate a block storage device, and using it like a disk drive. However, after some consideration I decided to implement it similar to the manner Ethan Winer had implemented his expanded memory interface for QuickPak Professional. The major reason for this design decision is simulation of a block device would not remove enough of the management required by the programmer. I think that although somewhat limited, the XMS driver provides a good amount of flexibilty while insulating the programmer from much of the work involved. Let's now take a look at the ten procedures that make up the XMS driver for QuickBASIC. I won't get into the calling syntax here (it is included as part of the ZIP file in XMS.DOC) but will instead provide some comment on the operation of each. If you wish to see these routines in action just run the small demo program included with the driver package. The QBNews Page 24 Volume 3, Number 1 March 29, 1992 INITXMS is the procedure to get the ball rolling. This procedure checks for the presence of HIMEM.SYS and determines the availability of XMS memory. This procedure returns the availability and size of any XMS memory on the machine in two integers. The size returned is the actual XMS available in K bytes (that is, this number times 1024 is the actual size). GETXMS is a function that allocates a block of XMS memory and returns its handle. The only parameter for this function is the size of the memory block you are requesting, in K bytes (1024 bytes is the smallest increment in which HIMEM.SYS will allocate XMS memory). You may specify a block of any size up to and including all available memory! Block sizes of several meg are commonplace. If desired, you may specify a number of different blocks for different purposes. FREEXMS will deallocate (free up) memory that was allocated using GETXMS. Simply call it with the handle of the block you wish to release. See the above paragraphs for the importance of using this call. ARRAY2XMS is a procedure that will copy any continuous block of conventional memory to an allocated block of XMS. This procedure will copy the block of conventional memory into the XMS block at offset zero into the block. One interesting use of this procedure is saving huge arrays to XMS with one call. In fact, given enough extended memory, you could conceivably copy the entire contents of conventional memory to XMS! You can use this call with any far array. The only limitation posed by this procedure is the number of bytes to move must be an even number. Odd numbers are forced even without comment or error. XMS2ARRAY is the compliment routine to ARRAY2XMS. Its function is to copy a block of memory from XMS to conventional memory. I have used these two routines to save and restore the screen directly from XMS. The only drawback to using this method to save and restore screens is HIMEM.SYS knows nothing of retrace checking for CGA monitors, and may cause some snow. Other uses include restoring huge arrays with one call and loading code stored in XMS. XGETELEMENT is one of those afterthought routines. If you know the size of the array elements you have stored in an XMS block (and you should), this procedure will allow retrieving a single element from that block. The demo program uses this routine to display any random element the user chooses. The procedure uses your element size and an element number to determine where in the XMS block to start reading. Please note this procedure assumes the first element of your stored array is item number one, not zero. Also, your element size must be an even number. XSETELEMENT is the compliment procedure to XGETELEMENT and has identical syntax. This procedure will allow the setting of any individual element stored in XMS memory. The demo program uses this procedure to change any chosen element to a new value of the user's choice. These two routines can let you set up a "virtual" array in The QBNews Page 25 Volume 3, Number 1 March 29, 1992 extended memory of any element size, up to and including all installed XMS! Next come three procedures that deal with error handling. These procedures were inspired by Ethan's QuickPak Professional and really bind the whole thing together. My personal salute to Ethan Winer for such insightful design! XMSERROR is a function that will return the current state of error, but not the specific error code. This is extremely handy when you wish to test for an error condition, but don't care what error has occurred. Very neat code can be written for error handling as so: . . CALL Array2XMS(SEG Array%(start%), XHandle%, NumBytes&) IF XMSError% THEN PRINT "Error transferring array to XMS" (branch to error handling code here) ELSE PRINT "Array transferred" END IF . . The status of errors is updated after every call in this driver except for INITXMS. Thus, any sucessful operation will clear the value of any previous error. It should be noted that if XMS memory is not available for any reason (such as HIMEM not installed, extended memory not installed or none available) this function will always show an error condition. This way programming logic will not have to adapt much to the presence or lack of XMS memory. WHICHXERROR is a function that will return the actual code of an error reported by XMSError. This routine would most likely be used inside an error handling routine to determine the course of action required by the program. If there is no outstanding error condition WhichXError will return a zero. WhichXError is designed so if XMS memory is not available for any reason this function will always return a "Function not supported" error. I thought it might cause some confusion if a call to a routine in this driver fails because XMS is not available and the reported error was "No Error"! SETXERROR is a sub you can use to set the error code returned by WhichXError. This sub will allow setting error codes in the range of 0 to 255 inclusive. I thought for completeness this procedure should be included. I also thought about allowing this sub to work if no XMS is installed but then decided against it. Again, some confusion could result if you set the error code to zero when no XMS was installed. So, if XMS is not available to the driver, this sub will have no The QBNews Page 26 Volume 3, Number 1 March 29, 1992 effect on anything. Well, that pretty much wraps it up. Included with the XMS package is a sample program and source code that demomstrates use of most of the routines in this package. One glaring error that stands out is a CRTL-C will abort this program prematurly (it uses standard DOS I/O so it can be interrupted easily) and lose the allocated memory until next reboot. I did this intentionally so you could see the results of such a programming practice. If you experiment, you'll see that when you try to run the demo again, it will tell you there is no available XMS memory! These routines have been tested pretty thoroughly, but not completely. It is after all just a prototype for something normally not possible within a QuickBASIC program. I hope it provides you with many hours of entertainment just pushing the driver to the limits! I have only scratched the surface of the potential of this driver. I am sure there are hundreds (perhaps thousands) of practical applications for blocks of memory larger than those you can access by any other means except straight protected mode programming. I would be interested to learn just what feats can be accomplished using this code as a starting point. If you have any comments concerning the XMS driver package or would like to ask me a question concerning it, feel free to contact Sequental Software's support BBS at (606) 561-5766 24 hours daily. There's no registration fee and you get full privs on the first call. I'm usually on it every day, so your question should be answered in about 24 hours or so. If you are more inclined to write, send your response to: Sequential Software Inc. P.O. Box 53 Somerset, KY 42502-0053 USA Make sure to put it to my attention. I will answer as soon as possible by mail. Have fun and good luck! SOURCE CODE FOR THIS ARTICLE CAN BE FOUND IN XMS.ZIP