Code for mouse wheel in VBA

Colleen45

Active Member
Joined
Jul 22, 2007
Messages
495
What VBA code do I use to use the mouse wheel on my own form, or is on one of the properties
Thank you
 
For anyone interested, the repository VBA-UserForm-MouseScroll now supports Modeless and Modal forms
Just donwloaded your demo workbook and took a quick look at your code ... This is some interesting stuff. Raising errors don't crash excel !.

I have a couple of questions if you don't mind:

1- What is the reason you are calling the ProcessMouseData sub via the MouseOverControl Class_Terminate event ?
2- Could you elaborate a bit on this section of the code for win64 :
VBA Code:
    #If Win64 Then 'Fake callback signature to force fix stack parameters
        Dim fakePtr As LongPtr: fakePtr = VBA.Int(AddressOf FakeCallback)
        Const delegateOffset As Long = 52        '
        CopyMemory ByVal fakePtr + delegateOffset, ByVal ptr + delegateOffset, PTR_SIZE
        ptr = fakePtr
    #End If
3- What is the actual secret behind the windows hook remainig stable despite using modeless form ?

Thank you.
 
Upvote 0

Excel Facts

Square and cube roots
The =SQRT(25) is a square root. For a cube root, use =125^(1/3). For a fourth root, use =625^(1/4).
1- What is the reason you are calling the ProcessMouseData sub via the MouseOverControl Class_Terminate event ?
The application still crashes if the user presses the 'Reset' button in the IDE while code is running in the MouseProc callback method. So, in order to minimize the time spent within the scope of the callback, I defer the processing to a Terminate event which basically runs asyncronously i.e. pressing Reset while in any other method (including the Terminate event itself) does not cause a crash because the callback has exited its scope before the Terminate event is fired and the stack frames were correctly popped. I could have used a call to Application.OnTime but that would have limited it's use to Excel and maybe Word - meanwhile a Terminate event is ubiquitous.

2- Could you elaborate a bit on this section of the code for win64 :
Already did for someone else here. If you are going through the examples, then note that the last block of code uses a memory tools library instead of CopyMemory for speed considerations. The person in that post is aware of the library so I did not link it there. If you never saw that memory library, then I am sure you will love some of the tools in there.

3- What is the actual secret behind the windows hook remainig stable despite using modeless form ?
The hook is removed in the callback which then defers the call asyncronously as mentioned above in 1. The processing takes place (stuff is scrolled) and then a call to DoEvents is done to allow the Application to process any other user input. Finally, the hook is reinstated. Due to the hooking/unhooking, the scrolling is smooth and there is no extra load on the CPU or memory because the callback doesn't get bombared with calls that it cannot handle.

Thanks for asking! Happy to respond if you have any other questions.
 
Upvote 0

@Cristian Buse

Thanks for the detailed explanation.

Using the a class terminate event trick the way you did is very neat. In the past, I have used a one-time windows timer callback (instead of Application.Ontime) for running asyncronous code in similar situations but it never occurred to me to run asyncronous code when a class instance goes out of scope.

I don't know much about the inner workings of the compiler but it seems like you are basically redirecting code execution from the fake proc to the actual callback proc.

This is an extraordinary find !!!

I was aware of the issue you describe with callback parameters in x64 callbacks but never found an explanation.

The thing that I don't really understand is that 52 bytes offset works for both, the Timer Callbak as well as for the Mouse hook callback despite the fact that the callback total paramters bytes in the former being 28 bytes and the latter being 48 bytes (not counting the function returns).

Also, since the FakeCallback proc is not executed, is there any reason why you have a call to UnHookMouse in it ?

BTW, do you happen to know of a debugger for vba ?

Thanks.
 
Upvote 0
@Jaafar Tribak

it seems like you are basically redirecting code execution from the fake proc to the actual callback proc.

This is an extraordinary find !!!
Thank you! Took a lot of trial and error.

