Delegate Types for Indirect Calls
There is native support for calling a function by pointer, by way of Delegate syntax. A delegate in twinBASIC is a function pointer type that’s compatible with LongPtr. AddressOf returns a delegate type, that’s also backwards compatible with LongPtr.
A delegate is also how a procedure takes another function as an argument, which other languages call a callback. See Passing a function as an argument.
Basic Usage
The syntax looks like this:
Private Delegate Function Delegate1 (ByVal A As Long, ByVal B As Long) As Long
Private Sub Command1_Click()
Dim myDelegate As Delegate1 = AddressOf Addition
MsgBox "Answer: " & myDelegate(5, 6)
End Sub
Public Function Addition(ByVal A As Long, ByVal B As Long) As Long
Return A + B
End Function
Passing a function as an argument or parameter (callbacks)
A procedure can take a function as an argument and call it. Other languages call this a callback. Writing one takes three steps:
- Declare a delegate type with the signature the function must have.
- Give the procedure a parameter of that type, and call the parameter as if it were a function.
- At the call, pass AddressOf followed by the name of the function.
SortNames below sorts an array of names. The caller chooses the order by passing a comparison function, and SortNames calls it through its isInOrder parameter:
' The signature every comparison function must have.
Public Delegate Function Comparer(ByVal a As String, ByVal b As String) As Boolean
' Sorts names() in place. isInOrder returns True if a can stay before b.
Public Sub SortNames(names() As String, ByVal isInOrder As Comparer)
Dim i As Long, j As Long, temp As String
For i = LBound(names) To UBound(names) - 1
For j = LBound(names) To UBound(names) - 1
If Not isInOrder(names(j), names(j + 1)) Then
temp = names(j)
names(j) = names(j + 1)
names(j + 1) = temp
End If
Next j
Next i
End Sub
Public Function ByAlphabet(ByVal a As String, ByVal b As String) As Boolean
Return a <= b
End Function
Public Function ByLength(ByVal a As String, ByVal b As String) As Boolean
Return Len(a) <= Len(b)
End Function
Public Sub SortDemo()
Dim names() As String = Array("Charlie", "Al", "Bob", "Dave", "Ed")
SortNames names, AddressOf ByAlphabet
Debug.Print Join(names, ", ")
SortNames names, AddressOf ByLength
Debug.Print Join(names, ", ")
End Sub
SortDemo prints:
Al, Bob, Charlie, Dave, Ed
Al, Ed, Bob, Dave, Charlie
Important
The function passed must match the delegate: the same number and types of parameters, the same ByVal or ByRef on each (a parameter with neither is ByRef), and the same return type. A function that does not match causes only a warning, TB0026 Mismatched delegate type, and the program still builds. The function then receives its arguments in a form it does not expect: if the delegate’s parameter is a Long and the function’s is an Integer, a value of 70000 arrives in the function as 4464. [EnforceErrors(TB0026)] on the module that uses AddressOf makes the mismatch an error; Compiler Warnings describes the other ways to do that.
A delegate type can also describe a callback that a Windows API function takes. See Advanced Usage.
Advanced Usage
The delegate type can also be used in interface/API declarations and as members of a User-defined type. For example, the ChooseColor API:
Public Delegate Function CCHookProc (ByVal hwnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
Public Type CHOOSECOLOR
lStructSize As Long
hwndOwner As LongPtr
hInstance As LongPtr
rgbResult As Long
lpCustColors As LongPtr
Flags As ChooseColorFlags
lCustData As LongPtr
lpfnHook As CCHookProc 'Delegate function pointer type instead of LongPtr
lpTemplateName As LongPtr
End Type
If you already have code assigning a Long/LongPtr to the lpfnHook member, it will continue to work normally, but now you can also have the type safety benefits of setting it to a method matching the Delegate:
Private Sub PickColor()
Dim tCC As CHOOSECOLOR
tCC.lpfnHook = AddressOf ChooseColorHookProc
' ...
End Sub
Public Function ChooseColorHookProc(ByVal hwnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
' ...
End Function