

    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 ----------------------------

