Releasing OLEAutoClient Objects
by Marc Van den Berghen, Ivar B. Jessen and Jean-Pierre Martel

Introduction

dBASE was one of the first platform of development to have automatic garbage collection. Over the years, this service was made more and more reliable up to the point that we almost never have to worry about orphaned objects in memory. One type of object that needs special care is the OLEAutoClient object.

The Objects’ Apoptosis

For each object, dBASE maintains a reference counter. Essentially, the reference counter is a hidden integer type property that dBASE increments or decreases when needed. When that counter goes to zero, the object commits suicide (or apoptosis). Normally, dBASE only creates apoptotic objects. That’s the mechanism by which dBASE does garbage collection. The number of references can be known by the refCount() method. It accepts only one parameter: the name of the object reference (without quotes.)
 
 
f = new form()
? refCount(f)  // 1
g = f
? refCount(f)  // 2
g = null
? refCount(f)  // 1
f = null
? refCount(f)  // Error - Expecting Object
   

What is the exact mechanism by which the object commits apoptosis? According to an interview given to the dBulletin by Marty Kay (the Director of Research and Development at dBase, LLC), all dBASE objects inherit an internal reference counting object. The class has an increment method and a decrement method for the reference counter. In the decrement method, it tests to see if the reference count has reached zero (after decrementing). When that’s the case, it calls the object’s destructor method which performs whatever cleanup is needed. Depending on the class, this may include closing or releasing various other objects and/or data structures, turning off notifications from other objects, etc. The last thing the destructor does is to release the memory occupied by the object.

A) Releasing “ordinary” dBASE objects

All visual components have their own release() method. It explicitly releases an object from memory, returning true if successful. When the object is a container (a form, a notebook or a container object), all the objects that belongs to that container are also destroyed.
 
 
f = new form()
f.entryfield1 = new entryfield(f)
? findInstance("Form") // Object
? type("f")            // "O" for Object
? refCount(f)          // 1

f.release()

? findInstance("Entryfield") // nothing
? type("f.entryfield1")      // "U" for Undefined
? refCount(f.entryfield1)    // Error - Attempt to access released object

? findInstance("Form") // nothing
? type("f")            // "O" for Object
? refCount(f)          // 1

   

In the code above, findInstance("Form") reports that the form object doesn’t exist anymore. Yet, type("f") says that the variable f is an object. Why? Because the variable passed to type() contains a value of type “Object” or “O” and a value that used to point to an object.  The pointer is still in the variable and has not been altered by calling the release() method of an object.

Because of the efficacy of dBASE’s garbage collection, the release() method is rarely needed. dBASE also has a release object command. It does the same thing as the release() method.  The command can be used for objects (like queries) which don’t have a release() method.
 
 
q = new query()
? findInstance("Query") // Object
? type("q")             // "O" for Object
? refCount(q)           // 1

release object q

? findInstance("Query") // nothing
? type("q")             // "O" for Object
? refCount(q)           // 1

release q  // here we release the variable q

? findInstance("Query") // nothing
? type("q")             // "U" for Undefined
? refCount(q)           // Error: Variable undefined

   

B) Releasing the object reference
 
 
f = new form()
   

The line above does two things: it creates a form object and a reference (or a pointer) to that object. Actually, the variable f contains the address in RAM where the form object is located.

There are two ways to destroy a reference to an object:

  1. We can empty the variable or change its value to something else (i.e. - f = null, f= "", f = 0, f = 5 ).
  2. We can release the variable (i.e.- release f).
The only difference is that the release command also deletes the variable f.

If dBASE didn’t have its automatic garbage collection, it would be dangerous to delete a pointer without first deleting the object that the pointer points to; we would cut the umbilical cord that connects the variable to the object without destroying the object itself. The latter would become an orphaned piece of memory, floating in RAM. That doesn’t happend because as soon as the object loses its last object reference, it commits apoptosis.
 
 
f = new form()
? findInstance("Form") // Object
? type("f")            // "O" for Object
? refCount(f)          // 1

f = null

? findInstance("form") // nothing
? type("f")            // "U" for Undefined
? refCount(f)          // Error: Expecting object

   

