I've been looking for some time for an elegant way to definitely release/close a handle (to a file or similar) in the event of an error and at the same time ensure that the Err object does not lose its current error status.
If my code is in a separate class, I can use its destructor ('Class_Terminate') to release the resources, as far as I am aware and that is how I use it often.
But it also happens that the code is in a procedure of a standard module, for example. Then I don't have a destructor.
If an error occurs after creating a handle in further processing (which I want to bubble up and therefore do not want to lose this error information), I want to ensure that an attempt is made to release/close the handle. During this 'cleanup', an error could possibly occur, but I would like to ignore it.
I have now found/created my solution by outsourcing the cleanup of the handle to a small additional procedure that receives the handle as a parameter.
I pass the address of this procedure and the handle to a new, specially created class called 'HandleReleaser'.
Its destructor now ensures that the procedure specified with its address is executed together with the handle as a parameter.
Since a destructor has its own error context in VBA (an error occurring in it does not change the Err object of the code actually executed), I do not lose it.
Furthermore, I always have to handle errors in the destructor, as it is a code entry point. For this reason, I put an 'On Error Resume Next' there, because I want to ignore errors when cleaning up (at least I tried). Defensive programming in the constructor is of course always additionally helpful.
I would now be interested to know if anyone sees a problem in this approach/solution that I don't see.
Standard module:
Class 'HandleReleaser':
If my code is in a separate class, I can use its destructor ('Class_Terminate') to release the resources, as far as I am aware and that is how I use it often.
But it also happens that the code is in a procedure of a standard module, for example. Then I don't have a destructor.
If an error occurs after creating a handle in further processing (which I want to bubble up and therefore do not want to lose this error information), I want to ensure that an attempt is made to release/close the handle. During this 'cleanup', an error could possibly occur, but I would like to ignore it.
I have now found/created my solution by outsourcing the cleanup of the handle to a small additional procedure that receives the handle as a parameter.
I pass the address of this procedure and the handle to a new, specially created class called 'HandleReleaser'.
Its destructor now ensures that the procedure specified with its address is executed together with the handle as a parameter.
Since a destructor has its own error context in VBA (an error occurring in it does not change the Err object of the code actually executed), I do not lose it.
Furthermore, I always have to handle errors in the destructor, as it is a code entry point. For this reason, I put an 'On Error Resume Next' there, because I want to ignore errors when cleaning up (at least I tried). Defensive programming in the constructor is of course always additionally helpful.
I would now be interested to know if anyone sees a problem in this approach/solution that I don't see.
Standard module:
Code:
Private Declare PtrSafe Function CreateFileW Lib "kernel32.dll" (ByVal lpFileName As LongPtr, ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As LongPtr) As LongPtr
Private Declare PtrSafe Function CloseHandle Lib "kernel32.dll" (ByVal hObject As LongPtr) As Long
Public Sub TestIt()
On Error GoTo Catch
TestItSub
Done:
Exit Sub
Catch:
Debug.Print "TestIt:", Err.Source, Err.Number & ":" & Err.Description
Resume Done
End Sub
Public Sub TestItSub()
On Error GoTo Catch
Const OPEN_EXISTING As Long = 3
Const FILE_SHARE_READ As Long = &H1
Const FILE_SHARE_WRITE As Long = &H2
Dim fileOrDirectoryHandle As LongPtr
fileOrDirectoryHandle = CreateFileW(StrPtr("z:\test.txt"), ByVal 0&, _
FILE_SHARE_READ Or FILE_SHARE_WRITE, _
ByVal 0&, OPEN_EXISTING, 0, 0)
Dim fileOrDirectoryHandleReleaser As HandleReleaser
Set fileOrDirectoryHandleReleaser = New HandleReleaser
fileOrDirectoryHandleReleaser.Init AddressOf ReleaseFileOrDirectoryHandle, fileOrDirectoryHandle
'// Do some work...
'// Oh, an error raised...
Err.Raise 123, "TestItSub", "Any error description"
'// Since the handle is automatically cleaned up when fileOrDirectoryHandleReleaser
'// runs out of scope, I don't have to do it manually here.
Done:
Exit Sub
Catch:
Err.Raise Err.Number, Err.Source, Err.Description
End Sub
Private Sub ReleaseFileOrDirectoryHandle(ByVal fileOrDirectoryHandle As LongPtr)
CloseHandle fileOrDirectoryHandle
End Sub
Class 'HandleReleaser':
Code:
Private Declare PtrSafe Function CallWindowProc Lib "user32.dll" Alias "CallWindowProcA" ( _
ByVal lpPrevWndFunc As LongPtr, ByVal hwnd As LongPtr, _
ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
Private Type TState
Address As LongPtr
Handle As LongPtr
End Type
Private this As TState
Public Sub Init(ByVal xAddress As LongPtr, ByVal xHandle As LongPtr)
this.Address = xAddress
this.Handle = xHandle
End Sub
Private Sub Class_Terminate()
On Error Resume Next
CallWindowProc this.Address, this.Handle, 0&, 0&, 0&
End Sub