4.1 Instituting a root interface

The new root interface, Fundamental, features three operations that facilitate interface navigation and reference counting. As all interfaces must directly or indirectly descend from this interface, all objects gain these new abilities. The revised interface hierarchy is shown in Figure 4.1. (The Destroy() operation has been removed from the Node interface in favor of the reference counting operations inherited from the root interface, and a new operation, PrintDebugInformation(), has been added to aid debugging.)

Listing 4.1 presents the C header file for the new base interface, along with documentation for the new mandatory operations. Figure 4.2 visually depicts the revised memory layout and its requirement that the first three members of all dispatch tables correspond to the three operations of the Fundamental interface.

Figure "advanced-interfaces"
Figure 4.1: Revised diagram of an interface hierarchy for arithmetic nodes
Figure "revised-custom-interface-pointer"
Figure 4.2: Revised memory layout of interfaces of the custom object model

Listing 4.1: Fundamental.h
/**
 * @file
 *
 * This file contains the interface <code>Fundamental</code>, which
 * all interfaces ultimately descend from. It contains operations that
 * facilitate interface navigation and reference counting.
 */

#ifndef INCLUSION_GUARD_FUNDAMENTAL
#define INCLUSION_GUARD_FUNDAMENTAL

#include <stdbool.h>
#include <wchar.h>

/**
 * The name of this interface.
 */
#define FUNDAMENTAL_NAME (L"se.polberger.components.Fundamental")

/**
 * Returns a reference to the underlying instance through a different
 * interface. <code>::AddReference()</code> is called automatically on
 * the new reference. If the class does not support the given
 * interface, this operation fails.
 *
 * @param[in] pFundamental
 *   the instance implementing this interface. Must not be
 *   <code>NULL</code>.
 * @param[in] pInterfaceName
 *   the name of the interface. If this interface is not implemented
 *   by the underlying instance, this operation fails. Must not be
 *   <code>NULL</code>.
 * @param[out] ppResult
 *   a pointer to the variable which shall hold the result returned by
 *   this operation, that is, an interface reference. This parameter
 *   may be <code>NULL</code>, in which case this operation may be
 *   used to query an object as to whether it supports a given
 *   interface, with no concern for a result interface.
 * @return
 *   <code>true</code> if the operation completes successfully,
 *   <code>false</code> otherwise.
 */
#define Fundamental_SwitchInterface(pFundamental,                       \
                                    pInterfaceName,                     \
                                    ppResult)                           \
  (pFundamental)->pDispatchTable->SwitchInterface((pFundamental),       \
                                                  (pInterfaceName),     \
                                                  (ppResult));

/**
 * Notifies the instance implementing this interface that a new
 * reference has been added. This call always succeeds (thus there is
 * no return value).
 *
 * @param[in] pFundamental
 *   the instance implementing this interface. May be
 *   <code>NULL</code>.
 */
#define Fundamental_AddReference(pFundamental)                          \
  if ((pFundamental) != NULL)                                           \
  {                                                                     \
    (pFundamental)->pDispatchTable->AddReference(pFundamental);         \
  }

/**
 * Notifies the instance implementing this interface that a previously
 * added reference has been removed. If this instance finds that there
 * are no live references to it, it automatically destroys
 * itself. This call always succeeds (thus there is no return value).
 *
 * @param[in] pFundamental
 *   the instance implementing this interface. May be
 *   <code>NULL</code>.
 */
#define Fundamental_RemoveReference(pFundamental)                       \
  if ((pFundamental) != NULL)                                           \
  {                                                                     \
    (pFundamental)->pDispatchTable->RemoveReference(pFundamental);      \
  }

struct Fundamental_DispatchTable_s;

/**
 * Variables of this type may be used to represent objects
 * implementing this interface.
 */
typedef struct
{
  const struct Fundamental_DispatchTable_s* pDispatchTable;
} Fundamental_t;

/**
 * This is the dispatch table, or vtable, for this interface. It
 * represents an indirection that enables the implementation of an
 * interface to be bound to at runtime. Interfaces wishing to extend
 * this interface must include the complete contents of this structure
 * as the first members of their dispatch tables, changing the
 * <code>Fundamental_t</code> type to match their own. (Only single
 * interface inheritance is supported.)
 */
