Using XMS in QB

This article was written by Fling-master

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. "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 [XMSControl]                   ;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! Then other programs won't be able to use that block until you restart your computer! 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 [XMSControl]                  ;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 [XMSControl]                  ;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 [XMSControl]                 ;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 [XMSControl]                 ;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 [XMSControl]                 ;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!

All site content is © Copyright 2001, HyperRealistic Games. This excludes content submitted by other people.