Friday, July 17, 2020

How to use a function pointer in VBA #2 (function with arguments and return value)

How to use a function pointer in VBA.

VB6 and VBA come with no support for function pointers.

In the previous chapter we tried to execute a pointer to a function with no return value or argument. In this chapter we also try using pointers of functions with arguments and return values.

Example.2

The source code on this page needs to be pasted into the standard module. If you paste it into Sheet1 or ThisWorkBook, a compile error will occur.


Option Explicit

'You can use a sample code for this site freely.
'Though this is not a duty, I am grateful that you describe that you reffered this site
'(https://akihitoyamashiro.blogspot.com/2020/07/how-to-use-function-pointer-in-vba-2.html),
'when you present this sample code in your web site.

Private Declare PtrSafe Function DispCallFunc Lib "OleAut32.dll" _
(ByVal pvInstance As LongPtr, _
   ByVal oVft As LongPtr, _
   ByVal cc As Long, _
   ByVal vtReturn As Integer, _
   ByVal cActuals As Long, _
   ByVal prgvt As LongPtr, _
   ByVal prgpvarg As LongPtr, _
   ByVal pvargResult As LongPtr) As Long

Enum tagCALLCONV
    CC_FASTCALL = 0
    CC_CDECL = 1
    CC_MSCPASCAL = CC_CDECL + 1
    CC_PASCAL = CC_MSCPASCAL
    CC_MACPASCAL = CC_PASCAL + 1
    CC_STDCALL = CC_MACPASCAL + 1
    CC_FPFASTCALL = CC_STDCALL + 1
    CC_SYSCALL = CC_FPFASTCALL + 1
    CC_MPWCDECL = CC_SYSCALL + 1
    CC_MPWPASCAL = CC_MPWCDECL + 1
    CC_MAX = CC_MPWPASCAL
End Enum

Public Function SixParamOneReturn( _
                ByVal longVal As Long, ByRef longRef As Long, _
                ByVal byteVal As Byte, ByRef byteRef As Byte, _
                ByVal strVal As String, ByRef strRef As String _
                ) As Long
                
    'ByVal...String in VB is equivalent to BSTR in VC++.
    'ByRef...String in VB is equivalent to BSTR* in VC++.
                
    Debug.Print "SixParamOneReturn"
    Debug.Print "longVal = " & longVal
    Debug.Print "longRef = " & longRef
    Debug.Print "byteVal = " & byteVal
    Debug.Print "byteRef = " & byteRef
    Debug.Print "strVal = " & strVal
    Debug.Print "strRef = " & strRef
    Debug.Print
    
    SixParamOneReturn = longVal + longRef
    
    longVal = 100
    longRef = 200
    byteVal = 10
    byteRef = 20
    strVal = "strVal"
    strRef = "strRef"
    MsgBox "EightParamOneReturn is called."
End Function

