The Ubiquitous IUnknown interface
Gopalan Suresh Raj

Food for Thought...

class CStockMarket : public IStockMarket {
    long m_lRefCount;
protected:
    ~CStockMarket (void);
public:
    CStockMarket (void);
    // IUnknown methods
    STDMETHODIMP QueryInterface (REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef (void);
    STDMETHODIMP_(ULONG) Release (void);
    // IStockMarket methods
    STDMETHODIMP getPrice (BSTR ticker, float* price);
};

                                                                                                               
- Yours Truly

CStockMarket::CStockMarket (void) : m_lRefCount (0) { return; }
CStockMarket::~CStockMarket (void) { return; }
STDMETHODIMP_(ULONG) CStockMarket::AddRef (void) {
    return InterlockedIncrement (&m_lRefCount);
}
STDMETHODIMP_(ULONG) CStockMarket::Release (void) {
    LONG res = InterlockedDecrement (&m_lRefCount);
    if (0 ==  res) delete this;
    return  res;
}
STDMETHODIMP CStockMarket::QueryInterface (REFIID riid, void** ppv) {
    ASSERT (ppv != 0);
            // or return E_POINTER in production
    if (riid == IID_IStockMarket) *ppv = static_cast <IStockMarket*> (this);
    else if (riid == IID_IUnknown) *ppv = static_cast <IStockMarket*> (this);
    else { ppv = 0; return E_NOINTERFACE; }
    reinterpret_cast <IUnknown*> (*ppv)->AddRef ();
    return S_OK;
}
STDMETHODIMP CStockMarket::getPrice (BSTR ticker, float* price) {
    *price = 100;
    return S_OK;
}

                                                                                                               
- Yours Truly


All COM+ interfaces derive from
IUnknown. Every COM+ component exposes at least the IUnknown interface. There is no default implementation of IUnknown as it is an abstract base class. The only reason that there is a class definition of IUnknown is to provide a signature for invoking its methods as they are implemented by a concrete class. The actual definition of IUnknown from the system header files also includes a declaration specification to ensure that the stack frame will be consistently formed by all COM clients and component implementations.

The
IUnknown interface is used to express the base functionality of all COM+ objects which are:
1. Life Time Control of the COM+ object (a 'la Variable Liveliness Notification) and
2. Type Coersion.

This
IUnknown interface as defined in the unkwn.idl as follows:

[local, object, uuid(00000000-0000-0000-C000-000000000046), pointer_default (unique) ]
interface IUnknown {
    HRESULT QueryInterface ([in] REFIID riid, [out, iid_is(riid)] void **ppvObject);
    ULONG AddRef ( );
    ULONG Release ( );
}


Lifetime Control of the COM+ object
COM+ mandates client programs to manage the reference count of each interface pointer that it uses. It is considered a very bad programming style to simply allow process termination to clean up any unreleased resources. Therefore, COM+ has very specific rules that the client has to follow. The
AddRef()/Release() implementation of the IUnknown interface sometimes controls the lifetime of a COM+ object. Some objects use a client-controlled reference count to control the lifetime of an object. Other objects do not allow COM reference counting to affect the lifetime of the object. However, for uniformity, clients always call AddRef and Release according to COM+'s reference counting rules. All interface pointers must adhere to this rule. So the client always follows the rules.

Rules for invoking AddRef/Release methods on COM+ objects

ULONG AddRef ( );
ULONG Release ( );

Look at the following client-side code fragment:

void AddRefReleaseExample (IUnknown ** ppOuterObject) {
    IUnknown *pObject1 = 0, *pObject2 = 0;

    // get pointers to a couple of COM+ Objects
    GetFirstObject (&pObject1);        // pointer obtained in function and object AddRef'ed
    GetSecondObject (&pObject2);    // pointer obtained in function and object AddRef'ed

    // set pObject1 to point to the second object
    if (pObject1) pObject1->Release ( );
    if (pObject1 = pObject2) pObject1->AddRef ( );

    if (*ppOuterObject = pObject2) (*ppOuterObject)->AddRef ( );

    // clean-up before we leave the method
    if (pObject1) pObject1->Release ( );
    if (pObject2) pObject2->Release ( );
 }

In the above code fragment note that:
1. The method returns a COM+ Object pointer to the client through the
ppOuterObject parameter.
2. AddRef'ing of interface pointers happen as close to the assignment as is possible.

The three most important rules to Resource Management in COM+ are:
1.
AddRef()must be called each time a non-null interface pointer is copied from one location in memory to the other.

2. Release() must be called each time a non-null interface pointer is overwritten or destroyed in memory.
3. Redundant
AddRef()/Release() operations can be eliminated if we have special knowledge of the two memeory locations involved.

Similarly,

Also Note the following:,
1. Calls to AddRef must be matched by an equal number of calls to Release on the same pointer value. This is to ensure that objects do per-instance reference counting properly. Interface pointers are resources, just like memory and other operating system primitives. Once a pointer has been AddRef'ed, you have to call Release in all code paths including exceptions.
2. Once all AddRef's have been offset by Release calls, the pointer value is considered invalid.
3. The ULONG results of AddRef and Release are not guaranteed to be accurate.
4. Be careful with exceptions. All Interface pointers should be set to zero after they have been released. This will ensure that you do not accidentally access an object that has deleted itself.

Type Coersion and QueryInterface

HRESULT QueryInterface (REFIID iidOfRequestedInterface, void ** ppObjectReturned);

QueryInterface is the typecast operator of COM+. It is used to access additional functionality of any COM+ object. In some ways, look at it as the logical equivalent of the dynamic_cast<> operator available in the C++ language or the type-casting functionality available in the Java language.

When you have an interface pointer to an object and would like to access some other interface of the same object, you would invoke QueryInterface through the interface that you are holding on the object. If the call to QueryInterface had succeeded and had returned an S_OK result, the interface pointer returned as the second parameter will contain a valid AddRef'ed COM+ object that corresponds to the requested interface. When you are finished using the component, it is your responsibility to call Release through the new interface pointer.

However, if the call to QueryInterface fails, it means that the object does not support the requested interface, the function will return
E_NOINTERFACE and the second parameter will not have a valid object. If the QueryInterface call fails, you need not call Release on the object.

Take a look at the following IDL:

import "unknwn.idl";

[uuid(31325851-E808-11d3-987E-006097A7D34F), object]
interface IEmployee : IUnknown {
  HRESULT getName ([out, retval] BSTR* name);
  HRESULT getSSN ([out, retval] BSTR* ssn);
};
 
[uuid(31325852-E808-11d3-987E-006097A7D34F), object]
interface IDeveloper : IEmployee {
  HRESULT developCode( );
};
 
[uuid(31325853-E808-11d3-987E-006097A7D34F), object]
interface IArchitect : IDeveloper {
  HRESULT writeSpecifications ( );
  HRESULT produceDesignDocs ( );
};
 
[uuid (31325850-E808-11d3-987E-006097A7D34F), version (1.0)]
library Roles {
 importlib ("stdole32.tlb");

 [uuid(31325854-E808-11d3-987E-006097A7D34F)]
 coclass DevelopmentTeam {
  interface IArchitect;
  interface IDeveloper;
 };
}

The following examples illustrate the use of QueryInterface to traverse the type heirarchy of an object using Visual C++, Visual Basic and Visual J++.
Shown below is the Visual C++ code-snippet of a COM client trying to use
QueryInterface:

void TestingHeirarchyTraversal (IEmployee* pEmployee) {

 IDeveloper* pDeveloper = NULL;
 HRESULT hr = pEmployee->QueryInterface (IID_IDEVELOPER, (void**) pDeveloper);
 if (hr == S_OK) {
   pDeveloper->developCode ( );
   pDeveloper->Release ( );
 }

 IArchitect* pArchitect = NULL;
 HRESULT hr = pEmployee->QueryInterface (IID_IARCHITECT, (void**) pArchitect);
 if (hr == S_OK) {
   pArchitect->produceDesignDocs ( );
   pArchitect->Release ( );
 }

}

Shown below is the Visual Basic code-snippet of a COM client trying to use QueryInterface:

Public Sub TestingHeirarchyTraversal (pEmployee as Object )

 If TypeOf pEmployee Is IEmployee Then
   Dim pDeveloper as Roles.IDeveloper
    pDeveloper = pEmployee
' coerce type using QI
    pDeveloper.developCode
    Set pDeveloper = Nothing
' release the object
 
    Dim pArchitect as Roles.IArchitect
    pArchitect = pEmployee
' coerce type using QI
    pArchitect.produceDesignDocs
    Set pArchitect = Nothing
' release the object
 End If
End Sub

Shown below is the Visual J++ code-snippet of a COM client trying to use QueryInterface:

public void TestingHeirarchyTraversal (roles.IEmployee pEmployee)  {

   roles.IDeveloper pDeveloper = (roles.IDeveloper) pEmployee;
// coerce type using QI
   if (pDeveloper)  {
     pDeveloper.developCode ( );
     com.ms.com.ComLib.release (pDeveloper);
// release the object
   }

   roles.IArchitect pArchitect = (roles.IArchitect) pEmployee;
// coerce type using QI
    if (pArchitect)  {
     pArchitect.produceDesignDocs ( );
     com.ms.com.ComLib.release (pArchitect);
// release the object
   }
}

Note that in all the above code-snippets, Release is called only when a new pointer is successfully returned by Query interface.




 

click here to go to
My Basic COM+ Tutorial...
click here to go to
My Advanced COM+/DNA Tutorial HomePage...

 

About the Author...
Gopalan Suresh Raj is a Software Architect, Developer and an active Author. He is contributing author to a couple of books "Enterprise Java Computing-Applications and Architecture" and "The Awesome Power of JavaBeans". His expertise spans enterprise component architectures and distributed object computing. Visit him at his Web Cornucopia© site (http://www.execpc.com/~gopalan) or mail him at gopalan@execpc.com.

 


Go to the Component Engineering Cornucopia page

This site was developed and is maintained by Gopalan Suresh Raj

This page has been visited times since March 13,1999.

Last Updated : Mar 14,'99

If you have any questions, comments, or problems regarding this site, please write to me I would love to hear from you.


Copyright (c) 1997-2000, Gopalan Suresh Raj - All rights reserved. Terms of use.

All products and companies mentioned at this site are trademarks of their respective owners.