John W. Colby
jwcolby at colbyconsulting.com
Fri Mar 5 06:10:37 CST 2004
When I started using classes one of the first problems I encountered is the problem of tracking what classes loaded, and did they properly unload. Loading a class and not unloading it when you are finished causes a memory leak where the memory for the class is not returned to Windows. Sometimes Access will return the memory when Access closes, occasionally it can cause Access not to close correctly and this causes not only memory leaks (HUGE) but other problems as well. These ghost Access instances cannot be seen in Windows 98 and AFAIK there is no way to close them in versions of Windows prior to A2K. With A2K and above you have to use the task manager. Either way it is ugly. It is therefore critical that we keep track of what is loaded and ensure that we unload them when we are done. In my current framework I use a long integer that each class directly increments as it loads. This was my first tool, taken from example code from Shamil. '*+ Module Variable declaration Private mlngObjCounter As Long 'A COUNT OF ALL INSTANCES OF CLASSES IN THE SYSTEM Public Sub IncObjCounter() mlngObjCounter = mlngObjCounter + 1 end sub Public Sub DecObjCounter(strObjName As String) mlngObjCounter = mlngObjCounter - 1 end sub While this was useful and at least told me a gross count of what was loaded, I decided I really wanted to be able to see the names of the class instances loaded so I added a class. In order to do this I added a collection into which I would add the name of the class loading, and remove that class' name as the class unloaded. Private mlngObjCounter As Long 'A COUNT OF ALL INSTANCES OF CLASSES IN THE SYSTEM Public mcolObjNames As Collection Public Sub IncObjCounter(strObjName As String) mlngObjCounter = mlngObjCounter + 1 mcolObjNames.Add strObjName end sub Public Sub DecObjCounter(strObjName As String) mlngObjCounter = mlngObjCounter - 1 mcolObjNames.Remove strObjName end sub I could then use a function to read out the strings in the collection: Public Function ObjNames() As String On Error GoTo Err_ObjNames Dim strName As Variant Dim str For Each strName In mcolObjNames If Len(str) > 0 Then str = str & "; " & vbCrLf & strName Else str = strName End If Next strName ObjNames = str end function This works reasonably well and is the system I have lived with for quite awhile now. For the new framework I decided to store a pointer to the actual class instead of just the name of the class. The class calls a function that stores a pointer to itself in the collection as it loads, and a function that removes its pointer as it unloads. I do this because I now have a single place to go to look at and manipulate if necessary any class instance. One of the problems we deal with is "where is the pointer to this class stored"? In other words, a class can be loaded in code in a form, and the pointer stored in a variable in the form's header, or perhaps in a function in some piece of code and just stored in a variable local to that function, or perhaps in some class and stored in a collection in that class. If you want to go look at the class, execute a method, examine a property etc. how do you "get at" a pointer to the class? By storing a pointer to every class loaded in a collection we can now: a. get a count of all classes loaded by examining the collection count b. get a list of the names by iterating the collection asking each class it's name and building up a string c. get at the actual class and manipulate it if needed by getting the pointer from this class. For this reason I have decided to use this method. disclaimer: it is important to realize that classes do not truly unload from memory until the last pointer to the class is set to nothing. Thus if you instantiate a class, store a pointer in this collection and a variable in a form header (for example) the class will not unload until you delete the pointer in the collection AND in the form. If your code stores a pointer in another variable somewhere, the class will not close until ALL THREE pointers are set to nothing. My class init() / term() methods save the pointers in this collection as well as a child collection of the classes parent object (more on that later), and as the class unloads it will clean out these two instances. However if YOUR code saves a pointer in some other variable, I don't know that and can't clean that up, so the class will remain open until YOU set your pointer to nothing. I am toying with using a collection in each class that is a pointer to all the variables holding pointers to that class. I.e. if you need to store a pointer to the class in a variable of your own (perhaps to sink events?) then you call a method of the class passing in the variable you will use. The class puts a pointer to itself in that variable AND ... saves a pointer to that variable in a collection inside the class. Now the class CAN clean up all those errant pointers to itself by simply iterating it's own PointersToSelf collection setting each variable to nothing and removing the variable from its collection. Well, gotta go earn money now. Comments welcomed. John W. Colby www.ColbyConsulting.com