Ensure releasing/closing of a handle without losing err-state in case

AHeyne

Registered User.
Local time
Today, 19:49
Joined
Jan 27, 2006
Messages
129
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:

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
 
Your code looks right to me.
The question for me is whether you will still know exactly how to use this structure after a few months ;)

Just one question about your intention: why don't you use a class for CreateFileW & Co?
Code:
Public Sub TestItSub()
  ...
     With New FileSupportAndFileHandleObserver  ' use a better name ;)  ... use case: run CreateFileW inside class and CloseHandle on Terminate
         fileOrDirectoryHandle = .CreateFileW(...)
         DoSomeThingWithFileHandle fileOrDirectoryHandle
     End With
...
End Sub

Private Sub DoSomeThingWithFileHandle(byval FileHandle as LongPtr)
     '// Oh, an error raised...
     Err.Raise 123, "TestItSub", "Any error description"
end sub

Or only "the observer / terminator ;)":
Code:
Public Sub TestItSub()
  ...
     With New FileOrDirectoryHandleReleaser ... use case: store handle (LongPtr) and run CloseHandle on terminate
         .FileDirectoryHandle = CreateFileW(...)
         DoSomeThingWithFileHandle .FileDirectoryHandle
     End With
...
End Sub

Private Sub DoSomeThingWithFileHandle(byval FileHandle as LongPtr)
     '// Oh, an error raised...
     Err.Raise 123, "TestItSub", "Any error description"
end sub

Technically, your variant is more flexible because you inject the CloseHandler function.
But then I would design the class in such a way that the number of parameters is flexible and the class has no reference to FileHandle & Co.
It simply calls a procedure when the instance is destroyed.

The disadvantage from my point of view is that an extra procedure is required for CloseHandle.
Therefore, the integration of CloseHandle directly into the class could be less flexible, but perhaps more readable in the long run.
 
Last edited:
Thanks for your reply Josef.

I would prefere a real finally block there, that would make life easier. :)

The question for me is whether you will still know exactly how to use this structure after a few months

I guess I will find places in code where I used it before then, in case I can't remember. ;)

why don't you use a class for CreateFileW & Co?

Oh, I do so, mostly, as I wrote, but sometimes for what reason ever do not. Maybe old code which I want to optimize but not completely rewrite.

And CreateFileW is just a sample for this thread, the API could vary like encryption or mutex or others.

Technically, your variant is more flexible because you inject the CloseHandler function.
But then I would design the class in such a way that the number of parameters is flexible and the class has no reference to FileHandle & Co.
It simply calls a procedure when the instance is destroyed.

Yes, maybe I will find other use cases for this approach later on and enhance it with more flexibility.

The disadvantage from my point of view is that an extra procedure is required for CloseHandle.

I agree, but there is no other way in this scenario.
 
Last edited:
I like dependency injection, so I like your approach.

Code:
With New Finalizer(FinalizeProcedure:=...)
     .FinalizerParameter(0) = CreateFileW(...)
     DoSomeThingWithFileHandle .FinalizerParameter(0)
End With

Unfortunately, VBA does not play along so well. We have to make compressions.

But I also like easy-to-read code, so a few thoughts on that: ;)

1. A few ideas for the class name:
- HandleFinalizer
- FinalizationExecutor
- TerminationExecutor
(intentionally without FileHandle in the name)

2. Usage:
Code:
With New HandleFinalizer
     .FinalizeProcedureAddress = AddressOf ReleaseFileOrDirectoryHandle
     .Handle = CreateFileW(...)
     DoSomeThingWithFileHandle .Handle
End With
 
Last edited:
I like dependency injection...
Me too, very powerful, clean and good to maintain.

Regarding your sample about parametrized constructors: I like to use static classes in VBA having a public method named `Create` which then can be feeded with parameters and returning a new instance of 'itself' or in more detail returning it as an implemented interface.

Then you could do so:

Code:
With HandleFinalizer.Create(AddressOf ReleaseFileOrDirectoryHandle)
     .Handle = CreateFileW(...)
     DoSomeThingWithFileHandle .Handle
End With

But for the example I wanted to show/discuss this would have been too off topic.

But I also like easy-to-read code

Absolutely. Sometimes I find myself spending way to much time thinking about naming... :ROFLMAO:
I like code that you can read like a book.

I like your suggestion 'HandleFinalizer'. And I can imagine using an even more abstract name that makes the technology even more universally applicable. :)

Your 'HandleFinalizer' sample is very close to how I would implement it finally. (y)
As I wrote above I would use a static class implementing and returning an interface by `Create`.

It's nice to see that we tick very similarly when it comes to this.
 
[dependency injection]
... very powerful, clean and good to maintain.
... and nice to test.

I like to use static classes in VBA having a public method named `Create` which then can be feeded with parameters and returning a new instance of 'itself' or in more detail returning it as an implemented interface.
I like that. It is even nicer with an interface, as you can then separate the methods. Only Create procedure and support methods/properties are visible in the static class, the actual members are available via the interface.


I find myself spending way to much time thinking about naming...
Sometimes I feel that is the hardest part of programming ;)
 
Last edited:

Users who are viewing this thread

Back
Top Bottom