Page 1 of 2 12 LastLast
Results 1 to 40 of 42

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

  2. #2

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

    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
    5,709

    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.
    Last edited by fafalone; May 1st, 2020 at 02:04 PM.

  4. #4

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

    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
    Location
    Sofia, Bulgaria
    Posts
    5,156

    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
    5,709

    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.
    Last edited by fafalone; May 1st, 2020 at 04:24 PM.

  7. #7

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

    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,541

    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
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,709

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

    Yeah I just commented it out and left a note for anyone who might copy it from my code that they need to put it back.

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

    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,541

    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
    5,709

    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).
    Last edited by fafalone; May 1st, 2020 at 08:00 PM.

  13. #13

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

    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
    63

    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,541

    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
    63

    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,541

    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
    5,709

    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.
    Last edited by fafalone; May 5th, 2020 at 11:28 PM.

  19. #19
    Hyperactive Member
    Join Date
    Jun 2016
    Location
    España
    Posts
    508

    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
    60

    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

  21. #21
    Member
    Join Date
    Apr 2019
    Posts
    63

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

    Struggling to get this working in 64 bit... I'm probably missing something, but wondered whether anyone had any idea what?

    Code:
    'Routine is basically an AddressOf function for VB code pages (forms, classes, etc)
    ' Based on Paul Caton's zAddressOf method that can find function pointers within VB forms, classes, etc. Redesign
    ' by LaVolpe and 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.
    '@param {ByRef Object} VbCodePage - VBA Class / Form / Workbook / Sheet containing the method to obtain a pointer of
    '@param {Long} nOrdinal - The ordinal whose function pointer/address is to be returned. Ordinals are always one-bound and
    '       counted from the bottom of the code page. I.E. The last method is ordinal #1, 2nd last is #2 etc. Keep public methods
    '       near the top of the class & private/friend near bottom because VB will move public ones closer to top during runtime
    '       offsetting the ordinals.
    '@param {CodePageTypeEnum} CodePageType - CodePageType when passed can help the function scan the code page more efficiently
    '@param {ByRef Long} nMethodCount - The number of methods is returned with the number of user-defined methods in the code page. [OUT]
    '@param {ByRef Long} nLastMethodOffset - The address after the last user-defined method. [OUT]
    '@returns {Long} On success, the function pointer else 0. If 0 nMethodCount and nLastMethodOffset may not be updated.
    '@remark Modified by sancarn to make 64 bit compatible
    '@dev 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
    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 LongPtr
        
        If nOrdinal < 1 Then Exit Function
        If VbCodePage Is Nothing Then Exit Function
        
        Dim bSig As Byte, bVal As Byte
        Dim nAddr As LongPtr, vOffset As LongPtr, nFirst As LongPtr
        Dim nMethod As LongPtr, nAttempts&, n&
        Dim minAddrV As LongPtr, maxAddrV As LongPtr, minAddrM As LongPtr, maxAddrM As LongPtr
        
        '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.
        Dim MBI(0 To 6) As LongPtr
        
        '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), SIZEOF_PTR ' 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), 7 * SIZEOF_PTR 'CHECK:28
                    If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit For
                    minAddrV = MBI(0): maxAddrV = minAddrV + MBI(3) - SIZEOF_PTR  ' set min/max range
                End If
                CopyMemory nMethod, ByVal vOffset, SIZEOF_PTR ' 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), 7 * SIZEOF_PTR 'CHECK:28
                        If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit For
                        minAddrM = MBI(0): maxAddrM = minAddrM + MBI(3) - SIZEOF_PTR  ' 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 + SIZEOF_PTR               ' 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) - SIZEOF_PTR  ' set min/max range
            End If
            
            CopyMemory nMethod, ByVal vOffset, SIZEOF_PTR    ' 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), SIZEOF_PTR * 7 'CHECK:28
                If (MBI(5) And &HFE) = 0 Or (MBI(5) And &H101) <> 0 Then Exit Do
                minAddrM = MBI(0): maxAddrM = minAddrM + MBI(3) - SIZEOF_PTR  ' 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 = CLng(vOffset - nFirst) \ SIZEOF_PTR
        nLastMethodOffset = CLng(vOffset)
        
        ' Return the function pointer for requested ordinal, if a valid ordinal
        If nOrdinal <= nMethodCount Then
            CopyMemory GetAddressOfEx, ByVal vOffset - (nOrdinal * SIZEOF_PTR), SIZEOF_PTR
        End If
    End Function

  22. #22
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

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

    Quote Originally Posted by sancarn View Post
    Struggling to get this working in 64 bit... I'm probably missing something, but wondered whether anyone had any idea what?
    64-bit? You mean in the VBA of some MS-Office file?

    I doubt very seriously if you'll ever get it to work. Three reasons come to mind immediately: 1) there's a thunk (i.e., machine code), and that's written for a 32-bit environment (either 32-bit Windows, or running in the Windows WoW), 2) there are some "magic numbers" in that code that are very specific for VB6, and 3) it makes tons of assumptions about memory addresses (including vTable entries) being 32-bit (i.e., 4-bytes) each.

    I wrote something similar in another CodeBank entry, but it's not going to work in 64-bit VBA either.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  23. #23
    Member
    Join Date
    Apr 2019
    Posts
    63

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

    Quote Originally Posted by Elroy View Post
    64-bit? You mean in the VBA of some MS-Office file?
    Indeed

    Quote Originally Posted by Elroy View Post
    1) there's a thunk
    No there isn't? The thunk variant is wqweto's This on the other hand uses no thunk as far as I can see?

    Quote Originally Posted by Elroy View Post
    3) it makes tons of assumptions about memory addresses (including vTable entries) being 32-bit (i.e., 4-bytes) each
    Yes, this if you look at my sample is dealt with I believe, which is the main reason why I was asking the question

    Quote Originally Posted by Elroy View Post
    2) there are some "magic numbers" in that code that are very specific for VB6
    Yes, this is the major concern, these magic numbers however were clearly figured out, so one assumes they can be determined again for VBA if someone knows how / is willing to teach me how

  24. #24
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,156

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

    @Elroy: I though that you already had this implemented much better here: https://www.vbforums.com/showthread....thin-an-object

    It has to be "translated" to x64 i.e. GetMemX -> CopyMemory but the code is shorter and not that shaky (no probing).

    I'm just worried about VTable entries in x64 VBA not being native pointers though, as discussed here: https://www.vbforums.com/showthread....g-Obj-pointers

    cheers,
    </wqw>

  25. #25
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

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

    Quote Originally Posted by sancarn View Post
    No there isn't? The thunk variant is wqweto's This on the other hand uses no thunk as far as I can see?
    You're actually correct. However, to actually make any use of a Class-AddressOf, you'd need to use a thunk because of all the (under the hood) differences in the way a class procedures works as opposed to a BAS procedure. So, you'll still need a thunk.

    Quote Originally Posted by sancarn View Post
    Yes, this is the major concern, these magic numbers however were clearly figured out, so one assumes they can be determined again for VBA if someone knows how / is willing to teach me how
    Well yeah, maybe. As stated elsewhere, one of the nice things about VB6 is that we can depend on the infrastructure to be extremely unchanging. But there's no reason whatsoever that this needs to be true for the VBA. It could change for every release of MS-Office, or even for specific updates. And any of this could negate any particular set of magic numbers. There's no way I'd put any magic numbers like this in production code of the VBA. You'd be asking for trouble.


    Quote Originally Posted by wqweto View Post
    @Elroy: I though that you already had this implemented much better here: https://www.vbforums.com/showthread....thin-an-object

    It has to be "translated" to x64 i.e. GetMemX -> CopyMemory but the code is shorter and not that shaky (no probing).

    I'm just worried about VTable entries in x64 VBA not being native pointers though, as discussed here: https://www.vbforums.com/showthread....g-Obj-pointers

    cheers,
    </wqw>
    Hi Wqweto. Yes, and I posted it all in the CodeBank here with extensive documentation. And I don't use any magic numbers. However, I do use certain VB6 internal structures that provide vTable offsets for any particular procedure. And there's no guarantee that those internal structures will be the same in the VBA. In fact, they can't be the same for VBA 64-bit.

    ---------------

    And Sancarn, sorry but I've just got no interest at this time in figuring any of this out for the VBA (32-bit or 64-bit).
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  26. #26
    Member
    Join Date
    Apr 2019
    Posts
    63

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

    Quote Originally Posted by Elroy View Post
    And Sancarn, sorry but I've just got no interest at this time in figuring any of this out for the VBA (32-bit or 64-bit).
    That's totally fine Elroy, no one asked you (specifically) to contribute. Your thread will be a good basis though, even if VBA is changed. You're right, of course, that there are no guarantees, but equally theres no reason one shouldn't try either. The benefit of having such a procedure far outweighs the risk imo.

  27. #27
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,709

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

    sancarn right off the bat I can tell you this is incorrect:

    Code:
        '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.
        Dim MBI(0 To 6) As LongPtr
    The correct x64 size of this structure is 48, and you've defined it as 56 bytes. So you're probably accessing off limits memory.

    The offsets for the members the code uses will be different too, so you'll need to adjust that.

    This is the full structure, so base adjustments off this:

    Code:
    Private Type MEMORY_BASIC_INFORMATION
        BaseAddress As LongPtr
        AllocationBase As LongPtr 'x64 offset: 8
        AllocationProtect As Long 'x64 offset: 16
        #If Win64 Then
        PartitionId As Integer 'x64 offset: 20
        #End If
        'compiler will insert padding on x64, 2 bytes
        RegionSize As LongPtr 'x64 offset: 24
        State As Long 'x64 offset: 32
        Protect As Long 'x64 offset: 36
        Type As Long 'x64 offset: 40
        'compiler will insert padding on x64, 4 bytes
    End Type
    It's probably best to explicitly define this structure and use the members themselves... MBI(3) is .RegionSize and MBI(5) is .Protect.



    I think another problem might be the 'magic numbers'
    Case 0: vOffset = nAddr + &H1C 'known offset for VB Class,DataEnvironment,Add-in,DHTMLPage etc

    ... they're address offsets. Even if they're in the same places, and we don't know that they are, they'd be at multiple of 8 instead of 4. So you'd expect the class offset above to be &H38 instead of &H1C.


    But even with all the correct adjustments for x64 it doesn't work. My guess is the other magic numbers, for the function signature (values for bVal).
    Last edited by fafalone; Nov 13th, 2022 at 06:00 PM.

  28. #28
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,749

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

    how to get address by method name in class,form1,usercontrol

  29. #29
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

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

    Quote Originally Posted by xiaoyao View Post
    how to get address by method name in class,form1,usercontrol
    Read THIS THREAD. It's all outlined in there with working code.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  30. #30
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,749

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

    does it support on vba64 ,make new timer1.cls for timer events?

  31. #31
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,749

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

    Quote Originally Posted by hennyere View Post
    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 
    End Function
    can't get address of usercontrol method/functions

  32. #32
    Junior Member
    Join Date
    Feb 2024
    Posts
    30

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

    Thank you very much for your excellent article, I have some questions to ask you.

    ' 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


    I don't know what &H81 and &H58 stand for and how to get information about using them, can you explain?


    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
    I don't understand the logic of this paragraph. Can you explain it?

  33. #33
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

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

    LaVolpe has moved on and no longer participates in these forums.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  34. #34
    Junior Member
    Join Date
    Feb 2024
    Posts
    30

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

    Because the class function pointer is very important for vb6 to use, this is my private exploration, and the original author's note is a little different

    ' 2) Byte &H33 xor eax?eax Process code generated when using the P-code engine
    ' 3) Byte &HE9 jmp XXXXXXXX Process code generated when using the compiled machine code
    ' 4) Byte &H81 add dword ptr ss:[esp+0x4],XXXX Access procedures generated for public variables?
    regardless of P-Code usage
    ' 5) Byte &H58 Didn't notice when it appeared

    Tips from buddies would be greatly appreciated!

  35. #35
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

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

    I go into it all in excruciating detail here. Enjoy.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  36. #36
    Junior Member
    Join Date
    Feb 2024
    Posts
    30

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

    Quote Originally Posted by Elroy View Post
    I go into it all in excruciating detail here. Enjoy.
    Hi?Elroy?
    Thank you very much for your generous and speedy recovery, I've been reading your posts on the subject and have gained a lot of knowledge about the inner workings of vb, thank you.

    I tend to use private functions for callback operations, and want to be able to adapt to the IDE and compile to Pcode or machine code for various occasions, and want the final source code to be easy to maintain. So LaVolpe's GetAddressOfEx is the way to go, but since I don't understand how it works, I'm worried about stability for serious use.https://www.vbforums.com/images/smilies/biggrin.gif

  37. #37
    Frenzied Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    1,393

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

    You can always call a private method of a class using the ubiquitous "DispCallFunc" function so you're not limited to using LaVolpe's GetAddressOfEx.

    However the preferred way to use callback operations is by implementing a custom interface containing your callback function.

  38. #38
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

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

    Quote Originally Posted by TomCatChina View Post
    I tend to use private functions for callback operations...
    In the link I gave you, I go through how to use Private functions. But it's similar to the way LaVolpe is doing it, but I'm not leaning on any magic-numbers to find the procedure entry points. I'm just doing it straight through the vTable. IDK, using magic-numbers in a search through executable code always made me very nervous. What's to prevent those magic-numbers from appearing for a reason other than identifying a procedure's entry point.

    Quote Originally Posted by TomCatChina View Post
    ... want to be able to adapt to the IDE and compile to Pcode or machine code...
    In the end, none of that matters because, in all cases, you have to go through the object's vTable, which is the same regardless of whether it's compiled or IDE, or p-code or binary code. So you really don't have to worry about that.

    Quote Originally Posted by TomCatChina View Post
    ... and want the final source code to be easy to maintain.
    That's a tricky one. If you're insisting on keeping your object methods declared as Private, then they're always going to be positionally dependent to find an AddressOf via the vTable. In other words, if you do something as simple as swap the position of your methods around, you'll break your code.

    The only way to avoid this is to declare them as Public and then use that Vb6ComCodeObjectAddressOf function in the link I gave you above.

    Quote Originally Posted by VanGoghGaming View Post
    You can always call a private method of a class using the ubiquitous "DispCallFunc" function so you're not limited to using LaVolpe's GetAddressOfEx.
    If you're wanting to directly use a procedure within an object for API callback (subclassing or hooking), DispCallFunc isn't going to help. And, if we're not doing some kind of callback, why are we worried about AddressOf in the first place?
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  39. #39
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

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

    And TomCatChina, you really should read through that link I gave you.

    Even after you get the address of a procedure in an object, you can't just call it like a procedure in a BAS module.

    Procedures in objects have an extra argument implicitly inserted in the call, and they also return their results differently than a function in a BAS module. So, both LaVolpe and me patch in a piece of machine code (i.e., a "thunk") to rearrange things so that you can call class methods just like BAS procedures.

    There's really no other way to do it without a thunk.

    -------------

    And just a further FYI, VB6 (compiled or p-code) takes care of all of this under-the-hood. But, when you're using a custom "AddressOf" for an object's procedure, those issues are no longer taken care of.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  40. #40
    Junior Member
    Join Date
    Feb 2024
    Posts
    30

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

    Quote Originally Posted by Elroy View Post
    And TomCatChina, you really should read through that link I gave you.
    Yes,I have read your post carefully and clearly understand your point of view.

    Quote Originally Posted by Elroy View Post
    Even after you get the address of a procedure in an object, you can't just call it like a procedure in a BAS module.
    I realize that all of vb's class procedures follow the COM standard with THIS pointer, implied return parameters, and HRESULT return values, and that with clever THUNK code, these side effects can be eliminated for other code to call in STDCALL fashion.

    Quote Originally Posted by Elroy View Post
    The only way to avoid this is to declare them as Public and then use that Vb6ComCodeObjectAddressOf function in the link I gave you above.
    Yes, this is the most robust way to do it, and the source code is easy to maintain, at the cost of using a public process.

    Thank you for your enthusiastic reply, and thank LaVolpe for his work.

Page 1 of 2 12 LastLast

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