As we can see, nulling the object reference doesn’t fool the automatic garbage collection: on the contrary, it triggers the object’s apoptosis.

Releasing OLEAutoClient Objects

The OLEAutoclient class is an OOP mean to run a foreign application under dBASE. After instantiation, the OLEAutoClient objects are half-foreign to dBASE; their properties can be inspected but instances of their class can’t be found within dBASE.
 
 
f = new form()
f.oActiveX = new ActiveX(f)
inspect(f.oActiveX)        // We can inspect the ActiveX object
? findInstance("ActiveX")  // Object (i.e.- exists in RAM)

f.oExcel = new OLEAutoClient("Excel.Application")
inspect(f.oExcel)                // We can inspect the OLEAutoClient object
? findInstance("OLEAutoClient")  // null - can't be found

   

Actually, our mean to see that an instance of Excel really exists is through Windows’ Task Manager (Ctrl-Alt-Del).
 
 
f = new form()
f.oExcel = new OLEAutoClient("Excel.Application")
inspect(f.oExcel)                // We can inspect the OLEAutoClient object
? findInstance("OLEAutoClient")  // null - can't be found
Ctrl-Alt-Del                     // Excel is listed by the Task manager
   

The Task manager allows us to see that OLEAutoClient objects can’t be released like other dBASE objects.
 
 
f = new form()
f.oActiveX = new ActiveX(f)
inspect(f.oActiveX)       // We can inspect the ActiveX
? findInstance("ActiveX") // Object (i.e.- exists in RAM)
release object f.oActiveX
inspect(f.oActiveX)       // "Error: Attempt to access a released object"
? findInstance("ActiveX") // null - can't be found

f.oExcel = new OLEAutoClient("Excel.Application")
inspect(f.oExcel)         // We can inspect the OLEAutoClient object
Ctrl-Alt-Del              // Excel.exe is  listed among the loaded applications
release object f.oExcel
inspect(f.oExcel)         // We still can inspect the OLEAutoClient object
Ctrl-Alt-Del              // Excel is still listed among the loaded applications

   

So how can OLEAutoClient object be released? Simply by nulling their object reference.
 
 
f = new form()
f.oExcel = new OLEAutoClient("Excel.Application")
inspect(f.oExcel) // We can inspect the OLEAutoClient object
Ctrl-Alt-Del      // Excel.exe is  listed among the loaded applications
f.oExcel = null
inspect(f.oExcel) // "Error: Attempt to access a released object"
Ctrl-Alt-Del      // Excel is NOT listed among the loaded applications
   

Technically, OLE automation server is done through COM (COM stands for Component Object Model.)  Its main purpose to describe the way universal objects should be created, destroyed, handled and programmed in order to be able to work in totally different surroundings (i.e. in a Visual Basic application, a dBASE application or as an Excel spreadsheet). COM rules not only the nuts and bolts of OLE automation, but also ActiveXes, OCXes, OLE Drag’n drop and Component objects (OLE Automation being the most powerful of these techniques). Of course, there are differences between all these objects, but they are all COM-objects which obey its specific rules.

There are mainly three variations of COM objects: In-Process Server (IPS), Local Server (LS) and Remote Server.

As you can imagine by the names, In-Process Servers are created and run in the same memory that was allocated to the calling process. In general, these COM objects are to be found in DLLs (but not necessarily). Most ActiveX objects are IPS. By far, they are the fastest objects. They run in the same address area as their parent. If they have to share data, it can be sent back and forth without problem.

Local Servers are (normally) EXE files. If they are run from dBASE, they create their own process with a memory area of their own. LS are much slower, because the communication with the parent object has to be done over process-boundaries which is not a trivial thing.

Last but not least, there are the Remote Servers, which can run on another computer. In this case, they have even more boundaries to pass (network, internet, etc.) and are more complicated to handle. Remote Servers are part of an extended set of rules called DCOM (Distributed COM.)

