Closed NicoR45 closed 9 months ago
Hi @NicoR45 ,
Thanks for raising the issue!
Presumably this is an issue when downloading the raw file, which I will make it Windows-style as requsted.
Can you please download the available zip and then import the MouseOverControl.cls module? Does it still import as a standard module or does it import properly as a class module?
Thanks!
I noticed the same behavior, using the zip file as you recommended indeed prevents this issue :)
Thanks @guwidoe ,
I followed the stepes outlined here and it still doesn't want to update the line endings. Any idea what I am doing wrong?
I never had to deal with this issue before so unfortunately no.
Hi @Greedquest ,
Apologies for contacting you here, but do you have any idea how to solve this. It seems whatever I do does not fix the line endings when downloading the raw file. Thanks!
https://github.com/Greedquest/ListObject-WithEvents/issues/2
Is a workaround after cloning
then I believe
*.bas eol=crlf linguist-language=VBA
*.cls eol=crlf linguist-language=VBA
*.doccls eol=crlf linguist-language=VBA
*.twin linguist-language=VBA
Is what I use in my gitattributes although let me just check if it downloads correctly
More info: GitHub will perform the normalisation when you download as zip, even if the files are stored with LF only on github
https://stackoverflow.com/a/76548787/6609896
Ok I'm re-reading and none of this is news to you. If you followed the steps to re-apply line endings in that help page you listed then I don't have any bright ideas I'm afraid. You could clone the repo, run that powerquery on it, commit changes. See in notepad++ whether the CRLF in gitattributes is actually being respected
Thanks @Greedquest !
Darn. Lost hope now.
I basically have:
*.bas text eol=crlf linguist-language=VBA
*.cls text eol=crlf linguist-language=VBA
*.frm text eol=crlf linguist-language=VBA
I only added the text
word today but that did nothing. Also followed these steps which also did nothing.
I just downloaded one of your cls
files and it downloads with lf (not crlf) so same issue.
You might be interested in this trick. It basically fixes the way the arguments are pushed on the stack for x64 by faking the delegate signature. This also solves it for SetTimer. The gain is that one can now step through the code in x64 whereas before that would have caused a crash. It still crashes when pressing Reset
while in the scope of the callback but that's another issue.
@cristianbuse Yeah I just checked my own files have only the LF - although that is laziness since I expect users to download my .xlam or clone my repo, not download raw files individually. But linefeed correction is not mandatory in git, it is to avoid merge conflicts if one dev is on UNIX, one is on Windows, you would get a conflict of linefeed characters when both push the same file on github. So Git normalises all to lf
before pushing to ensure this isn't an issue. However you can disable that automatic behaviour to ensure crlf gets pushed to github surely?
git config --global core.autocrlf false
Then you commit all your crlf
s to git. Also it seems if you double up in the gitattributes file you specify one linefeed style for OnCommit, one for OnCheckout. Ensure the OnCommit is crlf
and that's what should end up on github IIUC.
But untested
I don't understand that trick - what is the magic number and what is the argument alignment issue it solves? I remember you mentioning it but it was over my head at the time? But I got my asm working! I have a lightweight COM object implementing IEnumVariant using a simple VTABLE of vba function pointers. Allocated using CoTaskMemAlloc. Crucially, it has a custom IUnknown::Release written in asm rather than vba. This Release pops a messagebox and calls cotaskmemfree on the instance, even if End or a stop button is hit. So guaranteed cleanup
@Greedquest
git config --global core.autocrlf false
It was indeed set to true but I was under the impression that .gitattributes takes precedence over autocrlf. It also seems the data on GitHub has the wanted crlf - just the download raw button is the issue.
But I got my asm working!
Would definitely want to see that working because I tried following your SO posts and it's beyond my skill level.
I don't understand that trick - what is the magic number and what is the argument alignment issue it solves.
The magic number is an offset. The AddressOf
operator returns the address of a delegate, not the actual function address.
Consider the following scenario:
UserForm1
with code:
Option Explicit
Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hWnd As LongPtr) As Long
Private m_hWnd As LongPtr
Private Sub UserForm_Initialize() IUnknown_GetWindow Me, VarPtr(m_hWnd) End Sub
Public Property Get GetHWnd() As LongPtr GetHWnd = m_hWnd End Property
- a standard module:
```VBA
Option Explicit
Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private f As UserForm1
Sub TestTimer()
Set f = New UserForm1
SetTimer f.GetHWnd, 1, 100, AddressOf TimerProc
End Sub
Sub TimerProc(ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal nIDEvent As LongPtr, ByVal wTime As Long)
Stop 'BOOM. Crashes on x64, cannot step through code. Works fine on x32
KillTimer hWnd, nIDEvent
End Sub
When running TestTimer
, the first time the callback is called, code stops on the Stop
statement which causes a crash on x64 (same issue with a mouse hook). Works fine on x32.
Now consider this variation of the standard module (no parameters in the callback):
Option Explicit
Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private f As UserForm1
Private m_hwnd As LongPtr
Private m_nIDEvent As LongPtr
Sub TestTimer()
Set f = New UserForm1
m_hwnd = f.GetHWnd
m_nIDEvent = 1
SetTimer m_hwnd, m_nIDEvent, 100, AddressOf TimerProc
End Sub
Sub TimerProc()
Stop 'Works fine on both x64 and x32
KillTimer m_hwnd, m_nIDEvent
End Sub
which proves that something is wrong with how the parameters are pushed on the stack.
Finally, this fixes the issue:
Option Explicit
Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private f As UserForm1
Sub TestTimer()
Set f = New UserForm1
Dim ptrFake As LongPtr: ptrFake = VBA.Int(AddressOf FakeProc) 'Points to a delegate object, not the function itself
Dim ptrProc As LongPtr: ptrProc = VBA.Int(AddressOf TimerProc)
'
Dim addrFake As LongPtr: addrFake = ptrFake + PTR_SIZE * 6 + 4 'PTR_SIZE * 6 + 4 = 52 on x64
Dim addrProc As LongPtr: addrProc = ptrProc + PTR_SIZE * 6 + 4
'
MemLongPtr(addrFake) = MemLongPtr(addrProc) 'Swap the function that the delegate actually calls from FakeProc to TimerProc
'
SetTimer f.GetHWnd, 1, 100, ptrFake 'Pass the address of the fake delegate which then points to the correct callback
End Sub
Sub FakeProc() 'Never called
End Sub
Sub TimerProc(ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal nIDEvent As LongPtr, ByVal wTime As Long)
Stop 'Works fine on both x64 and x32
KillTimer hWnd, nIDEvent
End Sub
I'm sure it will make sense now. This obviously doesn't solve the issue when 'Reset' is pressed in the IDE but that's only an issue within the scope of the actual callback, hence I am working around that by postponing the processing to a Terminate
event which will be asyncronous like here. At least this trick allows debugging 😄 .
I would of course want to get rid of any crashes once and for all hence very curious of what you've achieved with asm. Many thanks!
OK so at (AddressOf ...Proc) + 52
, there exists some value x which you swap in FakeProc for the same value from TimerProc.
What exactly goes on in those first 52 bytes of "prologue"?
So you have the prologue from FakeProc, but the actual body from TimerProc. That prologue is the setup for a Sub() with zero args prologue rather than a Sub() with 4 args, which by trial and error you have found is what the debugger requires to not crash things. Strange. What's strange is that for 64 bit, the first 4 args are passed in registers not on the stack. So I'm more surprised you have weird stack misalignment in 64 bit than in 32 bit.
I guess all we can say is that those 52 bytes of prologue code in the version with args are corrupting the registers or the stack somehow.
Incidentally have you tried using a debugger. I have been experimenting with one called x64 debug and it's actually surprisingly easy. I solved all the asm questions pretty rapidly (was a bug in tB :'( I was stuck on for ages - my asm worked in VBA. Still tB is great for trial and error since it doesn't totally crash when you do something dumb)
PS thanks for the thorough answer!
@Greedquest
What exactly goes on in those first 52 bytes of "prologue"?
They are identical. Seems like a signature thing for some kind of delegate object. I think a good analogy is that I am swapping a method address inside some weird virtual table.
which by trial and error you have found is what the debugger requires to not crash
Yes 😆 . Lots of trial and error
What's strange is that for 64 bit, the first 4 args are passed in registers not on the stack.
I read a while ago about this on x32 and x64 when doing the instance redirection in LibMemory so I am surprised as well. There are a lot of x64 bugs though - just one more.
Incidentally have you tried using a debugger.
No, I did not know there is such a thing for VBA. Can you please give me a link?
Also, where can I find the finalized ASM bit?
Many thanks!
Went away, hence no reply!
The debugger I've been using is called x64 dbg https://x64dbg.com/ . It's pretty cool, I tried winDBG or something like that which Microsoft makes and looks a lot like an office product, however if turned out to be command based rather than graphical like the vba debugger. They're both assembly code debuggers - i.e. they give the raw bytes in memory and let you step through each instruction. What's great with x64 debug is it annotates all the assembly opcodes, uses arrows to show the jumps, even matches function pointers to known dll entries so you can see what function is being called.
The way I used it is launch excel or twinbasic. Open the debugger and attach to process excel.exe you need to learn the keyboard shortcut for "continue without breaking on exceptions". Run VBA and put a vba breakpoint somewhere in the code. Now use hex( adressof foo) or varptr(myassemblycode) to get a pointer to the stuff you are interested in in the immediate window.
While VBA is paused, Back in the assem debugger, hit ctrl g and paste the hex to jump to that address in memory. Now put a breakpoint here (click in margin just like in vbide) and continue execution of the VBA. Now the breakpoint gets hit - vbide freezes but the debugger lets you step through.
Inspect registers set values, jump to a line. Really good.
That's my improvised approach anyway, and it helped me find some tricky bugs in my injected assembly (tB passes by ref the this ptr, but VBA passes by val! Nasty bug)
I recommend this video it's only 8 mins and was the only thing I watched:
https://youtu.be/vq_VkoCgk3c?si=WheQhnh_c8D-4bSd
(I don't necessarily agree with what he's doing but the tutorial is pretty great if you have background knowledge)
RE the code I've been working on, I need to do one more thing which is to call IUnknown:: Release - you can imagine this is important for a cleanup routine.
But I've got it working to display a message box and call CoTaskMemFree after a stop button press or an end statement, which I'm pretty happy about. I also need to write the 32 bit version but that can wait.
I'll probably post on CR a write up because it is quite interesting but I'm really out of my depth and need bug checking
Many thanks @Greedquest for the detailed guidance.
Hi @Greedquest ,
This might be of interest to you.
Problem : in Microsoft 365 MSO (Version 2302 Build 16.0.16130.20690) 32-bit VBA Editor, when the MouseOverControl.cls file is imported, it is imported into the Modules folder instead of the Class Modules folder.
Solution : the issue appears to be line endings. When downloaded the MouseOverControl.cls file from Github, the line endings are UNIX-style (LF or \n) and from my local files they are Windows-style (CR + LF or \r\n). After switching the downloaded file with Windows-style line endings the file imported correctly as a Class.
Can you make the file download Windows-style please?