Rapid-Q Documentation by William Yu (c)1999 Appendix C: User Defined Types

Introduction to UDTs

User defined types (or UDTs) are structures that you can use in most BASIC languages, Rapid-Q is no exception. You can think of a UDT as a list of variables in a specific order.

Using TYPE
Here's an example of a user defined type:
    TYPE TWorld
CityName(100) AS STRING
Population(100) AS LONG
END TYPE

DIM World AS TWorld
As you can see, you can have arrays within UDTs. You can also create arrays of UDTs:
    TYPE TWorld
CityName(100) AS STRING
Population(100) AS LONG
END TYPE

DIM Country(100) AS TWorld
Sample code would use it something like this:
Country(1).CityName(15) = "Seattle"
Country(1).Population(15) = 2343589
There is currently an issue with the memory address of your array (within UDT). Please note that this is hardly ever noticed unless you're going to pass one of these structures to a DLL that require arrays within UDTs. This address problem won't affect your program, but it's hard to explain (without getting too deep). NOTE: it only occurs when you create arrays of UDTs that have arrays within UDTs. In the first example (higher up), there is no address problem since you're not creating an array of UDTs. You can safely pass that to a DLL.
 

What is not supported
Unlike other languages such as Visual Basic, Pascal, C/C++, you cannot "nest" a UDT inside another UDT.

TYPE POINT
    X as INTEGER
    Y as INTEGER
END TYPE

TYPE Rectangle
    Top     AS POINT
   Bottom   AS POINT        'this won't work
END TYPE

'You would have to write the code this way...
TYPE Rectangle
    TopX     AS INTEGER
    TopY     AS INTEGER
    BottomX  AS INTEGER
    BottomY  AS INTEGER
END TYPE

Using this method, a problem is that you have to be careful not to repeat variable names from the nested UDT. Unfortunately this makes for some messy code when working with header files, Windows API calls, and  and source code from other languages.

When you make a UDT and pass it to a DLL, you must have the UDT allocated in memory contiguously. You can use  QMEMORYSTREAM and its pointer to make sure it is correctly formatted. You can try pass arrays of UDTs by writting multiple UDTs to a single memory stream. Please see: chapter9.html #97

  UDTs and memory pointers
When you pass a UDT to a SUB, FUNCTION, or DLL, you will pass the pointer to the structure. If you read chapter 9 you will know how to pack the UDT into one contiguous block of memory into a QMEMORYSTREAM and get its pointer by QMEMORYSTREAM.Pointer property. Unlike C/C++you can't get the pointer to each element of the UDT, you will have to calculate an offset if you need to get a pointer. This is shown in the next examples

TYPE dataType
   x          AS INTEGER   '4 bytes
   y          AS INTEGER   '4 bytes
   z(300)     AS SINGLE    'only 4 bytes allocated (just an array pointer?)
END TYPE

So how do you store all the data in the array into one block of memory? The fix is to save the UDT to a QMEMORYSTREAM

DIM MyUDT AS dataType
DIM Mem AS QMemoryStream
Mem.WriteUDT(MyUDT)

'you can also copy the array to another arry if needed
Mem.position=8   'go to the start of the array
Mem.LoadArray(NewArray(3),13)

Example#2 How to get parts of the UDT (also see help on "Pointers")

TYPE MyType
   Name AS STRING*30
   Phone AS STRING*8
   Age AS INTEGER
   z(1 to 3)  AS SINGLE
END TYPE

DIM Person AS MyType
Person.Name = "Joe"
Person.Phone = "555-5555"

DIM Mem AS QMemoryStream
Mem.WriteUDT(Person)
ShowMessage str$(Mem.Pointer)  
ShowMessage str$(sizeof(Person)) 
Person.z(1) = 2.23451
ShowMessage str$(Person.z(1)) 
showmessage str$(varptr(Person.Age))  'this doesn't work
showmessage str$(@Person.Age)          'gets the value of the variable
'manually need to calculate offset!!!
ShowMessage str$(Mem.Pointer + 38 + SizeOf(INTEGER))


Variable length strings and Object pointers are not supported

Only fixed and simple data types (like INTEGER, SINGLE, LONG, STRING *12) can be inside a UDT. For example, the next code sement will not compile..

TYPE MyUDT
    a  AS QRECT
    b  AS QD3DVector
    c  AS STRING
END TYPE

BUT if you define the type with EXTENDS it works...

TYPE newT  EXTENDS  QOBJECT
    a  AS QRECT
    b  AS QD3DVector
    c  AS STRING
END TYPE

Now your code runs ok, but you will probably run into bugs if you use the UDT methods:
Qmemorystream.ReadUDT(newT)
Qmemorystream.WriteUDT(newT)

--And you CAN'T make arrays of them --

DIM NewObj(1 to 100) as newT       'this compiles
NewObj(1).left = 10                'compiler error

So how do you make an array of custom components? You can try QCANVAS instead. It is the only Qcomponent that appears to allow arrays when EXTENDED. 

$typecheck on
TYPE NewT EXTENDS QCANVAS
    a as integer
    b as string
    c as Qd3dVector
    d as Qbitmap
    e as single
    'd(1 to 5) as QBitmap        'can't have arrays of objects in an object
    e(1 to 5) as single             'errors occur with nested arrays however, 
END TYPE


dim NewObj(1 to 6) as NewT
NewObj(2).a = 2
NewObj(2).b = "aaaaaaaaaaaaaaaaaahhh"
NewObj(1).c.x = 1
NewObj(1).c.dvx = 1
'NewObj(6).d(2).Width = 60 'bug
'NewObj(6).d(1).Height = 60 'bug
'NewObj(1).e(1) = 1.0009 'bug
NewObj(6).d.Width = 60
NewObj(6).d.Height = 60
NewObj(1).e = 1.0009

Still you can't use arrays inside custom components, you will have
to use the template like in this example (see the docs)

TYPE Arrays<DataType, Size> EXTENDS QOBJECT
ArrayItem(Size) AS DataType
and to call it in code

TYPE MyObject<SINGLE, Size> EXTENDS QOBJECT
ArrayItem(Size) AS DataType
... methods/subs/functions here
END TYPE

DIM myVar AS MyObject<SINGLE, 1000>

MyVar.ArrayItem(999) = 1.234


---------------------------------------
Copying UDTs
To copy 2 UDTs, just assign the two:

    TYPE TStuff
A AS LONG
B AS INTEGER
END TYPE

DIM S1 AS TStuff
DIM S2 AS TStuff

S1 = S2
Contents in S2 are copied to S1. When dealing with arrays within UDTs, the arrays are not copied, only referenced. To copy arrays of UDTs, you'll have to manually copy each one:
    DIM S1(100) AS TStuff
DIM S2(100) AS TStuff

FOR I = 1 TO 100
S1(I) = S2(I)
NEXT


Unhooking TYPE There are actually 2 ways to create your types, with TYPE and STRUCT. STRUCT is actually mirrored in TYPE, to unhook the two, you have to undefine TYPE.
    $UNDEF TYPE

TYPE TWorld
CityName(100) AS STRING
Population(100) AS LONG
END TYPE

DIM World AS TWorld
Using TYPE and STRUCT are not the same. If you unhook the two, TYPE now reverts back to the old style (pre-May 17 version), where arrays of UDTs is not allowed. Why the heck would I want a stripped down version? Well, perhaps performance. The old style TYPE lacks in many things, but makes up in performance, if that's really necessary.


<- Branching Contents Conditions ->