The most fundamental rules of COM handle the lifetime of the created objects. Every COM object must have AddRef() Release() methods. These methods manage an internal reference counter which is responsible for the lifetime of the object. When to call AddRef() and  Release()  seems to be pretty straightforward. When an object is created, add one to the reference counter. If another variable points to the object, increment the reference counter. When a variable is released, decrement it and so on. When the reference counter reaches zero, there is nothing that points to the object anymore and it will release itself from memory, freeing all ressources and everything that was allocated to it (releasing itself is imposed by COM rules.)

In reality, there are dozens of rules with dozens of exceptions to these rules on when to call the AddRef() and when to call the Release() methods. It is almost impossible to write a COM object from scratch. You need a good COM-helper. For example, with Delphi, the developer don’t have to know all these rules; they are encapsulated for him by Delphi.

Let’s summarize. The lifetime of COM objects is defined by a reference counter. When it goes to zero, the object releases itself. Every method and property of a COM object is hidden from the outside world unless it is explicitely said to be visible (thus accessible). Usually, the Release() method is kept hidden, so you can’t call it from within dBASE (contrary to dBASE’s stock objects). To be sure that the internal COM Release() method is called, you must null its object reference variable. So x = null is a valid way to call the COM object’s Release() method, but possibly not enough to free the ressources if there is another variable that points to the COM object — as long as the reference count is different from zero, the object stays in memory. Make sure that all variables are null, and the COM will disappear from memory by itself. One major exception is MS-Word, which need its quit() method to be called in order to be released from memory.
 
 
f = new form()
f.oWord = new OLEAutoClient("word.Application")
inspect(f.oWord) // We can inspect the OLEAutoClient object
Ctrl-Alt-Del     // WinWord is  listed among the loaded applications
f.oWord.quit(0)
inspect(f.oWord) // "Error: Attempt to access a released object"
Ctrl-Alt-Del     // Word is NOT listed among the loaded applications
   

Unfortunately, that is not all. Under Windows, there is something called ROT (Running-Objects Table). Imagine you create two different COM objects.
 
 
x = oleautoclient("x1") ; x.value = 0
y = oleautoclient("x1") ; y.value = 10
   

These are two separate objects, having their own properties.
 
 
x.value<>y.value
   

However, this is not always the case: COM objects have the possibility to register themselves in the Running-Objects Table. When this is the case, a second instance may point to the object already in the ROT or may create an independent instance of the object. It depends on how the object was designed by the programmer. If you call many times an application that was designed to point to its object already in the ROT, there will be only one OLEAutoClient object created. It will handle all the methods and properties of all the virtual copies of that application that seem to be opened. That makes easier to exchange data between the virtual instances of that OLE Server.

If you create two instances of a normal COM object, they have separate reference counters. However if you create an object that registers in the ROT and that was designed to allow only one instance of itself be loaded, it has only one counter. In that case, even if you null out one variable that points to the COM object, that object is not released because somewhere another application also has a variable pointing to the same OLE server. Unfortunately, it is often difficult to say which action or which other application forced the server to increment its reference counter (remember all the exceptions we talked about earlier).

To complicate things a bit more, if the OLE server has a user interface, the user is allowed to shut it down, leaving all clients with an empty object. That could be problematic and can give all kind of errors. This is explicitely allowed by the COM rules. On the other hand, a client is explicitely forbidden to force the release of the OLE server (there may be other clients active). That’s why release object oExcel doesn’t work.

Conclusion

There is always a chance that a COM object stays in memory because its reference counter is not zero. And this is likely to happen more often with servers that register in the ROT, like the MS-Office applications.

That being said, as a general rule, we can say that nulling the variable(s) pointing to an OLEAutoClient object is the best way of releasing it. That causes its counter to be decremented by one. When the count gets to zero, the object destroys itself: because of this, they are apoptotic objects like the dBASE stock objects. Not all objects have a release() method and not all objects can be released with the release object command. However nearly all objects have reference-counting. Therefore, nulling the reference usually does the job best.

To be able to see the list of applications registred in the ROT, Marc Van den Berghen has created an application called Running-Objects Table Inspector. ROT_Inspector.exe is available from the link at the end of this article.

To download the application above, click here
(it is a 248 Kb zipped file)