typedef struct Fundamental_DispatchTable_s
{
  bool (*SwitchInterface)(Fundamental_t* pFundamental,
                          wchar_t* pInterfaceName,
                          Fundamental_t** ppResult);
  void (*AddReference)(Fundamental_t* pFundamental);
  void (*RemoveReference)(Fundamental_t* pFundamental);
} Fundamental_DispatchTable_t;

#endif // INCLUSION_GUARD_FUNDAMENTAL

Fundamental::SwitchInterface() enables clients that hold a reference to an object through a certain interface to switch to another interface that the object implements (or, alternatively, to simply query an object as to whether it supports a given interface). To facilitate this, it must be possible to refer to an interface at runtime. In other words, every interface must have a runtime-accessible name. Elaborate means have been devised for component models in industry to give interfaces and other such entities runtime names. For simplicity, this example uses a simple text string, which is understood to have been crafted in such a way that the name has a high probability of being unique. All interfaces used here use the simple, but very effective, method of using Internet top-level domain names as part of the name, thus delegating the responsibility for ensuring the uniqueness of the names to third-party naming authorities (domain name registrars). This is also the approach taken by the Java programming language and platform. The name of the Fundamental interface is given as the contents of the FUNDAMENTAL_NAME macro.

Fundamental::AddReference() and Fundamental::RemoveReference(), which realize the reference counting scheme used by this object model, are called when a reference is added to an object, and when a reference is removed, respectively. A newly-created object assumes that at least one client holds a reference to it.

Using the reference counting operations properly can be error-prone. An object may be passed as an input argument to another operation without adding a reference, but if one is returned to a caller using an output argument, one must be added. Also, if an object reference is saved to a part of the system memory that is outside the call stack (a global variable or the heap), a reference also needs to be added. If these rules are not obeyed, objects may be unexpectedly destroyed, or may continue to consume memory despite no longer being needed.

A class implementing multiple interfaces may want to either provide one constructor per supported interface, or one universal constructor that accepts the name of the desired interface as an argument. If the latter path is taken, the constructor should return a Fundamental instance that the client is expected to typecast to the proper type. DefaultBinaryOperatorNode uses this approach; the new function prototype and its documentation can be seen in Listing 4.2.

Listing 4.2: Excerpt from a revised version of DefaultBinaryOperatorNode.h
/**
 * Creates an instance of the <code>DefaultBinaryOperatorNode</code>
 * class.
 *
 * @param[in] operator
 *   the binary operator of this node.
 * @param[in] pLeftOperand
 *   the left operand of this node. Must not be <code>NULL</code>.
 * @param[in] pRightOperand
 *   the right operand of this node. Must not be <code>NULL</code>.
 * @param[in] pInterfaceName
 *   the name of the interface that the reference should be returned
 *   through. Must not be <code>NULL</code>.
 * @param[out] ppResult
 *   a pointer to the variable which shall hold the instance of this
 *   class. Must not be <code>NULL</code>.
 * @return
 *   <code>true</code> if the operation completes successfully,
 *   <code>false</code> otherwise.
 */
bool DefaultBinaryOperatorNode_Create(BinaryOperator_t operator,
                                      Node_t* pLeftOperand,
                                      Node_t* pRightOperand,
                                      wchar_t* pInterfaceName,
                                      Fundamental_t** ppResult);

Apart from requiring all interfaces to directly or indirectly descend from Fundamental, the nature of the binary standard has not changed since Chapter 3 to support classes implementing multiple unrelated interfaces. This can be seen in Listing 4.1: The Fundamental_t and Fundamental_DispatchTable_t types follow the conventions set forth in Chapter 3. Thus, the onus is fully on the implementation of the classes themselves to realize this support.

An excerpt from the revised implementation of DefaultBinaryOperatorNode is shown in Listing 4.3. To illustrate the new support for implementing multiple interfaces, this class now also implements the Scriptable interface, which descends directly from Fundamental. (Scriptable makes it possible to call an object without having compile-time knowledge of the interfaces it implements, and is described more fully in section 4.2.)

Listing 4.3: Excerpt 1 from a revised version of DefaultBinaryOperatorNode.c
struct DefaultBinaryOperatorNode_InstanceData_s;

/**
 * This structure represents an interface node, which enables access
 * to instances of the class contained in this file.
 */
