Quantcast
Viewing all articles
Browse latest Browse all 1529

[vb6] AddressOf for Class, Form, UC methods

This will be of little use to the masses, but can be of good use for thunk creators/users and the curious.

VB gives us the AddressOf function which can return the function address of any method within a bas module. We have no such function to return addresses within non-module code pages like classes, forms, usercontrols, etc. This can be a suitable substitute.

Why would you even need these addresses? Normally, you wouldn't. The most obvious case would be if you wanted to call a class function, particularly a private one, from a thunk. Can apply if wanting to do same thing from a bas module, but there are easier workarounds for calling private class methods from a module.

Here is code that you can use, both are well commented. Note that the passed VbCodeObject parameter to the ClassAddressOf function ideally would be from within the code object itself. For example, ObjPtr(myUserControlInstance) from a form is not the same as ObjPtr(Me) from within the usercontrol itself. The logic below utilizes the VB typelib info generated from VB code objects. Please read the comments within the code.

Code:

Private Declare Function DispCallFunc Lib "oleaut32.dll" (ByVal pvInstance As Long, ByVal offsetinVft As Long, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByVal paTypes As Long, ByVal paValues As Long, ByRef retVAR As Variant) As Long
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)


Public Function ClassAddressOf(VbCodeObject As Object, Optional ByVal isPrivateMethod As Boolean = True, Optional ByVal Ordinal As Long = -1&) As Long

    ' Notes within context of this function...
    '  Method is any sub, function or property
    '  Statement is a method statement, whether declared public, private, friend or not declared
    '      Sub DoIt() is a method statement
    '      Function DoItEx(lParam As Long) As Boolean is a method statement
    '      Property Get & Property Let are two statements not one
    '  Private is anything but public. Friend and Private are considered Private
    ' It does not matter if your public and private method statements are intermixed
   
    ' Warnings...
    ' You should NOT include public variables at the top of your code page, before the 1st public/private method statement.
    '  Why? VB will add a Public Get,Let (and maybe a Set) statement when the project is run.
    '      if the variable is type object or variant, a public Set statement is added else it is not
    '  If public variables exist, the passed Ordinal parameter must take this into consideration else wrong address returned.
    '  To avoid this problem, properly create the public Get,Let property statements for the variable and then change
    '      the variable from public to private.
    ' If you add, remove, or move any statements be sure to double check the passed Ordinal is still correct.
    ' It is your responsibility to ensure the ordinal is not outside your total public/private statement counts
    ' If the code page only contains private methods, no public methods, you must pass the Ordinal as negative,
    '  working from the end of the page. In such a case, the only reliable offset is the end of the vTable.
   
    ' Parameters...
    ' VbCodeObject is a non-Module, i.e., class, form, usercontrol, property page, etc
    ' isPrivateMethod is True if you want function pointer for a private/friend method else False for public
    ' Ordinal is a positive number when starting from the first private/public statement
    '      else a negative number when starting from the last private/public statement
    '  Ordinal cannot be zero, must be +/-(1 to number of private/public statements)
    ' Return value of zero indicates function failed
   
    ' Examples:
    ' 1) Return the final private method address: ClassAddressOf(someObject, True, -1)
    '      Note: function defaults to this, so this works too: ClassAddressOf(someObject)
    ' 2) Return the first private method address: ClassAddressOf(someObject, True, 1)
    ' 3) Return the 2nd public method address: ClassAddressOf(someObject, False, 2)
    ' 4) Return the next-to-last public method address: ClassAddressOf(someObject, False, -2)

    If VbCodeObject Is Nothing Then Exit Function
    If Ordinal = 0& Then Exit Function
   
    Dim ITInfo As IUnknown  ' ITypeInfo interface
    Dim lPointer As Long, fOffset As Integer
    Dim fCount As Integer, vTblSize As Integer
   
    ' offset 16 = IDispatch.GetTypeInfo
    pvCallObjFunction ObjPtr(VbCodeObject), 16&, vbLong, Empty, 0&, 0&, VarPtr(ITInfo)
    If ITInfo Is Nothing Then Exit Function
       
    ' offset 12 = ITypeInfo.GetTypeAttr
    pvCallObjFunction ObjPtr(ITInfo), 12&, vbLong, Empty, VarPtr(lPointer)
    If lPointer = 0& Then Exit Function
   
    ' https://msdn.microsoft.com/en-us/library/windows/desktop/ms221003%28v=vs.85%29.aspx
    ' offset 44 = TYPEATTR structure's cFuncs member
    CopyMemory fCount, ByVal lPointer + 44&, 2&
    ' offset 50 = TYPEATTR structure's cbSizeVft member
    CopyMemory vTblSize, ByVal lPointer + 50&, 2&
   
    ' offset 76 = ITypeInfo.ReleaseTypeAttr
    pvCallObjFunction ObjPtr(ITInfo), 76&, vbEmpty, Empty, lPointer
       
    If isPrivateMethod = True Then
        If Ordinal < 0& Then fOffset = vTblSize + (Ordinal * 4&)
    ElseIf Abs(Ordinal) > fCount Then
        Exit Function  ' result would be > number of public method statements
    End If
    If fOffset = 0& Then
        lPointer = 0&
        If isPrivateMethod = True Or Ordinal < 0& Then fOffset = fCount - 1
        ' offset 20& = ITypeInfo.GetFuncDesc
        pvCallObjFunction ObjPtr(ITInfo), 20&, vbLong, Empty, fOffset, VarPtr(lPointer)
        If lPointer = 0 Then Exit Function
           
        ' https://msdn.microsoft.com/en-us/library/windows/desktop/ms221425%28v=vs.85%29.aspx
        ' offset 28 = FUNCDESC structure's oVft member
        CopyMemory fOffset, ByVal lPointer + 28&, 2&
        If isPrivateMethod Then
            fOffset = fOffset + Ordinal * 4
        ElseIf Ordinal < 0& Then
            fOffset = fOffset + (Ordinal + 1&) * 4
        Else
            fOffset = fOffset + (Ordinal - 1&) * 4
        End If
        ' offset 80 = ITypeInfo.ReleaseFuncDesc
        pvCallObjFunction ObjPtr(ITInfo), 80&, vbEmpty, Empty, lPointer
    End If
    Set ITInfo = Nothing
    ' 28 is absolute smallest offset one can have with a VB codepage object: IUnknown+IDispatch vTable size
    If Not (fOffset > 27 And fOffset < vTblSize) Then Exit Function
   
    CopyMemory lPointer, ByVal ObjPtr(VbCodeObject), 4&
    CopyMemory ClassAddressOf, ByVal lPointer + fOffset, 4&

