WRITING YOUR OWN EXTENSIONS TO MAXLIB It isn't hard to write your own custom extensions to MAXLIB, once you get a bit of knowledge and some encouragement. This section discusses tactics and gives extensive examples for writing several of the most commonly requested extensions, including creating 2D arrays and using unsigned integers as array indices. The examples use XMSArray, but will work just as well with EMSArray, just by substituting EMS for XMS in the names of routines. The basic trick to writing your own extensions is to write "wrapper" code around XMSArray, so that it hides any manipulations required to extend XMSArray and give it new capabilities. The simplest extension to understand is probably that which lets you dimension and access arrays using unsigned integers as indices. Such wrapper code might look like this: Handle% = DimXMS%((CvIn(1), CvIn(65535), Length%) InXMS Handle%, CvIn(50000), InValue FirstElement?? = CvOut(LBoundXMS%(Handle%)) LastElement?? = CvOut(UBoundXMS%(Handle%)) FUNCTION CvIn (Unsigned??) AS INTEGER CvIn = CINT(UnSigned?? - 32768) END FUNCTION FUNCTION CvOut (IsSigned%) AS WORD CvOut = CWRD(IsSigned% + 32768) END FUNCTION The CvIn function remaps the unsigned integer range (0 to 65535) onto the signed integer range (-32768 to 32767), by subtracting 32768; CvOut maps signed back to unsigned. You can use these same wrappers with PowerBASIC arrays, to simulate unsigned indices. You can also use a wrapper (actually, a set of them) to dimension arrays in XMSArray with more than 64K-1 elements in them. You can accomplish this by taking advantage of the fact that XMSArray only accepts fixed length data. For example, you might want to dimension one very enormous array to hold a lot of 10 byte long strings, like this: Handle% = DimXMS%(1, 1024000, 10) '1024000 * 10 is too big!! But XMSArray won't let you! Not directly, at least. However, it will let you dimension this next array of the same size. Handle% = DimXMS%(1, 32000, 320) '32000 * 320 is OK! The rest of the trick is very simple. Both XMSArray and EMSArray treat your source or target variable as a mere address in memory, where they will read or write the proper number of bytes. (This comes in very handy, as you will see in all the following examples!) In this case, because you know the length of each piece of data you are seeking, and the length of each element in the XMS array, you can calculate which of the 320 byte elements holds the particular 10 bytes of data you want to access, and where within that 320 byte element it is positioned. This example code dimensions an array of 32000 elements of 320 bytes each, and shows some wrapper code for putting a value into a particular 10 byte string from that array, then retrieving it again: InitXMSArray Handle% = DimXMS%(1, 32000, 320) DIM MyString AS STRING * 10 MyString = "Some data!" StringIn MyString, Handle%, 102400 ' put 1024000th string StringOut MyString, Handle%, 102400 ' get 1024000th string SUB StringIn (StrngIn AS STRING * 10, Handle%, Which&) DIM TempArry(0 TO 31) AS STATIC STRING * 10 RealElement% = CINT(Which& \ 32) 'integer division IF ISTRUE(Which& MOD 32) THEN INCR RealElement% OutXMS TempArry(0), Handle%, RealElement% TempTarget% = (Which& - 1) MOD 32 TempArry(TempTarget%) = StrngIn InXMS Handle%, RealElement%, TempArry(0) END SUB SUB StringOut (StrngOut AS STRING * 10, Handle%, Which&) DIM TempArry(0 TO 31) AS STATIC STRING * 10 RealElement% = CINT(Which& \ 32) 'integer division IF ISTRUE(Which& MOD 32) THEN INCR RealElement% 'adjust it OutXMS TempArry(0), Handle%, RealElement% TargetString% = (Which& - 1) MOD 32 StrngOut = TempArry(TargetString%) END SUB Notice that the code creates a temporary array that contains as many bytes (32 elements * 10 bytes) as one 320 byte element in the array, and uses it as the target "variable" for InXMS and OutXMS. This makes it possible to extract the right 10 byte string, without using MID$, just by referencing an index of the temporary array. Similar techniques can be used to write to the array. What about 2-dimensional arrays? The simplest way to create 2-dimensional arrays with XMSArray is to "hard wire" some wrappers that are based directly on the known data type and known dimensions of these arrays. Here's a very complete example of some hard wired code that creates a large 2d array with 140000 elements (apparently). It even incorporates the CvIn and CvOut functions from earlier: '--------------------- START CODE --------------------------- $LINK ".\MAXLIB.PBL" ' <-- assumes current directory $INCLUDE ".\MAXLIB.BI" ' <-- assumes current directory TYPE BigType 'this could be any TYPE One AS STRING * 5 'with an even numbered length Two AS String * 5 END TYPE DECLARE FUNCTION DimBigMatrix% () DECLARE SUB InBig (Handle%, Dim1??, Dim2%, InValue AS BigType) DECLARE SUB OutBig (Handle%, Dim1??, Dim2%, OutValue AS BigType) DECLARE FUNCTION CvIn (Unsigned??) AS INTEGER DECLARE FUNCTION CvOut (IsSigned%) AS WORD InitXMSArray '<-- required BigMatrix% = DimBigMatrix% 'dims BigMatrix(1 TO 35000, 1 TO 4) DIM BigVar AS BigType 'BigType is 10 bytes long BigVar.One = "zZzzX" ' use random data BigVar.Two = "99331" InBig BigMatrix%, 33000, 2, BigVar DIM BigVar2 AS BigType ' this is uninitialized - empty OutBig BigMatrix%, 33000, 2, BigVar2 ' fill it PRINT BigVar2.One ' prints "zZzzX" PRINT BigVar2.Two ' prints "99331" END '============================================================ FUNCTION DimBigMatrix% 'all the dimensions are hard-coded '============================================================ ElementSize% = 40 '4 elements in 2d * LEN(BigType) First% = CvIn(1) 'remap unsigned integers Last% = CvIn(35000) DimBigMatrix% = DimXMS%(First%, Last%, ElementSize%) END FUNCTION '========================================================== SUB InBig (Handle%, Dim1??, Dim2%, InValue AS BigType) '========================================================== ' This puts InValue into BigMatrix array at (Dim1, Dim2) DIM Dim2Arry (1 TO 4) AS STATIC BigType RealElement% = CvIn(Dim1??) 'remap the unsigned int OutXMS Dim2Arry(1), Handle%, RealElement% Dim2Arry(Dim2%) = InValue InXMS Handle%, RealElement%, Dim2Arry(1) END SUB '========================================================== SUB OutBig (Handle%, Dim1??, Dim2%, OutValue AS BigType) '========================================================== ' Gets the value at BigMatrix(Dim1, Dim2) into OutValue DIM Dim2Arry (1 TO 4) AS STATIC BigType RealElement% = CvIn(Dim1??) 'remap the unsigned int OutXMS Dim2Arry(1), Handle%, RealElement% OutValue = Dim2Arry(Dim2%) END SUB FUNCTION CvIn (Unsigned??) AS INTEGER CvIn = CINT(UnSigned?? - 32768) END FUNCTION FUNCTION CvOut (IsSigned%) AS WORD CvOut = CWRD(IsSigned% + 32768) END FUNCTION '------------------------- END CODE --------------------------- Notice that the FUNCTION DimBigMatrix creates a 1-dimensional array to simulate a 2-dimensional array. The "real" array contains the same number of elements as the first dimension of the simulated 2-dimensional array. Also note, that each element in the "real" array is big enough to hold all the elements in the simulated second dimension. The SUB InBig uses a temporary array that is exactly the same size (4 elements * 10 bytes) as each element of the 1-dimensional array created by DimBigArray. (Gee, haven't we seen this trick somewhere else?) First, InBig fills the temporary array with all the current values from one 40 byte element, then it changes only that part of the temporary array that corresponds to the 2-dimensional element we need to change. Then it writes the temporary array back into XMS, using it as the source "variable" for InXMS. This "hard-wired" method works best when you have just a few arrays, with known dimensions and data types. Then you only have a few hard-wired SUBS and FUNCTIONS to write. Even better would be to write a set of wrappers that let you dimension and access every kind of 2-dimensional array, always using the same all-purpose code. As it happens, I have some fairly complete example code that shows how to do exactly that. It requires PB 3.1 or better, and this code is limited to a total number of elements of 64K-1. To calculate total elements, multiply the elements in the first dimension by the elements in the second dimension. The code, once more, takes advantage of the fact that, when you pass a variable to InXMS or OutXMS, they treat that variable purely as an address in memory -- as a target for OutXMS, or a source for InXMS. In this case, In2XMS and Out2XMS use a buffer that can hold a varying number of bytes of data. By using the buffer as an intermediate holding area for the data as it is written to or read from the array, the code becomes freed from knowing beforehand how many bytes it must handle. To enlarge or shrink the buffer, you only need to change the value of %MAXSIZE, which represents the largest single element the code can handle. The other thing to note is that the handle returned by Dim2XMS is actually a TYPE variable that contains a variety of pertinent information, instead of just an integer. I'll leave it to you to experiment with the more advanced features of XMSArray, like RedimXMS or FileInXMS. You only need to think through how they would need to be modified to work with the following code. '-------------------------- START CODE ---------------------------- $LINK ".\MAXLIB.PBL" '<--- current directory: is this right? $INCLUDE ".\MAXLIB.BI" '<--- current directory: is this right? TYPE TwoDimHandle FirstUBound AS INTEGER SecondUBound AS INTEGER Multiplier AS WORD Length AS INTEGER MaxHandle AS INTEGER END TYPE DECLARE SUB Dim2XMS (Handle AS TwoDimHandle, A1%, B1%, A2%,_ B2%, ElemLen%) DECLARE SUB In2XMS (Handle AS TwoDimHandle, Index1%, Index2%, ANY) DECLARE SUB Out2XMS (ANY, Handle AS TwoDimHandle, Index1%, Index2%) DECLARE SUB Erase2XMS (Handle AS TwoDimHandle) %ERRORTOOMANY = -5 'MAXLIB's internal "too many elements" error %MAXSIZE = 100 'individual elements must be <= %MAXSIZE '//////////////////////// BEGIN MAIN \\\\\\\\\\\\\\\\\\\\\\\\\\ InitXMSArray 'Create the equivalent of DIM Array2 (1 TO 16000, 1 TO 4) AS WORD DIM Array2 AS TwoDimHandle Dim2XMS Array2, 1, 16000, 1, 4, 2 IF ErrorCode% = %ERRORTOOMANY THEN PRINT "Hey! That's too big an array!" END END IF DIM InValue AS WORD DIM OutValue AS WORD InValue = 56789 'Do the equivalent of Array2(123, 4) = InValue In2XMS Array2, 123, 4, InValue 'Do the equivalent of OutValue = Array2(123, 4) Out2XMS OutValue, Array2, 123, 4 PRINT OutValue 'did it work? should PRINT 56789 IF OutValue = InValue THEN PRINT "Succeeded." ELSE PRINT "Failed." END IF Erase2XMS Array2 END '///////////////////////// END OF MAIN \\\\\\\\\\\\\\\\\\\\\\\\\ '==================================================================== SUB Dim2XMS (Handle AS TwoDimHandle, A1%, B1%, A2%, B2%, ElementLen%) '==================================================================== TotalFirstDim& = (B1% - A1%) + 1 TotalSecondDim& = (B2% - A2%) + 1 TotalElements& = TotalFirstDim& * TotalSecondDim& IF TotalElements& > 65535& THEN SetErrorCode %ERRORTOOMANY ELSE Handle.FirstUBound = A1% Handle.SecondUBound = A2% Handle.Length = ElementLen% Handle.Multiplier = CINT(TotalSecondDim&) RealStart% = A1% - Handle.FirstUBound - 32768 RealEnd% = CINT(TotalElements& - 32767 + 1) Handle.MaxHandle = DimXMS%(RealStart%, RealEnd%, ElementLen%) END IF END SUB '================================================================= SUB In2XMS (Handle AS TwoDimHandle, Index1%, Index2%, ANY) '================================================================= ' If you are going to use this SUB to access elements longer than ' 100 bytes, you'll need to increase the buffer size. DIM BUFFER AS STRING * %MAXSIZE DIM BufSeg AS WORD DIM BufOff AS WORD Length% = Handle.Length ' how long is our ANY parameter? BufSeg = VARSEG(BUFFER) ' where is our buffer? BufOff = VARPTR(BUFFER) ! Push DS ; save required registers ! Push SI ! Push DI ! Mov AX, BufSeg ! Mov ES, AX ! Mov DI, BufOff ; load ES:DI with address of BUFFER ! Lds SI, [BP+6] ; load DS:SI with address of ANY parameter ! Mov CX, Length% ; load CX with number of bytes to move ! Rep Movsb ; copy CX bytes of ANY into BUFFER ! Pop DI ; restore the required registers ! Pop SI ! Pop DS FirstPlace% = (Index1% - Handle.FirstUBound) + 1 SecondPlace% = (Index2% - Handle.SecondUBound) + 1 RealIndex% = -32768 + (FirstPlace% * Handle.Multiplier) + SecondPlace% 'move value from BUFFER into XMS array InXMS Handle.MaxHandle, RealIndex%, BUFFER END SUB '================================================================== SUB Out2XMS (ANY, Handle AS TwoDimHandle, Index1%, Index2%) '================================================================== DIM BUFFER AS STRING * %MAXSIZE DIM BufSeg AS WORD DIM BufOff AS WORD FirstPlace% = (Index1% - Handle.FirstUBound) + 1 SecondPlace% = (Index2% - Handle.SecondUBound) + 1 RealIndex% = -32768 + (FirstPlace% * Handle.Multiplier) + SecondPlace% OutXMS BUFFER, Handle.MaxHandle, RealIndex% Length% = Handle.Length ' how long is our ANY parameter? BufSeg = VARSEG(BUFFER) ' where is our buffer? BufOff = VARPTR(BUFFER) ! Push DS ; save required registers ! Push SI ! Push DI ! Les DI, [BP+18] ; load ES:DI with address of ANY parameter ! Mov SI, BufOff ! Mov AX, BufSeg ! Mov DS, AX ; load ES:DI with address of BUFFER ! Mov CX, Length% ; load CX with number of bytes to move ! Rep Movsb ; copy CX bytes of BUFFER into ANY param ! Pop DI ; restore the required registers ! Pop SI ! Pop DS END SUB '======================================== SUB Erase2XMS (Handle AS TwoDimHandle) '======================================== 'this could hardly be simpler: EraseXMS Handle.MAXHandle END SUB '-------------------------- END CODE ----------------------------