3D PLM Enterprise Architecture

Middleware Abstraction - Object Modeler

Using Components

Creating component instances, asking for interface pointers, calling interface methods, and managing component lifecycle
Technical Article

Abstract

Many 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:


What Are Components and Interfaces?

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:

  1. it is a point thanks to the CATIPoint interface that provides methods to get and set its coordinates
  2. it behaves as a movable object by means of the CATIMove interface with which you translate or rotate it
  3. it behaves as a graphic object due to the CATIAttributes interface to manage its color, associated symbol, and pickability
  4. it behaves as a displayable object through the CATIDraw interface to draw it on the screen.

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]

Creating a Component Instance

Component instances can be created using:

The component supplier should describe how its component can be instantiated. These two ways are discussed below.

Using the CATInstantiateComponent Global Function

When a component enables the CATInstantiateComponent global function, create an instance of this component as follows:

...
#include "CATInstantiateComponent.h"
...
  // Create a CATCmp instance and retrieving a pointer to IUnknown from CATCmp
  IUnknown * pIUnknownOnCATCmp = NULL;
  HRESULT rc = ::CATInstantiateComponent("CATCmp", IID_IUnknown, (void **) &pIUnknownOnCATCmp);
...

where:

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.

Using the new Operator

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]

Querying a Component for Interfaces

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]

Using QueryInterface

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 and CATBaseUnknown

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.

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:

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]

Comparing Two Interface Pointers

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]

Managing Component Lifecycle

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]

Reference Counting

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]

When Using AddRef and Release

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:

[Top]

Using Smart Interface Pointers

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:

[Top]

Usage Recommendations

Here are described the main scenarios that can happen, and how to make reference counting safe applications.

[Top]

Usage Recommendations for Calling QueryInterface

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.

[Top]

Recommendations for Using AddRef and Release in Client Applications

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 Use Interface Pointers Only

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:

When you don't need to call AddRef:

When you must calll Release:

[Top]

You Use Smart Interface Pointers Only

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]

You Make Interface Pointers and Smart Interface Pointers Coexist

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.

[Top]


In Short

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]


References

[1] About Globally Unique IDentifiers
[2] What Is HRESULT?
[Top]

History

Version: 1 [Jan 2000] Document created
[Top]

Copyright © 2000, Dassault Systèmes. All rights reserved.