The API declaration:
Private Declare Function CallWindowProc Lib "user32.dll" Alias "CallWindowProcA" ( _
ByVal lpPrevWndFunc As Long, _
ByVal HWnd As Long, _
ByVal msg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
will crash Excel when provided with a non-existent function pointer for the lpPrevWndFunc
parameter.
Similarly,
Private Declare Sub RtlMoveMemory Lib "kernel32" (ByRef Destination As LongPtr, _
ByRef Source As LongPtr, _
ByVal Length As Long)
isn't happy when Destination
or Source
don't exist.
I think these errors are memory access violations. I assume Windows tells the caller that it's doing something it can't1 - perhaps it sends a message to Excel and there's no handler for it? MSDN has this to say about it:
System errors during calls to Windows dynamic-link libraries (DLL) or Macintosh code resources do not raise exceptions and cannot be trapped with Visual Basic error trapping. When calling DLL functions, you should check each return value for success or failure (according to the API specifications), and in the event of a failure, check the value in the Err object's LastDLLError property. LastDLLError always returns zero on the Macintosh. (emphasis my own)
But in these instances I have no values to check for errors, I just get a crash.
1: Provided it catches the error which it might not always, if say a memory rewrite is valid but undefined. But certainly writing to restricted memory or calling fake pointers should be caught before they're executed right?
I'm most interested in:
What causes this crash (both how it is triggered, and what exactly the mechanism is behind it - how does Excel know it needs to be crashing?). What is the message channel over which these errors are being communicated, and can I intercept them with VBA code?
Whether the crash can be pro-actively (i.e. sanitising inputs etc.) or retro-actively (handling errors) prevented.
I think (1) will likely shed light on (2) and vice-versa
Anyway, if anyone knows how to handle API errors like these without Excel crashing, or how to avoid them happening, or anything that would be fab. On Error Resume Next
doesn't seem to work...
Sub CrashExcel()
On Error Resume Next 'Lord preserve us
'Copy 300 bytes from one non existent memory pointer to another
RtlMoveMemory ByVal 100, ByVal 200, 300
On Error Goto 0
Debug.Assert Err.LastDllError = 0 'Yay no errors
End Sub
Motivation
There are 2 main reasons I'm asking about this:
Developing code (the process of debugging etc.) is made much harder when Excel crashes every time I make a mistake. This is not something that can be solved by simply getting it right myself (and exposing a different interface to client code, which uses my existing correct implementations of API calls) because I rarely get it right first time!
I would like to create robust code which is able to handle errors in user input (e.g. invalid function pointers or memory write locations). This can be dealt with to an extent by, for example, abstracting function pointers away into callable classes, but that's not a general solution for other kinds of dll errors (and still doesn't deal with 1.)
Specifically, I'm trying to develop a friendly interface to wrap WinAPI timers. These require callback functions to be registered with them, which (given the limitations of VBA) have to come in the form of Long
function pointers (generated with the AddressOf
keyword).
The callbacks come from user code and may be invalid. The whole point of my wrapping is to improve stability of the API calls, and this is one area that needs improvement.
The memory copy problem is probably out of scope of this question, it's to do with making generators in VBA, but I think the same error handling techniques would be applicable there too, and it makes for an easier example.
I also get errors and crashes from the Timer API generating too many unhandled messages for Excel. Once again I wonder, how does Windows tell Excel "Time to crash now", why can't I intercept that instruction and deal with the error myself (i.e. Kill all the Timers I made & flush the message queue)?