Important: |
---|
This is retired content. This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |
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 Using CUnknown, which describes the CUnknownbase 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.
The methods in IUnknownenable an application to query for interfaces on the component and manage the component's reference count. This section contains the following topics.
The reference count is an internal variable, incremented in the IUnknown::AddRefmethod and decremented in the IUnknown::Releasemethod. The base classes manage the reference count and synchronize access to the reference count among multiple threads.
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 IUnknown::QueryInterfacemethod. 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 QueryInterfacemethod of one component and the QueryInterfacemethod 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.
Component aggregation must be transparent to the caller. Therefore, the aggregate must expose a single IUnknowninterface, with the aggregated component deferring to the outer component's implementation. Otherwise, the caller would see two different IUnknowninterfaces 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 IUnknowndelegates the work to the appropriate place: to the outer component, if there is one, or to the component's internal version. A nondelegating IUnknowndoes 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::CreateInstancemethod. One parameter is a pointer to the aggregating component's IUnknowninterface, 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 IUnknowninterface 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 IUnknowncalls 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 IUnknownin a base class called CUnknown. You can use CUnknownto 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 CUnknownor from another base class.
This section contains the following topics.
CUnknownimplements 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 CUnknowndeletes 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 GetInterfacesets 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 NonDelegatingQueryInterfacemethod instead. This enables you to support all the interfaces that the parent class supports.
As mentioned earlier, the delegating version of IUnknownis 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 GetOwnerretrieves a pointer to the IUnknowninterface 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.
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 CUnknownconstructor method.
The following example shows a derived class that supports IUnknownand 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 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.
Last updated on Tuesday, May 18, 2004