The thing that I don't really understand is that 52 bytes offset works for both, the Timer Callbak as well as for the Mouse hook callback despite the fact that the callback total paramters bytes in the former being 28 bytes and the latter being 48 bytes (not counting the function returns).
The 52 bytes offset has nothing to do with the method signature. Just to clarify, the AddressOf operator does not return the address of the actual method. Instead it returns the address of a delegate object that VB* creates behind the scenes. In other words, the API calls back into the delegate which then calls back into the actual method. This is easy to prove, while the swap successfully directs the call from the fake method to the target one (for any API using AddressOf), if you directly call the fake method from VBA then it will get entered as normal thus proving that we have not swapped the actual function adresses.
A good analogy would be this:
- person B has the address of person A written in a notebook
- similarly Person D has the address of person C in their notebook
- we are basically swapping the 53rd page in the notebooks of person B and person D :)
- person A and C haven't physically moved their address, however, person B has now the address of person C and person D has the address of person A

Also, since the FakeCallback proc is not executed, is there any reason why you have a call to UnHookMouse in it ?
Yes - just in case something unforseen goes wrong. I was not able to create a situation where this happens, so it's useless. This is similar to how some people add Err.Raise statements within all methods within an interface class - should not happen that those errors get raised because users should not instantiate the interface class, but does happen :oops:

BTW, do you happen to know of a debugger for vba ?
I do not. The person in that comment I linked in my previous post did mention something called x64 debug but I did not find it and he hasn't reponded when I asked for the link.
 
Upvote 0
@Cristian Buse

Thanks for the explanation.

the AddressOf operator does not return the address of the actual method. Instead it returns the address of a delegate object that VB* creates behind the scenes.
- Been using AddressOf for ages but never knew this. Did you learn about this in some advanced VB* textbook or something ?
And when you say (delegate object), do you mean a propper vtable in memory with pointers to the IUnknown and maybe the IDispatch interfaces ?

- Now, going back to your mousehook project , I don't see in the code below any swapping taking place as much as I see overriding the FakeCallback delegate.
VBA Code:
Private Function GetCallbackPtr() As LongPtr
    Dim ptr As LongPtr: ptr = VBA.Int(AddressOf MouseProc)
    #If Win64 Then 'Fake callback signature to force fix stack parameters
        Dim fakePtr As LongPtr: fakePtr = VBA.Int(AddressOf FakeCallback)
        Const delegateOffset As Long = 52
        '
        CopyMemory ByVal fakePtr + delegateOffset, ByVal ptr + delegateOffset, PTR_SIZE
        ptr = fakePtr
    #End If
    GetCallbackPtr = ptr
End Function

If my understanding is correct, after calling CopyMemory, fakePtr no longer contains a pointer to a delegate object created by vb for the FakeCallback function. Instead, fakePtr now contains a pointer to the delegate object created by vb for the MouseProc function.

Assuming my assumptions above are correct, I imagine the flow of code execution by VB will now be a 3 step process as follows:
Delegate of FakeCallback ----> Delegate of MouseProc -----> Actual MouseProc function

Having said all that, I still don't see the logic as to why\how just delegating the call to an intermediary fake function actually fixes the callback stack parameters issue.