Public Sub Test2()
    'The annotations that were explained in Test1 have been omitted.

    Dim lDispCallFuncResult As Long
    Dim vFuncResult As Variant

    vFuncResult = Empty
    
    Dim lVal As Long, vlVal As Variant
    Dim lRef As Long, vlRef As Variant
    Dim bVal As Byte, vbVal As Variant
    Dim bRef As Byte, vbRef As Variant
    Dim sVal As String, vsVal As Variant
    Dim sRef As String, vsRef As Variant
    
    lVal = 12
    lRef = 34
    bVal = 56
    bRef = 78
    sVal = "string1"
    sRef = "string2"
    
    'DispCallFunc method
    'The variables passed to the called function all have to
    'be passed in Variant form so all the variables are
    'declared as Variant variables.
    
    'It is easier to create a Variant array and assign the
    'array but to show there is no need for it to be an
    'array, we will not create an array.

    'Calling by reference, as instructed by VB's ByRef,
    'involves not declaring variables, but we have to assign
    'pointers to variables.
        
    vlVal = lVal 'Because it is the ByVal variable, set a value.
    vlRef = VarPtr(lRef) 'It is the ByVal variable so assign
                         'pointers. In C it corresponds to &IRef.
    vbVal = bVal
    vbRef = VarPtr(bRef)
    vsVal = sVal
    vsRef = VarPtr(sRef)
    
    'After declaring the Variant variables
    'along with set each Variant variable's type as an integer array.
    'we set each Variant variable's address as a long array.
    
    Dim iVarTypes(0 To 5) As Integer 'hard to tell, but
                                     'first letter is a small letter i.
    Dim lVarPtrs(0 To 5) As LongPtr
    
    iVarTypes(0) = VarType(vlVal) 'VbVarType.vbLong is assigned
    lVarPtrs(0) = VarPtr(vlVal)   '"VarPtr(vlVal)" corresponds to
                                  '"&vlVal" in C.
    
    iVarTypes(1) = VarType(vlRef) 'The pointer is in Long form
                                  'so VbVarType.vbLong is assigned.
    lVarPtrs(1) = VarPtr(vlRef)
    
    iVarTypes(2) = VarType(vbVal) 'VbVarType.vbByte is assigned.
    lVarPtrs(2) = VarPtr(vbVal)
    
    iVarTypes(3) = VarType(vbRef) 'The pointer is in Long form
                                  'so VbVarType.vbLong is assigned.
    lVarPtrs(3) = VarPtr(vbRef)
    
    iVarTypes(4) = VarType(vsVal) 'VbVarType.vbString is assigned.
    lVarPtrs(4) = VarPtr(vsVal)
    
    iVarTypes(5) = VarType(vsRef) 'The pointer is in Long form
                                  'so VbVarType.vbLong is assigned.
    lVarPtrs(5) = VarPtr(vsRef)
        
    'The returned value's type is long so we set the 4th argument
    '(the returned value's type) as vbLong(=3).
    
    'There are 6 variables, we set the 5th argument to 6.

    'For the 6th argument we set the first address in the
    'integer array that stores each variant variable's inner type.

    'For the 7th argument we set the first address in the
    'long array that stores each variant variable's address.

    lDispCallFuncResult = DispCallFunc( _
                                       0, _
                                       AddressOf SixParamOneReturn, _
                                       tagCALLCONV.CC_STDCALL, _
                                       VbVarType.vbLong, _
                                       6, _
                                       VarPtr(iVarTypes(0)), _
                                       VarPtr(lVarPtrs(0)), _
                                       VarPtr(vFuncResult) _
                                       )

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'In this section, if the declaration of the DispCallFunc
    'is as follows, then the 6th, 7th and 8th arguments VarPtr are
    'unnecessary.
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

    'Private Declare PtrSafe Function DispCallFunc Lib "OleAut32.dll" _
    (ByVal pvInstance As LongPtr, _
       ByVal oVft As LongPtr, _
       ByVal cc As Long, _
       ByVal vtReturn As Integer, _
       ByVal cActuals As Long, _
       ByRef prgvt As Integer, _
       ByRef prgpvarg As LongPtr, _
       ByRef pvargResult As Variant) As Long


    'Regarding the 6th, 7th and 8th arguments, we change
    'ByVal pointer's type to ByRef variable's type

    '6th argument ByVal ... Long to ByRef ... Integer
    '7th argument ByVal ... LongPtr to ByRef ... LongPtr
    '8th argument ByVal ... Long to ByRef ... Variant

    'Because of this getting the address value is done
    'automatically by VBA, and we can do the DispCallFunc
    'part in the following way

    
    
    'lDispCallFuncResult = DispCallFunc( _
                                        0, _
                                        AddressOf SixParamOneReturn, _
                                        tagCALLCONV.CC_STDCALL, _
                                        VbVarType.vbLong, _
                                        6, _
                                        iVarTypes(0), _
                                        lVarPtrs(0), _
                                        vFuncResult _
                                        )
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

    
    Debug.Print "Test2"
    Debug.Print "lDispCallFuncResult = " & lDispCallFuncResult
    
    Debug.Print "vFuncResult = " & vFuncResult
    
    Debug.Print "lVal = " & lVal 'lVal value is left as is.
    Debug.Print "lRef = " & lRef 'lRef value is passed to a pointer
                                 'so the original value is affected
                                 'by the changes where it is called.

    Debug.Print "bVal = " & bVal
    Debug.Print "bRef = " & bRef
    Debug.Print "sVal = " & sVal
    Debug.Print "sRef = " & sRef
    
End Sub



Until now, I assumed you understand DispCallFunc's call procedure. Actually, there are not many cases where functions within VBA is called with DispCallFunc.
Also, because standard WIN32API are created with the STDCALL calling convention, you can call them if you do a Declare Function. In such a case, there are few cases where calls are made with DispCallFunc.

Thus I will try to call functions created with the CDECL calling convention in the next section.


No comments: