Hello fellow QB programmers. This tutorial will give you a start to programming routines for the Extended Memory Specification, or XMS. Please note that this is for advanced QB programmers only! I also expect you know some basic assembly. If not than I would suggest not reading this. Don't say I didn't warn you! =) Now that that's out of the way, I can begin.
Is your game, utility, or whatever running out of memory? Do you get all red in the face when QuickBASIC stops your program and displays the "Out of memory" error? If so then XMS is for you. XMS gives you huge amounts of memory which is limited by your computers RAM. This will usually be enough for most QB programs. (In fact I would say all QB programs.) "Great," you say "So how do I use it then?" Well first you need to find out if your computer has XMS. (I feel sorry for you if it doesn't.) This is determined via interrupt 2Fh. The following example routine will check if it exists, initialize it for use, AND return the version installed:
XMSInit PROC mov ax,4300h ;This function lets us detect XMS int 2Fh ;Use interrupt 2Fh. cmp al,80h ;If AL isn't 80h then XMS is not present! jne NoXMS mov ax,4310h ;Since XMS is present lets get the version number int 2Fh ;and the address of the control function. mov word ptr [XMSControl],bx ;XMSControl must be a DWORD. mov word ptr [XMSControl+2],es xor ax,ax ;Now run function 00h which returns the version. call [XMSControl] ;Call the XMS Control function. ret NoXMS: mov ax,-1 ;If there's no XMS driver we return a -1 to QB. ret ENDP XMSInit
If you found that confusing the read the comments carefully. There's a funny thing about the version number returned. If you try to print it, it won't be the right number. Use this code to fix that:
XMSversion$ = str$(version% / &H100)+"."+str$(version% AND &HFF)
That will return the version as a string. version% would be the number returned by the above function. If you don't have version 3.0 or later than these functions I'm showing you might not work.
Now wouldn't you like to know how much memory is available to you? Well there's a function for that. Function 08h. It returns the largest free block in kb into AX, and the total free memory in kb into DX. Here's an example routine:
XMSInfo PROC freemem:WORD, totalmem:WORD mov ah,08h ;Use function 08h call [XMSApi] ;Call the API mov bx,totalmem ;Put the value in DX in totalmem. mov [bx],dx mov bx,freemem ;Put the value of AX in freemem mov [bx],ax ret ENDP XMSInfo
That will return the available memory in freemem and the total memory in totalmem. One thing about the above routine, it won't show an amount greater than 64MB. There is a function which will but I'm not showing you it now. I want you first to understand how it all works. Next issue you'll see. =P
Ok now, to actually use XMS you have to allocate a block of it. That is, you have to tell the computer that you want x amount of XMS for use in your program. We can do that via function 09h. With this routine it is not possible to allocate over 64MB or memory. There is a routine for it but I'll show you it next issue. Before you call the routine you have to have the amount of kb you want to allocate in DX. After you call it AX will be 0001h if it was successful. If not 0000h is returned. DX will also return the 16-bit handle of the allocated block. "Handle? What the heck!?" The handle you get is your "password" to accessing the block of memory. So when you allocate memory make sure that you keep the handle! There is no way to get it back and worse, you can't deallocate the block without it! Basically without it, you're screwed. So before I get ahead of myself, here's an example routine to allocate a block:
XMSAlloc PROC kb:WORD mov ah,09h ;Use function 09h mov dx,kb ;Move kb into DX. call [XMSApi] ;Call the API cmp ax,0001h ;Is AX = 0001h? If it is then it worked! jne AllocErr ;If it isn't then . . . too bad! =P mov ax,dx ;Move the handle into AX so its returned. ret AllocErr: ;Since we can't get a handle if it didn't work we don't ret ;want DX to get into AX. ENDP XMSAlloc
There ya go! That will allocate up to 64MB. So lets find out how to deallocate a block of memory.
Deallocating isn't that hard. It basically works the same way. We need to make use of function 0Ah for it. DX must hold the handle of the allocated block to be freed up. After called AX will have 0001h in it if successful. If not the its 0000h. Here's the code:
XMSDeAlloc PROC handle:WORD mov ah,0Ah ;Use function 0Ah mov bx,handle ;Put the handle into DX mov dx,[bx] call [XMSApi] ;Call the API ret ENDP XMSDeAlloc
Pretty simple ain't it? Now if used as a function it'll return 0001h if successful.
Now you've probably asked yourself: "How do I know what error occurs?" Well every XMS function will return an error code into BL if an error happened. There's quite a few error codes so I'll put 'em in a nice table for you all to see!
80h | Function not implemented | 92h | DX less than /HMAMIN= parameter | A4h | SOffset is invalid | ABh | Block is locked |
81h | VDISK device detected | 93h | HMA not allocated | A5h | DHandle is invalid | ACh | Block lock count overflow |
82h | A20 error occured | 94h | A20 line still enabled | A6h | DOffset is invalid | ADh | Lock failed |
8Eh | General driver error occured | A0h | All XMS allocated | A7h | Len is invalid | B0h | Smaller UMB is available |
8Fh | Unrecoverable driver error occured | A1h | All available XMS handles in use | A8h | Move has an invalid overlap | B1h | No UMBs are available |
90h | HMA does not exist | A2h | Handle is invalid | A9h | A parity error occurs | B2h | UMB segment number is invalid |
91h | HMA already in use | A3h | SHandle is invalid | AAh | Block is not locked |
Some of those errors won't apply here so you can ignore them. (Like the ones dealing with the A20 line.) What you could do is make the routine check if an error occured and then it could return that to QB. Although you don't have to.
Alright, now comes moving between XMS and QB. To do this you have to define a data structure at the beginning of your assembly source file like the one below:
MoveXMS struc ;Our main XMS data structure Len dd ? ;number of bytes to transfer SHandle dw ? ;handle of the source block SOffset dd ? ;offset into the source block DHandle dw ? ;handle of the destination block DOffset dd ? ;offset into the destination block MoveXMS ends XMSmove MoveXMS ? ;This makes it so we can use it =)
Using this structure we'll be able to specify what variable you want to move from QB to XMS and vice-versa. The best way to explain this I think is with an example. This sample routine will let you move data from QB to XMS:
XMSPut proc handle:WORD, segm:WORD, offs:WORD, bytes:DWORD, XMSoffs:DWORD mov eax,bytes ;Get the length of the data to move into Len mov XMSmove.Len,eax mov XMSmove.SHandle,0000h ;SHandle must be set to 0000h to move TO XMS. mov bx,offs ;Now we move the offset of the variable to SOffset. mov word ptr [XMSmove.SOffset],bx mov bx,segm ;Then the segment goes to SOffset+2 mov word ptr [XMSmove.SOffset+2],bx mov bx,handle ;Now we must move the destination handle. This is the mov XMSmove.DHandle,bx ;handle which we're moving the data to. mov eax,XMSoffs ;This is the location in the specified handle to move mov XMSmove.DOffset,eax ;the data to. mov si,Offset XMSmove ;Now we get the offset address of the data structure into SI mov ah,0bh ;And make sure that we call function 0Bh call [XMSApi] ;Now call the XMS driver to move the data. ret endp XMSPut
There. That will correctly move data from a variable, array, whatever in QB to the specified XMS handle. DON'T use this function unless you've allocated a block! If the move was successful AX will return 0001h or else it returns 0000h. I would make this routine a function so that you can tell from QB if it was successful or not. If that was too complicated for you than just go over it again slowly until you get it. Now, how do we move data from XMS to a variable in QB? Well we really only have to change a little bit of the above code:
XMSGet proc handle:WORD, segm:WORD, offs:WORD, bytes:DWORD, XMSoffs:DWORD mov eax,bytes ;Get the length to move into Len. mov XMSmove.Len,eax mov bx,handle ;Get the handle of the XMS block with the wanted data into mov XMSmove.SHandle,bx ;SHandle as XMS is now the source. mov eax,XMSoffs ;Also get the offset into the handle into SOffset. mov XMSmove.SOffset,eax mov XMSmove.DHandle,0000h ;DHandle must be 0000h so that we can pass the segment and ;offset of a QB variable to DOffset. mov bx,offs ;Get the offset of the QB variable. mov word ptr [XMSmove.DOffset],bx mov bx,segm ;And the segment. mov word ptr [XMSmove.DOffset+2],bx mov si, Offset XMSmove ;Now get the offset of the data structure into SI. mov ah,0bh ;Use function 0Bh call [XMSApi] ;Call the API! ret endp XMSGet
That will return the same as the other function into AX depending on what happened. As you probably saw we just used function 0Bh like before just we switched some things around. Not that hard, eh? Now all that's left for me to show you this issue is how to Reallocate a XMS block. This is handy if you need more memory for your game or something, I really don't know =). To reallocate a block you call subfunction 0Fh. Heres the commented code:
XMSReAlloc proc handle:WORD, kb:WORD mov ah,0Fh ;Use subfunction 0Fh mov bx,handle ;Get the handle to deallocate into DX. mov dx,[bx] mov bx,kb ;Get the new amount to resize the handle to into BX. call [XMSApi] ;Call the API. mov bx,handle ;Return the handle to QB. mov [bx],ax endp XMSReAlloc
As you can see it's pretty similar to the allocation routine. This like the allocation routine will only allocate up to 64MB. There is a routine that'll allow more (up to 4GB) but I want you to understand these first before you dive in. And thats all I have to say for this issue. Next issue I'll show you the other allocation routines and we'll combine this into a lib you can use. If you still don't understand any of this than don't hesitate to e-mail me
© Copyright 1999-2000 The QB Times.
This tutorial was taken from The QB Times with permission from the webmaster(s) of the site. You may not take this article and place it on any other website.