- Finally, I see you breakdown the 52 offset value as ('PTR_SIZE * 6 + 4 = 52 on x64) what' exacltly does the 6 represent ? and the added 4 ? is that somewhat related to the vtable entries if we are talking about a propper object created by VB* ? but that again woudn't match with the 3 or 7 vtable entries.

Thank you.
 
Last edited:
Upvote 0
@Jaafar Tribak

- Been using AddressOf for ages but never knew this. Did you learn about this in some advanced VB* textbook or something ?
No. I made a deduction based on my findings.

And when you say (delegate object), do you mean a propper vtable in memory with pointers to the IUnknown and maybe the IDispatch interfaces ?
Yes and no. You probably know that you can access standard modules methods like this so it makes sense there is an underlying mechanism that does the same thing with AddressOf. The structure seems to be different though - meaning it definitely does not look like the AddressOf operator returns the address of an object derived from IUnknown or IDispatch. Unfortunately, the pointers at that 52 offset indicate a memory address where we don't have read permission.

- Now, going back to your mousehook project , I don't see in the code below any swapping taking place as much as I see overriding the FakeCallback delegate.
If my understanding is correct, after calling CopyMemory, fakePtr no longer contains a pointer to a delegate object created by vb for the FakeCallback function. Instead, fakePtr now contains a pointer to the delegate object created by vb for the MouseProc function.

Assuming my assumptions above are correct, I imagine the flow of code execution by VB will now be a 3 step process as follows:
Delegate of FakeCallback ----> Delegate of MouseProc -----> Actual MouseProc function
If one thing is certain is that AddressOf does not return the address of the method. Let's call this thing a delegate entity rather than a delegate object - probably should have said that since the start.

There is one good reason why I think we are definitely not swapping the whole delegate entity. If we were to swap the whole thing then surely nothing would change and the crash would still occur as if we did not swap at all. What this means is that those 52 bytes preceding the address we swap as well as the following 28 bytes (52 + 8+ 28) really do play a role because otherwise the crash would not get fixed.

So, what is this 88 byte entity? I can only think of the following:
1) it's a custom type structure
2) it's some sort of a manager object which is clearly not COM compatible

Regardless if 1) or 2) above is the actual implementation, at the 52 bytes offset we could have one of the following:
- function pointers OR
- actual PCode or assembly OR
-the address of an object being delegated with calling back

I obviously do now know which one it is or if something else but it's clear that the stuff we are swapping is unrelated with the method's signature.

Having said all that, I still don't see the logic as to why\how just delegating the call to an intermediary fake function actually fixes the callback stack parameters issue.
Without being able to prove it, I think I am swapping the address of the method while retaining the method signature which then somehow forces the intermediate entity to clean the stack itself.

- Finally, I see you breakdown the 52 offset value as ('PTR_SIZE * 6 + 4 = 52 on x64) what' exacltly does the 6 represent ? and the added 4 ? is that somewhat related to the vtable entries if we are talking about a propper object created by VB* ? but that again woudn't match with the 3 or 7 vtable entries.
That was a quick copy paste from a test code dealing with Timers. The posted code in the Mouse Scroll repo uses 52 offset directly.
I initially wrote the code for x32 as well and I was still thinking that the offset is a multiple of pointer positions (as in a vtable) but then I ditched the whole idea as I really did not need the fix for x32, only x64. I simply forgot to replace that for the Timers example which was only intended to explain the issue and how the fix works - please ignore.

Finally, this is how the 88 bytes look when compared using the addresses returned by AddressOf for 2 methods in the same module:
1697449134644.png

These are all 4 bytes values (Long). You can see that the pointers we are swapping are at bytes 52 to 60. All the other bytes are different with the exception of bytes 20 to 24. That could be a 4 byte flag or another 8 byte pointer between bytes 20 and 28 which just happen to point to close addresses (i.e. the high Long is the same while the low Long is different). Swapping those does not seem to do anything.

I realize this won't answer your questions but this is all I know so far about this thing. Cheers!
 
Upvote 0
Thanks @ Cristian Buse
You probably know that you can access standard modules methods like this so it makes sense there is an underlying mechanism that does the same thing with AddressOf
No. I didn't know that. This is some advanced stuff. Very cool but will require some serious reading. I have just dowloaded the addins from the link above and I will try to get my head around the whole thing. I am sure getting a deeper understanding of vba inner workings can translate into overcoming some known limitations.

Again, thanks for letting me know about all this interesting info.
 
Upvote 0

Forum statistics

Threads
1,223,911
Messages
6,175,333
Members
452,636
Latest member
laura12345

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top