Implementing IUnknown in DirectShow

DirectShow is based on the Component Object Model (COM). If you write your own filter, you must implement it as a COM object. The DirectShow base classes provide a framework from which to do this. Using the base classes is not required, but it can simplify the development process. This topic describes some of the internal details of COM objects and their implementation in the DirectShow base classes.

This topic assumes that you know how to program COM client applications — in other words, that you understand the methods in IUnknown — but does not assume any prior experience developing COM objects. DirectShow handles many of the details of developing a COM object. If you have experience developing COM objects, you should read Using CUnknown, which describes the CUnknown base class.

COM is a specification, not an implementation. It defines the rules that a component must follow; putting those rules into effect is left to the developer. In DirectShow, all objects derive from a set of C++ base classes. The base class constructors and methods do most of the COM "bookkeeping" work, such as keeping a consistent reference count. By deriving your filter from a base class, you inherit the functionality of the class. To use base classes effectively, you need a general understanding of how they implement the COM specification.

This topic contains the following topics.

  • How IUnknown Works
  • Using CUnknown
  • Summary

How IUnknown Works

The methods in IUnknown enable an application to query for interfaces on the component and manage the component's reference count. This section contains the following topics.

  • Reference Count
  • Interface Queries
  • Aggregation and Delegation

Reference Count

The reference count is an internal variable, incremented in the IUnknown::AddRef method and decremented in the IUnknown::Release method. The base classes manage the reference count and synchronize access to the reference count among multiple threads.

Interface Queries

Querying for an interface is also straightforward. The caller passes two parameters: an interface identifier (IID), and the address of a pointer. If the component supports the requested interface, it sets the pointer to the interface, increments its own reference count, and returns S_OK. Otherwise, it sets the pointer to NULL and returns E_NOINTERFACE. The following example shows the general outline of the IUnknown::QueryInterface method. Component aggregation, described in the next section, introduces some additional complexity.

if (IID == IID_IUnknown)
    set pointer to (IUnknown *)this
    AddRef
    return S_OK

else if (IID == IID_ISomeInterface)
    set pointer to (ISomeInterface *)this
    AddRef
    return S_OK

else if ... 

else
    set pointer to NULL
    return E_NOINTERFACE

The only difference between the QueryInterface method of one component and the QueryInterface method of another is the list of IIDs that each component tests. For every interface that the component supports, the component must test for the IID of that interface.

Aggregation and Delegation

Component aggregation must be transparent to the caller. Therefore, the aggregate must expose a single IUnknown interface, with the aggregated component deferring to the outer component's implementation. Otherwise, the caller would see two different IUnknown interfaces in the same aggregate. If the component is not aggregated, it uses its own implementation.

To support this behavior, the component must add a level of indirection. A delegating IUnknown delegates the work to the appropriate place: to the outer component, if there is one, or to the component's internal version. A nondelegating IUnknown does the work, as described in the previous section.

The delegating version is public and keeps the name IUnknown. The nondelegating version is renamed INonDelegatingUnknown. This name is not part of the COM specification, because it is not a public interface.

When the client creates an instance of the component, it calls the IClassFactory::CreateInstance method. One parameter is a pointer to the aggregating component's IUnknown interface, or is NULL if the new instance is not aggregated. In its constructor method, the component uses this parameter to store a member variable that indicates which IUnknown interface to use, as shown in the following example.

CMyComponent::CMyComponent(IUnknown *pOuterUnkown)
{
    if (pOuterUnknown == NULL)
        m_pUnknown = (IUnknown *)(INonDelegatingUnknown *)this;
    else
        m_pUnknown = pOuterUnknown;

    [ ... more constructor code ... ]
}

Each method in the delegating IUnknown calls its nondelegating counterpart, as shown in the following example.

HRESULT QueryInterface(REFIID iid, void **ppv) 
{
    return m_pUnknown->QueryInterface(iid, ppv);
}

By the nature of delegation, the delegating methods look identical in every component. Only the nondelegating versions change.

Using CUnknown

