Expose multiple interfaces

If an object must expose multiple behaviors, the object must be able to return multiple interfaces. To enable an object to return multiple interfaces, you can derive the object from the various interfaces by using multiple inheritance, or derive the object from a separate implementation hierarchy and derive nested classes from the appropriate interfaces.

The nested class solution, which is used by the , has the following benefits:
  • It allows the COM-compliant exposure of multiple interfaces.
  • It allows delegation, the ability of a container class to expose an interface belonging to a class it contains. For more details, see Object Containment and Delegation.
  • It creates multiple implementations of reference counting code for each interface, making it easier to track the reference counts for each interface. By tracking references to individual interfaces, your application can optimize object storage by allocating or deallocating part of an object based on whether a specific interface has an outstanding reference count. For example, if an object exposes ITLargeObject and it uses ITLargeObjectManager to implement its functions, it can call ITLargeObjectManager::Close() when the ITLargeObject interface reference count drops to 0 so that the number of open smart large objects is minimized.

For a demonstration of the nested-class model, see the ifval.cpp example. The ifval.cpp example is driven by the contain.cpp example application.

The following code excerpts from ifval.cpp illustrate the implementation of an array of integers value object that exposes both ITDatum and ITContainCvt interfaces:
  1. Define the private data structures.
    typedef mi_integer bitarray_t;

    This structure is not exposed to the application.

  2. Define the object class. Instead of using inheritance on the parent object, use nested classes to define the individual interfaces.
    class Bitarray
    {
    public:
        // ITDatum-derived nested class. This just passes work through
        // the parent pointer into the parent object
        class XITDatum : public ITDatum
        {
        public:
            // ...
        } datum_interface;
    
        // ITContainCvt-derived nested class
        // This just passes work through the parent
        // pointer into the parent object
        class XITContainCvt : public ITContainCvt
        {
        public:
            // ...
        } containcvt_interface;
        // ...
    };
  3. Build the object.
    // Implementation
    Bitarray::Bitarray(ITMVDesc *mv) 
        : refcount(1), 
          typeinfo(*mv->vf_origtypeinfo),
          conn(*mv->vf_connection),
          isupdated(FALSE)
    {
      // NULL?
      isnull = mv->vf_libmivaluetype == MI_NULL_VALUE;
    
      // set up interfaces
      datum_interface.parent = this;
      containcvt_interface.parent = this;
    
      if(!isnull)
        value = *(bitarray_t *)mv->vf_data;
    }
  4. Define the class factory mapping and entry point.
    ITFactoryList BitarrayFactory("bitarray",
                                         Bitarray::MakeValue);
    
    // Create the Bitarray object, and return pointer to 
    // it's ITValue implementation
    ITValue *
    Bitarray::MakeValue(ITMVDesc *mv)
    {
        Bitarray *impl = new Bitarray(mv);
        return (ITValue *)&impl->datum_interface;
    }
  5. Define the base class methods for objects and return the address of the nested interfaces when requested by the application.
    ITOpErrorCode
    Bitarray::QueryInterface(const ITInterfaceID &iid,
                                    void **ifptr)
    {
        int result = IT_QUERYINTERFACE_SUCCESS;
    
        // Return different interfaces as appropriate by referencing
        // nested class members.
        switch (ITIIDtoSID(iid))
        {
            case ITEssentialSID:
            case ITValueSID:
            case ITDatumSID:
                *ifptr = (void *) &datum_interface;
                break;
    
            case ITContainCvtSID:
                *ifptr = (void *) &containcvt_interface;
                break;
    
            default:
                result = IT_QUERYINTERFACE_FAILED;
                *ifptr = NULL;
                break;
        }
        if (result == IT_QUERYINTERFACE_SUCCESS)
            AddRef();
    
        return result;
    }

    This object does not support delegation, so there is only one real QueryInterface implementation on the object.

  6. Define the reference counting code.
    unsigned long
    Bitarray::AddRef()
    {
        return ++refcount;
    }
    
    unsigned long
    Bitarray::Release()
    {
        if (--refcount <= 0)
        {
            delete this;
            return 0;
        }
        else
        {
            return refcount;
        }
    }
  7. Implement the ITDatum interface methods.
    const ITString &
    Bitarray::Printable()
    { 
        if(IsNull())
            return printable_value = "null";
        char buf[32];
        ostrstream cstream(buf, sizeof buf);
        cstream << value << ends;
        return printable_value = cstream.str();
    }
  8. Implement the ITContainCvt interface.
    ITBool
    Bitarray::ConvertTo(long item, int &dbvalue)
    {
        if (IsNull() || item >= NumItems())
            return false;
        dbvalue = !!(value & (1 << (NBITS - 1 - item)));
        return true;
    }

    This interface converts the member value from the object into a host variable.

  9. Declare pass-through methods for the nested interfaces. The methods call the corresponding method in the parent class.
    ITOpErrorCode
    Bitarray::XITDatum::QueryInterface(const ITInterfaceID &ifiid,
                                              void **resultif)
    {
        return parent->QueryInterface(ifiid, resultif);
    }
    
    unsigned long
    Bitarray::XITDatum::AddRef()
    {
        return parent->AddRef();
    }
    
    unsigned long
    Bitarray::XITDatum::Release()
    {
        return parent->Release();
    }
    
    const ITString &
    Bitarray::XITDatum::Printable()
    {
        return parent->Printable();
    }
    
    const ITTypeInfo &
    Bitarray::XITDatum::TypeOf()
    {
        return parent->TypeOf();
    }
    
    ITBool
    Bitarray::XITDatum::IsNull()
    {
        return parent->IsNull();
    }
    
    ITBool
    Bitarray::XITDatum::SameType(ITValue *v)
    {
        return parent->SameType(v);
    }
    
    ITBool
    Bitarray::XITDatum::CompatibleType(ITValue *v)
    {
        return parent->CompatibleType(v);
    }
    
    ITBool
    Bitarray::XITDatum::Equal(ITValue *v)
    {
        return parent->Equal(v);
    }
    
    ITBool
    Bitarray::XITDatum::LessThan(ITValue *v)
    {
        return parent->LessThan(v);
    }
    
    ITBool
    Bitarray::XITDatum::IsUpdated()
    {
        return parent->IsUpdated();
    }
    
    ITBool
    Bitarray::XITDatum::FromPrintable(const ITString &v)
    {
        return parent->FromPrintable(v);
    }
    
    ITBool
    Bitarray::XITDatum::SetNull()
    {
        return parent->SetNull();
    }
    
    ITOpErrorCode
    Bitarray::XITContainCvt::QueryInterface(const ITInterfaceID &ifiid,
                                                   void **resultif)
    {
        return parent->QueryInterface(ifiid, resultif);
    }
    
    unsigned long
    Bitarray::XITContainCvt::AddRef()
    {
        return parent->AddRef();
    }
    
    unsigned long
    Bitarray::XITContainCvt::Release()
    {
        return parent->Release();
    }
    
    ITBool
    Bitarray::XITContainCvt::ConvertTo(long item, int &value)
    {
        return parent->ConvertTo(item, value);
    }
    
    long
    Bitarray::XITContainCvt::NumItems()
    {
        return parent->NumItems();
    }
    
    ITBool
    Bitarray::XITContainCvt::ConvertFrom(long item, int value)
    {
        return parent->ConvertFrom(item, value);
    }