How can I handle DLL errors in VBA? How can I handle DLL errors in VBA? vba vba

How can I handle DLL errors in VBA?


From a comment:

Surely if I make an erroneous API call, something's gotta let Excel/my code know that it was bad

Not necessarily. If you ask an API function (e.g. RtlMoveMemory) to overwrite memory at a location for which you supply a pointer, it will cheerfully attempt to do so. Then a number of things could happen:

  • If the memory is not writable (e.g. code), then you will be lucky enough to get an access violation which will terminate the process before it can do any more damage.

  • If the memory happens writable, it will be overwritten and thus corrupted, after which all bets are off.

From your comment:

I'm designing code to attach user supplied callback functions

An alternative would be to design an interface with methods that your client code can implement. Then require the client to pass an instance of a class that implements that interface.

If your clients are VBA, then an easy way to define an interface is to create a public VBA class module with one or more empty methods. By convention, you should name this class with an I (for interface) prefix - e.g. IMyCallback. The empty method(s) (Subs or Functions) can have any signature you want, but I'll keep it simple:

Example:

Class module name: IMyCallbackOption ExplicitPublic Sub MyMethod()End Sub

Alternatively, and better if your clients use languages other than VBA, you can use IDL to define the interface, compile it into a type library, and reference the type library from your VBA project. I won't go into that any further here but ask another question if you want to follow it up.

Then your clients should create a class (VBA class module) that implements this interface in whatever way they choose, e.g. by creating a class module ClientCallback:

Class module name: ClientCallbackOption ExplicitImplements IMyCallbackPrivate Sub IMyCallback_MyMethod()    ' Client adds his implementation hereEnd Sub

You then expose an argument of type IMyCallback and your client can pass an instance of his class.

Your method:

Public Sub RegisterCallback(Callback as IMyCallback)    ...End Sub

Client code:

Dim objCallback as New ClientCallbackRegisterCallback Callback…

You can then implement your own callback function that is called from a Timer, and safely call the client code via the interface.


Some of these Windows API calls can be dangerous. If you want to ship Windows API functionality as a library feature then it would be courteous not to expose you clients to such danger. So, you are best to implement your own interface layer.

Below is code that ships the Windows Timer API as a library feature that is safe for use because it passes string names of callback code instead of pointers.

This code was first published on my blog. Also on that blog post I discuss alternatives to Application.Run if you want options.

Option ExplicitOption Private Module'* Brought to you by the Excel Development Platform blog'* First published at https://exceldevelopmentplatform.blogspot.com/2019/05/vba-make-windows-timer-as-library.htmlPrivate Declare Function ApiSetTimer Lib "user32.dll" Alias "SetTimer" (ByVal hWnd As Long, ByVal nIDEvent As Long, _                        ByVal uElapse As Long, ByVal lpTimerFunc As Long) As LongPrivate Declare Function ApiKillTimer Lib "user32.dll" Alias "KillTimer" (ByVal hWnd As Long, ByVal nIDEvent As Long) As LongPrivate mdicCallbacks As New Scripting.DictionaryPrivate Sub SetTimer(ByVal sUserCallback As String, lMilliseconds As Long)    Dim retval As Long  ' return value    Dim lUniqueId As Long    lUniqueId = mdicCallbacks.HashVal(sUserCallback) 'should be unique enough    mdicCallbacks.Add lUniqueId, sUserCallback    retval = ApiSetTimer(Application.hWnd, lUniqueId, lMilliseconds, AddressOf TimerProc)End SubPrivate Sub TimerProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, _        ByVal dwTime As Long)    ApiKillTimer Application.hWnd, idEvent    Dim sUserCallback As String    sUserCallback = mdicCallbacks.Item(idEvent)    mdicCallbacks.Remove idEvent    Application.Run sUserCallbackEnd Sub'****************************************************************************************************************************************' User code below'****************************************************************************************************************************************Private Sub TestSetTimer()    SetTimer "UserCallBack", 500End SubPrivate Function UserCallBack()    Debug.Print "hello from UserCallBack"End Function