Array Descriptor

DOS specific questions.
Post Reply
Allen
Posts: 8
Joined: Sep 13, 2008 21:38
Location: U S A

Array Descriptor

Post by Allen »

Using ONLY the QB PEEK function, how would I be able to extract the information of the array descriptor and print each elements. This procedure should be able to print the elements of the array regardless if its 1, 2, or more dimensions.

I'm following the information by Ethan Winer's book 'PC Magazine Basic Technicques and Utilities' it has the description of an array descriptor but no examples.

Example:

DIM Array1$r(1 to 5)
Array1$r(1) = "orange"
Array1$r(2) = "apples"
Array1$r(3) = "pear"
Array1$r(4) = "pineapple"
Array1$r(5) = "orange"

Call PrintArray ( Array1$() )

DIM Array2$r(1 to 5, 1 to 25)
Array2$r(1, 1) = "orange"
.
.
.
Array2$r(5, 25) = "orange"

Call PrintArray ( Array2$() )


Sub PrintArray (Array$())

'-- print the elements using only PEEK regardless dimension size

End Sub
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
Location: SK, Canada

Post by 1000101 »

While generally not recommended to hax the internal structures, it can be done.

Your first stop is in the fbc code tree in the svn on sourceforge (still with me? :P).

This is specifically what you want: http://fbc.svn.sourceforge.net/viewvc/f ... iew=markup
Allen
Posts: 8
Joined: Sep 13, 2008 21:38
Location: U S A

Post by Allen »

I read the article but I was hoping for a QB solution instead of FB.
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
Location: SK, Canada

Post by 1000101 »

Mr. Google probably knows what the qb array descriptor looks like. I've played with the string descriptor (writing textmode libraries for DOS it helps to know what the language descriptors look like), but not arrays.
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

I have experimented with array descriptors, but only from assembly language procedures called from QB. I can’t see any way to create a generic solution for the problem without accessing the array descriptor, and I can’t see any way to get the address of the descriptor in a pure QB application.
Zippy
Posts: 1295
Joined: Feb 10, 2006 18:05

Post by Zippy »

@MichaelW

The fb manual states that varptr() returns the address of a string's descriptor. I've looked briefly at this, am confuzzed by what I see using the return and peek().
stylin
Posts: 1253
Joined: Nov 06, 2005 5:19

Post by stylin »

Allen, if you mean QuickBASIC, then here's what you have to do:

1. In one module, DECLARE a procedure that takes an array like normal. Write some code that calls the procedure.
2. In another module, define a TYPE that matches the structure of the array descriptor.
3. In the same module, define the procedure to take a parameter of this TYPE (instead of an array).
4. Work with the TYPE variable in the procedure to manipulate the array.
5. Compile and link the modules from the command-line (the interpreter will complain).

What's happening is the first module (the one that calls the procedure) will pass the address of the array descriptor, while the procedure in the second module will interpret the stack as having a TYPE variable's address; it's just a big, ugly kind of casting operation (from pointer to array descriptor to pointer to TYPE variable). The QuickBASIC interpreter won't allow you to do this, however.. and I don't believe QBASIC supports multiple modules.

Anyway, I've read Winer's book too (really good), and IIRC he does do some stuff with array descriptors, I think it was some directory searching snippet, or some such. I haven't played around much with the array descriptor, but here's a simple all QB example of how to do the same with string descriptors:

clean.bas: compile with `BC.EXE clean.bas, clean.obj;`

Code: Select all

declare sub ReverseString ( s as string )

dim a as string
a = "abcd"
ReverseString a
print "[" ; a ; "]"
dirty.bas: compile with `BC.EXE dirty.bas, dirty.obj;`

Code: Select all

type QBStringDescriptor
    size as integer
    chardata as integer
end type

sub ReverseString ( desc as QBStringDescriptor )
    print "number of chars in the string: "; desc.size
    print "near offset to character data: "; desc.chardata
    
    dim a as integer, b as integer
    
    ' var a = strptr( the_string )
    ' var b = a + len( the_string ) - 1
    a = desc.chardata
    b = desc.chardata + desc.size - 1
    
    do while a < b
        ' var tmp = *a
        ' *a = *b
        ' *b = tmp
        dim tmp as integer
        tmp = peek( a )
        poke a, peek( b )
        poke b, tmp
        
        ' a += 1 : b += 1
        a = a + 1
        b = b - 1
    loop
end sub
Link modules together with `LINK.EXE clean.obj dirty.obj, program.exe;`

Hope this helps, sorry if I've misunderstood your question.
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