typedef struct
{
  /**
   * The dispatch table, or vtable. The type is not specified, as this
   * structure is not used to access the dispatch table (whose type
   * may vary), but to access the instance data.
   */
  const void* pDispatchTable;

  /**
   * The instance data.
   */
  struct DefaultBinaryOperatorNode_InstanceData_s* pInstanceData;
} DefaultBinaryOperatorNode_InterfaceNode_t;

/**
 * This structure represents the instance data of
 * <code>DefaultBinaryOperatorNode</code> objects.
 */
typedef struct DefaultBinaryOperatorNode_InstanceData_s
{
  /**
   * The interface node that makes it possible to access this instance
   * through the <code>BinaryOperatorNode</code> interface (and all
   * interfaces that this interface descends from).
   */
  DefaultBinaryOperatorNode_InterfaceNode_t
    interfaceNodeBinaryOperatorNode;

  /**
   * The interface node that makes it possible to access this instance
   * through the <code>Scriptable</code> interface (and all interfaces
   * that this interface descends from).
   */
  DefaultBinaryOperatorNode_InterfaceNode_t interfaceNodeScriptable;

  /**
   * The reference count of this instance. When this drops to zero,
   * the instance is automatically destroyed.
   */
  unsigned int referenceCount;

  /**
   * The binary operator used by this object.
   */
  BinaryOperator_t operator;

  /**
   * The node representing the left operand. This member is never
   * <code>NULL</code>.
   */
  Node_t* pLeftOperand;

  /**
   * The node representing the right operand. This member is never
   * <code>NULL</code>.
   */
  Node_t* pRightOperand;
} DefaultBinaryOperatorNode_InstanceData_t;

static const BinaryOperatorNode_DispatchTable_t
  gBinaryOperatorNodeDispatchTable;
static const Scriptable_DispatchTable_t gScriptableDispatchTable;

bool DefaultBinaryOperatorNode_Create(BinaryOperator_t operator,
                                      Node_t* pLeftOperand,
                                      Node_t* pRightOperand,
                                      wchar_t* pInterfaceName,
                                      Fundamental_t** ppResult)
{
  DefaultBinaryOperatorNode_InstanceData_t* pInstanceData = NULL;

  bool result =
    (pLeftOperand != NULL) &&
    (pRightOperand != NULL) &&
    (pInterfaceName != NULL) &&
    (ppResult != NULL);

  if (result)
  {
    pInstanceData =
      malloc(sizeof(DefaultBinaryOperatorNode_InstanceData_t));
    result = pInstanceData != NULL;
  }

  if (result)
  {
    pInstanceData->interfaceNodeBinaryOperatorNode.pDispatchTable =
      &gBinaryOperatorNodeDispatchTable;
    pInstanceData->interfaceNodeBinaryOperatorNode.pInstanceData =
      pInstanceData;

    pInstanceData->interfaceNodeScriptable.pDispatchTable =
      &gScriptableDispatchTable;
    pInstanceData->interfaceNodeScriptable.pInstanceData =
      pInstanceData;

    pInstanceData->referenceCount = 1;
    pInstanceData->operator = operator;

    /* We are about to make copies of the operand nodes passed to this
     * constructor that we will keep as part of our instance data. As
     * a result, we need to add references to them.
     */
    Node_AddReference(pLeftOperand);
    pInstanceData->pLeftOperand = pLeftOperand;
    Node_AddReference(pRightOperand);
    pInstanceData->pRightOperand = pRightOperand;

    /* Switch to the desired interface. This operation automatically
     * adds a reference to the reference it returns.
     */
    result = Fundamental_SwitchInterface(
      (Fundamental_t*)&(pInstanceData->interfaceNodeBinaryOperatorNode),
      pInterfaceName,
      ppResult);
  }
  else if (ppResult != NULL)
  {
    *ppResult = NULL;
  }

  /* Regardless of whether we have failed or succeeded in switching to
   * the desired interface, we need to remove the reference this
   * constructor has added to the instance (implicitly, by setting the
   * reference count to 1).
   */
  if (pInstanceData != NULL)
  {
    Fundamental_RemoveReference(
      (Fundamental_t*)&(pInstanceData->interfaceNodeBinaryOperatorNode));
  }

  return result;
}

