The default method for passing parameters for both these routines is
implicitly by reference, which differs both from GFABASIC-32's default of
ByVal and the
explicit method of passing parameters by reference using
ByRef. Basically, passing values
implicitly by reference is a hybrid form used in VB: if the value being passed is a variable AND the type of that variable matches the declared type of the parameter, then the variable will be passed ByRef; otherwise, it will be passed ByVal. (Actually a copy is passed by reference, but the effect is similar to if it was passed ByVal. For a more in-depth description of how this all works, see
here.)
It is important to keep this fact in mind when using these commands as, otherwise, you may find variables you intended to pass by value taking on changes made inside the called routine as they are actually being passed by reference; and, similarly, if you are passing a variable by reference but GFABASIC-32 does not recognise it as being exactly the same as that of the parameter, changes made within the subroutine will not be passed back to calling routine. Default behaviour can be changed - as with GFABASIC routines - by using ByRef and ByVal, but it is Strongly advised to use ByVal and/or ByRef explicitly or otherwise to stick to Procedure/Function subroutines.
Note that implicit referencing does not work with Arrays, User-Defined Types and Hash Tables: these are all sent by reference, the last two needing this to be explicitly stated by using ByRef, as with normal GFABASIC routines.
To better illustrate how implicit by referencing works, see the following example:
' FunctionVar 'default' passing matching variable
Local a = "George"
Print hellof(a) // Prints "Hello George"
Print a : Print // Prints "Hello George"
' FunctionVar 'default' passing non-matching variable
Local a$ = "George"
Print hellof(a$) // Prints "Hello George"
Print a$ : Print // Prints "George"
' FunctionVar 'default' passing literal
Print hellof("George") // Prints "Hello George"
Print
' Sub 'default' passing matching variable
a = "George"
hellos(a) // Prints "Hello George"
Print a : Print // Prints "Hello George"
' Sub 'default' passing non-matching variable
a$ = "George"
hellos(a$) // Prints "Hello George"
Print a$ : Print // Prints "George"
' Sub 'default' passing literal
hellos("George") // Prints "Hello George"
FunctionVar hellof(a)
a = "Hello " & a
hellof = a
EndFunc
Sub hellos(a)
a = "Hello " & a
Print a
EndSub
To get a better idea of how this works, change the type of the parameter in both subroutines to 'As String'.
The following example asks you to select different shapes to draw and counts how many of each type you select; it will, however, not let you redraw the same shape you have just drawn.
Global ct%(1 To 4), lastshape%
Local wh% = WinHeight(370), ww% = WinWidth(230)
OpenW Fixed 1, 10, 10, ww%, wh% : Win_1.AutoRedraw = 1
Ocx Command cmd(1) = "Draw Circle", 10, 10, 100, 25
Ocx Command cmd(2) = "Draw Square", 120, 10, 100, 25
Ocx Command cmd(3) = "Draw Filled Circle", 10, 45, 100, 25
Ocx Command cmd(4) = "Draw Filled Square", 120, 45, 100, 25
DisplayCount(ct%())
Do : Sleep : Until Win_1 Is Nothing
Procedure DisplayCount(ByRef ct%(), Optional shape%)
// ct%() - A pointer to the 32-bit Integer array holding the current count stats - this is incremented 'in-procedure', which updates the parent array as well
// shape% - The array element to increment dependent upon which shape was drawn; if not passed, then it defaults to zero.
If shape% <> 0 Then Inc ct%(shape%)
Text 10, 300, "Circles:" : Text 85, 300, ct%(1)
Text 10, 315, "Squares:" : Text 85, 315, ct%(2)
Text 10, 330, "Filled Circles:" : Text 85, 330, ct%(3)
Text 10, 345, "Filled Squares:" : Text 85, 345, ct%(4)
EndProcedure
Procedure DrawShape(shape%, Optional filled? = True, ParamArray coords())
// shape% - A ByVal Int32 parameter describing the shape to be drawn
// filled? - An optional ByVal Boolean parameter (defaults to TRUE (-1) if not passed) determining whether the shape is filled or not
// coords() - An array of additional optional parameters holding values for x%, y% and possibly x1%, y1% and r% depending upon shape%
Local x%, y%, x1%, y1%, r%
x% = coords(0), y% = coords(1) // Assign x% and y% from the first two values in coords()
Select shape%
Case 1 // Circle
r% = coords(2) // Assign r% from coord(2)
If filled? : PCircle x%, y%, r%
Else : Circle x%, y%, r%
EndIf
Case 2 // Square
If Dim?(coords()) = 4 // If four parameters passed in coords()...
x1% = coords(2), y1% = coords(3) // ...then assign x1% and y1% from the third and fourth values...
Else // ...else...
x1% = x% + 190 : y1% = y% + 190 // ...assume a width and height of 190 pixels.
EndIf
If filled? : PBox x%, y%, x1%, y1%
Else : Box x%, y%, x1%, y1%
EndIf
EndSelect
EndProcedure
Sub cmd_Click(Index%)
// An 'event procedure' which is activated when one of the four command buttons is clicked
// Index% - An expected or mandatory parameter which indicates which of the four command buttons was clicked.
Exit Sub If Index% = lastshape% // If selected shape the same as the last one, do not redraw and count
Color $FFFFFF : DrawShape(2, , 20, 90, 210, 280) : Color 0
Select Index%
Case 1 : DrawShape(1, False, 115, 185, 95)
Case 2 : DrawShape(2, False, 20, 90)
Case 3 : DrawShape(1, , 115, 185, 95)
Case 4 : DrawShape(2, , 20, 90)
EndSelect
lastshape% = Index
DisplayCount(ct%(), Index%)
EndSub
Function WinHeight(height%) As Int32
// This function assigns the 32-bit integer value to the function name and returns this value when the function is complete
WinHeight = height% + Screen.cyFixedFrame + Screen.cyCaption
EndFunction
Function WinWidth(width%)
// This function uses the Return command to return a 32-bit integer
Return width% + (Screen.cxFixedFrame * 2)
EndFunction
Subroutines can be recursive; that is, they can call themselves to perform a given task. However, recursion can lead to stack overflow. For similar reasons, the Static keyword usually isn't used with recursive subroutines.
Errors can occur when the function name is used as the return value, as shown in the example below:
' Courtesy of 'Code Lab'
Type vector
x As Double
y As Double
EndType
Dim a As vector, b As vector, c As vector, d As vector
Print "a:", a.x, a.y
c = tovector(a, 1) : Print "c:", c.x, c.y
Print "b:", b.x, b.y
d = tovector(b, 0) : Print "d:", d.x, d.y
Print "d.x shouldn't have this value"
Function tovector(ByRef v As vector, which) As vector
If which = 1
tovector.x = 10
Else
tovector.y = 5
EndIf
EndFunction
To work around this problem, use a locally defined variable as the return value instead as in the example below:
' Courtesy of Thomas Müller-Wirts
Type vector
x As Double
y As Double
EndType
Dim a As vector, b As vector, c As vector, d As vector
Print "a:", a.x, a.y
c = tovector(a, 1) : Print "c:", c.x, c.y
Print "b:", b.x, b.y
d = tovector(b, 0) : Print "d:", d.x, d.y
Print "d.x is now zero as it should be"
Function tovector(ByRef v As vector, which) As vector
Local vr As vector
If which = 1
vr.x = 10
Else
vr.y = 5
EndIf
Return vr
EndFunction
There is an obscure error involving a Boolean passed to Variant parameters from procedures containing a Gosub...Return construct. See here for more details.
With most Optional Parameters, if a value is not sent then a temporary variable is created within the receiving subroutine which can then be referenced and changed as required; this, however, is not the case when the optional parameter is a String: in that instance, only a Null pointer is sent so any attempts to reference or change the variable - barring checking its status with IsMissing - returns an Access Error as shown by the example below:
Print Test(3)
Function Test(a, Optional s$)
If IsMissing(s$)
s$ = "No value" // Access Error on this line
EndIf
Return s$
EndFunction
The best way to get round this oddity is either by judicious use of the IsMissing function as below...
Print Test(3)
Print Test(3, "A Value")
Function Test(a, Optional s$)
If IsMissing(s$) Return "No Value"
Return s$
EndFunction
or creating a temporary string within the subroutine and using it as follows:
Print Test(3)
Print Test(3, "A Value")
Function Test(a, Optional s$)
Local sv$ = Iif(IsMissing(s$), "No Value", s$)
Return sv$
EndFunction
This last example also demonstrates how to get round the inability to set a default value to an Optional String parameter.[Reported by James Gaite, 05/08/2019]
It is strongly advised NOT to assign values to Variant and String parameters passed ByVal as this can cause an Access Denied error (but not always); to get around this, create a local variable of the same type and value as the parameter and assign any changed value to this local variable instead. To paraphrase Sjouke Hamstra's blog on this issue, changing the value of the parameter creates an orphan variant or string upon the closure of the procedure which leaks memory; this also can cause the release of memory of the orginal variable which was passed as the parameter which is where an error will be raised on the next attempt to reference it.[Reported by James Gaite, 10/03/2022]