wideman-one 
Last edit: 05-03-17 Graham Wideman

Delphi

Understanding Delphi COM (OLE) Interface References, AddRef and All That
Article created: 98-07-26

Contents

Download: If you are reading this page off my web site, then download the demo app and its source code, and these pages here: GWRefCt.zip (if reading from CD or hard-drive, you already got this zip and expanded it, so the link is disabled.)

Orientation

If you are going to work with COM (also known as OLE) then you need to understand how COM interface references work, and how they are supported in Delphi. This includes the subject of reference counting, the RefCount variable, and the _AddRef and _Release procedures.

There are numerous sections in popular Delphi books on the overall topic, but some of the published explanations of reference counting seem to be ambiguous or wrong. This article and accompanying demo is an attempt to clarify the situation.

Version applicability: This article applies to (and the software has been tested with) Windows NT 4 (SP3) and Windows 95. It uses techniques that have applied certainly since 1995, hence should work with NT 3.5x, and who knows, might even work with NT 5 and Windows 98!. The code is for Delphi 3, I expect it to work in Delphi 4, and it may also work with Delphi 2.

Basics: Interfaces, IUnknown and Reference Counting

In the COM world (as in Java), an "interface" is essentially a set of related functions. A COM object is an object that implements one or more COM interfaces, or in other words offers certain sets of functions. There are many COM interfaces predefined by Windows, and applications can define more. 

In Delphi, you create these objects with a class declaration (like any other Delphi objects) with a little extra syntax to say that you intend this class to be COM-capable, and that it will implement one or more of these interfaces (sets of functions).  You must then of course provide these functions as part of your class, along with any other functions and data you desire. Your object looks and works just like any other Delphi object, but it just "happens" to have functions needed for one (or more) COM interfaces, and you can pass it to Windows API functions that need COM objects.

All COM interfaces are said to "inherit from" the mother of all interfaces, "IUnknown".  This is just another way of saying that all interfaces must include the functions that are in IUnknown. IUnknown contains two main behaviors:

So, if you are going to play with COM objects, you really need to know when to call AddRef and Release, right? Not exactly.

Delphi COM Reference Types

Delphi provides types of variables that are references to COM objects.   These types have names like IUnknown, IDataObject and so forth.  So if you create an object of type TMyCOMObject that implements, say, IDataObject, you can refer to it with variables of type TMyCOMObject, or with variables of type IDataObject. What's the difference in using one versus the other?

Delphi Rules for COM References

Now that we've introduced Delphi's ability to create COM objects, and Delphi's provision of Ixxx COM interface types with automatic reference counting, we can identify some simple rules for using all this.

Never call AddRef or Release! If you declare variables to be of type IUnknown, or ISomeOtherInterface, then Delphi will handle all the reference counting for you.  In other words, Delphi generates hidden calls to AddRef and Release in all the right places.   Examples include:

Situation Automatic action
  • MyIxxxVariable := SomeCOMObject;
  • Release (on object MyIxxxVariable was pointing to, if any).
  • AddRef (on SomeCOMObject)
  • MyIxxxVariable := nil;
  • Release (on object MyIxxxVariable was pointing to)
  • SomeDelphiProc(MyIxxxVariable)
  • For input parameters:
    • on entry: AddRef
    • on exit:  Release
  • MyIxxxVariable goes out of scope (eg, at end of procedure)
  • Release (on object MyIxxxVariable was pointing to)

This is contrary to what you may read elsewhere (for example the Delphi books listed below), but if you are unsure, try the demo application. There are some special wrinkles, see below.

Don't Call Destroy!  The whole idea of COM reference counting is that COM will delete the object when there are no references left.  Now of course, if you created the object, and you know there are no references left (or don't care), then go ahead and Destroy it if you want to. It may be a COM-style object, but unless you actually passed it outside your app, nobody knows about it but your code, so you are free to do with it whatever you like. (And of course, if for some reason you are implementing the Release function, then you will have to call Destroy.  You would rarely be in that position since you can inherit the IUnknown functions from TInterfacedObject or TCOMObject.)

So in the normal case, the object should be left to destroy itself when RefCount decrements to zero.  To put it another way, if you see a need to explicitly call Destroy, that probably indicates that there is some other design problem.

For Auto Destroy, there must have been at least one reference!   If you create an object and only assign it to a Delphi object variable, but never assign it to a COM interface Ixxx type variable, then RefCount will never increment, and never decrement, so Release is never called and in turn never calls Destroy.  Hence to have the object deleted automatically, you must at some point create a COM reference to it -- not a big constraint considering that there's little point creating a COM object if you aren't going to refer to it with a COM reference!

Wrinkles

"Out" Parameters Can't Be "In" Parameters

The following shows the declaration for a function that accepts a COM object input parameter, and returns a COM object through an "out" parameter:

Procedure SomeProc(COMObjectIn: ISomeCOMInterface; out COMObjectOut: ISomeCOMInterface)

Normally you pass an object in via an "in" parameter (no special keyword) and return an object via an "out" parameter... no problem.  However, since the "out" keyword looks like the Delphi "Var" parameter, you might be tempted to try feeding an object into the procedure via an out parameter.

This doesn't work. Immediately on entry, Delphi nils the reference (which would also delete the object if RefCount was 1 on entry).

Note that this is contrary to Delphi Developer's Handbook (1998) page 465.

Calling an API function, passing a COM object out of your application.

When calling a procedure or function and passing a COM object outside your app (eg: to a Windows API function), it's the caller's responsibility to "allocate the resource before handing it over".  This amounts to AddRef-ing before calling, and Release-ing on return. Does Delphi do this for you automatically?  Basically yes, if you do things sensibly. Examples:

Example 1:  This works fine
---------------------------
Var MyIxxxVariable: ISomeCOMInterface;
....
  MyIxxxVariable := TMyCOMObject.Create;  // implicit AddRef 
  SomeAPIFunc(MyIxxxVariable);            // works fine
Example 2:  This doesn't work properly....
--------------------------------------
Var MyDelphiVariable:  TMyCOMObject;
....
  MyDelphiVariable := TMyCOMObject.Create;  // no AddRef 
  SomeAPIFunc(MyDelphiVariable);    // type conversion, but no AddRef!
Example 3:  But this does work properly
---------------------------------------
Var MyDelphiVariable:  TMyCOMObject;
....
  MyDelphiVariable := TMyCOMObject.Create;  // no AddRef 
  SomeAPIFunc(MyDelphiVariable as ISomeCOMInterface);
        // type conversion, and AddRef

Your Code Fetches a COM Object

Since it's the caller's responsibility to allocate the resources it's handing to you, you do not need to call AddRef nor Release when you get an object from outside your application.  The fact that you have been given a reference means that RefCount is already taken care of.  If you "pass that object around" inside your code (assigning the object to other Ixxx-type variables, or passing it to other procedures), then Delphi will, as usual, handle the AddRefs and Releases automatically.

The Final Wrinkle

The final wrinkle is, of course, that I haven't necessarily stumbled across all the wrinkles... I would be very interested to hear of instances where there is a need to call AddRef or Release.

Resources/References

Copyright Status

Please feel free to distribute or publish this article and associated code, provided some note of credit for me remains attached. Permission is also granted to include this article on CDROM collections, again provided credit remains attached. Thanks!


Go to:  ["Up" to Main Delphi Page]    wideman-one