DirectShow implements IUnknown in a base class called CUnknown. You can use CUnknown to derive other classes, overriding only the methods that change across components. Most of the other base classes in DirectShow derive from CUnknown, so your component can inherit directly from CUnknown or from another base class.

This section contains the following topics.

INonDelegatingUnknown

IUnknown

Class Constructor

INonDelegatingUnknown

CUnknown implements INonDelegatingUnknown. It manages reference counts internally, and in most situations your derived class can inherit the two reference-counting methods with no change. Be aware that CUnknown deletes itself when the reference count drops to zero. On the other hand, you must override NonDelegatingQueryInterface, because the method in the base class returns E_NOINTERFACE if it receives any IID other than IID_IUnknown. In your derived class, test for the IIDs of interfaces that you support, as shown in the following example.

STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_ISomeInterface)
    {
        return GetInterface((ISomeInterface*)this, ppv);
    }
    // default
    return CUnknown::NonDelegatingQueryInterface(riid, ppv);
}

The utility function GetInterface sets the pointer, increments the reference count in a thread-safe way, and returns S_OK. In the default case, call the base class method and return the result. If you derive from another base class, call its NonDelegatingQueryInterface method instead. This enables you to support all the interfaces that the parent class supports.

IUnknown

As mentioned earlier, the delegating version of IUnknown is the same for every component, because it does nothing more than invoke the correct instance of the nondelegating version. For convenience, the header file Combase.h contains a macro, DECLARE_IUNKNOWN, which declares the three delegating methods as inline methods. It expands to the following code.

STDMETHODIMP QueryInterface(REFIID riid, void **ppv) {      
    return GetOwner()->QueryInterface(riid,ppv);            
};                                                          
STDMETHODIMP_(ULONG) AddRef() {                             
    return GetOwner()->AddRef();                            
};                                                          
STDMETHODIMP_(ULONG) Release() {                            
    return GetOwner()->Release();                           
};

The utility function GetOwner retrieves a pointer to the IUnknown interface of the component that owns this component. For an aggregated component, the owner is the outer component. Otherwise, the component owns itself. Include the DECLARE_IUNKNOWN macro in the public section of your class definition.

Class Constructor

Your class constructor should invoke the constructor method for the parent class, in addition to anything it does that is specific to your class. The following example is a typical constructor method.

CMyComponent(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr) 
    : CUnknown(tszName, pUnk, phr)
{ 
    /* Other initializations */ 
};

The method takes the following parameters, which it passes directly to the CUnknown constructor method.

  • tszName specifies a name for the component.
  • pUnk is a pointer to the aggregating IUnknown.
  • pHr is a pointer to an HRESULT value, indicating the success or failure of the method.

Summary

The following example shows a derived class that supports IUnknown and a hypothetical interface named ISomeInterface.

class CMyComponent : public CUnknown, public ISomeInterface
{
public:

    DECLARE_IUNKNOWN;

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
    {
        if( riid == IID_ISomeInterface )
        {
            return GetInterface((ISomeInterface*)this, ppv);
        }
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }

    CMyComponent(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr) 
        : CUnknown(tszName, pUnk, phr)
    { 
        /* Other initializations */ 
    };

    // More declarations will be added later.
};

This example illustrates the following points.

  • The CUnknown class implements the IUnknown interface. The new component inherits from CUnknown and from any interfaces that the component supports. The component could derive instead from another base class that inherits from CUnknown.
  • The DECLARE_IUNKNOWN macro declares the delegating IUnknown methods as inline methods.
  • The CUnknown class provides the implementation for INonDelegatingUnknown.
  • To support an interface other than IUnknown, the derived class must override the INonDelegatingQueryInterface method and test for the IID of the new interface.
  • The class constructor invokes the constructor method for CUnknown.

The next step in writing a filter is to enable an application to create new instances of the component. This requires an understanding of DLLs and their relation to class factories and class constructor methods. For more information, see Creating a DLL in DirectShow.

Last updated on Wednesday, April 13, 2005

© 2005 Microsoft Corporation. All rights reserved.