Placing the procedure that accesses the descriptor in a separate QB module to avoid the parameter type checking did not occur to me. The code below is based on the descriptor information here. The main module declares the array and then passes it to the Display procedure in the second module. When an entire array is passed QB passes the array descriptor by reference. From the POV of Display procedure the value passed is a pointer to a variable of type ArrayDescriptorType. The code simply displays the contents of the descriptor.
module1.bas:

Code: Select all

'
' Note the AS ANY, used here to disable type checking.
'
DECLARE SUB Display (arg AS ANY)

DIM a(1, 2) AS STRING

Display a()

DO
LOOP UNTIL INKEY$ <> ""
module2.bas:

Code: Select all

'====================================================================

'---------------------------------------------------------
'' This layout assumes that the array has two dimensions.
'---------------------------------------------------------

TYPE ArrayDescriptorType
  dataOffset                  AS INTEGER
  dataSegment                 AS INTEGER
  farHeapDescriptorPointer    AS INTEGER
  farHeapDescriptorBlockSize  AS INTEGER
  numberOfDimensions          AS STRING * 1
  featureByte                 AS STRING * 1
  adjustedOffset              AS INTEGER
  elementLength               AS INTEGER
  numberOfElements2           AS INTEGER
  firstElement2               AS INTEGER
  numberOfElements1           AS INTEGER
  firstElement1               AS INTEGER
END TYPE

'------------------------------------------
'' Constants for interpreting featureByte.
'------------------------------------------

CONST cFeatureFar = 1
CONST cFeatureHuge = 2
CONST cFeatureStatic = 64
CONST cFeatureString = 128

SUB Display (ad AS ArrayDescriptorType)

    PRINT "dataOffset "; HEX$(ad.dataOffset); "h"
    PRINT "dataSegment "; HEX$(ad.dataSegment); "h"
    PRINT "farHeapDescriptorPointer ";
    PRINT HEX$(ad.farHeapDescriptorPointer); "h"
    PRINT "farHeapDescriptorBlockSize";
    PRINT ad.farHeapDescriptorBlockSize
    PRINT "numberOfDimensions"; ASC(ad.numberOfDimensions)
    IF ASC(ad.featureByte) AND cFeatureFar THEN PRINT "Far",
    IF ASC(ad.featureByte) AND cFeatureHuge THEN PRINT "Huge",
    IF ASC(ad.featureByte) AND cFeatureStatic THEN PRINT "Static",
    IF ASC(ad.featureByte) AND cFeatureString THEN PRINT "String",
    PRINT
    PRINT "adjustedOffset"; ad.adjustedOffset
    PRINT "elementLength"; ad.elementLength
    PRINT "numberOfElements2"; ad.numberOfElements2
    PRINT "firstElement2"; ad.firstElement2
    PRINT "numberOfElements1"; ad.numberOfElements1
    PRINT "firstElement1"; ad.firstElement1

END SUB
makeit.bat (note that this will work only for QuickBASIC 4.5, and that the bin and lib paths will need to be modified to something suitable):

Code: Select all

set binpath=d:\qb45\bin\
set lib=d:\qb45\lib\

:: BC sourcefile[,[objectfile][,listingfile]]][options][;]

%binpath%bc module1.bas,,module1.lst /a /ah /c:512 /e /o /t /x;

pause

%binpath%bc module2.bas,,module2.lst /a /ah /c:512 /e /o /t /x;

pause

:: LINK [options] objfiles [,[exefile] [,[mapfile] [,[libraries] [,[deffile]]]]][;]

%binpath%link /ex /noe /nod:brun45.lib /inf module1.obj module2.obj,test,,bcom45.lib qb.lib;

pause

Code: Select all

dataOffset 36h
dataSegment 924h
farHeapDescriptorPointer 0h
farHeapDescriptorBlockSize 0
numberOfDimensions 2
Static        String
adjustedOffset 54
elementLength 4
numberOfElements2 3
firstElement2 0
numberOfElements1 2
firstElement1 0
stylin
Posts: 1253
Joined: Nov 06, 2005 5:19

Post by stylin »

It would probably be best split ArrayDescriptorType into two TYPEs, the main descriptor structure and the variable-count dimension info structure. FreeBASIC's implementation does this as well.

Also, I cannot remember if array() as any is a valid parameter declaration -- I think it is, but it's been awhile -- but if so, it would be preferrable to arg as any.. it's nice to let the compiler do some work for you. :)
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

I agree that it would be better to split ArrayDescriptorType into two structures, but I was coding in quick and dirty mode. The reason I used AS ANY is because I wanted the code to work with an array of any type, but I see now that the declaration should have been:

DECLARE SUB Display (array() AS ANY )
Allen
Posts: 8
Joined: Sep 13, 2008 21:38
Location: U S A

