AbstractMany CAA services are offered using CAA components hidden behind a curtain of interfaces. We discuss here the ways for clients to create instances of these components, to use them by means of pointers to interfaces they implement, and to manage their lifecycles. This article is divided into the following sections:
|
A component is a reusable object provided in a binary form that can be instantiated and used by client applications to stand for a real object, such as a mathematical surface or a solid feature, or a software object, such as dialog box or an end user command, or finally a set of services, such as a model checker or a stress analyzer. A CAA component is modeled using a class or several classes linked together to provide the component type and the component behaviors.
An interface represents either the type or the behavior, or a part of the behavior, of the implementing component. To allow for this, the interface is made of a set of operations that the implementing component is supposed to perform. When you get an interface pointer to a given component, you get in fact a handle to the component that enables you to request this component to behave accordingly, that is, to perform some of these operations. You can also ask the component to return another handle, that is, a pointer to another interface it implements, and you can thus skip from one handle to another, depending on what you want the component to do.
An interface is also a contract between the interface designer, the component developer, and you, as a client application programmer. This contract includes the interface name and specifications, such as the interface mission, the operations, their signatures and purposes, and the compliance of the component to these specifications. This should not change with the time. Only additions to the specifications are allowed.
The main advantage of interfaces is to enable client applications to use components using a standard and lasting protocol, hiding the implementation details and allowing the components to change with no impact on the client applications. In addition, this standard protocol hides the actual component location and makes the client application independent from the way the component is actually created and accessed, in the same process or in another process, on the same machine or on another machine.
The handle to the component is a pointer to an interface that the component implements, that is, for which the component provides the code of the methods performing the operations.
The interfaces are shown on the following figure as plug-in jacks used by clients to plug to the component as if it were an electronic component, following the UML notation and the way used by Microsoft to graphically represent interfaces in different publications.
As an example, imagine you are writing a client application which deals with points in a 2D space. We can assume that the what we expect from a 2D point is summarized in the following figure of a point component you will use by means of the interfaces it implements.
This point component has a type interface and three behavioral interfaces:
Note that the last three interfaces could be shared by other geometric components, such as the 2D line or the 2D circle, and would be of course implemented differently for these components.
[Top]
Component instances can be created using:
CATInstantiateComponent
global function. Using this
component factory function is the recommended solution since this doesn't
couple at build time your application with the component shared lib or DLL,
but this must be enabled by the component suppliernew
operator applied to the component main class.The component supplier should describe how its component can be instantiated. These two ways are discussed below.
When a component enables the CATInstantiateComponent
global
function, create an instance of this component as follows:
... #include " |
where:
CATCmp
is the name of the component main classIID_IUnknown
is the IID of the interface to which you want a
pointer from CATCmppIUnknownOnCATCmp
is the retrieved pointer.When creating such as component, always handle it as an IUnknown or CATBaseUnknown pointer. Usually, CAA methods request a CATBaseUnknown pointer. Then you can ask for a pointer to any interface this component implements.
There is no include statement to include the component main class header file, and doing so, there is no build time dependency between your application and the one that supplies the component.
If the component doesn't enable its instantiation using the CATInstantiateComponent
global function, you need to instantiate its main class using the new
operator.
... #include "CATCmp.h" ... // Create a CATCmp instance IUnknown * pIUnknownOnCATCmp = NULL; pIUnkownOnCATCmp = (IUnknown *) new CATCmp(); ... |
When creating such a component, always handle it as an IUnknown or CATBaseUnknown pointer. Usually, CAA methods request a CATBaseUnknown pointer.Then you can ask for a pointer to any interface this component implements.
This way of doing binds your application with the one that supplies the component at build time. This means that if the latter is modified, your application must be rebuilt.
[Top]
When you write a client application, you should never handle pointers to the
implementation objects that make up the component. Usually, you already have a
pointer to an interface, either passed as a parameter to the method you are
writing, or a pointer to IUnknown or CATBaseUnknown returned by
the component factory function or new
operator.
Once you have got a pointer to an interface, you can request the component to
execute operations among those exposed by the interface, that is execute the
methods that provide the code to these operations. The component can of course
implement other interfaces. Using the pointer you have already, you can
determine whether this component implements a given interface, and get a pointer
to this interface, by means of the QueryInterface
method. QueryInterface
has the following signature:
HRESULT __stdcall QueryInterface(const IID & iid, void ** ppv);
The first parameter is the IID of the queried interface. IID stands for
Interface IDentifier and is unique. It is defined by the interface supplier as a
GUID [1]. The interface IID is an extern
that is always built using the prefix IID_ concatenated to the interface name,
such as IID_IUnknown
, IID_CATBaseUnknown
, or IID_CATI3DGeoVisu
.
If the component implements the interface, a pointer to this interface is
returned in ppv
, and NULL
is returned otherwise.
QueryInterface
returns an HRESULT
[2],
which is S_OK
if the query is successful, E_NOINTERFACE
if the query fails, or E_UNEXPECTED
if an unknown error occurs.
QueryInterface
is shared by all the interfaces. It can be used
from any pointer to any interface a component implements to get a pointer to any
other interface this component implements. This can also be expressed as: QueryInterface
is reflexive, symmetric, and transitive.
Reflexive | From a pointer to an interface, you can always get a pointer to the same interface |
Symmetric | If you got a pointer to CATIYY from a pointer to CATIXX, you can get a pointer to CATIXX from this pointer to CATIYY |
Transitive | If you got a pointer to CATIYY from a pointer to CATIXX, and if you got a pointer to CATIZZ from this pointer to CATIYY, you can also get a pointer to CATIZZ from the pointer to CATIXX |
[Top]
This example shows a very common case you'll meet often. You should code a
method that creates a component instance from which you get a pointer to a given
interface and execute methods this interface provides. To do this, use QueryInterface
as follows:
... #include "CATInstantiateComponent.h" // To get the CATInstantiateComponent global function #include "CATErrorDef.h" // To declare return code values and testing macros #include "CATIXX.h" // To get the CATIXX interface ... HRESULT ClientComponent::ClientMethod() { HRESULT rc = E_FAIL; // Create a CATCmp instance IUnknown * pIUnknownOnCATCmp = NULL; rc = ::CATInstantiateComponent("CATCmp", IID_IUnknown, (void **) &pIUnknownOnCATCmp); if (SUCCEEDED(rc) && NULL != pIUnknownOnCATCmp) { // Initialize to NULL the pointer to the requested interface CATIXX * pIXXOnCATCmp = NULL; // Is CATIXX supported by CATCmp rc = pIUnknownOnCATCmp->QueryInterface(IID_CATIXX, (void **) & pIXXOnCATCmp); pIUnkownOnCATCmp->Release(); // OK with pIUnknownOnCATCmp if (SUCCEEDED(rc) && NULL != pIXXOnCATCmp) { // You can use the interface pointer rc = pIXXOnCATCmp->MXX1(); pIXXOnCATCmp->Release(); // Release it as soon as you're finished with it } else if (FAILED(rc)) ... // Process the error } else if (FAILED(rc)) ... // Process the error return rc; } ... |
In this example, pIUnknownOnCATCmp
is a pointer to the IUnknown
interface implemented by a given component CATCmp for which you need a
pointer to another interface, say the CATIXX interface. pIUnknownOnCATCmp
is first initialized to NULL
and its actual value is returned by
the component factory function CATInstantiateComponent
. Then the
pointer to CATIXX is initialized to NULL
, and QueryInterface
is called using the interface IID passed as IID_CATIXX
. Calling QueryInterface
implies that the pointer you pass as the second argument is of the appropriate
type to hold the address of the interface you query. For example, the pointer pIXXOnCATCmp
should be created with the CATIXX type, or with a type from which CATIXX
derives, otherwise, you get a core dump at run-time. Use the macros SUCCEEDED
and FAILED
to check the returned value, and never compare it
directly with S_OK
or E_NOINTERFACE
. Include the CATErrorDef.h
file to get the definition of these macros. If you're finished with pIUnknownOnCATCmp
,
release it (see Managing Component Lifecycle to know about
Release
), and if the interface query was successful, use the
returned pointer. Once you're finished with pIXXOnCATCmp
, release
it to enable the component to properly manage its lifecycle.
[Top]
IUnknown, supplied by CAA for UNIX and by the Microsoft's Component Object Model (COM) for Windows, is the base interface. CATBaseUnknown derives from IUnknown. CATBaseUnknown is the base class for all CAA interfaces and CAA components.
QueryInterface
, AddRef
, and Release
.The
last two methods are described in Managing Component
Lifecycle.Since all the interfaces you will use derive from IUnknown and from CATBaseUnknown, any pointer to any interface can be seen as a pointer to IUnknown or to CATBaseUnknown. This implies that:
QueryInterface
to retrieve a pointer to IUnknown or to CATBaseUnknown.In addition, for a given component, the pointer to IUnknown or to CATBaseUnknown
is constant, that is the same value is returned by successive QueryInterface
calls to the same component for the IUnknown interface, or for the CATBaseUnknown
interface.
This is very useful when you need to process polymorphically pointers to different interfaces for different components in the same variable. A pointer to IUnknown or to CATBaseUnknown is thus a universal handle to any component. Nevertheless, it gives you no information about the component which remains "unknown". The only thing you can do is to query a pointer to another interface implemented by the component. In fact, you can also manage reference counting with a pointer to IUnknown or to CATBaseUnknown.
[Top]
You may often need to determine whether two interface pointers, pointing to the same interface or not, refer to the same component instance. If the two interface pointers point to the same interface, you might want to compare directly the two interface pointer values. Depending on the way the interface is implemented, the comparison can fail even if the component instance is the same. If the two interface pointers point to different interfaces, you cannot of course compare the two interface pointers.
Here is the only valid and safe method that applies in any cases: from each
interface pointer, use QueryInterface
to retrieve a pointer to IUnknown,
or possibly to CATBaseUnknown, and compare the two interface pointers.
Since QueryInterface
always returns, for any interface pointer to a
given component instance, the same IUnknown pointer, or the same CATBaseUnknown
pointer, you are sure that if the two values compare, the underlying component
instance is the same. This is shown in the following example.
... // Assume we have two interface pointers pIXXOnCATCmp and pIYYOnCATCmp // Create two pointers to IUnknown IUnknown * pIUnknownOnCATCmpFromIXX = NULL; IUnknown * pIUnknownOnCATCmpFromIYY = NULL; // Retrieve the pointer to IUnknown from pIXXOnCATCmp HRESULT rc = pIXXOnCATCmp->QueryInterface( IID_IUnknown, (void **) & pIUnknownOnCATCmpFromIXX); if (SUCCEEDED(rc) && NULL != pIUnknownOnCATCmpFromIXX) { // Retrieve the pointer to IUnknown from pIYYOnCATCmp rc = pIYYOnCATCmp->QueryInterface( IID_IUnknown, (void **) & pIUnknownOnCATCmpFromIYY); if (SUCCEEDED(rc) && NULL != pIUnknownOnCATCmpFromIYY) { if (pIUnknownOnCATCmpFromIXX==pIUnknownOnCATCmpFromIYY) { // The underlying component instance is the same for both interface pointers } else { // Different component instances are pointed to } } } ... |
[Top]
When you program a C++ application without using the CAA V5 Object Model, you
usually create instances of your classes using the new
operator if
you want that the lifecycle of these instances should not be restricted by the
scope in which you create them. new
allocates on the heap the
appropriate storage for the instance and returns a pointer to the instance
created. Once you do not need an object anymore, you delete it using the delete
operator which frees the storage and makes it available to other uses. To make
an optimized usage of the storage, you need to carefully manage your new
/delete
pairs, and use delete
at the appropriate moment, not too early and
not too late.
When you use the CAA V5 Object Model in your client application, you can use
these operators, but you can also create components by means of component
factory functions which instantiate the components and return a pointer to IUnknown.
You can additionally get other pointers to interfaces these components implement
by means of the QueryInterface
method. You can also get a pointer
to an interface a component implements without having yourself created the
component. A given client application has thus no visibility on whether an
existing component should be kept because it is used, or deleted because it is
unused. The component lifecycle and the resulting storage management cannot thus
be performed by the client application alone.
To overcome this problem and rather than letting you struggle with the component lifecycle without having the required information to decide what to do, it seems to be better and easier to let you inform the component that you need to use one of its interfaces, and inform it when you don't need this interface anymore. Starting and ending using an interface are information you perfectly manage, and you leave the component decide to delete itself when it becomes useless to any client application. Reference counting is the way you'll use to inform components.
[Top]
The client application knows which interfaces it uses, and which interfaces
it doesn't use anymore. We will use this knowledge to manage interface reference
counting using the AddRef
and Release
methods,
declared by the IUnknown interface and implemented by the CATBaseUnknown
class. When a client queries for an interface, and gets a pointer to this
interface, QueryInterface
increments the reference count by calling
AddRef
. As soon as the client application doesn't need the
interface anymore, this client application decrements the reference count by
calling Release
. As long as client applications have interface
pointers to the component, it can be reached and used, and its reference count
is greater than 0. When the reference count for a given component reaches 0, it
cannot be reached because all the interface pointers to this component are
released, and it is automatically deleted. Let's reuse the previous example
about QueryInterface
:
... #include "CATInstantiateComponent.h" // To get the CATInstantiateComponent global function #include "CATErrorDef.h" // To declare return code values and testing macros #include "CATIXX.h" // To get the CATIXX interface ... HRESULT ClientComponent::ClientMethod() { HRESULT rc = E_FAIL; // Create a CATCmp instance IUnknown * pIUnknownOnCATCmp = NULL; rc = ::CATInstantiateComponent("CATCmp", IID_IUnknown, (void **) &pIUnknownOnCATCmp); if (SUCCEEDED(rc) && NULL != pIUnknownOnCATCmp) { // Initialize to NULL the pointer to the requested interface CATIXX * pIXXOnCATCmp = NULL; // Is CATIXX supported by CATCmp rc = pIUnknownOnCATCmp->QueryInterface(IID_CATIXX, (void **) & pIXXOnCATCmp); pIUnkownOnCATCmp->Release(); // OK with pIUnknownOnCATCmp if (SUCCEEDED(rc) && NULL != pIXXOnCATCmp) { // You can use the interface pointer rc = pIXXOnCATCmp->MXX1(); pIXXOnCATCmp->Release(); // Release it as soon as you're finished with it // Cmp is deleted } else if (FAILED(rc)) ... // Process the error } else if (FAILED(rc)) ... // Process the error return rc; } ... |
The CATInstantiateComponent
global function creates an instance
of the CATCmp component and returns a pointer to IUnknown. The
global function calls AddRef
to set the reference count to 1. Then QueryInterface
is used to get a pointer to CATIXX and calls AddRef
too to
increment the reference count whose value is now 2. The IUnknown pointer
becomes useless and is released. The reference count decrements to 1. As soon as
pIXXOnCATCmp
becomes useless, it is released and the reference
count reaches 0. CATCmp cannot be reached anymore, since the client
application has released all the handles it had onto the component. CATCmp
is then automatically deleted.
As a client application developer, you simply request the component instantiation using the component name and retrieve a pointer to IUnknown from this component, then query a pointer to another interface from this component, and finally inform the component that you do not need it any longer by releasing the interface pointers.
[Top]
You can fall into problems you will debug with difficulty if the reference
count reaches 0 and if you still needs the interface pointer, meaning that there
were less AddRef
calls than Release
calls. On the
opposite, if the component still exists whereas it should be deleted, you will
get memory leaks. So the following rules should always apply:
AddRef
whenever you create an interface pointer, except
when using the functions that return an interface pointer. These functions
must call AddRef
before returning. For example, CATInstantiateComponent
and QueryInterface
call AddRef
, so you don't need
to call AddRef
in your client application after you get an
interface pointer from such a global function or method that retrieve an
interface pointerAddRef
if you assign an existing interface pointer to
another interface pointer, for example using the assignment operatorRelease
when the interface pointer becomes useless to
you, and never use the delete
operator with interface pointers,
or with IUnknown pointers you get using the new
operator
when you create a component instance.[Top]
Sometimes, CAA functions or methods use smart interface pointers, also called handlers, instead of interface pointers, and you need to manage the coexistence between them, for example when you get a smart interface pointer from a function and need to pass it as an interface pointer to another function.
Have a look at the following code. The pointer pIXXOnCATCmp
,
returned by QueryInterface
for the IID_CATIXX
interface identifier from an existing pointer to IUnknown, is used to
execute the method MXX1
, and then released to decrement the
reference count, since the QueryInterface
method has incremented it
using the AddRef
method:
... CATIXX * pIXXOnCATCmp = NULL; // QueryInterface makes pIXXOnCATCmp->AddRef() before returning pIXXOnCATCmp HRESULT rc = pIUnknownOnCATCmp->QueryInterface(IID_CATIXX, (void**) &pIXXOnCATCmp); if (SUCCEEDED(rc) && NULL != pIXXOnCATCmp) { pIXXOnCATCmp->MXX1(); pIXXOnCATCmp->Release(); // OK with pIXXOnCATCmp } ... |
This can be simplified as follows using smart interface pointers.
... CATIXX_var spIXXOnCATCmp(pIUnknownOnCATCmp); if (NULL_var != spIXXOnCATCmp) spIXXOnCATCmp->MXX1(); ... |
The CATIXX_var class instance is used to create a reference to a CATIXX
instance from pIUnknownOnCATCmp
by calling QueryInterface
.
The smart interface pointer can be used as a pointer to execute MXX1
,
and doesn't require from you to manage reference counting since it does it by
itself. As soon as the smart interface pointer goes out of scope, it is
automatically released. When no smart pointer refers to the object any more,
that is when the object cannot be reached by any application, it is deleted
without you need to worry about it.
The operators = (assignment operator) , ! (logical negation operator), == (equality operator), and != (inequality operator) are redefined for smart interface pointers. For example, instead of writing this code to get a pointer to CATIYY from a pointer to CATIXX and use both:
... CATIXX * pIXXOnCATCmp = NULL; HRESULT rc = pCATBaseUnknownOnCATCmp->QueryInterface( IID_CATIXX, (void**) &pIXXOnCATCmp); pCATBaseUnknown->Release(); if (SUCCEEDED(rc) && NULL != pIXXOnCATCmp) { pIXXOnCATCmp->MXX1(); CATIYY * pIYYOnCATCmp = NULL; rc = pIXXOnCATCmp->QueryInterface( IID_CATIYY, (void**) &pIYYOnCATCmp); pIXXOnCATCmp->Release(); if (SUCCEEDED(rc) && NULL != pIYYOnCATCmp) { pIYYOnCATCmp->MYY1(); pIYYOnCATCmp->Release(); } } ... |
you can get another interface smart pointer on the same object using the assignment operator as follows:
... CATIXX_var spIXXOnCATCmp(pCATBaseUnknownOnCATCmp); if (NULL_var != spIXXOnCATCmp) { spIXXOnCATCmp->MXX1(); ... CATIYY_var spIYYOnCATCmp = spIXXOnCATCmp; if (NULL_var != spIYYOnCATCmp) { spIYYOnCATCmp->MYY1(); ... |
To summarize with getting smart interface pointers, you can:
CATIXX_var spIXXOnCATCmp(pCATBaseUnknownOnCATCmp); |
CATIYY_var spIYYOnCATCmp(spIXXOnCATCmp); |
CATIYY_var spIYYOnCATCmp= spIXXOnCATCmp; |
SUCCEEDED
or FAILED
, but compare it with the null smart interface pointer
NULL_var
if (NULL_var != spCATIXX) |
[Top]
Here are described the main scenarios that can happen, and how to make reference counting safe applications.
[Top]
Here are the recommendations on how to use QueryInterface
for
retrieving a given interface pointer from component, and how to use the returned
interface pointer.
NULL
before calling QueryInterface
QueryInterface
SUCCEEDED
and FAILED
,
and test that the retrieved pointer is not NULL, before using it. The
interface pointers returned as output parameters of functions such as QueryInterface
are valid and usable if and only if SUCCEEDED
returns TRUE
.
Never test the interface pointer itself[Top]
These recommendations depends on whether, in a given method, you use interface pointers only, smart pointers only, or if interface pointers coexist with smart interface pointers.
[Top]
You need to call AddRef
whenever you create an interface
pointer, whatever the means you use. Nevertheless, the functions that return
interface pointers call AddRef
for you, such as CATCreateExternalObject
and QueryInterface
.
When you must call AddRef
:
AddRef
when you assign an interface pointer to a local
variable
CATIXX * pIXXOnCATCmp2 = pIXXOnCATCmp; pIXXOnCATCmp2->AddRef(); |
AddRef
when you return an interface pointer as an out or
inout parameter of your method
HRESULT CAAClientCmp::CAAClientMethod(CATIXX ** pIXXOnCATCmp) { ... pIXXOnCATCmp->AddRef(); ... |
AddRef
when you return an interface pointer as a method
return value
CATIXX * pIXXOnCATCmp CAAClientCmp::CAAClientMethod() { ... pIXXOnCATCmp->AddRef(); return pIXXOnCATCmp; } |
AddRef
when you store an interface pointer as a class
data member
... pIXXOnCATCmp->AddRef(); ClientCmp::IntPtr = pIXXOnCATCmp; ... |
When you don't need to call AddRef
:
AddRef
for an IUnknown pointer returned by CATInstantiateComponent
,
such as pIUnknownOnCATCmp
below. CATInstantiateComponent
has already done it.
... IUnknown * pIUnknownOnCATCmp = NULL; HRESULT rc = ::CATInstantiateComponent("CATCmp", IID_IUnknown, (void **) &pIUnknownOnCATCmp); ... |
AddRef
for an IUnknown returned by the new
operator applied to a component class, such as pIUnknownOnCATCmp
below. new
has already done it.
... IUnknown * pIUnknownOnCATCmp = NULL; pIUnknownOnCATCmp = (IUnknown *) new CATCATCmp(); ... |
AddRef
for a pointer to an interface returned by QueryInterface
,
such as pIXXOnCATCmp
below. QueryInterface
has
already done it.
... HRESULT rc = pIUnknownOnCATCmp->QueryInterface(IID_CATIXX, (void**) &pIXXOnCATCmp); ... |
When you must calll Release
:
Release
as soon as you're finished with an interface
pointer, and at least prior to leaving its scope
{ ... CATIXX * pIXXOnCATCmp = NULL; ... // Get pIXXOnCATCmp using QueryInterface or any other method pIXXOnCATCmp->MXX1(); // Use it pIXXOnCATCmp->MXX2(); ... pIXXOnCATCmp->Release(); // Release it as soon as you're finished with it ... } // at least prior to leaving its scope |
Release
before overwriting a local variable or data
member that contains an AddRef'ed interface pointer
... CATIXX * pIXXOnCATCmp = NULL; ... // Get pIXXOnCATCmp using QueryInterface or any other method pIXXOnCATCmp->MXX1(); // Use it pIXXOnCATCmp->MXX2(); ... pIXXOnCATCmp->Release(); // Release it before overwriting it pIXXOnCATCmp = pIXXOnCATCmp2; pIXXOnCATCmp->AddRef(); ... |
Release
before leaving the destructor of an object that
has an non-null interface pointer as a data member
CAAClientCmp::CAAClientCmp() { ... _pIXXOnCATCmp->Release(); // Release the interface pointer stored as a ... // data member before leaving the destructor } |
[Top]
Some CAA functions use smart interface pointers. If for a given interface of
a given component, you need to use a smart interface pointer and no interface
pointer, you don't need to call AddRef
when you create or retrieve
the smart interface pointer, whatever the means you use, and you don't need to
call Release
when you're finished with it.
{ ... // spIXXOnCATCmp is automatically AddRef'ed when created CATIXX_var spIXXOnCATCmp = ::ReturnASmartPointer(); spIXXOnCATCmp->MXX1(); // Use spIXXOnCmp ... } // spIXXOnCATCmp is automatically released when going out of scope |
[Top]
This happens when a method you call uses an interface pointer onto a component, and another requests a smart interface pointer to the same interface onto the same component. In this case, the interface pointer should become a smart interface pointer before being passed to the second method. For example, assume that you call a CAA function that returns a smart interface pointer, and that you call after another function to which you need to pass this smart interface pointer, but that accepts only an interface pointer.
... CATIXX_var spIXXOnCATCmp = ::ReturnASmartPointerToCATIXX(); ... CATIXX * pIXXOnCATCmp = spIXXOnCATCmp; // Create an interface pointer pIXXOnCATCmp->AddRef(); // Call AddRef ... ::UseAPointerToCATIXX(CATIXX * pIXXOnCATCmp); ... pIXXOnCATCmp->Release(); // Call Release when you're finished with it |
The reverse can also happen.
... CATIXX * pIXXOnCATCmp = ::ReturnAPointerToCATIXX(); ... CATIXX_var spIXXOnCATCmp = pIXXOnCATCmp; // Create a smart interface pointer pIXXOnCATCmp->dRelease(); // Call Release when you're finished with pIXXOnCmp ... ::UseASmartPointerToCATIXX(spIXXOnCATCmp); ... |
Some trouble can happen when you get a pointer to an interface, or a smart interface pointer as a returned value, or as an output parameter, of a function you call. Let's examine the most common cases with the two following functions, one returning a pointer to an interface, the other returning a smart pointer.
CATIXX * ReturnAPointerToCATIXX(); CATIXX_var ReturnASmartPointerToCATIXX(); |
The two pointers returned have incremented the reference counter in the method that create them.
... CATIXX_var spIXXOnCATCmp = ::ReturnAPointerToCATIXX(); ... // Use spIXXOnCATCmp } // spIXXOnCATCmp is automatically released when going out of scope |
The reference count is set to 1 by the function, and incremented to 2 by the smart interface pointer creation. You use the smart interface pointer that ais utomatically released when going out of scope. This decrements the reference count once only. Never do that. Do this instead.
... CATIXX_var spIXXOnCATCmp = ::ReturnAPointerToCATIXX(); spIXXOnCATCmp->Release(); // Use spIXXOnCATCmp to release the returned interface pointer ... // Use spIXXOnCATCmp } // spIXXOnCATCmp is automatically released when going out of scope |
As soon as you have retrieved the smart interface pointer, use it to call
Release
to decrement the reference count. When going out of
scope, the smart interface pointer is automatically released and the
reference count decrements again. Avoid this. You can
easily skip from this case to the next.
... CATIYY_var spIYYOnCATCmp = ::ReturnAPointerToCATIXX(); ... |
You cast the returned pointer to the interface CATIXX to a CATIYY smart interface pointer. You will not be able to release the returned pointer to CATIXX, since you have no variable to handle it. The reference count will never reach 0, and the object will never be deleted. Never do that. Do this instead.
... CATIXX * pIXXOnCATCmp = ::ReturnAPointerToCATIXX(); CATIYY_var spIYYOnCATCmp = pIXXOnCATCmp; pIXXOnCATCmp->Release(); ... |
... CATIXX * pIXXOnCATCmp = spIXXCATCmp; ... |
This is not smart, and you must call AddRef
and Release
on the interface pointer.
... CATIXX * pIXXOnCATCmp = ::ReturnASmartPointerToCATIXX(); ... |
The returned smart pointer is a volatile variable. This makes a core dump with unchained TIEs, or with any TIE if the returned smart pointer is the only handle on to the component.
[Top]
You access components through their interfaces and execute the methods these interfaces provide. You don't need to worry about how the interfaces are implemented.
The interface is a contract between the interface developer and you as client application programmer. The interface should not change with the time, and your client applications which use these interfaces never need to be rebuilt when a new version of the code that contains the interface implementations is installed.
The QueryInterface
method enables you to know whether a
component implements a given interface, and retrieve a pointer to this
interface, from an existing pointer to another interface this component also
implements.
IUnknown and CATBaseUnknown are the foundations of the interfaces. In addition, CATBaseUnknown provides the component basic behavior.
Client applications have no means to manage component lifecycle by themselves since they have not enough visibility on the implementations of the components of which they use interfaces. Reference counting offers the ability for client applications to inform the components that they use them, or that they don't need it any more. The components can then remain as long as they are used, and can be deleted as soon as they are not used anymore.
Using smart pointers seems to let you forget reference counting for interface pointers while keeping the reference count accurate. It's an illusion and raises more problems than it solves, and decreases performance. Be very careful if you need to make interface pointers and smart interface pointers coexist.
[Top]
[1] | About Globally Unique IDentifiers |
[2] | What Is HRESULT? |
[Top] |
Version: 1 [Jan 2000] | Document created |
[Top] |
Copyright © 2000, Dassault Systèmes. All rights reserved.