static bool DefaultBinaryOperatorNode_SwitchInterface(
  DefaultBinaryOperatorNode_InterfaceNode_t* pInterfaceNode,
  wchar_t* pInterfaceName,
  Fundamental_t** ppResult)
{
  bool result = (pInterfaceNode != NULL) && (pInterfaceName != NULL);
  DefaultBinaryOperatorNode_InstanceData_t* pThis =
    pInterfaceNode->pInstanceData;

  if (result)
  {
    if ((wcscmp(pInterfaceName, FUNDAMENTAL_NAME) == 0) ||
        (wcscmp(pInterfaceName, NODE_NAME) == 0) ||
        (wcscmp(pInterfaceName, BINARY_OPERATOR_NODE_NAME) == 0))
    {
      if (ppResult != NULL)
      {
        *ppResult =
          (Fundamental_t*)&(pThis->interfaceNodeBinaryOperatorNode);
      }
    }
    else if (wcscmp(pInterfaceName, SCRIPTABLE_NAME) == 0)
    {
      if (ppResult != NULL)
      {
        *ppResult = (Fundamental_t*)&(pThis->interfaceNodeScriptable);
      }
    }
    else
    {
      result = false;
    }
  }

  if (ppResult != NULL)
  {
    if (result)
    {
      Fundamental_AddReference(*ppResult);
    }
    else
    {
      *ppResult = NULL;
    }
  }

  return result;
}

static void DefaultBinaryOperatorNode_AddReference(
  DefaultBinaryOperatorNode_InterfaceNode_t* pInterfaceNode)
{
  if (pInterfaceNode != NULL)
  {
    DefaultBinaryOperatorNode_InstanceData_t* pThis =
      pInterfaceNode->pInstanceData;
    pThis->referenceCount++;
  }
}

static void DefaultBinaryOperatorNode_RemoveReference(
  DefaultBinaryOperatorNode_InterfaceNode_t* pInterfaceNode)
{
  if (pInterfaceNode != NULL)
  {
    DefaultBinaryOperatorNode_InstanceData_t* pThis =
      pInterfaceNode->pInstanceData;

    pThis->referenceCount--;

    if (pThis->referenceCount == 0)
    {
      Node_RemoveReference(pThis->pLeftOperand);
      Node_RemoveReference(pThis->pRightOperand);
      free(pThis);
    }
  }
}