Post by Allen »

Your Display subprogram is exactly what I am trying to achieve. I had to make some modification to my original code. Instead of passing just the array itself, I created a type structure to hold, hopefully the correct address pointer.

I have posted this same question on other forums and even wrote to Ethan Winer asking him for some example of his array descriptor but I have yet to receive any response. I'm hoping you guys could find the answer before then.

Sorry for this unorthodox question.

Code: Select all

Type ArrayDescriptorType
  dataOffset                  As Integer
  dataSegment                 As Integer
  farHeapDescriptorPointer    As Integer
  farHeapDescriptorBlockSize  As Integer
  numberOfDimensions          As String * 1
  featureByte                 As String * 1
  adjustedOffset              As Integer
  elementLength               As Integer
  numberOfElements2           As Integer
  firstElement2               As Integer
  numberOfElements1           As Integer
  firstElement1               As Integer
End Type

Type ArrayType
  Address As Long
End Type

Dim Descr as ArrayType
Dim Array$(1 To 50)

Descr.Address = VARPTR(Array$(1))     <-- THIS IS JUST A GUESS

Display Descr

Sub Display(Descr As ArrayType)

  Dim AD As ArrayDescriptorType

  DescAdd& = Descr.Address
  
  AD.dataOffset = PEEK(DescAdd&)
  AD.dataSegment = PEEK(DescAdd& + 2)
    .
    .

  '-- PRINT THE AD ELEMENT INFORMATIONS HERE
  PRINT "      DATA OFFSET: "; AD.dataOffset
  PRINt "SEGMENT OFFSET: "; AD.dataSegment
    .
    .
  PRINT "FIRST ELEMENT1: "; AD.firstElement1


  '-- PRINT THE STRING ELEMENTS HERE REGARDLESS OF THE 
  '    NUMBER OF DIMENSIONS
  DEF SEG = AD.dataSegment
  Offset& = AD.dataOffset

  FOR I = 1 TO AD.numberOfElements1
    FOR J = 1 TO StringLength                   <--- JUST GUESSING HERE
      PRINT CHR$(PEEK(Offset&));
      Offset& = Offset& + 1
    NEXT
    PRINT
  NEXT

  '-- I'M JUST GUESSING AT THIS POINT BUT THIS IS BASICALLY WHAT
  '    I AM AFTER THE ABILITY TO PRINT THE ELEMENTS OF THE ARRAY
  '    USING ONLY PEEK WITHOUT PASSING THE ARRAY ITSELF.

End Sub
Coxebo
Posts: 1
Joined: Nov 19, 2019 17:55

Re: Array Descriptor

Post by Coxebo »

Okay folks, here's how I got to the array descriptor of a QBasic array. Unsurprisingly it uses the same format as QuickBasic.

Code: Select all

FUNCTION ArrPtr& (X() AS STRING)
DEF SEG = VARSEG(ArrPtr&)
ArrPtr& = PEEK(VARPTR(ArrPtr&) + 15) * 256& + PEEK(VARPTR(ArrPtr&) + 14) + VARSEG(ArrPtr&) * 16&
END FUNCTION
This will return a flat pointer to the array descriptor. It works by abusing the fact that the function return value and the function arguments must reside in the same stack frame, which will have the same layout every time. You can use the returned pointer like so:

Code: Select all

SUB DumpArr (A() AS STRING)
p& = ArrPtr(A())
DEF SEG = p& \ 16
p% = p& AND 15
PRINT HEX$(p&); " "; HEX$(PeekW(p% + 2)); ":"; HEX$(PeekW(p%)); " FHD[Ptr="; HEX$(PeekW(p% + 4)); " BS="; HEX$(PeekW(p% + 6)); "] flags="; HEX$(PEEK(p% + 9)); " (0)="; HEX$(PeekW(p% + 10)); " ES="; HEX$(PeekW(p% + 12));
i% = PEEK(p% + 8)
IF i% - 1 AND -16 THEN
    PRINT " dims="; HEX$(i%)
ELSE
    PRINT " (";
    i% = i% * 4 + p% + 10
    DO
        PRINT LTRIM$(STR$(PeekW(i% + 2))); " TO"; STR$(PeekW(i%) - 1 + PeekW(i% + 2));
        i% = i% - 4
        IF i% < p% + 14 THEN EXIT DO
        PRINT ", ";
    LOOP
    PRINT ")"
END IF
END SUB

FUNCTION PeekW% (o%)
PeekW% = ((PEEK(o% + 1) XOR 128) - 128) * 256 OR PEEK(o%)
END FUNCTION
Have fun hacking everyone!
Post Reply