End Function

Private Function pvCallObjFunction(ByVal InterfacePointer As Long, ByVal VTableOffset As Long, _
                            ByVal FunctionReturnType As Integer, ByRef FunctionReturnValue As Variant, _
                            ParamArray Args() As Variant) As Long
                           
' Used to call active-x or COM objects, not standard dlls

    ' InterfacePointer passed as ObjPtr(someObj) or a Long value representing a COM interface
    ' VTableOffset is zero-bound multiple of 4. Ex: IUnknown:Release is 3rd method, offset = 8 = (3-1)*4
    ' FunctionReturnType is the variable/variant type returned by called virtual function; generally it is vbLong
    '    if method returns VOID, pass vbEmpty or 0. Note: VB subs do not return VOID, so use vbLong
    ' FunctionReturnValue is return result from the called virtual function. Can pass EMPTY if not wanted.
    ' Args(): Pass ByRef using VarPtr(someVariable). Strings passed via StrPtr()
    ' If function succeeds, zero is returned else error code
 
  'Const CC_STDCALL = 4 << 3rd parameter passed to DispCallFunc
    Dim pCount As Long, vParams As Variant
    Dim vPtr() As Long, vType() As Integer

    FunctionReturnValue = Empty
    pCount = UBound(Args) + 1&
    If pCount = 0& Then                                ' no parameters
        pvCallObjFunction = DispCallFunc(InterfacePointer, VTableOffset, 4&, FunctionReturnType, _
                          pCount, 0&, 0&, FunctionReturnValue)
    Else
        vParams = Args()
        ReDim vPtr(0 To pCount - 1&)                    ' need matching array of parameter types
        ReDim vType(0 To pCount - 1&)                  ' and pointers to the parameters
        For pCount = 0& To pCount - 1&
            vPtr(pCount) = VarPtr(vParams(pCount))
            vType(pCount) = VarType(vParams(pCount))
        Next                                            ' call the function now
        pvCallObjFunction = DispCallFunc(InterfacePointer, VTableOffset, 4&, FunctionReturnType, _
                          pCount, VarPtr(vType(0)), VarPtr(vPtr(0)), FunctionReturnValue)
    End If
End Function

The return value from ClassAddressOf, when non-zero, would be the public or private method's address. Because we are talking about COM objects here, you should be aware that the 1st parameter sent to a COM method directly by its address is the ObjPtr() of the object instance whose method is being called.

If you'd like to play, here is a test class to play with:
Code:

Option Explicit

Public Sub pbTest1()
    MsgBox "Got public method #1"
End Sub
Public Sub pbTest2()
    MsgBox "Got public method #2"
End Sub

Private Sub pvTest1()
    MsgBox "Got private method #1"
End Sub

Private Sub pvTest2()
    MsgBox "Got private method #2"
End Sub

In a new project, add a form (with 1 button) and the test class above. Add the above functions to the form. Behind the button's click event, add this & play:
Code:

    Dim clsObj As Class1
    Dim bPrivate As Boolean, lOffset As Long, lAddress As Long
   
    Set clsObj = New Class1
    Select Case MsgBox("Want Private method triggered?", vbYesNoCancel)
        Case vbYes: bPrivate = True
        Case vbCancel: Exit Sub
    End Select
    Select Case MsgBox("Do you want the Last method triggered?", vbYesNoCancel)
        Case vbYes: lOffset = -1
        Case vbNo: lOffset = 1
        Case Else: Exit Sub
    End Select
    lAddress = ClassAddressOf(clsObj, bPrivate, lOffset)
    If lAddress = 0& Then
        MsgBox "Failed to find the address of the target method", vbExclamation + vbOKOnly
    Else
        pvCallObjFunction 0&, lAddress, vbLong, Empty, ObjPtr(clsObj)
    End If

Another note & tidbit. The ClassAddressOf returns the address of the function. If it were modified to also return the offset of the function (fOffset within the code), the method could be called another way... Notice we do not pass the ObjPtr() in the param array since we are passing it as the 1st parameter.
Code:

pvCallObjFunction ObjPtr(clsObj), fOffset, vbLong, Empty, [any parameters]
Some caveats.

This code is not truly intended for generic use, its intended purpose is for custom use. In other words, you wouldn't willy-nilly try to call any COM object using the offsets or addresses returned by the code unless you are 100% familiar with the COM object, its vTable order and each method's parameter requirements. The code being able to return the correct function address is dependent on:
1) Passing the correct ordinal to return an address from
2) Revalidating ordinals anytime you add, move, or delete methods
3) Ensuring your ordinal does not exceed the number of method statements in the object.

Calling the function by its pointer is all on you; the provided code does not do that, it only shows examples of ways that it can be done. If using the code to get the address for thunk usage, then the thunk was probably written well enough to know how to call the COM method's function based on the pointer you provided it.

Viewing all articles
Browse latest Browse all 1529

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>