So what does a developer do when he can’t sleep at 3am? He decides to write an article about self-healing object variables!
So What Are Self-Healing Object Variables?
In plain English, these are variables that are intelligent enough that they initialize themselves once and retain their value for the entire session. If reset, they will reinitialize again automatically. They are like Global Variables on steroids. It is just one more step you can take as a developer to improve the overall performance and give your application a little boost, and this for any VBA application (not just Access)!
My first recollection of being exposed to the concept was back in, I believe 2003-2005ish, by MVP Dirk Goldgar who was answering one of my Access question in a discussion forum. He didn’t call it SHOV, but that is effectively what it was. Implementing it drastically helped with performance with a database I was struggling with, and from then on I was hooked! No clue who originally came up with the idea, and it may be taken from another programming language for all I know, but it’s been around for a long time.
Why Do We Want To Use Self-Healing Object Variables?
When we talk about Object Variables, we are most often talking about the use of the CreateObject() function, or in the case of Access specifically the use of CurrentDb. So things like
Set oExcel = CreateObject("Excel.Application")
Or
Set db= CurrentDb
These actions are straining, especially when done repetitively. The process of creating ActiveX reference, by using commands like CreateObject(), can cause significant delays, so we want to minimize such activities as much as possible to give the best user experience possible.
The idea behind Self-Healing Variables is you do it once, but hold it in memory that way subsequent calls don’t need to perform the initialization again, as it is still already in memory from the 1st call, ready to go.
Using Self-Healing variables can simplify your code since you no longer need to initialize such variables in your code and can significantly improve overall performance. On slower machines the performance boost achieved by using self-healing variables can be quite noticeable.
Why Not Use TempVars For This?
Simply put, because you can’t! TempVars cannot store objects.
What about a Global Variable?
Although, you could technically use a Global Variable to hold an Object Variable, it can get reset and does not offer the same ability that a Self-Healing Variable offers to automatically reinitialize itself.
Don’t misunderstand me though, this technique does indeed use Global variables but in conjunction with a function that determines whether or not to reinitialize the variable or not, that is the key difference!
How Do We Create and Use Self-Healing Object Variables?
Enough talk, let’s dive in!
Below are some simple examples of Self-Healing variables for the most common applications that I use. You’ll see a pattern, so try and expand it to other programs beyond those listed below.
Also, notice that I created my Self-Healing variables to work both as Early or Late Binding, that why the code isn’t what you’d find elsewhere. You simply change the Conditional Compiler Directive variable (Excel_EarlyBind, Outlook_EarlyBind , …) to switch between the 2. The choice is use. This way, everyone is happy.
So far, I’ve provided 12 examples of Self-Healing variables. These are the ones I consider most common for most developers. Simply click on one of the shortcuts provided below to jump straight to that example:
- Microsoft Access
- Microsoft Excel
- Microsoft Outlook
- Microsoft PowerPoint
- Microsoft Word
- File System Object (FSO)
- Windows Management Instrumentation (WMI)
- Regular Expressions (RegEx)
- Shell
- Script Control
- WScript (WshShell & WshNetwork)
- UIAutomationClient
- HTMLFile/HTMLDocument (Microsoft HTML Object Library – MSHTML)
- ADODB
As you will no doubt see for yourself, they are all very similar in design, so you can easily use any one of them as a template to build others you may need.
Access
From an External Application
If we follow the same pattern as all the other examples we would do:
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft Access XX.X Object Library
#Const Access_EarlyBind = True
#If Access_EarlyBind = True Then
Private pAccess As Access.Application
#Else
Private pAccess As Object
#End If
#If Access_EarlyBind = True Then
Public Function oAccess() As Access.Application
#Else
Public Function oAccess() As Object
#End If
'***** Only work if using full version of Access, NOT Runtime editions! *****
On Error GoTo Err_Handler
If pAccess Is Nothing Then
Debug.Print "*** Setting oAccess ***"
#If Access_EarlyBind = True Then
Set pAccess = New Access.Application
#Else
Set pAccess = CreateObject("Access.Application")
#End If
End If
Set oAccess = pAccess
Exit_Procedure:
DoEvents
Exit Function
Err_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Function oAccess" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Exit_Procedure
End Function
Public Sub oAccess_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pAccess = Nothing
End Sub
and this does work, in a general sense, but there is a particularity with Access, that is the Runtime Edition! With the Runtime Edition it is impossible to use CreateObject(“Access.Application”) or even GetObject(, “Access.Application”). So the above code is fully functional, but only for full editions of Access. If you need to work with Runtime Edition installations then we would need to modify the code to:
#Const Access_EarlyBind = False
#If Access_EarlyBind = True Then
Private pAccess As Access.Application
#Else
Private pAccess As Object
#End If
#If Access_EarlyBind = True Then
Public Function oAccess_RT(ByVal sFile As String) As Access.Application
#Else
Public Function oAccess_RT(ByVal sFile As String) As Object
#End If
On Error GoTo Err_Handler
Dim oShell As Object
Dim sDbFile As String
Dim sCmd As String
If Not pAccess Is Nothing Then
On Error GoTo ResetSHOV
Set pAccess = GetObject(sFile)
End If
If pAccess Is Nothing Then
Set pAccess = GetObject(sFile)
If pAccess Is Nothing Then
ResetSHOV:
On Error GoTo Err_Handler
Debug.Print "*** Setting oAccess ***"
'Workaround for Can't Create error 429 in runtime editions
sCmd = "msaccess.exe """ & sFile & """"
Call Shell(sCmd)
DoEvents
Set pAccess = GetObject(sFile)
End If
End If
Set oAccess_RT = pAccess
Exit_Procedure:
Exit Function
Err_Handler:
If Err.Number = 429 Then
GoTo ResetSHOV
Else
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Function oAccess_RT" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Exit_Procedure
End If
End Function
Public Sub oAccessRT_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pAccess = Nothing
End Sub
where we use Shell to start the database if it isn’t already running. And remember, with Runtime you can’t just open/use msaccess.exe, it has to use/open a file. That’s why we have to include a file with the function call.
Within Access Itself – CurrentDb
In Access the most common use of Self-Healing variables would be to replace the use of CurrentDb. To do so, we create the following:
Private pCurrentDb As DAO.Database
Public Function oCurrentDb() As DAO.Database
If pCurrentDb Is Nothing Then
Debug.Print "*** Setting oCurrentDb ***"
Set pCurrentDb = CurrentDb
End If
Set oCurrentDb = pCurrentDb
End Function
Public Sub oCurrentDb_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pCurrentDb = Nothing
End Sub
and then we can take code such as:
Public Function listTables(Optional bShowSys As Boolean = False) As String
Dim oCurrentDb As DAO.Database
Dim tdfs As DAO.TableDefs
Dim tdf As DAO.TableDef
Set oCurrentDb = CurrentDb()
Set tdfs = oCurrentDb.TableDefs
For Each tdf In tdfs 'loop through all the fields of the tables
If (Left(tdf.Name, 4) = "MSys" Or Left(tdf.Name, 1) = "~") _
And bShowSys = False Then GoTo Continue
Debug.Print tdf.Name
Continue:
Next
Exit_Function:
On Error Resume Next
If Not tdf Is Nothing Then Set tdf = Nothing
If Not tdfs Is Nothing Then Set tdfs = Nothing
If Not oCurrentDb Is Nothing Then Set oCurrentDb = Nothing
Exit Function
End Function
into
Public Function listTables(Optional bShowSys As Boolean = False) As String
Dim tdfs As DAO.TableDefs
Dim tdf As DAO.TableDef
Set tdfs = oCurrentDb.TableDefs
For Each tdf In tdfs 'loop through all the fields of the tables
If (Left(tdf.Name, 4) = "MSys" Or Left(tdf.Name, 1) = "~") _
And bShowSys = False Then GoTo Continue
Debug.Print tdf.Name
Continue:
Next
Exit_Function:
On Error Resume Next
If Not tdf Is Nothing Then Set tdf = Nothing
If Not tdfs Is Nothing Then Set tdfs = Nothing
Exit Function
End Function
I know it doesn’t seem significant, but it actually is. The former example will repetitively keep calling CurrentDb each time it is called. Whereas the latter will only make that initialization call once, store it in the property so every subsequent call simply uses what is already in memory, no need to call CurrentDb again.
Also, notice how we end up with simpler code, no more dimming, setting and clearing the CurrentDb variable.
Think of all the places you use CurrentDb: OpenRecorset, TableDefs, QueryDefs, … and you can simply all that code and give a small boost to each one of those calls.
Excel
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft Excel XX.X Object Library
#Const Excel_EarlyBind = False
#If Excel_EarlyBind = True Then
Private pExcel As Excel.Application
#Else
Private pExcel As Object
#End If
#If Excel_EarlyBind = True Then
Public Function oExcel() As Excel.Application
#Else
Public Function oExcel() As Object
#End If
On Error GoTo Err_Handler
If pExcel Is Nothing Then
Debug.Print "*** Setting oExcel ***"
#If Excel_EarlyBind = True Then
Set pExcel = New Excel.Application
#Else
Set pExcel = CreateObject("Excel.Application")
#End If
End If
Set oExcel = pExcel
Exit_Procedure:
DoEvents
Exit Function
Err_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Property Get oExcel" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Exit_Procedure
End Function
Public Sub oExcel_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pExcel = Nothing
End Sub
Outlook
PowerPoint
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft PowerPoint XX.X Object Library
#Const PowerPoint_EarlyBind = False
#If PowerPoint_EarlyBind = True Then
Private pPowerPoint As PowerPoint.Application
#Else
Private pPowerPoint As Object
#End If
#If PowerPoint_EarlyBind = True Then
Public Function oPowerPoint() As PowerPoint.Application
#Else
Public Function oPowerPoint() As Object
#End If
On Error GoTo Err_Handler
If pPowerPoint Is Nothing Then
Debug.Print "*** Setting oPowerPoint ***"
#If PowerPoint_EarlyBind = True Then
Set pPowerPoint = New PowerPoint.Application
#Else
Set pPowerPoint = CreateObject("PowerPoint.Application")
#End If
End If
Set oPowerPoint = pPowerPoint
Exit_Procedure:
DoEvents
Exit Function
Err_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Property Get oPowerPoint" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Exit_Procedure
End Function
Public Sub oPowerPoint_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pPowerPoint = Nothing
End Sub
Word
File System Object (FSO)
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft Scripting Runtime
#Const FSO_EarlyBind = False
#If FSO_EarlyBind = True Then
Private pFSO As Scripting.FileSystemObject
#Else
Private pFSO As Object
#End If
#If FSO_EarlyBind = True Then
Public Function oFSO() As Scripting.FileSystemObject
#Else
Public Function oFSO() As Object
#End If
If pFSO Is Nothing Then
Debug.Print "*** Setting oFSO ***"
#If FSO_EarlyBind = True Then
Set pFSO = New FileSystemObject
#Else
Set pFSO = CreateObject("Scripting.FileSystemObject")
#End If
End If
Set oFSO = pFSO
End Function
Public Sub oFSO_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pFSO = Nothing
End Sub
Windows Management Instrumentation (WMI)
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft WMI Scripting VX.X Library
#Const WMI_EarlyBind = False
#If WMI_EarlyBind = True Then
Private pWMI As WbemScripting.SWbemServices
#Else
Private pWMI As Object
#End If
'Self-healing oWMI property
#If WMI_EarlyBind = True Then
Public Function oWMI(Optional sHost As String = ".") As WbemScripting.SWbemServices
#Else
Public Function oWMI(Optional sHost As String = ".") As Object
#End If
On Error GoTo Err_Handler
If pWMI Is Nothing Then
Debug.Print "*** Setting oWMI ***"
'Set pWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & sHost & "\root")
Set pWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & sHost & "\root\cimv2")
End If
Set oWMI = pWMI
Exit_Procedure:
DoEvents
Exit Function
Err_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Property Get oWMI" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Exit_Procedure
End Function
Public Sub oWMI_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pWMI = Nothing
End Sub
Regular Expressions (RegEx)
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft VBScript Regular Expressions X.X
#Const RegEx_EarlyBind = False
#If RegEx_EarlyBind = True Then
Private pRegEx As RegExp
#Else
Private pRegEx As Object
#End If
'Self-healing oRegEx property
#If RegEx_EarlyBind = True Then
Public Function oRegEx() As RegExp
#Else
Public Function oRegEx() As Object
#End If
On Error GoTo Err_Handler
If pRegEx Is Nothing Then
Debug.Print "*** Setting oRegEx ***"
#If RegEx_EarlyBind = True Then
Set pRegEx = New RegExp
#Else
Set pRegEx = CreateObject("VBScript.RegExp")
#End If
End If
Set oRegEx = pRegEx
Exit_Procedure:
Exit Function
Err_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Property Get oRegEx" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Exit_Procedure
End Function
Public Sub oRegEx_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pRegEx = Nothing
End Sub
Shell
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft Shell Controls And Automation
#Const Shell_EarlyBind = False
#If Shell_EarlyBind = True Then
Private pShell As Shell32.Shell
#Else
Private pShell As Object
#End If
#If Shell_EarlyBind = True Then
Public Function oShell() As Shell32.Shell
#Else
Public Function oShell() As Object
#End If
If pShell Is Nothing Then
Debug.Print "*** Setting oShell ***"
#If Shell_EarlyBind = True Then
Set pShell = New Shell32.Shell
#Else
Set pShell = CreateObject("Shell.Application")
#End If
End If
Set oShell = pShell
End Function
Public Sub oShell_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pShell = Nothing
End Sub
Usage Example
Sub GetPCMemory()
'Physical memory installed, in bytes.
Debug.Print oShell.GetSystemInformation("PhysicalMemoryInstalled")
End Sub
Script Control
' Note : !!!!! -- MSScriptControl does not work in 64-bit Office -- !!!!!
'For 64-bit refer to https://github.com/tablacus/TablacusScriptControl
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft Script Control 1.0
#Const ScriptControl_EarlyBind = False
#If ScriptControl_EarlyBind = True Then
Private pScriptControl As MSScriptControl.ScriptControl
#Else
Private pScriptControl As Object
#End If
#If ScriptControl_EarlyBind = True Then
Public Function oScriptControl() As MSScriptControl.ScriptControl
#Else
Public Function oScriptControl() As Object
#End If
If pScriptControl Is Nothing Then
Debug.Print "*** Setting oScriptControl ***"
#If ScriptControl_EarlyBind = True Then
Set pScriptControl = New ScriptControl
#Else
Set pScriptControl = CreateObject("MSScriptControl.ScriptControl")
#End If
End If
Set oScriptControl = pScriptControl
End Function
Public Sub oScriptControl_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pScriptControl = Nothing
End Sub
WScript
WScript.Shell and WScript.Network
' Req'd Refs: Late Binding -> None required
' Early Binding -> Windows Script Host Object Model
#Const WSH_EarlyBind = False
#If WSH_EarlyBind = True Then
Private pWshShell As IWshRuntimeLibrary.WshShell
Private pWshNetwork As IWshRuntimeLibrary.WshNetwork
#Else
Private pWshShell As Object
Private pWshNetwork As Object
#End If
#If WSH_EarlyBind = True Then
Public Function oWshShell() As IWshRuntimeLibrary.WshShell
#Else
Public Function oWshShell() As Object
#End If
If pWshShell Is Nothing Then
Debug.Print "*** Setting oWshShell ***"
#If WSH_EarlyBind = True Then
Set pWshShell = New IWshRuntimeLibrary.WshShell
#Else
Set pWshShell = CreateObject("WScript.Shell")
#End If
End If
Set oWshShell = pWshShell
End Function
Public Sub oWshShell_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pWshShell = Nothing
End Sub
#If WSH_EarlyBind = True Then
Public Function oWshNetwork() As IWshRuntimeLibrary.WshNetwork
#Else
Public Function oWshNetwork() As Object
#End If
If pWshNetwork Is Nothing Then
Debug.Print "*** Setting oWshNetwork ***"
#If WSH_EarlyBind = True Then
Set pWshNetwork = New IWshRuntimeLibrary.WshNetwork
#Else
Set pWshNetwork = CreateObject("WScript.Network")
#End If
End If
Set oWshNetwork = pWshNetwork
End Function
Public Sub oWshNetwork_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pWshNetwork = Nothing
End Sub
UIAutomationClient
Please note that it is not possible to perform Late Binding with the UIAutomationClient library so you must add the UIAutomationClient library to your VBA References.
Private pUIA As UIAutomationClient.CUIAutomation
Public Function oUIA() As UIAutomationClient.CUIAutomation
If pUIA Is Nothing Then
Set pUIA = New UIAutomationClient.CUIAutomation
End If
Set oUIA = pUIA
End Function
Public Sub oUIA_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pUIA = Nothing
End Sub
HTMLFile/HTMLDocument (Microsoft HTML Object Library – MSHTML)
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft HTML Object Library
#Const HTMLFile_EarlyBind = False
#If HTMLFile_EarlyBind = True Then
Private pHTMLFile As MSHTML.HTMLDocument
#Else
Private pHTMLFile As Object
#End If
#If HTMLFile_EarlyBind = True Then
Public Function oHTMLFile(Optional bForceRefresh As Boolean = False) As MSHTML.HTMLDocument
#Else
Public Function oHTMLFile(Optional bForceRefresh As Boolean = False) As Object
#End If
If pHTMLFile Is Nothing Or bForceRefresh Then
Debug.Print "*** Setting oHTMLFile ***"
#If HTMLFile_EarlyBind = True Then
Set pHTMLFile = New MSHTML.HTMLDocument
#Else
Set pHTMLFile = CreateObject("HTMLFile")
#End If
End If
Set oHTMLFile = pHTMLFile
End Function
Public Sub oHTMLFile_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pHTMLFile = Nothing
End Sub
ADODB
ADODB Stream
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft ActiveX Data Objects x.x Library
#Const ADODBStream_EarlyBind = False
#If ADODBStream_EarlyBind = True Then
Private pADODBStream As ADODB.stream
#Else
Private pADODBStream As Object
#End If
'Self-healing oADODBStream property
#If ADODBStream_EarlyBind = True Then
Public Function oADODBStream() As ADODB.stream
#Else
Public Function oADODBStream() As Object
#End If
On Error GoTo Err_Handler
If pADODBStream Is Nothing Then
Debug.Print "*** Setting oADODBStream ***"
#If ADODBStream_EarlyBind = True Then
Set pADODBStream = New ADODB.stream
#Else
Set pADODBStream = CreateObject("ADODB.Stream")
#End If
End If
Set oADODBStream = pADODBStream
Exit_Procedure:
Exit Function
Err_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Property Get oADODBStream" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Exit_Procedure
End Function
Public Sub oADODBStream_Clear()
'Be sure to always run this when closing your Form/DB to avoid
' hidden instances from running in the background!
Set pADODBStream = Nothing
End Sub
Usage Example
'---------------------------------------------------------------------------------------
' Procedure : ReadFileAsText
' Author : Daniel Pineault, CARDA Consultants Inc.
' Website : http://www.cardaconsultants.com
' Purpose : This code opens a UTF-8 encoded text file and reads its entire contents
' ***** This version employs SHOV *****
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
' (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft ActiveX Data Objects x.x Library
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sFile : Fully qualified path and filename to read
'
' Usage:
' ~~~~~~
' ReadFileAsText("C:\Users\Dev\Desktop\Booking.json")
' Returns -> the content of the file in question
'
' Revision History:
' Rev Date(yyyy-mm-dd) Description
' **************************************************************************************
' 1 2012-05-14
' 2 2025-04-21 Initial Public Release
'---------------------------------------------------------------------------------------
Function ReadFileAsText(ByVal sFile As String) As String
On Error GoTo Error_Handler
With oADODBStream
.Type = adTypeText
.Charset = "utf-8"
.Open
.LoadFromFile sFile
ReadFileAsText = .ReadText
.Close
End With
Error_Handler_Exit:
On Error Resume Next
Exit Function
Error_Handler:
MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
"Error Source: ReadFileAsText" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occurred!"
Resume Error_Handler_Exit
End Function
Cleaning Up After Ourselves
Remember to cleanup up the Self-Healing Object Variables when closing the database, the use of a hidden form is great for this, by simply running the appropriate *_Clear() sub to ensure we don’t leave behind any orphaned hidden processes running in the background.
Speed Comparison
As I have stated SHOV simplify coding and speed up process. Some have questioned such a statement, so I did a little experiment. I set out to use a couple different techniques to see if a file existed.
I used:
- Dir
- FSO Early Binding
- FSO Late Binding
- SHOV FSO Early Binding
- SHOV FSO Late Binding
Then I build a test that performed 1000 iterations, logged the results, averaged the values and we end up with the following results:
| Test | Average Execution Time | % Diff From Best |
|---|---|---|
| SHOV FSO Early Binding | 0.0000284346 | |
| SHOV FSO Late Binding | 0.0000305281 | 7 |
| Dir | 0.0000428288 | 40 |
| FSO Early Binding | 0.0002661662 | 161 |
| FSO Late Binding | 0.0002695594 | 162 |
So, both SHOV approaches come out on top!
Using traditional approaches will always be slower if more than one call is done within your database.
Disclaimer/Notes:
If you do not have Microsoft Access, simply download and install the freely available runtime version (this permits running MS Access databases, but not modifying their design):Microsoft Access 2010 Runtime
Microsoft Access 2013 Runtime
Microsoft Access 2016 Runtime
Microsoft 365 Access Runtime
In no event will Devhut.net or CARDA Consultants Inc. be liable to the client/end-user or any third party for any damages, including any lost profits, lost savings or other incidental, consequential or special damages arising out of the operation of or inability to operate the software which CARDA Consultants Inc. has provided, even if CARDA Consultants Inc. has been advised of the possibility of such damages.
Download a Demo Database
Feel free to download a 100% unlocked copy by using the link provided below:
Download “VBA - Self-Healing Object Variables” Self-Healing-Variables.zip – Downloaded 7238 times – 79.55 KB

Hi Daniel,
I think something is missing from your Access example. Simply removing these two lines
Dim oCurrentDb As DAO.Database
Set oCurrentDb = CurrentDb()
from your listTables function isn’t gonna work. It gives me a “Compile error: Variable not defined” error on the line
Set tdfs = oCurrentDb.TableDefs
I’m sorry, I had forgotten to actually post the self-healing property that the other calls rely on.
First thing that came to my mind, is to use these self healing variables to store the Ribbon Pointer (which often gets lost), instead of writing it to a cell in excel, or a record field in access. Would that work? And would you recommend if for that use?
I’m afraid I don’t have enough information to give you a proper opinion. At the same time I say that, it costs you nothing to try it out and see.
Would love a way to not lose the ribbon!
How do you loose the ribbon exactly?
Thank you for a great tutorial, Daniel! I’m not sure if the following information is helpful for you.
I tested the code Microsoft Word. The code ran fine, but there was no self-healing. In other words, a new Word instance was created every time.
I tested for Microsoft Outlook (2016). It did create a self-healing variable. However, the code SendEmail didn’t finish due to error 287 (which your code trapped it).
Do you know why?
Out of curiosity, is there any way to automate the process of adding the reference I need after switching to early binding? Essentially, I want to save myself the step of going to Tools / Reference, etc. I found this code from NoLongerSet.com:
References.AddFromGUID “{00020813-0000-0000-C000-000000000046}”, 1, 0
, which adds the Excel reference using VBA.
So I was hoping for something like this,
#Const Excel_EarlyBind = True
#If Excel_EarlyBind = True Then
References.AddFromGUID “{00020813-0000-0000-C000-000000000046}”, 1, 0
Private pExcel As Excel.Application
#Else
Private pExcel As Object
#End If
but it didn’t work as that line of code must be wrapped in a function. Appreciate any thoughts. Thanks!
As indicated in the article: “Note Self-Healing variables do not seem to work with Word…”.
Outlook’s 287 error indicates that it wasn’t authorized to be automated. You have to enable automation and approved it.
I’ve never consider trying to do so, but I’m actually working on something along those lines for another project. Let me see if I can come up with something.
No, I don’t think it is possible to completely automate.
We can easily do something like:
Public Sub References_AddReferenceByGUID(ByVal sRefGUID As String, _ ByVal lMajor As Long, _ ByVal lMinor As Long) On Error GoTo Error_Handler Dim oRef As Access.Reference Dim bRefExists As Boolean For Each oRef In Application.References If sRefGUID = oRef.Guid Then If oRef.IsBroken = True Then Application.References.Remove oRef Else If oRef.Major = lMajor And oRef.Minor = lMinor Then bRefExists = True Else Application.References.Remove oRef End If End If End If Next oRef If bRefExists = False Then Application.References.AddFromGuid sRefGUID, lMajor, lMinor Error_Handler_Exit: On Error Resume Next If Not oRef Is Nothing Then Set oRef = Nothing Exit Sub Error_Handler: MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _ "Error Source: References_AddReferenceByGUID" & vbCrLf & _ "Error Number: " & Err.Number & vbCrLf & _ "Error Description: " & Err.Description & _ Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _ , vbOKOnly + vbCritical, "An Error has Occured!" Resume Error_Handler_Exit End Suband call it
Call References_AddReferenceByGUID("{420B2830-E718-11CF-893D-00A0C9054228}", 1, 0) 'Add FSO Ref LibraryCall References_AddReferenceByGUID("{4AC9E1DA-5BAD-4AC7-86E3-24F4CDCECA28}", 12, 0) 'Add ACE/DAO Ref LibraryCall References_AddReferenceByGUID("{00020813-0000-0000-C000-000000000046}", 1, 8) 'Add Excel Ref LibraryCall References_AddReferenceByGUID("{00062FFF-0000-0000-C000-000000000046}", 9, 5) 'Add Outlook Ref LibraryCall References_AddReferenceByGUID("{00020905-0000-0000-C000-000000000046}", 8, 6) 'Add Word Ref Librarybut it won’t work with conditional compilation directives as it won’t compile until run. It’s the case of the chicken and the egg! It’s still faster to have a series of such command that you can simply run vs Tools -> References…, then scroll, find, click the checkmark, Ok.
Hello Daniel, thanks for great tutorial. I have a question opening database with Access RT. Can we use this method to open password protected frontend file without having the user to enter the password. In full access version we can do that using
Dim app As Access.Application, strPath As String
Set app = New Access.Application
app.OpenCurrentDatabase strPath, True, “password”
But with runtine it gives error 429. Do you know if it possible by any mean in runtime? I’m away from PC for few days and couldn’t check method of getting object in RT as explained in your post.
Runtime can’t create new instances.
There are 2 ideas that come to mind:
1. Use APIs to locate the windows…
2. Shell out opening the file, then bind to it using GetObject(), close it that instance of the db using CloseCurrentDatabase, with that db object reopen the db using OpenCurrentDatabase to supply the pwd this time.
For Item 2, I use a bogus blank database as the initial database that you shell to so as to minimize the process as much as possible. No point opening a database that might be 100+MB and might have startup code, when you can simply open a 400KB database!
Hi Daniel,
The code for “`oExcel_Clear“` is supposed to clear the Excel instance created earlier by oExcel. I did a simple check but when I looked my Task Manager, the Excel instance was still there. Should I add the following code into oExcel_Clear: oExcel.Quit: Set oExcel = Nothing?
Thanks!
While I understand what you are saying, having been there myself in the past, no. Closing the application and clearing the variable are 2 distinct things. Imagine exporting data to Excel and in your last action clearing the variable also closed Excel?! No, you would want to leave it open for your users. As such each should have its own function and that is what I do in my modules for Excel, Word, … I have both a close application and clear function. Then I apply the appropriate action/function as required. As such, I would advise again combining them into one, simply not a good idea, keep things as flexible as possible and create a 2nd close application function if that is something you truly need.
Also, don’t confuse the Excel instance in the task manager with the VBA Excel instance. They are 2 distinct things!
I have a relatively small database and have only implemented ocurrentdb system and it’s already more responsive.
I do, however, have a small problem with the filesystem unit.
When I debug the following code snippet, line 1 is good.
Line 2 errors on “copyfile”, expecting a function or variable.
Line 5 is good only after implementing lines 3 & 4
Line 1 Set objFolder = oFSO.GetFolder(pathBUP)
Line 2 retval = oFSO.CopyFile(pathSRC, target, True)
Line 3 Dim FSO As Object
Line 4 Set FSO = CreateObject(“Scripting.FileSystemObject”)
Line 5 retval = FSO.CopyFile(pathSRC, target, True)
I tried to replicate the issue, but can’t.
Sub test_FSO()Dim objFolder As Object
Dim pathBUP As String
Dim pathSRC As String
Dim Target As String
Dim Target2 As String
pathBUP = "C:\temp"
pathSRC = "C:\temp\56405.bmp"
Target = "C:\temp\charts\56405b.bmp" 'copy and rename
Target2 = "C:\temp\charts\" 'just make copy with same name
Set objFolder = oFSO.GetFolder(pathBUP)
Call oFSO.CopyFile(pathSRC, Target, True)
Call oFSO.CopyFile(pathSRC, Target2, True)
End Sub
I can run the above, over and over, and it only initializes one and works every time.
You have the “Public Function oFSO()” and all the declarations in a standard module?
Also, there is no point in “retval =” as the method doesn’t return any information. You may like my article: https://www.devhut.net/vba-fso-files-folders-drives-and-more/.
Yes it is in a separate module along with the “oDBCurrent ” function.
I have removed the late binding options and finished up with this.
Private pFSO As Scripting.FileSystemObject
Public Function oFSO() As Scripting.FileSystemObject
If pFSO Is Nothing Then
Set pFSO = CreateObject(“Scripting.FileSystemObject”)
End If
Set oFSO = pFSO
End Function
What stumps me is why oFSO.getfolder() compiles but oFSO.copyfile doesn’t compile when they are both valid funtions of the filesystemobject.
I have used oFSO in a few different scenarios and this is the only one that causes a compile error.
I am happy to try any workaround you might suggest.
BTW. I am running Windows 10 with the latest updates on a tower PC with AMD mother board and AMD Rizen 1300 processor.
John
I’d start by commenting line by line, there’s got be to something else going on here. You could also create a new module, copy the code over and delete the current one.
How are your variables declared and defined?
Hi Daniel.
There were two problems.
I had to remove “retval =” and insert “call “.
Problem solved.
Thank you for your time.
John
I must be missing something, but can’t the same be achieved simply by declaring a global variable like this:
Public Obj as New SomeClass
No