Arrays as UDTs - Pete - 02-01-2026
@Unseen Machine
So I'm taking a look at John's demo here: https://qb64phoenix.com/forum/showthread.php?tid=4335&pid=38857#pid38857
It took me back to my Atari Basic days. Atari had well supported peek/poke documentation. Couldn't do exactly what you wanted with the keywords? Poke it! So it seems John took a similar approach to pass arrays as _Offset.
It's neat that it didn't require a lot of new stuff under the hood to accomplish this, a little over 3K or is that UK where John hails form?
I want to take his example and try it out on something SCREEN 0 simple. I think I'm suffering from heat exhaustion today. Played some golf, but should have gone to the beach, instead. Oh well, beats ice fishing.
Anyway, I'll reply back in this thread if I have any questions for John, or form any helpful insights for others. Until then, I suggest we all jut use COMMON SHARED for all variables... mostly because that really pisses off Bill.
Pete
RE: Arrays as UDTs - Pete - 02-02-2026
What I gleaned from Unseen... @Unseen Machine
Code: (Select All)
'$Include:'qb_universal.bi'
Dim MemSystem As _Offset
MemSystem = Var_New ' Library function.
text$(1) = "Q: What's the difference between a monkey and Steve?"
text$(2) = "A: A monkey eats the banana and the peel. Steve just eats the peel."
Dim m As _Offset
m = Var_New: Var_SetString m, text$(1): Var_Push MemSystem, m
m = Var_New: Var_SetString m, text$(2): Var_Push MemSystem, m
pete MemSystem
Sleep
Var_Free MemSystem
End
Sub pete (MemSystem As _Offset)
For i = 0 To 1
Dim node As _Offset: node = Var_At(MemSystem, i)
t$ = Var_GetString$(node)
Print t$
Next
End Sub
So by checking the output we can tell your system is totally valid!
Pete
QB_UNIVERSAL.h (Size: 2.09 KB / Downloads: 4)
QB_Universal.bi (Size: 799 bytes / Downloads: 2)
RE: Arrays as UDTs - Pete - 02-02-2026
Code: (Select All)
'$Include:'qb_universal.bi'
Dim MemSystem As _Offset
MemSystem = Var_New ' Library function.
Dim p As _Offset, c As _Offset
Dim fruit$(4), meat$(3)
fruit$(1) = "apple": fruit$(2) = "orange": fruit$(3) = "pear": fruit$(4) = "banana": meat$(1) = "steak": meat$(2) = "chicken": meat$(3) = "bacon"
p = Var_New: Var_SetString p, "Fruits": Var_Push MemSystem, p
For i = 1 To UBound(fruit$): c = Var_New: Var_SetString c, fruit$(i): Var_Push p, c: Next
p = Var_New: Var_SetString p, "Meats": Var_Push MemSystem, p
For i = 1 To UBound(meat$): c = Var_New: Var_SetString c, meat$(i): Var_Push p, c: Next
pete MemSystem
Sleep
Var_Free MemSystem
End
Sub pete (MemSystem As _Offset)
Dim node As _Offset, p As _Offset
For i = 0 To Var_Count(MemSystem) - 1
node = Var_At(MemSystem, i)
t$ = Var_GetString$(node)
Print "Parent> "; t$
p = Var_At(MemSystem, i)
For j = 0 To Var_Count(p) - 1
t$ = Var_GetString$(Var_At(p, j))
Print "Child>>> "; t$
Next
Next
End Sub
So @Unseen Machine am I getting the use of nodes correct here?
Pete
Edit: Well this part didn't work out. I put the parent array in a function and the output superimposes the meat over the fruit! I used "Fruit123" to make it easier to see. So the parent array "Meat" is displayed as: Meatt123
Code: (Select All)
'$Include:'qb_universal.bi'
Dim MemSystem As _Offset
MemSystem = Var_New ' Library function.
Dim p As _Offset
Dim fruit$(4), meat$(3)
fruit$(1) = "apple": fruit$(2) = "orange": fruit$(3) = "pear": fruit$(4) = "banana": meat$(1) = "steak": meat$(2) = "chicken": meat$(3) = "bacon"
p = Parent_Node("Fruit123", MemSystem): Child_Node fruit$(), p
p = Parent_Node("Meat", MemSystem): Child_Node meat$(), p
pete MemSystem
Sleep
Var_Free MemSystem
End
Function Parent_Node%& (Parent As String, MemSystem As _Offset)
Dim a As _Offset
a = Var_New: Var_SetString a, Parent: Var_Push MemSystem, a: Parent_Node = a
End Function
Sub Child_Node (array$(), p As _Offset)
Dim c As _Offset
For i = 1 To UBound(array$)
c = Var_New: Var_SetString c, array$(i): Var_Push p, c
Next
End Sub
Sub pete (MemSystem As _Offset)
Dim node As _Offset, p As _Offset
For i = 0 To Var_Count(MemSystem) - 1
node = Var_At(MemSystem, i)
t$ = Var_GetString$(node)
Print "Parent> "; t$
p = Var_At(MemSystem, i)
For j = 0 To Var_Count(p) - 1
t$ = Var_GetString$(Var_At(p, j))
Print "Child>>> "; t$
Next
Next
End Sub
Now if I take the function and move it into the main, it works...
Code: (Select All)
'$Include:'qb_universal.bi'
Dim MemSystem As _Offset
MemSystem = Var_New ' Library function.
Dim p As _Offset
Dim fruit$(4), meat$(3)
fruit$(1) = "apple": fruit$(2) = "orange": fruit$(3) = "pear": fruit$(4) = "banana": meat$(1) = "steak": meat$(2) = "chicken": meat$(3) = "bacon"
Dim a As _Offset
a = Var_New: Var_SetString a, "Fruit123": Var_Push MemSystem, a: Parent_Node = a: p = a: Child_Node fruit$(), p
a = Var_New: Var_SetString a, "Meat": Var_Push MemSystem, a: Parent_Node = a: p = a: Child_Node meat$(), p
pete MemSystem
Sleep
Var_Free MemSystem
End
Sub Child_Node (array$(), p As _Offset)
Dim c As _Offset
For i = 1 To UBound(array$)
c = Var_New: Var_SetString c, array$(i): Var_Push p, c
Next
End Sub
Sub pete (MemSystem As _Offset)
Dim node As _Offset, p As _Offset
For i = 0 To Var_Count(MemSystem) - 1
node = Var_At(MemSystem, i)
t$ = Var_GetString$(node)
Print "Parent> "; t$
p = Var_At(MemSystem, i)
For j = 0 To Var_Count(p) - 1
t$ = Var_GetString$(Var_At(p, j))
Print "Child>>> "; t$
Next
Next
End Sub
So is something buggy or did I miss something in the function?
RE: Arrays as UDTs - SMcNeill - 02-03-2026
If these are calling out to C-style functions, they may need to be CHR$(0) null terminated.
RE: Arrays as UDTs - Unseen Machine - 02-03-2026
(02-03-2026, 02:28 AM)SMcNeill Wrote: If these are calling out to C-style functions, they may need to be CHR$(0) null terminated. @SMcNeill Thats probably it, ill have a look for you @Pete tomorrow mate! Thanks for giving it a blast though! Makes me happy to have finally made something useful!
Unseen
RE: Arrays as UDTs - Kernelpanic - 02-03-2026
"Arrays as UDTs," what do you mean by that? Do you mean record arrays?
RE: Arrays as UDTs - Pete - 02-03-2026
STEVE REALLY IS AMAZING! Even if he only eats the peel.
So I added Parent = Parent + Chr$(0) to the function, and it works as intended. No more fruit in Pete's meat. Ah, forget that last part.
I still think some things need to be investigated. For starters, we don't have to add a terminal chr$(0) to the child process, only the parent one. Next, I still don't understand that the chr$(0) is not needed if the Var_ functions are moved out of the QB4 Parent_Node%& function, and into the main. That behavior is the type of inconsistency certain to create problems for other users. (Except for non-functional users). 
To @Kernalpanic this started out as a way to get arrays in user define types. This is close. We still cannot call our array mytype.array() and pop it in the type definition with the rest of the mytype variables, but we can pass all of our arrays as a single array() name in our subs or functions. So no more: Sub ThisSub (mytype as udts, FruitArray$(), MeatArray$(), VeggieArray$(), DairyArray$()). Now we just use: Sub ThisSub (mytype as udts, MemSystem As _Offset).
Now for practical use, I'll have to weigh the benefit above with the fact we have to unpack that memsystem to use those arrays. That will be somewhat involved, but until I try it out on some complex stuff, I won't know just how involved. One concern I see is will I have to unpack it for every sub call in a program that would otherwise share arrays in most of the subroutines.
Anyway, thanks to Steve, here is the working code without the parent string overlap problem...
Code: (Select All)
'$Include:'qb_universal.bi'
Dim MemSystem As _Offset
MemSystem = Var_New ' Library function.
Dim p As _Offset
Dim fruit$(4), meat$(3)
fruit$(1) = "apple": fruit$(2) = "orange": fruit$(3) = "pear": fruit$(4) = "banana": meat$(1) = "steak": meat$(2) = "chicken": meat$(3) = "bacon"
p = Parent_Node("Fruit123", MemSystem): Child_Node fruit$(), p
p = Parent_Node("Meat", MemSystem): Child_Node meat$(), p
pete MemSystem
Var_Free MemSystem
End
Function Parent_Node%& (Parent As String, MemSystem As _Offset)
Dim a As _Offset
Parent = Parent + Chr$(0) ' C null terminator. <----- Required when a 'Parent' string is sent to a sub or function.
a = Var_New: Var_SetString a, Parent: Var_Push MemSystem, a: Parent_Node = a
End Function
Sub Child_Node (array$(), p As _Offset)
Dim c As _Offset
For i = 1 To UBound(array$)
c = Var_New: Var_SetString c, array$(i): Var_Push p, c
Next
End Sub
Sub pete (MemSystem As _Offset)
Dim node As _Offset, p As _Offset
For i = 0 To Var_Count(MemSystem) - 1
node = Var_At(MemSystem, i)
t$ = Var_GetString$(node)
Print "Parent> "; t$; "{" ' See, the chr$(0) is automatically chopped off.
p = Var_At(MemSystem, i)
For j = 0 To Var_Count(p) - 1
t$ = Var_GetString$(Var_At(p, j))
Print "Child>>> "; t$; "{"
Next
Next
End Sub
Pete
RE: Arrays as UDTs - SMcNeill - 02-03-2026
Ideally, something like this would track string and size both on the back side of the c header.
A format such as: Length of string stored as LONG, then the string stored. Then you just read the first four bytes, get the length, and then know exactly how many bytes to get for the string itself.
The issue is QB64 does NOT null terminate strings. "HELLO" + CHR$(0) + "WORLD" is a perfectly valid QB64 string. In C, that would read as *just* "HELLO" + terminator. The way to bypass that termination character is to count bytes and say, "OH I NEEDS 11 BYTES TO RETURN!!"
Unseen needs to change his structure to store length + string, and not just string alone, so it'd be usable in QB64 without issues.
RE: Arrays as UDTs - Unseen Machine - 02-03-2026
Code: (Select All) __declspec(dllexport) int QB_GetStringLength(QBHandle h) {
return h ? (int)((QBNode*)h)->s.size() : 0;
}
Add that to the .h file (just above the last } at the bottom
Code: (Select All)
FUNCTION Var_GetStringLength& ALIAS QB_GetStringLength (BYVAL Str AS _OFFSET)
That to the .bi declares block
And demo usage :
Code: (Select All)
'$DYNAMIC
'$INCLUDE:'qb_universal.bi'
SCREEN _NEWIMAGE(800, 600, 32)
' --- DATA INITIALIZATION ---
DIM SHARED MenuSystem AS _OFFSET
MenuSystem = Var_New
' Build Edit Menu
DIM EditMenu AS _OFFSET: EditMenu = BuildMenu("&Edit")
AddWordItem EditMenu, "A"
AddWordItem EditMenu, "ABC"
AddWordItem EditMenu, "ABCDE"
AddWordItem EditMenu, "ABCDEFGH"
AddWordItem EditMenu, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
FOR i% = 0 TO 4
PRINT Var_GetStringLength(Var_At(EditMenu, i%))
NEXT
SLEEP
Var_Free EditMenu
SYSTEM
' --- SUBS AND FUNCTIONS ---
FUNCTION BuildMenu%& (Title AS STRING)
DIM m AS _OFFSET: m = Var_New
Var_SetString m, Title
Var_Push MenuSystem, m
BuildMenu = m
END FUNCTION
SUB AddWordItem (parent AS _OFFSET, text AS STRING)
DIM n AS _OFFSET: n = Var_New
Var_SetString n, text
Var_Push parent, n
END SUB
RE: Arrays as UDTs - Pete - 02-03-2026
(02-03-2026, 06:09 PM)SMcNeill Wrote: Ideally, something like this would track string and size both on the back side of the c header.
A format such as: Length of string stored as LONG, then the string stored. Then you just read the first four bytes, get the length, and then know exactly how many bytes to get for the string itself.
The issue is QB64 does NOT null terminate strings. "HELLO" + CHR$(0) + "WORLD" is a perfectly valid QB64 string. In C, that would read as *just* "HELLO" + terminator. The way to bypass that termination character is to count bytes and say, "OH I NEEDS 11 BYTES TO RETURN!!"
Unseen needs to change his structure to store length + string, and not just string alone, so it'd be usable in QB64 without issues.
Speaking of tracking string and length...
Pure QB64 method to pass arrays.
Code: (Select All)
Type foo
index As String
array As String
build As String
id As Integer
End Type
Dim z As foo
Concat_Arrays z: Display_Arrays z
Sub Concat_Arrays (z As foo)
While -1
i = i + 1: j = 0
Do
Read a$
If a$ = "eof" Then Exit While
If a$ = "eol" Then Exit Do
z.index = z.index + LTrim$(Str$(i)) + "," + LTrim$(Str$(Len(a$))) + "|" ' Parent and length of string added.
z.array = z.array + a$
j = j + 1
Loop
Wend
End Sub
Sub Display_Arrays (z As foo)
ReDim a(10), Fruits$(10), Veggies$(10), Meats$(10)
Do
Unpack z: If z.id = 0 Then Exit Do
If z.id <> oldid Then a = 0
a = a + 1
Select Case z.id
Case 1: Fruits$(a) = z.build
Case 2: Veggies$(a) = z.build
Case 3: Meats$(a) = z.build
End Select
oldid = z.id
Loop
For i = 1 To UBound(Fruits$)
If Len(Fruits$(i)) Then Print Fruits$(i)
Next
Print
For i = 1 To UBound(Veggies$)
If Len(Veggies$(i)) Then Print Veggies$(i)
Next
Print
For i = 1 To UBound(Meats$)
If Len(Meats$(i)) Then Print Meats$(i)
Next
End Sub
Sub Unpack (z As foo)
Static seed, c
j = InStr(seed, z.index, ","): If j = 0 Then z.id = 0: Exit Sub
z.id = Val(Mid$(z.index, j - 1))
b = Val(Mid$(z.index, j + 1, InStr(Mid$(z.index, j + 1), "|")))
seed = j + 1
z.build = Mid$(z.array, c + 1, b)
c = c + b
End Sub
Data Fruits,Apple,Orange,Pear,Banana,Plum,eol
Data Veggies,Squash,Peas,Green Beans,Carrot,Celery,eol
Data Meats,Steak,Bacon,Chicken,Fish,eol
Data eof
We have other examples in a thread from September: https://qb64phoenix.com/forum/showthread.php?tid=3956
Well, as fun as this is, and I have to go try Unseen's header fixes, I cant see myself duplicating the routine, that gathers the arrays from memory, in every sub they will be needed in. One or two, fine, several, I can't see myself doing that, because it no longer would be a time saver.
Anyway, this reminds me of the start of that song from the time Jesus was coding as a baby. You know... "Array in a manger..." 
Pete
- The 10 Commandments. It's not like its written in stone... Oh wait!
|