Simulated Extended Memory Arrays in QB, version 3 QBXMS is a set of QB routines to allow storage of data in extended memory (XMS) in much the same fashion that it stores data using arrays in conventional memory. It does NOT give you the ability to simply use the DIM statement to define a standard QB array of "unlimited" size. It simply provides an alternative method of storing data in XMS and lets you access it in a manner that in some sense simulates how you would store and retrieve data with a conventional array. The process is admittedly somewhat more cumbersome than using conventional arrays, and slower. (It has some additional disadvantages which I'll get to eventually.) This being my first attempt at anything like this, and all, I decided to keep things as simple as possible (while still maintaining usefulness). String XMS arrays are not supported and you are restricted to one- dimensional arrays. Further, you may only have up to 16 such arrays and their total combined XMS memory cannot be more than 30.9375 MB. The four standard numerical data types *are* supported: INTEGER (2 bytes), LONG (4 bytes), SINGLE (or "real"--4 bytes), and DOUBLE (8 bytes). Further, XMS arrays are 1-based. (The first element has index 1.) Just as with normal QB arrays, XMS arrays must be dimensionalized before you can put data in them. Instead of DIM, however, you do this by calling the subroutine DIMXMS. The syntax of the call is CALL DIMXMS (ARRAY, N, TYPE, EC) where ARRAY is a character string representing the name of the array, N is the number of elements in the array (a LONG integer), TYPE is a character string giving the data type ("INTEGER", "SINGLE", "DOUBLE", or "LONG") and EC is an error code (single precision/4-byte variable). The only restriction on N, other than the amount of available XMS is that, being a LONG integer, it can't be larger than 2^31 - 1 (2147483647). Since, even for a 2-byte per element INTEGER array, this would give an array size of 4,096 MB, N really isn't a limiting factor here. In the call to DIMXMS, EC must be listed as a variable, not an explicit number. EC is returned from DIMXMS as 0 if DIMXMS was able to initialize the array properly (or at least thinks it did). It is returned as 1 if it detects an inavailability of XMS (more on this later) and 2 if you've already maxed the number of XMS arrays out at 16 when you tried to initialize a new one. Normally, DIMXMS will initialize all array elements to zero. This may be time-consuming except for very small arrays. (And if they're very small arrays, why are you trying to store them in XMS?) Such initialization is often unnecessary. You can turn it off by setting EC to any nonzero number before calling DIMXMS. (This must be done before each call since DIMXMS, when it's performing successfully, resets EC to zero.) Subroutine DIMXMS cannot be used to perform a REDIM operation; it will terminate execution if you call it with the name of an array that already exists. REDIMXMS, however, can be used for that purpose. The calling syntax is exactly the same as with DIMXMS. If you merely want to clear an already existing extended memory array and reallocate its memory for reuse by DIMXMS, you can do that with subroutine CLRXMS: CALL CLRXMS (ARRAY) Now, this is where things get slightly cumbersome. How you store data in an XMS array and retrieve data from it depends on what type of array it is (INTEGER, SINGLE, etc.). There are four subroutines used to store data in the arrays, one for each data type, and four functions used to get data out of the arrays, one again for each data type. Let's say you have an array "A", for definitiveness, and you want to assign a number X to element I of this array. You do this by calling a PUT$$$ subroutine, where $$$ is SNG for a SINGLE array, INT for an INTEGER array, LNG for a LONG array, and DBL for a DOUBLE array. The syntax of the call is CALL PUT$$$ ("A", I, X) Note that I must be a LONG integer and X must be of the same data type as signified by $$$. The routines will detect discrepancies between the specified array and the type they're programmed to handle and terminate program execution (with a STOP statement) if they find such discrepancies. (Nonexistent arrays and I > N / < 1 will also cause such termination.) How would you get X back out of array A? Use the appropriate GET$$$ function, where $$$ has the same interpretation as before. For example, X = GET$$$ ("A", I) where I is a LONG integer, would retrieve the Ith element of array A and put it in the variable X. (It is generally best if X's data type is compatible with $$$, although the general numeric conversion procedures of QB should apply.) Clearly, XMS arrays are global. There is no need to worry about how to pass them from one QB subroutine to the next. XMS exists no matter what subroutine you're in. (Of course, you can pass variables representing array *names* to subroutines.) You can use the function UXMS to find the number of elements stored in an array. (It's similar to QB's UBOUND function.) UXMS inputs the character string representing the name of an array and returns a LONG integer giving the array's size and, via the parameter list, the base address (LONG integer) of the array: N = UXMS (ARRAY, ADD) Subroutine XBSAVE can be used to achieve roughly the same functionality as QB's BSAVE command. Rather than work with address segments and offsets, however, XBSAVE just works with the name of an INTEGER extended memory array, starting from the beginning of that array. The format of the call is CALL XBSAVE (ARRAY, BYTES, FILE) where ARRAY is a string (literal or variable) giving the name of the INTEGER XMS array, BYTES is a LONG integer giving the number of bytes to save (it does NOT, of course, have QB's 65,535 constraint), and FILE is a string giving the name of the file to save the data to. The format of this file is not quite the same as a conventional BSAVE file--the primary reason being that BSAVE stores the number of bytes saved in the file as a 2-byte integer, which is totally incompatible with the purpose of XBSAVE. (QB's BLOAD command is NOT compatible with an XBSAVE file.) So here's the format of an XBSAVE file, where all byte references are 1-based: Byte 1 - character FDh, just like a BSAVE file; Bytes 2, 3, and 4 - they spell out "XMS"; Byte 5 - the high byte of a 3-byte integer giving the number of bytes saved; Bytes 6 and 7 - the low and middle bytes of the 3-byte integer giving the number of bytes saved (just like bytes 6 and 7 in a normal BSAVE file); Bytes 8 and on up - the data that got saved. Since BLOAD can't be used to load this data back into an array, conventional or otherwise, you need something else. Well, you're in luck! I included (like you couldn't guess ) subroutine XBLOAD for that purpose. The call is given by CALL XBLOAD (ARRAY, FILE) where both inputs are strings and have the same interpretation as before. Although both XBSAVE and XBLOAD are SIGNIFICANTLY SLOWER than BSAVE/BLOAD (you may want to have your program beep at you when they're finished), XBLOAD has what might be considered one advantage over BLOAD (other than the fact that it works with extended memory and doesn't have the 64K - 1 byte limit): you don't have to use DIMXMS to initialize ARRAY before calling XBLOAD--the subroutine does that itself via REDIMXMS. (By the way, now that I've said a couple of times that BLOAD can't work with this file, I'll point out that it CAN if two conditions are met: 1) byte 5 in the file (the high byte of the 3-byte integer) is zero--i.e., you saved less than 64K bytes, and 2) you aren't using BLOAD in a manner that requires the default segment and offset that BSAVE normally puts at bytes 2 - 5.) XBLOAD initializes ARRAY as an INTEGER array. XMSFREE is a stand-alone utility that will tell you how much extended memory you have. (It may not be useful if you have HIMEM.SYS or other memory managers installed, i.e., likely most of the time--see below.) Now, then. How do actually use the included files? You generally need three files. One of these is the one you're writing that you need QBXMS for. (An example MAIN routine XMS.BAS is included to give you a more concrete idea of how to use QBXMS.) At the top of your program, before your code, put the contents of QBXMS.INC. At the bottom, after your code, put the contents of QBXMS.BAS. (If you're running from within the QB IDE--I don't recommend it due to time considerations, use the /L option on the QB command line. Speaking of the interpretive environment, if execution time isn't a big problem for you, if you don't have QuickBasic but you *do* have QBasic, effective with QBXMS version 3, the software should work with Qbasic.) Sounds great, doesn't it? Are you looking for the catch? (You didn't think you were getting a free lunch, did you?) There is a price to pay for everything, and there's no exception here. There are a few conditions you have to put up with when using QBXMS. 1) Obviously, XMS arrays are somewhat more cumbersome than conventional ones. It's not like QBXMS is a driver that you can just load into memory and make otherwise normal QB programs suddenly work that wouldn't work before because the arrays were too big. You have to rewrite your code and be very careful about which routine you use to process which type of variable. You also need to pay close attention to the variable types being passed in the subroutine/function calls. 2) QBXMS.INC contains various COMMON SHARED ... and DIM SHARED ... statements that store various global variables. The TYPE REGISTERS ... END TYPE construct defines another kind of special variable. You must not use these variables in your own programming except in the context that QBXMS uses them. (Except for perhaps using the INREGS and OUTREGS variables for your own interrupt calls, don't even think about changing the other global variables unless you really understand how they're being used and are intentionally trying to modify the functionality of QBXMS.) 3) QBXMS does not use HIMEM.SYS or any other memory manager, per se. It merely uses interrupt 15 / function 87. This does not make QBXMS very "XMS-friendly." It tends to wipe out whatever data you may have stored in XMS memory. (QBXMS does not, however, use the first 64 KB of XMS--the high memory area, in an attempt to preserve whatever TSRs/drivers that you may have loaded there.) I typically use XMS as a ramdrive. After I run a program using QBXMS, my ramdrive directory typically looks like a bunch of nonsense. (If I ever get around to figuring out directory structures, I'll try to make QBXMS restore at least the "structural integrity" of ramdrives when it finishes. In the meantime, you can use SCANDISK to fix your ramdrive. SCANDISK still won't restore your lost data, but it *will* restore your FAT to what it would be for a nice, clean, empty directory structure.) 4) A more pressing problem is whether or not QBXMS will even work with memory managers installed. From what I can tell, the presence of HIMEM.SYS will not keep QBXMS from running. However, there is a quirck with HIMEM.SYS that I can't honestly say I understand. QBXMS queries the computer to find out how much XMS is available. On my computer, HIMEM.SYS interferes with this. With HIMEM.SYS loaded, my CPU informs me that I don't have any XMS when I query it in a manner that bypasses HIMEM.SYS. This does not happen on other computers. So, when QBXMS tries to find out how much XMS you have and gets a value of 0, it then queries the CPU to find out if HIMEM.SYS is installed. If it finds HIMEM.SYS, QBXMS simply takes the available XMS to be the maximum value it can deal with--31 MB (30.9375 after the 64 KB of HMA is reserved). (These numbers are, respectively, 15 and 14.9375 MB for a 286.) In other words, it would probably be best if you were cognizant of how much XMS you have when you use QBXMS. You can experiment with the standalone program XMSFREE if you wish. If running it from the DOS command line gives you a nonzero result and you know you have XMS managers loaded, then you can be reasonably sure that QBXMS is also going to be able to ascertain how much XMS you have. In the situation of being told there is no XMS memory while being informed that HIMEM.SYS is loaded, function FREEXMS (inside QBXMS.BAS) sets the global variable DRVEXIST to unity. (Otherwise, it's zero.) Your routine can test this variable and act accordingly. 5) Things get worse--and then they get slightly better. HIMEM.SYS may not stop QBXMS from running, but if you have EMM386 loaded, unless your computer works a lot differently than any of the machines I've tested QBXMS out on, running QBXMS is just an elaborate way of rebooting your computer. Further, if HIMEM.SYS doesn't interfere with QBXMS or XMSFREE ascertaining how much XMS you have, EMM386 will. (And I suspect that these comments apply to other XMS memory managers as well.) Okay, here's the silver lining. You probably don't have to completely kick all XMS managers out of memory (i.e., by rebooting in a different configuration). Whether or not HIMEM.SYS is the source of any problems, it may be the solution to problems with other drivers. Lets say the line in your CONFIG.SYS that loads HIMEM.SYS is something like: DEVICE=C:\DOS\HIMEM.SYS You may have various "/" options specified as well. Simply imagine their presence here if you wish. HIMEM.SYS can be made aware that you may want to bypass it and other drivers like EMM386 by using interrupt 15. It will allow you reserve XMS memory for use by that interrupt. If X is the number of KB of XMS that you want to reserve for QBXMS to use (i.e., your memory managers won't know this XMS exists), add 64 to X and call that number Y. Change the above line in your CONFIG.SYS to: DEVICE=C:\DOS\HIMEM.SYS /int15=Y When QBXMS or XMSFREE ask your CPU how much XMS is available, your CPU should now answer with a value of at least X. Further, there should be no problems with QBXMS rebooting your machine because it tried to access extended memory that other drivers think they own. This may also solve the problem of QBXMS trashing data already in XMS or otherwise scrambling your ramdrive directory structure, but I'm not really sure. (It may depend on your DOS version, ramdrive driver, or "computer type"--and I use that term in its broadest possible sense.) The version of this text file associated with the initial release of this software contained a discussion of how slow QBXMS was. Well, as it turns out, there were many reasons why the particular benchmark I was using was slow. I was able to significantly speed that particular program up. Nevertheless, XMS arrays are still slower than conventional arrays. As I mentioned in the first version of this file, I initially thought that this was because of the repeated interrupt calls and then I determined that, while they're certainly a factor, they aren't the bottleneck in and of themselves. Rather, as before, I still believe that if there is a bottleneck, it's just the overhead associated with calling the various subroutines/functions just to transfer 8 or less bytes at a time. In addition to the normal QB overhead associated with calling subroutines/ functions, the GET$$$/PUT$$$ routines have their own overhead. I should have also pointed out previously that the process of going in and out of protected mode isn't particularly fast either. If you only wanted to work with a 64K buffer in conventional memory (and your application allows this technique) and transfer data 64K at a time between it and XMS when necessary, things would be faster. The subroutine that actually transfers data to/from XMS is MEMTRN. The call to it is of the form CALL MEMTRN (SOURCE, DEST, BYTES) All parameters are LONG integers. SOURCE is the linear address of where data is being transferred from, DEST is the linear address of where it's going to, and BYTES is the number of bytes to transfer (<= 65,536 and even). I think another term for "linear address" is "huge address." If the conventional "offset segmented address" is segment:offset, the linear address is linear address = segment * 16 + offset. Note that when you use VARSEG and VARPTR to get the segment and offset of a numerical array or variable, these functions return numbers in a 2-byte INTEGER format. Hence, any segment or offset larger than 32,767 will be returned by these functions as a negative number. If you get negative numbers, add 65,536 to them before you calculate the huge address. Why do I mention all this? Because if SOURCE is an address in XMS (address 100000h, or above), DEST will likely be the linear address of an array in conventional memory, or vice versa. (Actually, in spite of what one of my references says, you can use this interrupt / function to transfer data from one place in conventional memory to another place in conventional memory. I am unable to find a significant speed difference between doing that and just using a FOR/NEXT loop and setting one array equal to another.) If you want to avoid writing over TSRs/drivers that you may have stored in high memory DEST should be above 1,114,111 when writing to XMS. (And it should be less than 589,826 when writing 64 KB to conventional memory.) So, to use MEMTRN, follow the following steps. 1. DIMension a 64 KB conventional array, call it ARRAY here: DIM ARRAY (1 TO SIZE) AS variable_type where size is whatever it has to be to make the array contain 65,536 bytes for the variable type used. 2. Use VARSEG and VARPTR to get this array's segmented address: DIM SM AS LONG,OS AS LONG SM = VARSEG( ARRAY(1) ) : IF SM < 0 THEN SM = SM + 65536& OS = VARPTR( ARRAY(1) ) : IF OS < 0 THEN OS = OS + 65536& 3. Use DIMXMS to define whatever XMS arrays you want to use. 4. DIMension an address variable, call it ADD here, as LONG: DIM ADD AS LONG 5. Decide which of your previously defined XMS arrays that you want to work with. Use the function UXMS to get the (huge) address of that array (now that it's been modified to return that parameter): N& = UXMS(xms_array_name, ADD) where N is a LONG integer that now gives the number of elements in xms_array_name. (It should be what you specified in the call to DIMXMS. Be careful using the results of UXMS. If N is returned as 0, as will happen if you didn't specify a valid array name, the address will be set to a fictitiously large, i.e., meaningless, number.) 6. Input data to your conventional array until it's full. 7. Call MEMTRN to transfer your 64K conventional array to XMS: CALL MEMTRN (SM * 16 + OS, ADD, 65536&) 8. Update XMS address: ADD = ADD + 65536& 9. Repeat from step 6 until XMS data storage is complete. 10. Data is transferred from XMS to your conventional ARRAY via CALL MEMTRN (ADD, SM * 16 + OS, 65536&) where you may need to reuse UXMS to get the base address of the XMS array if you didn't save it from the first call. 11. To transfer the next 64K out of XMS, update ADD (e.g., step 8) and repeat from step 10. What if your XMS array doesn't contain an integral number of 64K blocks of data? Well, when you go to use that last block after transferring it to your buffer in conventional memory, don't use more of the buffer than is valid. Do you really need to use MEMTRN this way instead of just using QBXMS in its "conventional" manner? Well, admittedly in spite of some possibly erroneous earlier beliefs of mine, probably not unless you're doing something in which "time is everything." Now for the legal stuff. Although I don't really think QBXMS can do any physical harm to your equipment or other aspects of your system, I must insist that, whatever risk using QBXMS has, said risk must be YOURS. (And I've already warned you about the possible loss of data on your ramdisk or otherwise stored in XMS.) Although, I'm not necessarily relinquishing copyright or desire for credit of authorship, I'm not too concerned about what you do with the software--except for selling it. As for hardware requirements, well, your computer has to have extended memory. I've tried it out with both versions 2 and 3 XMS (whatever "XMS version" means--if it means anything at all in regard to QBXMS since it doesn't use any extended memory management system). Hence, your CPU must be a 286 or better. (Although I haven't tried it out on a 286 with extended memory, it works when I use an array in conventional memory to simulate the presence of extended memory. Since this involves the same interrupt calls/ protected-real mode switching, it should be a valid test.) Glenn Stumpff 4960 Egret Ct. Dayton, Ohio 45424 CIS: 73137,3537