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
? findInstance("Form") // nothing
|
|
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
release q // here we release the variable q ? findInstance("Query") // nothing
|
|
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:
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
|
|
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")
|
|
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")
|
|
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.
(it is a 248 Kb zipped file) |