DirectShow and COM
This section describes how to support basic COM functionality in a DirectShow filter. It includes the following topics.
· How to Implement IUnknown
· How to Create a DLL
· How to Register DirectShow Filters
See Also
· Writing DirectShow Filters
Microsoft 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 article describes some of the internal details of COM objects and their implementation in the DirectShow base classes.
This article 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 the section 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 article contains the following topics.
· How IUnknown Works
· Using CUnknown
The methods in IUnknown enable an application to query for interfaces on the component and manage the component's reference count.
Reference Count
The reference count is an internal variable, incremented in the AddRef method and decremented in the 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 pseudocode shows the general outline of the 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
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 NULL if the new instance is not aggregated. The component uses this parameter to store a member variable indicating which IUnknown interface to use, as shown in the following example:
CMyComponent::CMyComponent(IUnknown *pOuterUnkown)
{
if (pOuterUnknown == NULL)
m_pUnknown = (IUnknown *)(INonDelegatingUnknown *)this;
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.
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.
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 CUnknown::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: Call parent class method.
// The CUnknown class must be in the inheritance chain.
return CParentClass::NonDelegatingQueryInterface(riid, ppv);
The utility function GetInterface (see COM Helper Functions) 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 CUnknown::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;
if( riid == IID_ISomeInterface )
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
// 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 NonDelegatingQueryInterface 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 How to Create a DLL.
This article describes how to implement a component as a dynamic-link library (DLL) in Microsoft DirectShow. This article is a continuation from How to Implement IUnknown, which describes how to implement the IUnknown interface by deriving your component from the CUnknown base class.
This article contains the following sections.
· Class Factories and Factory Templates
· Factory Template Array
· DLL Functions
Registering a DirectShow filter (as opposed to a generic COM object) requires some additional steps that are not covered in this article. For information on registering filters, see How to Register DirectShow Filters.
Before a client creates an instance of a COM object, it creates an instance of the object's class factory, using a call to the CoGetClassObject function. The client then calls the class factory's IClassFactory::CreateInstance method. It is the class factory that actually creates the component and returns a pointer to the requested interface. (The CoCreateInstance function combines these steps, inside the function call.)
The following illustration shows the sequence of method calls.
CoGetClassObject calls the DllGetClassObject function, which is defined in the DLL. This function creates the class factory and returns a pointer to an interface on the class factory. DirectShow implements DllGetClassObject for you, but the function relies on your code in a specific way. To understand how it works, you must understand how DirectShow implements class factories.
A class factory is a COM object dedicated to creating another COM object. Each class factory has one type of object that it creates. In DirectShow, every class factory is an instance of the same C++ class, CClassFactory. Class factories are specialized by means of another class, CFactoryTemplate, also called the factory template. Each class factory holds a pointer to a factory template. The factory template contains information about a specific component, such as the component's class identifier (CLSID), and a pointer to a function that creates the component.
The DLL declares a global array of factory templates, one for each component in the DLL. When DllGetClassObject makes a new class factory, it searches the array for a template with a matching CLSID. Assuming it finds one, it creates a class factory that holds a pointer to the matching template. When the client calls ...
michalr1n