As noted earlier, the BinaryOperatorNode interface mandates a very specific memory layout of the memory referenced by pointers to objects implementing this interface (by way of BinaryOperatorNode_t, which appears in Listing 3.6. The version of DefaultBinaryOperatorNode in Chapter 3 (see Listing 3.8) puts a pointer to the dispatch table of the sole interface it implements first in its instance data, and as the memory layout of the instance data is consistent with that required by the binary standard, pointers that reference DefaultBinaryOperatorNode objects can simply point to the instance data.

For classes that implement multiple interfaces, multiple pointers to dispatch tables could be put first in the instance data, as seen in Figure 4.3 (as before, the shaded areas denote memory content that is not relevant to the binary standard, and which the implementation may use for any purpose). Client variables always point directly to the dispatch table that the client accesses the object through—in this figure, the client variable points directly to the Scriptable dispatch table. If the class only implements one interface, client pointers conveniently also point to the start of the instance data, allowing easy access to this data. Implementing multiple unrelated interfaces complicates matters, though.

As the interfaces implemented by a class are statically known, it is possible to deduce the address of the start of the instance data statically, and the layout presented in Figure 4.3 is thus fully adequate. Indeed, many compilers for object-oriented languages, such as C++, use a variation of this memory layout. A requirement is that a function is only accessed through one interface, as which interface a function is accessed through must be statically known in order to find the instance data.

Figure "alternative-revised-defaultbinaryoperatornode-instance"
Figure 4.3: Proposed instance data of DefaultBinaryOperatorNode objects
Figure "revised-defaultbinaryoperatornode-instance"
Figure 4.4: Revised instance data of DefaultBinaryOperatorNode objects

However, for classes written directly in C, finding the start of the instance data is more involved. It would be possible to use pointer arithmetic, but for the sake of simplicity and clear code, an alternative approach will be used (which has the added benefit of allowing a single function to be accessed through many interfaces). This example uses interface nodes, one per implemented interface, that are compatible with types such as BinaryOperatorNode_t. Their first member points to the proper dispatch table, and their second (and last) member points back to the instance data, enabling functions, that now effectively take interface nodes as their first arguments, to retrieve a pointer to the instance data (which can be seen in Listing 4.3). This approach is depicted in Figure 4.4. Storing this data in a runtime-accessible data structure makes it easy for implementations to find the start of the instance data, as the offset to the address of the instance data is statically known.

As DefaultBinaryOperatorNode now implements multiple interfaces, the functions implementing the operations of these interfaces are now interface-agnostic, meaning that the formal interface argument is now of the type DefaultBinaryOperatorNode_InterfaceNode_t. As a result, interfaces that share some of the same operations can share the same implementation (this is especially useful for the operations defined in Fundamental).1 As a result, the dispatch tables need to use typecasting, making them somewhat harder on the eyes. As two separate interfaces are implemented, two dispatch tables are required as well; they are shown in Listing 4.4.

Listing 4.4: Excerpt 2 from a revised version of DefaultBinaryOperatorNode.c
/**
 * This is the dispatch table for the <code>BinaryOperatorNode</code>
 * interface implemented by this class.
 */
static const BinaryOperatorNode_DispatchTable_t
  gBinaryOperatorNodeDispatchTable =
{
  (bool (*)(BinaryOperatorNode_t*, wchar_t*, Fundamental_t**))
    DefaultBinaryOperatorNode_SwitchInterface,
  (void (*)(BinaryOperatorNode_t*))
    DefaultBinaryOperatorNode_AddReference,
  (void (*)(BinaryOperatorNode_t*))
    DefaultBinaryOperatorNode_RemoveReference,
  (bool (*)(BinaryOperatorNode_t*, bool*))
    DefaultBinaryOperatorNode_IsConstant,
  (bool (*)(BinaryOperatorNode_t*, unsigned int, unsigned int))
    DefaultBinaryOperatorNode_PrintDebugInformation,
  (bool (*)(BinaryOperatorNode_t*, BinaryOperator_t*))
    DefaultBinaryOperatorNode_Operator,
  (bool (*)(BinaryOperatorNode_t*, Node_t**))
    DefaultBinaryOperatorNode_LeftOperand,
  (bool (*)(BinaryOperatorNode_t*, Node_t**))
    DefaultBinaryOperatorNode_RightOperand
};

/**
 * This is the dispatch table for the <code>Scriptable</code>
 * interface implemented by this class.
 */
static const Scriptable_DispatchTable_t gScriptableDispatchTable =
{
  (bool (*)(Scriptable_t*, wchar_t*, Fundamental_t**))
    DefaultBinaryOperatorNode_SwitchInterface,
  (void (*)(Scriptable_t*))
    DefaultBinaryOperatorNode_AddReference,
  (void (*)(Scriptable_t*))
    DefaultBinaryOperatorNode_RemoveReference,
  (bool (*)(Scriptable_t*,
            wchar_t*,
            ScriptableReturnValue_t*,
            ScriptableArgumentType_t,
            ...))
    DefaultBinaryOperatorNode_InvokeOperation
};

The new version of the constructor in Listing 4.3 is somewhat more involved, as it needs to initialize the interface nodes. (They are part of the instance data, and thus require no extra work to allocate and deallocate.) There is also some work involved in playing by the rules of reference counting; it makes copies of the left and right operand nodes (storing them as part of its instance data), and thus needs to manually add references to them. It uses the implementation of Fundamental::SwitchInterface() to return a proper reference through the desired interface, and must take care to remove the reference added to its own untyped reference before returning the reference returned by Fundamental::SwitchInterface().

The implementation of Fundamental::SwitchInterface() is simple and functional, as are the implementations of the reference counting operations Fundamental::AddReference() and Fundamental::RemoveReference(). The simple strategy used in the implementation of Fundamental::SwitchInterface() does not scale well with a large number of implemented interfaces, though. It would benefit from the use of an efficient data structure, such as a hash table.

Footnotes

  1. Microsoft’s COM considers interfaces, once published to the outside world, frozen and thus unchangeable. When new features need to be added, a new interface is created, which may only differ from the original by having one new operation. COM classes often implement several versions of an interface to maintain backwards compatibility. Having operation implementations that are interface-agnostic means that a class can very easily support multiple interfaces with overlapping operations by simply pointing to the same functions from the different dispatch tables.