Results 1 to 20 of 20

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

  1. #1

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

    [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}

  2. #2

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

    Calling VB Methods by Pointer

    First a bit about COM so you can better understand the process. We'll use a VB class as a sample. When the class is created, its code is only created once, regardless how many new instances of that class are created. This means the same method, in all instances of that class, has the same function pointer. To distinguish which class instance is being called, COM requires the caller to send the ObjPtr of that instance as the first parameter of any called method.

    Ok, got that? You don't see that first parameter in any VB methods, you are actually looking at parameters #2 and on. If a function has no parameters, it really has two (explained next) and a Sub with no parameters really has one. The required first parameter is typically referred to as: pThis.

    VB hides pThis from you, but it does exist in every COM call. So, VB is hiding a parameter in each of its methods, is that all? No. VB Functions and Property Get methods also have a hidden internal final parameter. It is the caller's ByRef value that receives the method's return value. Subs and Property Let/Set methods do not have that 2nd hidden parameter. A VB method (even a sub) always returns something, known as an HRESULT. If an HRESULT is being returned, then how does a function return some other value? Answer: that hidden last parameter.

    In a class, Private Sub TestMe() looks a bit like this under the covers:
    Code:
    Private Function TestMe(ByVal pThis As Long) As HRESULT
    So internally, a Sub in VB is really a function without an extra hidden parameter as seen here.
    Private Function TestMe() As Long looks a bit like this under the covers:
    Code:
    Private Function TestMe(ByVal pThis As Long, ByVal ptrReturn As Long) As HRESULT
                     ' ptrReturn is a pointer to memory that receives the function's value
    Knowing this, how do we call a VB method by pointer? If it's a Function or Property Get, we need to include 2 "hidden" parameters else one "hidden" parameter when calling VB methods.

    There is the CallWindowProc API that can be used, but has severe limitations. That API always passes 4 parameters, no more, no less. We already know that one or two will be used for the hidden parameters, so that reduces the number of usable ones. But that isn't the worse part. If building a function for that API's use, it forces you to design your method for that API. This means that if that API were used, a VB Function or Property Get must be coded to have exactly 2 parameters, no more, no less. Those 2 parameters + 2 "hidden" parameters equals API's 4 passed parameters. For the other VB method types, they must have exactly 3 parameters, no more, no less.
    Code:
    ' Sample call to class Sub: TestMe() -- but...
    ' must include dummy params, so: TestMe(ByVal Dummy1 As Long, ByVal Dummy2 As Long, ByVal Dummy3 As Long)
        CallWindowProc ptrFunction, ObjPtr(class), 0, 0, 0
    
    ' Sample call to class Function: TestMe(ByVal anything As Long) As Long -- but...
    ' must include dummy param, so: TestMe(ByVal anything As Long, ByVal Dummy As Long) As Long
        Dim lRet As Long
        CallWindowProc ptrFunction, ObjPtr(class), 1908, 0, VarPtr(lRet)
    Another limitation with CallWindowProc besides the parameter count is the parameter sizes if needing to pass, ByVal vs. ByRef, vartypes like Double or Currency (8 bytes will use 2 API params). Passing 8 byte values/structures to COM is not uncommon. And passing a variant ByVal is not possible since it requires 4 API params (16 bytes) just for itself. When CallWindowProc API can't be used because of its limitations, you have two other choices: a thunk or another API: DispCallFunc. DispCallFunc has no limitations, but has a small learning curve if you have never used it. That API was specifically designed for calling COM methods and more
    Last edited by LaVolpe; May 6th, 2020 at 08:24 PM.

  3. #3
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,784

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

    On a hunch, I took this version and replaced zAddressOf in a UserControl using the self-sub code extensively (my ShellBrowse control), with no other changes, and this eliminated the incompatibility with IObjectSafety I posted about in this thread. No side effects found so far, checked both IDE and compiled. Thanks.

  4. #4

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

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

    Quote Originally Posted by fafalone View Post
    ...and this eliminated the incompatibility with IObjectSafety I posted about in this thread. No side effects found so far, checked both IDE and compiled. Thanks.
    Simply means there is a logic flaw in Paul's original work that my latest version doesn't suffer from. I'd really have to re-study Paul's original logic to figure out why. But honestly, not that interested. I have been using my own modifications for over a decade. This one is just the latest, but completely rewritten from scratch, and I have a thunk version too

    Edited: FYI, if you are using that function only for usercontrols, you can change the For:Next loop to start with 5 vs. 1. That will speed up the results even more in that specific case.
    Last edited by LaVolpe; May 1st, 2020 at 03:20 PM.
    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}

  5. #5
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Posts
    3,020

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

    Quote Originally Posted by LaVolpe View Post
    Edited: FYI, if you are only using that function for usercontrols, you can change the For:Next loop to start with 5 vs. 1. That will speed up the results even more in that specific case.
    Speed is non-issue, reliability is king here. Hope you tested this with different compilation options if these affect relevant codegen. I mean Fast code vs Small code vs No optimizations and various advanced options too.

    So if there is a form with several (private) SUBCLASSPROCs for various controls on it, how can GetAddressOfEx be used to find the addressed for these several SUBCLASSPROCs?

    Have to pass correct nOrdinal? Have to figure it out beforehand by counting private methods or am I missing something?

    cheers,
    </wqw>

  6. #6
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,784

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

    It is your modifications, "v2.0 Re-write by LaVolpe"

    But yeah, super low priority if you're not interested since this is a drop-in replacement that fixes it without breaking anything.

    Edit: Can just elimate that entire For loop right? Don't see any reason for it if it's From 5 To 5. Not seeing any problems with so far (changed the Exit For's to Exit Do's), but given how unrelated the whole thing with IObjectSafety is, I'm not trusting the obvious to be true. Recall it's just simply the presence of the IObjectSafety methods anywhere in the code, not like they're public, or at the end with the subclass/callback procs, or in any way different from the several other Implemented interfaces. What's the connection? Who knows. So I ask about what should be obvious with just elimating the For loop all together.

  7. #7

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

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

    Quote Originally Posted by wqweto View Post
    Hope you tested this with different compilation options if these affect relevant codegen. I mean Fast code vs Small code vs No optimizations and various advanced options too.
    I have tested quite a few including PCode. But I doubt any optimizations are going to change anything. VB seems to be using small stubs with an address to the actual function. The code returns the stub pointer. The optimizations will be applied to the function's actual code. The stub itself is just a few instructions.
    Quote Originally Posted by wqweto View Post
    So if there is a form with several (private) SUBCLASSPROCs for various controls on it, how can GetAddressOfEx be used to find the addressed for these several SUBCLASSPROCs?
    Ordinals.
    Quote Originally Posted by wqweto View Post
    Have to pass correct nOrdinal? Have to figure it out beforehand by counting private methods or am I missing something?
    You gotta count them from bottom to top, but ordinal #1 is the last private method. So add callbacks, that this function would be used for, towards the bottom of the code page.

    If this helps, I usually use banners and mark ordinals so I don't screw things up a year later when I revisit for enhancements... In this example, the following would be the last 2 methods in the code page:
    Code:
    ' //////////////// DO NOT ADD ANY NEW CODE FROM HERE TO THE END OF THE CODE PAGE \\\\\\\\\\\\\\\\\\\\
    	Private Function mySubclassProc( ... ) As Long
    		....
    	End Function	' ordinal #2
    	Private Function myHookProc( ... ) As Long
    		....
    	End Function	' ordinal #1
    ' //////////////////////////////// DO NOT ADD ANY NEW CODE BELOW THIS BANNER \\\\\\\\\\\\\\\\\\\\\\\\\\\
    But to be clear for others that may be reading this. You cannot just use the returned function pointer with typical APIs that want AddressOf (callback) because they will not know to include the required pThis parameter to call COM methods. Any subclassing would likely be via thunks which would know how to callback to COM if they were designed that way.
    Last edited by LaVolpe; May 1st, 2020 at 06:57 PM.
    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}

  8. #8

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

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

    Quote Originally Posted by fafalone View Post
    Edit: Can just elimate that entire For loop right? Don't see any reason for it if it's From 5 To 5. Not seeing any problems with so far (changed the Exit For's to Exit Do's), but given how unrelated the whole thing with IObjectSafety is, I'm not trusting the obvious to be true. Recall it's just simply the presence of the IObjectSafety methods anywhere in the code, not like they're public, or at the end with the subclass/callback procs, or in any way different from the several other Implemented interfaces. What's the connection? Who knows. So I ask about what should be obvious with just elimating the For loop all together.
    Yes, just initialize voffset to: vOffset = nAddr + &H7A4
    However if you are also using this for property pages or any other code pages, recommend leaving it in. Otherwise, might want to just rem that other stuff out vs removing it? Should the need to use it for other code pages, at least it's there?

    Quote Originally Posted by fafalone View Post
    It is your modifications, "v2.0 Re-write by LaVolpe"
    In that version, the thunks were rewritten, not the support code, i.e., zAddressOf. Looking at notes in that routine, the only contribution I added was an offset for PropertyPages. Bottom line, appears to be his original logic though. And if I'm honest, I don't know if any of my previous revisions would have fixed the problem you mentioned, simply because I'm not sure what caused the problem to begin with

    Edited: Problem with Paul Caton's routines discovered and fixed via PMs. Whether fafalone goes back to Paul's code or stays with mine...
    Last edited by LaVolpe; May 1st, 2020 at 06:24 PM.
    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}

  9. #9

  10. #10
    Hyperactive Member
    Join Date
    Dec 2008
    Location
    Argentina
    Posts
    320

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

    Hello, very good work, I tried to do a simple subclass but it doesn't work for me, what am I doing wrong, am I not understanding something?

    Class1
    Code:
    Option Explicit
    Private Declare Function VirtualQuery Lib "kernel32.dll" (ByVal addr As Long, pMBI As Any, ByVal lenMBI As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
    Private Declare Function SetWindowLongA Lib "USER32" (ByVal Hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
    Private Declare Function CallWindowProcA Lib "USER32" (ByVal lpPrevWndFunc As Long, ByVal Hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Private Const GWL_WNDPROC As Long = -4
    Private Const WM_DESTROY As Long = &H2
    Private Const WM_MOUSEMOVE As Long = &H200
     
    Private PrevWndProc As Long
     
    Public Function SubClass(Hwnd As Long)
        PrevWndProc = SetWindowLongA(Hwnd, GWL_WNDPROC, GetAddressOfEx(Me, 1))
    End Function
    
    Public Function UnSubClass(Hwnd As Long)
        PrevWndProc = SetWindowLongA(Hwnd, GWL_WNDPROC, PrevWndProc)
    End Function
    
    Private Function GetAddressOfEx(ByRef VbCodePage As Object, ByVal nOrdinal As Long, _
                                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 returns results faster in less code vs. using older brute-force style.
        
        ' 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.
        '   :: nMethodCount is returned with the number of user-defined methods in the code page
        '   :: nLastMethodOffset is returned with the VTable address of 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, their 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 AND 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 MBI&(0 To 6)    ' faux MEMORY_BASIC_INFORMATION structure
        ' This structure is key to not crashing while probing memory addresses.
        ' The 6th 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 at least
        ' four times until the right code page offset is found. After that, 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.
        
        CopyMemory nAddr, ByVal ObjPtr(VbCodePage), 4 ' host VTable
        For n = 1 To 5
            Select Case n
                Case 1: vOffset = nAddr + &H1C      ' known offset for VB Class,DataEnvironment,Add-in,DHTMLPage
                Case 2: vOffset = nAddr + &H9C      ' known offset for VB DataReport
                Case 3: vOffset = nAddr + &H6F8     ' known offset for VB Form, MDI
                Case 4: vOffset = nAddr + &H710     ' known offset for VB Property Page
                Case 5: 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
                MBI(5) = 0: VirtualQuery vOffset, MBI(0), 28
                If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit For
                
                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
                    MBI(5) = 0: VirtualQuery nMethod, MBI(0), 28
                    If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit For
                
                    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
            MBI(5) = 0: VirtualQuery vOffset, MBI(0), 28
            If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit Do
            
            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
            MBI(5) = 0: VirtualQuery nMethod, MBI(0), 28
            If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit Do
            
            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 - 4
        
        ' 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
    
    '////////////////////ORDINAL 1/////////////////////
    Private Function WindowProc(ByVal Hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
        WindowProc = CallWindowProcA(PrevWndProc, Hwnd, uMsg, wParam, lParam)
        Debug.Print uMsg
    End Function
    leandroascierto.com Visual Basic 6 projects

  11. #11

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

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

    Quote Originally Posted by LeandroA View Post
    Hello, very good work, I tried to do a simple subclass but it doesn't work for me, what am I doing wrong, am I not understanding something?
    I tried to explain that in post #7. Maybe I need to emphasize in post #1 instead (which I just did)
    I apologize. I should have foreseen a question similar to this and made it clear early on in the thread.

    But to be clear for others that may be reading this. You cannot just use the returned function pointer with typical APIs that want AddressOf (callback) because they will not know to include the required pThis parameter to call COM methods. Any subclassing would likely be via thunks which would know how to callback to COM if they were designed that way.
    Last edited by LaVolpe; May 1st, 2020 at 07:29 PM.
    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}

  12. #12
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,784

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

    Hmm... how did you determine this was faster?

    There's a part of my code that subclasses 4-10+ edit controls in a row, and with the old zAddressOf, there's no perceptible delay in them appearing after the click that triggers their creation, but with this method there's a 750-1000ms or so delay for 8 subclassings, a smaller delay for fewer. Switched the methods back and forth several times (and completely excluding the IObjectSafety thing I've been on about), and it's definitely a noticeable difference. (This is after the change you suggested to eliminate the For loop).

  13. #13

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

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

    @fafalone. Thanx for pointing out the delay. There was room for improvement. I ran tests on a class with over 500 methods and 100 public variables

    First. Tweaks were made and the routine is now 14x faster than before. However, when I compared against Paul Caton's most recent version, I can't beat it in speed and there is a reason. He uses IsBadCodePtr to verify memory is accessible.

    That function can trigger a page-guard. And basically, it tests access like:
    On Error Resume Next: ReadMemory: If Err (GPF) Then BadRead
    Raymond Chen's article: IsBadXxxPtr should really be called CrashProgramRandomly
    per Microsoft:
    Important. IsBadCodePtr is obsolete and should not be used. Despite its name, it does not guarantee that the pointer is valid or that the memory pointed to is safe to use
    I chose to use VirtualQuery which can prevent page-guard activation and is not obsolete. It is slower but I didn't want to use IsBadCodePtr.

    Second. I also added an optional Enum parameter to select the type of code page to be scanned. That would prevent people commenting out, or changing, the For:Next loop and also adjusts that loop automatically. Ultimately, it can optimize the loop, better efficiency.

    Third & Last. If you or anyone else is calling this routine multiple times on the same code page, you can actually improve performance if desired. Example if subclassing...

    - instead of calling the function multiple times in the same code page...
    Code:
        DoSubclass Command1.hWnd, GetAddressOfEx(Me, 1, cptUserControl)
        DoSubclass Command2.hWnd, GetAddressOfEx(Me, 2, cptUserControl)
        DoSubclass Command3.hWnd, GetAddressOfEx(Me, 3, cptUserControl)
        ... etc
    - call it once and use the optional offset. Just an option, more code but more efficient. However, the updated code is not slow. I think you would agree.
    Code:
        Dim nOffset As Long, pAddr As Long
        pAddr = GetAddressOfEx(Me, 1, cptUserControl, , nOffset)
        DoSubclass Command1.hWnd, pAddr 
        CopyMemory pAddr, ByVal nOffset - 8, 4 ' ordinal 2 * 4
        DoSubclass Command2.hWnd, pAddr
        CopyMemory pAddr, ByVal nOffset - 12, 4 ' ordinal 3 * 4
        DoSubclass Command3.hWnd, pAddr
    Last edited by LaVolpe; May 2nd, 2020 at 10:23 AM.
    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}

  14. #14
    Member
    Join Date
    Apr 2019
    Posts
    45

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

    Amazing LaVolpe!

    you cannot just use the returned function pointer with typical APIs that want an AddressOf
    To be fair this is likely one of the reasons why AddressOf was never implemented to work with class functions.

    I'm no expert with making thunks... How easy would it be to make a dynamic thunk which calls COM function pointers with the correct calling convention/arguments etc? In theory you can inject the objectPointer at runtime and allocate a new thunk in memory each time... I mean it's not very memory efficient of course, but it would likely be very useful if careful

    Code:
    class SomeClass
       private cb as long
       sub test()
          cb = GetThunkAddressOf(me,1,0,0)
          Call someCFunction(cb)
       end sub
       function myCallback() as long
         'whatever
         Call FreeThunkAddress(cb)
       end function
    end class
    Function GetThunkAddressOf(PagePtr as object, nOrdinal as integer, funcSubType as integer,nParams as integer)
       Dim thunk() as byte
       'populate thunk
       'thunk(...) = ObjPtr(pagePtr)
       'add params based on number of params in calling function (nParams+2)
       'Call to GlobalAlloc()
       'Call to VirtualProtect()
       'return pointer
    End Function
    Function FreeThunkAddress(ptr as long)
      'Call GlobalFree(ptr)
    End Function
    Thoughts? Not sure how possible this is but I remember a somewhat similar approach was taken for an AHK module (MCode) here: https://www.autohotkey.com/boards/vi...c.php?f=7&t=32
    Last edited by sancarn; May 5th, 2020 at 05:10 PM.

  15. #15

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

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

    In theory? Yes, but not so simple.

    You need to know whether it's a sub or function (i.e., how many "hidden" parameters to send to VB) and also the total size of all parameters (if any).

    You can't get that without TypeLib/TypeInfo unless one were to parse/disassemble the assembly code at the target function pointer to determine how many bytes are popped off the stack when the method returns. TypeLib/TypeInfo isn't going to exist for private methods. Via IDispatch, you may be able to get that info for Public methods.

    Edited: And before I went through all that trouble, I'd use DispCallFunc API. No dynamic thunk creation needed. It does require knowledge of params, ObjPtr, and function pointer. If you're going to create a dynamic thunk, you'd have all that info anyway. See post #2

    Edited #2: DispCallFunc can handle several calling conventions
    Last edited by LaVolpe; May 5th, 2020 at 06:23 PM.
    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}

  16. #16
    Member
    Join Date
    Apr 2019
    Posts
    45

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

    Quote Originally Posted by LaVolpe View Post
    In theory? Yes, but not so simple.

    You need to know whether it's a sub or function (i.e., how many "hidden" parameters to send to VB) and also the total size of all parameters (if any).

    You can't get that without TypeLib/TypeInfo unless one were to parse/disassemble the assembly code at the target function pointer to determine how many bytes are popped off the stack when the method returns. TypeLib/TypeInfo isn't going to exist for private methods. Via IDispatch, you may be able to get that info for Public methods.

    Edited: And before I went through all that trouble, I'd use DispCallFunc API. No dynamic thunk creation needed. It does require knowledge of params, ObjPtr, and function pointer. If you're going to create a dynamic thunk, you'd have all that info anyway. See post #2

    Edited #2: DispCallFunc can handle several calling conventions
    Right! Size! I forgot about that one, yes I guess you would have to do what many dynamic languages do. E.G. GetPtr(me,1,"PPI","P") for params: String, String, Integer and return type String. That way you can at least calculate it on demand, although you still have limited functionality as far as STRUCTs are concerned.

    I understand DispCallFunc is useful for calling directly indeed. Only unfortunate thing is it can't be passed directly to a C callback (correct me if I'm wrong).

    Anyhow, nevermind. Maybe if I ever get some experience with thunking I'll give it a go

  17. #17

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

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

    DispCallFunc can call CDecl, but if you need to pass a callback for that CDecl method, then you'll probably want a thunk for that callback to ensure no stack corruption & proper stack cleanup during calls to that callback. I have several thunks found here, including calling CDecl with/without callbacks.
    http://www.vbforums.com/showthread.p...re-A-new-breed
    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}

  18. #18
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,784

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

    The new update puts the delays back to imperceptible for the range of number of successive calls I need to cover, thanks.

    A couple people using my control had pointed out completely random, but infrequent, crashes that I absolutely couldn't run down or replicate as no specific set of steps could trigger it, maybe it was the IsBadCodePtr call? So glad to be able to use this instead.

  19. #19
    Addicted Member
    Join Date
    Jun 2016
    Location
    Espaņa
    Posts
    244

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

    very good work,
    I could provide an example project, subclassing and call the function pointier

    Greetings

  20. #20
    Member
    Join Date
    Oct 2018
    Posts
    44

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

    Quote Originally Posted by LaVolpe View Post
    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.

    Is this routine better than:

    Code:
    Private Function zAddressOf(ByVal oCallback As Object, ByVal nOrdinal As Long) As Long
    ' Note: used both in subclassing and hooking routines
        Dim bSub As Byte                                                        'Value we expect to find pointed at by a vTable method entry
        Dim bVal As Byte
        Dim nAddr As Long                                                         'Address of the vTable
        Dim I As Long                                                     'Loop index
        Dim j As Long                                                     'Loop limit
        RtlMoveMemory VarPtr(nAddr), ObjPtr(oCallback), 4                         'Get the address of the callback object's instance
        If Not zProbe(nAddr + &H1C, I, bSub) Then                                 'Probe for a Class method
            If Not zProbe(nAddr + &H6F8, I, bSub) Then                              'Probe for a Form method
                ' \\LaVolpe - Added propertypage offset
                If Not zProbe(nAddr + &H710, I, bSub) Then                            'Probe for a PropertyPage method
                    If Not zProbe(nAddr + &H7A4, I, bSub) Then                          'Probe for a UserControl method
                        Exit Function                                                   'Bail...
                    End If
                End If
            End If
        End If
        I = I + 4                                                                 'Bump to the next entry
        j = I + 2048                                                              'Set a reasonable limit, scan 256 vTable entries
        Do While I < j
            RtlMoveMemory VarPtr(nAddr), I, 4                                       'Get the address stored in this vTable entry
            If IsBadCodePtr(nAddr) Then                                             'Is the entry an invalid code address?
                RtlMoveMemory VarPtr(zAddressOf), I - (nOrdinal * 4), 4               'Return the specified vTable entry address
                Exit Do                                                               'Bad method signature, quit loop
            End If
            RtlMoveMemory VarPtr(bVal), nAddr, 1                                    'Get the byte pointed to by the vTable entry
            If bVal <> bSub Then                                                    'If the byte doesn't match the expected value...
                RtlMoveMemory VarPtr(zAddressOf), I - (nOrdinal * 4), 4               'Return the specified vTable entry address
                Exit Do                                                               'Bad method signature, quit loop
            End If
            I = I + 4                                                               'Next vTable entry
        Loop
    End Function

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