Results 1 to 40 of 42

Thread: [vb6] Getting AddressOf for VB Class/Object Modules

Threaded View

  1. #1

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    [vb6] Getting AddressOf for VB Class/Object Modules

    Terms...
    Object modules are code pages that are not bas-modules, i.e., class, form, etc.
    Methods are subs, functions, properties

    Here is my latest version of Paul Caton's zAddressOf method that does the trick. It uses his logic, but no longer resembles his original code. See post #13 for performance, safety & speed comparison to Paul Caton's code.

    But to be clear, you cannot just use the returned function pointer with typical APIs that want an AddressOf (callback) because they will not know to include the required pThis parameter to call COM methods. Any calls requiring a COM function pointer would likely be via thunks or DispCallFunc API which know how to call COM methods. If whatever wants the method address is not also asking for the Object or its ObjPtr, then it isn't for COM. See post #2 for more info on how COM calls work.

    Why would you need this? The main reason would be that you need or want to call a code page method by its method pointer. Actually calling the method is another topic discussed in post #2 below.

    Why would you want to do that? A couple scenarios, and creativity could produce other reasons.

    1. Some thunk requires a COM function pointer.
    2. For whatever reasons, you can only call the method by its pointer.
    3. Another possible reason is to privately call a method within your own object, i.e., a class method from outside the class, without declaring that method Public or Friend. I have only used that scenario once and it applied to a set of classes where I absolutely didn't want the coder to curiously execute a Friend method in one of the classes. So I protected it by declaring it Private and called it by its pointer instead of a public or friend declaration.

    Caveat: Can only be used for VB code pages in the current process.

    The code is heavily commented. Read those comments to better understand how it works. This works both in the IDE and when compiled in native code or P-code. You can replace CopyMemory with VB's GetMem4 and GetMem1 APIs if you so choose.

    Sample call: Debug.Print GetAddressOfEx(Me, 1) ' pointer to last method in the code page
    Code:
    Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (src As Any, dst As Any, ByVal cbLen As Long)
    Private Declare Function VirtualQuery Lib "kernel32.dll" (ByVal addr As Long, pMBI As Any, ByVal lenMBI As Long) As Long
    Private Enum CodePageTypeEnum
        cptUnknown = 0
        cptVbClass = 0
        cptVbDataReport = 1
        cptVbFormOrMDI = 2
        cptVbPropertyPage = 3
        cptVbUserControl = 4
    End Enum
    
    Private Function GetAddressOfEx(ByRef VbCodePage As Object, ByVal nOrdinal As Long, _
                                Optional ByVal CodePageType As CodePageTypeEnum = cptUnknown, _
                                Optional ByRef nMethodCount As Long, Optional ByRef nLastMethodOffset As Long) As Long
        
        ' Routine is basically an AddressOf function for VB code pages (forms, classes, etc)
        
        If nOrdinal < 1 Then Exit Function
        If VbCodePage Is Nothing Then Exit Function
        
        ' redesigned but based on Paul Caton's zAddressOf method that can find function
        '   pointers within VB forms, classes, etc. Redesign includes merging 2 routines,
        '   using known VB offsets, and use of VirtualQuery over IsBadCodePtr API. This
        '   revised logic is slower than Caton's latest versions, but will prevent
        '   unintended page-guard activation and will not fail to return results in
        '   cases where Caton's zAddressOf would due to his built-in loop restrictions.
        
        ' Modify the routine for large-address-aware application, as needed, i.e., pointer-safe math.
        
        ' Parameters
        '   :: VbCodePage is the VB class module (form, class, etc) containing the method ordinal
        '   :: nOrdinal is the ordinal whose function pointer/address is to be returned
        '       ordinals are always one-bound and counted from the bottom of the code page
        '       the last method is ordinal #1, second to last is #2, etc
        '       keep public methods near top of code page & private/friend near bottom because
        '       VB will move public ones closer to top during runtime, offsetting your ordinals.
        '   :: CodePageType when passed can help the function scan the code page more efficiently
        '   :: nMethodCount is returned with the number of user-defined methods in the code page
        '   :: nLastMethodOffset is returned with the address after the last user-defined method
        ' Return value
        '   If success, the function pointer will be returned, else zero is returned
        '   If zero is returned, nMethodCount and nLastMethodOffset may not be updated
        
        ' How this method works...
        ' With known offsets and function signatures, finding what we want is pretty easy.
        ' The function signature is simply the 1st byte of the function's code
        ' 1) If a function pointer is zero, then this is expected
        '       Seen typically when a code page uses Implements keyword
        ' Otherwise, there are four byte values we are interested in (the signature)
        ' 2) Byte &H33  start of XOR instruction in native code (always when in IDE)
        ' 3) Byte &HE9  start of XOR instruction in P-Code (only when compiled in P-Code)
        ' 4) Byte &H81  start of ADD instruction, regardless of P-Code usage
        ' 5) Byte &H58  start of POP instruction, regardless of P-Code usage
        
        Dim bSig As Byte, bVal As Byte
        Dim nAddr&, vOffset&, nFirst&
        Dim nMethod&, nAttempts&, n&
        Dim minAddrV&, maxAddrV&, minAddrM&, maxAddrM&
        Dim MBI&(0 To 6)          ' faux MEMORY_BASIC_INFORMATION structure
        ' (0) BaseAddress member    minimum range of committed memory (same protection)
        ' (3) Range member          maximum range BaseAddress+Range
        ' (5) Protect member
        ' This structure is key to not crashing while probing memory addresses.
        ' The Protect member of the structure is examined after each call. If it
        ' contains &H101 (mask), then the address is a page-guard or has no-access.
        ' Otherwise, if it contains &HFE (mask) then the address is readable.
        
        ' Step 1. Probe the passed code page to find the first user-defined method.
        ' The probe is quite fast. The outer For:Next loop helps to quickly filter the
        ' passed code page via the known offsets. The inner DO loop will execute up to
        ' four times to find the right code page offset as needed. After found, it will
        ' execute as little as one time or several times, depending on Implements usage
        ' and number of Public variables declared within the code page. That inner loop
        ' has a fudge-factor built in should some signature exist that is not known yet.
        ' However, no others have been found, to date, after the known offsets of the
        ' correct code page.
        
        If CodePageType <= cptUnknown Or CodePageType > cptVbUserControl Then
            n = 0: nAttempts = 4
        Else
            n = CodePageType: nAttempts = n
        End If
        CopyMemory nAddr, ByVal ObjPtr(VbCodePage), 4 ' host VTable
        
        For n = n To nAttempts                      ' search in ascending order of offsets
            Select Case n
                Case 0: vOffset = nAddr + &H1C      ' known offset for VB Class,DataEnvironment,Add-in,DHTMLPage
                Case 1: vOffset = nAddr + &H9C      ' known offset for VB DataReport
                Case 2: vOffset = nAddr + &H6F8     ' known offset for VB Form, MDI
                Case 3: vOffset = nAddr + &H710     ' known offset for VB Property Page
                Case 4: vOffset = nAddr + &H7A4     ' known offset for VB UserControl
            End Select
            
            nAttempts = 4                           ' fudge-factor
            Do
                ' First validate the VTable slot address. If invalid, unsupported code page type
                If vOffset < minAddrV Or vOffset > maxAddrV Then
                    MBI(5) = 0: VirtualQuery vOffset, MBI(0), 28
                    If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit For
                    minAddrV = MBI(0): maxAddrV = minAddrV + MBI(3) - 4  ' set min/max range
                End If
                CopyMemory nMethod, ByVal vOffset, 4 ' get function address at VTable slot
                If nMethod <> 0 Then                ' zero = implemented, skip
                
                    ' Next validate the function pointer. If invalid, unsupported code page type
                    If nMethod < minAddrM Or nMethod > maxAddrM Then
                        MBI(5) = 0: VirtualQuery nMethod, MBI(0), 28
                        If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit For
                        minAddrM = MBI(0): maxAddrM = minAddrM + MBI(3) - 4  ' set min/max range
                    End If
                
                    CopyMemory bVal, ByVal nMethod, 1 ' get the 1st byte of that method
                    If bVal = &H33 Or bVal = &HE9 Then
                        nFirst = vOffset            ' cache the location of first user-defined method
                        bSig = bVal: Exit For       ' cache the function signature & done
                    ElseIf bVal <> &H81 Then        ' if not one of these 4 signatures, decrement attempts
                        If bVal <> &H58 Then nAttempts = nAttempts - 1
                    End If
                End If
                vOffset = vOffset + 4               ' look at next VTable slot
            Loop Until nAttempts = 0
        Next
        
        If nFirst = 0 Then Exit Function           ' failure
        ' If failure, then likely one of two reasons:
        ' 1) Unsupported code page
        ' 2) Code page has no user-defined methods
        
        ' Step 2. Find the last user-defined method.
        ' VB stacks user-defined methods contiguously, back to back. So, to find the last method,
        ' we simply need to keep looking until a signature no longer matches or we hit end of page.
        Do
            ' Validate the VTable slot address. If invalid, end of code page & done
            vOffset = vOffset + 4
            If vOffset < minAddrV Or vOffset > maxAddrV Then
                MBI(5) = 0: VirtualQuery vOffset, MBI(0), 28
                If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit Do
                minAddrV = MBI(0): maxAddrV = minAddrV + MBI(3) - 4  ' set min/max range
            End If
            
            CopyMemory nMethod, ByVal vOffset, 4    ' get function pointer at VTable slot
            If nMethod = 0 Then Exit Do             ' if zero, done because doesn't match our signature
            
            ' Validate the function pointer. If invalid, end of code page & done
            If nMethod < minAddrM Or nMethod > maxAddrM Then
                MBI(5) = 0: VirtualQuery nMethod, MBI(0), 28
                If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit Do
                minAddrM = MBI(0): maxAddrM = minAddrM + MBI(3) - 4  ' set min/max range
            End If
            
            CopyMemory bVal, ByVal nMethod, 1       ' get function's signature
        Loop Until bVal <> bSig                     ' done when doesn't match our signature
        
        ' Now set the optional parameter values
        nMethodCount = (vOffset - nFirst) \ 4
        nLastMethodOffset = vOffset
        
        ' Return the function pointer for requested ordinal, if a valid ordinal
        If nOrdinal <= nMethodCount Then
            CopyMemory GetAddressOfEx, ByVal vOffset - (nOrdinal * 4), 4
        End If
    
    End Function
    FYI: The object's VTable is scanned and the methods in your code page are more or less in VTable order. So if the order of your methods doesn't magically change when you run the project, no issues should arise. But they can magically change and when they do, their ordinals can change. At runtime, VB will always move Public methods ahead of all private/friend methods within the VTable. This means if your last two methods were a private one followed by a public one, the ordinals for both methods changed at runtime. The public one was placed towards the top and that private method is now the last method in the VTable.

    If using this code, always keep your public methods towards the top of the code page to prevent this issue.
    Last edited by LaVolpe; May 6th, 2020 at 08:20 PM. Reason: tweaked to increase speed & added ENUM - expand comments
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width