In an application that I built recently, I needed to persist several variables, without loosing their state, for duration that the application was in use. After some research, I found this post on StackOverflow that led me in the right direction and from there I encapsulated that logic into a class.
I have tested this with custom classes and it works as intended. Note that the class is Pre-Declared (i.e. Attribute VB_PredeclaredId = True
) because I wanted this to function more or less like Access's TempVars
object, which does not have to be "Newed-up" to obtain availability to properties/methods.
To use this class you must add references to the Common Language Runtime Execution Engine 2.4 Library
and the mscorlib.dll
respectively. Below I have provided the code to the add those pro-grammatically, as neither are easy to find in the References listing.
As for feedback, I am looking to make sure that my implementation is not implicitly causing some sort of a memory leak (kind of an important factor, ha). As per the link posted above, the object should only persist for the duration that Excel is open. That being said, If you plan to use this class in one of your projects, you need to call the RemoveAll
method during the Workbook_BeforeClose
event. You have to remember that the user may close your application, but leave other instances of Excel open, which means that the AppSessionVariables
would still be "alive". If the user were to close excel entirely, then Session Variables would be cleared and you needn't worry, but that's only in a perfect world, and we developers know more than anyone that no such world exists.
Class: AppSessionVariables
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "AppSessionVariables"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private Const APP_SESSION_VARIABLES_NAME As String = "APP_SESSION_VARIABLES"
Public Enum AppSessionVariablesError
Error_AlreadyExists = vbObjectError + 1042
Error_DoesNotExist
End Enum
Private Type TAppSessionVariables
dictSessionVars As Object
End Type
Private this As TAppSessionVariables
Private Sub Class_Initialize()
If this.dictSessionVars Is Nothing Then Set this.dictSessionVars = GetPersistentDictionary()
End Sub
Private Function GetPersistentDictionary() As Object
Dim host As mscoree.CorRuntimeHost
Dim domain As mscorlib.AppDomain
Dim dict As Object
Set host = New mscoree.CorRuntimeHost
host.Start
host.GetDefaultDomain domain
If IsObject(domain.GetData(APP_SESSION_VARIABLES_NAME)) Then
Set dict = domain.GetData(APP_SESSION_VARIABLES_NAME)
Else
Set dict = CreateObject("Scripting.Dictionary")
domain.SetData APP_SESSION_VARIABLES_NAME, dict
End If
Set GetPersistentDictionary = dict
End Function
Public Sub Add(ByVal Name As String, ByVal Value As Variant)
ThrowIfAlreadyExists Name
If IsObject(Value) Then
Set this.dictSessionVars(Name) = Value
Else
this.dictSessionVars(Name) = Value
End If
End Sub
Public Function Exists(ByVal Name As String) As Boolean
Exists = this.dictSessionVars.Exists(Name)
End Function
Public Sub Remove(ByVal Name As String)
On Error Resume Next
this.dictSessionVars.Remove Name
End Sub
Public Sub RemoveAll()
this.dictSessionVars.RemoveAll
End Sub
Public Function Item(ByVal Name As String) As Variant
ThrowIfDoesNotExist Name
If IsObject(this.dictSessionVars(Name)) Then
Set Item = this.dictSessionVars(Name)
Else
Item = this.dictSessionVars(Name)
End If
End Function
Public Function Count() As Long
Count = this.dictSessionVars.Count
End Function
Private Sub ThrowIfAlreadyExists(ByVal Name As String)
If Exists(Name) Then
Err.Raise AppSessionVariablesError.Error_AlreadyExists, TypeName(Me), _
"This Variable already exists."
End If
End Sub
Private Sub ThrowIfDoesNotExist(ByVal Name As String)
If Not Exists(Name) Then
Err.Raise AppSessionVariablesError.Error_DoesNotExist, TypeName(Me), _
"This does not exist."
End If
End Sub
Adding the References:
Place the following in a regular module and runCheckAndFixReferences
Private Const MSCOREE_GUID As String = "{5477469E-83B1-11D2-8B49-00A0C9B7C9C4}"
Private Const MSCOREE_MAJOR_VERSION As Long = 2
Private Const MSCOREE_MINOR_VERSION As Long = 4
Private Const MSCORLIB_GUID As String = "{BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}"
Private Const MSCORLIB_MAJOR_VERSION As Long = 2
Private Const MSCORLIB_MINOR_VERSION As Long = 4
Private Sub CheckAndFixReferences()
AddIfNotExists MSCOREE_GUID, MSCOREE_MAJOR_VERSION, MSCOREE_MINOR_VERSION
AddIfNotExists MSCORLIB_GUID, MSCORLIB_MAJOR_VERSION, MSCORLIB_MINOR_VERSION
End Sub
Private Sub AddIfNotExists(ByVal Guid As String, MajorVersion As Long, MinorVersion As Long)
Const ErrorAlreadyExists As Long = 32813
On Error GoTo ReferenceAlreadyExists
ThisWorkbook.VBProject.References.AddFromGuid Guid, MajorVersion, MinorVersion
CleanExit:
Exit Sub
ReferenceAlreadyExists:
If Err.Number = ErrorAlreadyExists Then Resume CleanExit
End Sub
Usage:
For the purpose of testing, you can create the following simple class:
**Class: TestClass**
Option Explicit
Private Type TTestClass
PropertyOne As Boolean
End Type
Private this As TTestClass
Public Property Let PropertyOne(ByVal value As Boolean)
this.PropertyOne = value
End Property
Public Property Get PropertyOne() As Boolean
PropertyOne = this.PropertyOne
End Property
And then place and run the following subs in a module:
Private Sub SetVariable()
Dim TestObject As TestClass
Set TestObject = New TestClass
TestObject.PropertyOne = True
If Not AppSessionVariables.Exists("TestObject") Then
AppSessionVariables.Add "TestObject", TestObject
End If
If Not AppSessionVariables.Exists("TestVar") Then
AppSessionVariables.Add "TestVar", "TestVar"
End If
End Sub
Private Sub GetVariable()
Debug.Assert AppSessionVariables.Item("TestObject").PropertyOne
Debug.Print AppSessionVariables.Item("TestVar")
AppSessionVariables.RemoveAll
End Sub