Professional Documents
Culture Documents
Directshow and COM
Directshow and COM
This section describes how to support basic COM functionality in a DirectShow filter. It includes the
following topics.
See Also
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
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
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
Using CUnknown
How IUnknown Works
The methods in IUnknown enable an application to query for interfaces on the component and
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
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,
AddRef
return S_OK
AddRef
return S_OK
else if ...
else
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.
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
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
if (pOuterUnknown == NULL)
else
m_pUnknown = pOuterUnknown;
Each method in the delegating IUnknown calls its nondelegating counterpart, as shown in the
following example:
HRESULT QueryInterface(REFIID iid, void **ppv)
By the nature of delegation, the delegating methods look identical in every component. Only the
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
INonDelegatingUnknown
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)
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
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
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
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)
/* Other initializations */
};
The method takes the following parameters, which it passes directly to the CUnknown constructor
method.
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;
/* Other initializations */
};
};
from CUnknown and from any interfaces that the component supports. The component could
derive instead from another base class that inherits from CUnknown.
methods.
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 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
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.
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
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
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 IClassFactory::CreateInstance, the class factory calls the instantiation
The benefit of this architecture is that you can define just a few things that are specific to your
component, such as the instantiation function, without implementing the entire class factory.
Factory Template Array
// of the component
The two function pointers, m_lpfnNew and m_lpfnInit, use the following type definitions:
typedef CUnknown *(CALLBACK *LPFNNewCOMObject)(LPUNKNOWN pUnkOuter, HRESULT *phr);
The first is the instantiation function for the component. The second is an optional initialization
function. If you provide an initialization function, it is called from inside the DLL entry-point function.
Suppose you are creating a DLL that contains a component named CMyComponent, which inherits
from CUnknown. You must provide the following items in your DLL:
The initialization function, a public method that returns a new instance of CMyComponent.
A global array of factory templates, named g_Templates. This array contains the factory
A global variable named g_cTemplates that specifies the size of the array.
if (pNewObject == NULL) {
*pHr = E_OUTOFMEMORY;
return pNewObject;
CFactoryTemplate g_Templates[1] =
&CLSID_MyComponent, // CLSID
};
The CreateInstance method calls the class constructor and returns a pointer to the new class
instance. The parameter pUnk is a pointer to the aggregating IUnknown. You can simply pass this
parameter to the class constructor. The parameter pHr is a pointer to an HRESULT value. The class
constructor sets this to an appropriate value, but if the constructor fails, set the value to
E_OUTOFMEMORY.
The NAME macro generates a string in debug builds but resolves to NULL in retail builds. It is used in
this example to give the component a name that appears in debug logs but does not occupy memory
The CreateInstance method can have any name, because the class factory refers to the function
pointer in the factory template. However, g_Templates and g_cTemplates are global variables that
the class factory expects to find, so they must have exactly those names.
DLL Functions
A DLL must implement the following functions so that it can be registered, unregistered, and loaded
into memory.
DllMain: The DLL entry point. The name DllMain is a placeholder for the library-defined
function name. The DirectShow implementation uses the name DllEntryPoint. For more
Of these, the first three are implemented by DirectShow. If your factory template provides an
initialization function in the m_lpfnInit member variable, that function is called from inside the DLL
entry-point function. For more information on when the system calls the DLL entry-point function, see
function named AMovieDllRegisterServer2 that does the necessary work. Your component can
}
STDAPI DllUnregisterServer()
However, within DllRegisterServer and DllUnregisterServer you can customize the registration
process as needed. If your DLL contains a filter, you might need to do some additional work. For more
In your module-definition (.def) file, export all the DLL functions except for the entry-point function.
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
This section describes how to write a transform filter, defined as a filter that has exactly one input pin
and one output pin. To illustrate the steps, this section describes a hypothetical transform filter that
outputs run-length encoded (RLE) video. It does not describe the RLE-encoding algorithm itself, only
the tasks that are specific to DirectShow. (DirectShow already provides an RLE codec through the AVI
Compressor filter.)
This section assumes that you will use the DirectShow base class library to create filters. Although you
can write a filter without it, the base class library is strongly recommended.
Note Before writing a transform filter, consider whether a DirectX Media Object (DMO)
would fulfill your requirements. DMOs can do many of the same things as filters, and the
programming model for DMOs is simpler. DMOs are hosted in DirectShow through the DMO
Wrapper filter, but can also be used outside of DirectShow. DMOs are now the
See Also
Assuming that you decide to write a filter and not a DMO, the first step is choosing which base class to
CTransformFilter is designed for transform filters that use separate input and output
buffers. This kind of filter is sometimes called a copy-transform filter. When a copy-transform
filter receives an input sample, it writes new data to an output sample and delivers the output
trans-in-place filters. When a trans-in-place filter receives a sample, it changes the data inside
that sample and delivers the same sample downstream. The filter's input pin and output pin
CTransformFilter, but includes functionality for dropping frames if the downstream renderer
falls behind.
CBaseFilter is a generic filter class. The other classes in this list all derive from
CBaseFilter. If none of them is suitable, you can fall back on this class. However, this class also
the memory resides on a graphics card, read operations are significantly slower. Moreover,
even a copy transform can cause unintended read operations if you do not implement it
carefully. Therefore, you should always do performance testing if you write a video
transform.
For the example RLE encoder, the best choice is either CTransformFilter or
CVideoTransformFilter. In fact, the differences between them are largely internal, so it is easy to
convert from one to the other. Because the media types must be different on the two pins, the
CTransInPlaceFilter class is not appropriate for this filter. This example will use CTransformFilter.
};
Each of the filter classes has associated pin classes. Depending on the specific needs of your filter, you
might need to override the pin classes. In the case of CTransformFilter, the pins delegate most of
their work to the filter, so you probably don't need to override the pins.
You must generate a unique CLSID for the filter. You can use the Guidgen or Uuidgen utility; never
copy an existing GUID. There are several ways to declare a CLSID. The following example uses the
DEFINE_GUID macro:
[RleFilt.h]
// {1915C5C7-02AA-415f-890F-76D94C85AAF1}
DEFINE_GUID(CLSID_RLEFilter,
0x1915c5c7, 0x2aa, 0x415f, 0x89, 0xf, 0x76, 0xd9, 0x4c, 0x85, 0xaa, 0xf1);
[RleFilt.cpp]
#include <initguid.h>
#include "RleFilt.h"
Notice that one of the parameters to the CTransformFilter constructor is the CLSID defined earlier.
When two pins connect, they must agree on a media type for the connection. The media type
describes the format of the data. Without the media type, a filter might deliver one kind of data, only
The basic mechanism for negotiating media types is the IPin::ReceiveConnection method. The
output pin calls this method on the input pin with a proposed media type. The input pin accepts the
connection or rejects it. If it rejects the connection, the output pin can try another media type. If no
suitable types are found, the connection fails. Optionally, the input pin can advertise a list of types
that it prefers, through the IPin::EnumMediaTypes method. The output pin can use this list when it
The CTransformFilter class implements a general framework for this process, as follows:
The input pin has no preferred media types. It relies entirely on the upstream filter to
propose the media type. For video data, this makes sense, because the media type includes the
image size and the frame rate. Typically, that information must be supplied by an upstream
source filter or parser filter. In the case of audio data, the set of possible formats is smaller, so it
may be practical for the input pin to offer some preferred types. In that case, override
When the upstream filter proposes a media type, the input pin calls the
The output pin will not connect unless the input pin is connected first. This behavior is typical
for transform filters. In most cases, the filter must determine the input type before it can set the
output type.
When the output pin does connect, it has a list of media types that it proposes to the
The output pin will also try any media types that the downstream filter proposes.
To check whether a particular output type is compatible with the input type, the output pin
class must implement them. None of these methods belongs to a COM interface; they are simply part
media type to the transform filter. This method takes a pointer to a CMediaType object, which is a
thin wrapper for the AM_MEDIA_TYPE structure. In this method, you should examine every relevant
field of the AM_MEDIA_TYPE structure, including the fields in the format block. You can use the
accessor methods defined in CMediaType, or reference the structure members directly. If any field is
not valid, return VFW_E_TYPE_NOT_ACCEPTED. If the entire media type is valid, return S_OK.
For example, in the RLE encoder filter, the input type must be 8-bit or 4-bit uncompressed RGB video.
There is no reason to support other input formats, such as 16- or 24-bit RGB, because the filter would
have to convert them to a lower bit depth, and DirectShow already provides a Color Space Converter
filter for that purpose. The following example assumes that the encoder supports 8-bit video but not
4-bit video:
HRESULT CRleFilter::CheckInputType(const CMediaType *mtIn)
if ((mtIn->majortype != MEDIATYPE_Video) ||
(mtIn->subtype != MEDIASUBTYPE_RGB8) ||
(mtIn->formattype != FORMAT_VideoInfo) ||
return VFW_E_TYPE_NOT_ACCEPTED;
VIDEOINFOHEADER *pVih =
reinterpret_cast<VIDEOINFOHEADER*>(mtIn->pbFormat);
if ((pVih->bmiHeader.biBitCount != 8) ||
(pVih->bmiHeader.biCompression != BI_RGB))
return VFW_E_TYPE_NOT_ACCEPTED;
}
// Check the palette table.
return VFW_E_TYPE_NOT_ACCEPTED;
return VFW_E_TYPE_NOT_ACCEPTED;
// Everything is good.
return S_OK;
In this example, the method first checks the major type and subtype. Then it checks the format type,
to make sure the format block is a VIDEOINFOHEADER structure. The filter could also support
VIDEOINFOHEADER2, but in this case there would be no real benefit. The VIDEOINFOHEADER2
structure adds support for interlacing and non-square pixels, which are not likely to be relevant in 8-
bit video.
If the format type is correct, the example checks the biBitCount and biCompression members of
the VIDEOINFOHEADER structure, to verify that the format is 8-bit uncompressed RGB. As this
example shows, you must coerce the pbFormat pointer to the correct structure, based on the format
type. Always check the format type GUID (formattype) and the size of the format block (cbFormat)
The example also verifies that the number of palette entries is compatible with the bit depth and the
format block is large enough to hold the palette entries. If all of this information is correct, the
Note This step is not required for filters that derive from CTransInPlaceFilter.
The CTransformFilter::GetMediaType method returns one of the filter's preferred output types,
referenced by index number. This method is never called unless the filter's input pin is already
connected. Therefore, you can use the media type from the upstream connection to determine the
An encoder typically offers a single preferred type, representing the target format. Decoders generally
support a range of output formats, and offer them in order of descending quality or efficiency. For
example, the list might be UYVY, Y211, RGB-24, RGB-565, RGB-555, and RGB-8, in that order. Effect
filters may require an exact match between the output format and the input format.
The following example returns a single output type, which is constructed by modifying the input type
ASSERT(m_pInput->IsConnected());
if (iPosition < 0)
return E_INVALIDARG;
if (iPosition == 0)
HRESULT hr = m_pInput->ConnectionMediaType(pMediaType);
if (FAILED(hr))
return hr;
pMediaType->subtype = static_cast<GUID>(fccMap);
pMediaType->SetVariableSize();
pMediaType->SetTemporalCompression(FALSE);
ASSERT(pMediaType->formattype == FORMAT_VideoInfo);
VIDEOINFOHEADER *pVih =
reinterpret_cast<VIDEOINFOHEADER*>(pMediaType->pbFormat);
pVih->bmiHeader.biCompression = BI_RLE8;
pVih->bmiHeader.biSizeImage = DIBSIZE(pVih->bmiHeader);
return S_OK;
// else
return VFW_S_NO_MORE_ITEMS;
In this example, the method calls IPin::ConnectionMediaType to get the input type from the input
pin. Then it changes some of the fields to indicate the compression format, as follows:
It assigns a new subtype GUID, which is constructed from the FOURCC code 'MRLE', using
flag to FALSE and the lSampleSize member to zero, indicating variable-sized samples.
indicating that every frame is a key frame. (This field is informational only, so you could safely
ignore it.)
Note This step is not required for filters that derive from CTransInPlaceFilter.
with the current input type. The method is also called if the input pin reconnects after the output pin
connects.
The following example verifies whether the format is RLE8 video; the image dimensions match the
input format; and the palette entries are the same. It also rejects source and target rectangles that do
if (mtOut->majortype != MEDIATYPE_Video)
return VFW_E_TYPE_NOT_ACCEPTED;
if (mtOut->subtype != static_cast<GUID>(fccMap))
return VFW_E_TYPE_NOT_ACCEPTED;
if ((mtOut->formattype != FORMAT_VideoInfo) ||
return VFW_E_TYPE_NOT_ACCEPTED;
ASSERT(mtIn->formattype == FORMAT_VideoInfo);
if ((pBmiOut->biPlanes != 1) ||
(pBmiOut->biBitCount != 8) ||
(pBmiOut->biCompression != BI_RLE8) ||
(pBmiOut->biWidth != pBmiIn->biWidth) ||
(pBmiOut->biHeight != pBmiIn->biHeight))
{
return VFW_E_TYPE_NOT_ACCEPTED;
RECT rcImg;
return VFW_E_INVALIDMEDIATYPE;
return VFW_E_INVALIDMEDIATYPE;
if (pBmiOut->biClrUsed != pBmiIn->biClrUsed)
return VFW_E_TYPE_NOT_ACCEPTED;
return VFW_E_TYPE_NOT_ACCEPTED;
return VFW_E_TYPE_NOT_ACCEPTED;
// Everything is good.
return S_OK;
Pin Reconnections
Applications can disconnect and reconnect pins. Suppose an application connects both pins,
disconnects the input pin, and then reconnects the input pin using a new image size. In that case,
CheckTransform fails because the dimensions of the image no longer match. This behavior is
reasonable, although the filter could also try reconnecting the output pin with a new media type.
See Also
Reconnecting Pins
Note This step is not required for filters that derive from CTransInPlaceFilter.
After two pins agree on a media type, they select an allocator for the connection and negotiate
allocator properties, such as the buffer size and the number of buffers.
In the CTransformFilter class, there are two allocators, one for the upstream pin connection and one
for the downstream pin connection. The upstream filter selects the upstream allocator and also
chooses the allocator properties. The input pin accepts whatever the upstream filter decides. If you
The transform filter's output pin selects the downstream allocator. It performs the following steps:
1. If the downstream filter can provide an allocator, the output pin uses that one. Otherwise,
the output pin creates a new allocator.
2. The output pin gets the downstream filter's allocator requirements (if any) by calling
IMemInputPin::GetAllocatorRequirements.
3. The output pin calls the transform filter's CTransformFilter::DecideBufferSize method,
which is pure virtual. The parameters to this method are a pointer to the allocator and an
ALLOCATOR_PROPERTIES structure with the downstream filter's requirements. If the
downstream filter has no allocator requirements, the structure is zeroed out.
4. In the DecideBufferSize method, the derived class sets the allocator properties by calling
IMemAllocator::SetProperties.
Generally, the derived class will select allocator properties based on the output format, the
downstream filter's requirements, and the filter's own requirements. Try to select properties that are
compatible with the downstream filter's request. Otherwise, the downstream filter might reject the
connection.
In the following example, the RLE encoder sets minimum values for the buffer size, buffer alignment,
and buffer count. For the prefix, it uses whatever value the downstream filter requested. The prefix is
typically zero bytes, but some filters require it. For example, the AVI Mux filter uses the prefix to write
RIFF headers.
HRESULT CRleFilter::DecideBufferSize(
AM_MEDIA_TYPE mt;
HRESULT hr = m_pOutput->ConnectionMediaType(&mt);
if (FAILED(hr))
return hr;
}
ASSERT(mt.formattype == FORMAT_VideoInfo);
pProp->cbBuffer = DIBSIZE(*pbmi) * 2;
if (pProp->cbAlign == 0)
pProp->cbAlign = 1;
if (pProp->cBuffers == 0)
pProp->cBuffers = 1;
FreeMediaType(mt);
ALLOCATOR_PROPERTIES Actual;
hr = pAlloc->SetProperties(pProp, &Actual);
if (FAILED(hr))
return hr;
return E_FAIL;
return S_OK;
The allocator may not be able to match your request exactly. Therefore, the SetProperties method
returns the actual result in a separate ALLOCATOR_PROPERTIES structure (the Actual parameter,
in the previous example). Even when SetProperties succeeds, you should check the result to make
Custom Allocators
By default, all of the filter classes use the CMemAllocator class for their allocators. This class
allocates memory from the virtual address space of the client process (using VirtualAlloc). If your
filter needs to use some other kind of memory, such as DirectDraw surfaces, you can implement a
custom allocator. You can use the CBaseAllocator class or write an entirely new allocator class. If
your filter has a custom allocator, override the following methods, depending on which pin uses the
allocator:
If the other filter refuses to connect using your custom allocator, your filter can either fail the
connection, or else connect with the other filter's allocator. In the latter case, you might need to set
an internal flag indicating the type of allocator. For an example of this approach, see CDrawImage
Class.
The upstream filter delivers media samples to the transform filter by calling the
IMemInputPin::Receive method on the transform filter's input pin. To process the data, the
transform filter calls the Transform method, which is pure virtual. The CTransformFilter and
output sample. Before the filter calls the method, it copies the sample properties from the input
If the Transform method returns S_OK, the filter delivers the sample downstream. To skip a frame,
The following example shows how the RLE encoder might implement this method. Your own
hr = pSource->GetPointer(&pBufferIn);
if (FAILED(hr))
return hr;
hr = pDest->GetPointer(&pBufferOut);
if (FAILED(hr))
return hr;
pDest->SetSyncPoint(TRUE);
return S_OK;
This example assumes that EncodeFrame is a private method that implements the RLE encoding. The
encoding algorithm itself is not described here; for details, see the topic "Bitmap Compression" in the
First, the example calls IMediaSample::GetPointer to retrieve the addresses of the underlying
filter needs this information so that it can manage the buffer properly. Finally, the method calls
IMediaSample::SetSyncPoint to set the key frame flag to TRUE. Run-length encoding does not use
any delta frames, so every frame is a key frame. For delta frames, set the value to FALSE.
Time stamps. The CTransformFilter class timestamps the output sample before calling the
Transform method. It copies the time stamp values from the input sample, without modifying
them. If your filter needs to change the time stamps, call IMediaSample::SetTime on the
output sample.
Format changes. The upstream filter can change formats mid-stream by attaching a media
type to the sample. Before doing so, it calls IPin::QueryAccept on your filter's input pin. In the
CheckTransform. The downstream filter can also change media types, using the same
mechanism. In your own filter, there are two things to watch for:
If your filter does accept format changes, check for them inside the Transform
output samples synchronously inside the Receive method. The filter does not create any worker
threads to process the data. Typically, there is no reason for a transform filter to create worker
threads.
You do not have to implement AddRef or Release. All of the filter and pin classes derive from
QueryInterface
All of the filter and pin classes implement QueryInterface for any COM interfaces they inherit. For
example, CTransformFilter inherits IBaseFilter (through CBaseFilter). If your filter does not
For example, suppose your filter implements a custom interface named IMyCustomInterface. To
Override NonDelegatingQueryInterface to check for the IID of your interface and return a
public:
DECLARE_IUNKNOWN
};
if (riid == IID_IMyCustomInterface) {
return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);
Object Creation
If you plan to package your filter in a DLL and make it available to other clients, you must support
CoCreateInstance and other related COM functions. The base class library implements most of this;
you just need to provide some information about your filter. This section gives a brief overview of
First, write a static class method that returns a new instance of your filter. You can name this method
anything you like, but the signature must match the one shown in the following example:
CUnknown * WINAPI CRleFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)
if (pFilter== NULL)
*pHr = E_OUTOFMEMORY;
return pFilter;
Next, declare a global array of CFactoryTemplate class instances, named g_Templates. Each
CFactoryTemplate class contains registry information for one filter. Several filters can reside in a
single DLL; simply include additional CFactoryTemplate entries. You can also declare other COM
CFactoryTemplate g_Templates[] =
g_wszName,
&CLSID_RLEFilter,
CRleFilter::CreateInstance,
NULL,
NULL
};
Define a global integer named g_cTemplates whose value equals the length of the g_Templates
array:
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
Finally, implement the DLL registration functions. The following example shows the minimal
STDAPI DllUnregisterServer()
The previous examples show how to register a filter's CLSID for COM. For many filters, this is
sufficient. The client is then expected to create the filter using CoCreateInstance and add it to the
filter graph by calling IFilterGraph::AddFilter. In some cases, however, you might want to provide
additional information about the filter in the registry. This information does the following:
Enables clients to discover the filter using the Filter Mapper or the System Device
Enumerator.
Enables the Filter Graph Manager to discover the filter during automatic graph building.
The following example registers the RLE encoder filter in the video compressor category. For details,
see How to Register DirectShow Filters. Be sure to read the section Guidelines for Registering Filters,
REGFILTERPINS sudPinReg[] = {
// Input pin.
{ 0, FALSE, // Rendered?
FALSE, // Output?
FALSE, // Zero?
FALSE, // Many?
0, 0,
},
// Output pin.
{ 0, FALSE, // Rendered?
TRUE, // Output?
FALSE, // Zero?
FALSE, // Many?
0, 0,
};
REGFILTER2 rf2FilterReg = {
1, // Version number.
MERIT_DO_NOT_USE, // Merit.
2, // Number of pins.
};
STDAPI DllRegisterServer(void)
HRESULT hr = AMovieDllRegisterServer2(TRUE);
if (FAILED(hr))
return hr;
if (SUCCEEDED(hr))
hr = pFM2->RegisterFilter(
);
pFM2->Release();
return hr;
STDAPI DllUnregisterServer()
HRESULT hr = AMovieDllRegisterServer2(FALSE);
if (FAILED(hr))
return hr;
if (SUCCEEDED(hr))
hr = pFM2->UnregisterFilter(&CLSID_VideoCompressorCategory,
g_wszName, CLSID_RLEFilter);
pFM2->Release();
return hr;
}
Also, filters do not have to be packaged inside DLLs. In some cases, you might write a specialized
filter that is designed only for a specific application. In that case, you can compile the filter class
directly in your application, and create it with the new operator, as shown in the following example:
int main()
IBaseFilter *pFilter = 0;
if (!pF)
return 1;
pF->QueryInterface(IID_IBaseFilter,
reinterpret_cast<void**>(&pFilter));
return 0;
See Also
Intelligent Connect
Creating a Filter Property Page
This section describes how to create a property page for a custom DirectShow filter, using the
CBasePropertyPage class. The example code in this section shows all the steps needed to create a
property page. The example shows a property page for a hypothetical video effect filter that supports
a saturation property. The property page has a slider, which the user can move to adjust the filter's
saturation level.
The filter must support a way for the property page to communicate with it, so that the property page
can set and retrieve properties on the filter. Possible mechanisms include the following:
This example uses a custom COM interface, named ISaturation. This is not an actual DirectShow
interface; it is defined only for this example. Start by declaring the interface in a header file, along
STDMETHOD(GetSaturation)(long *plSat) = 0;
STDMETHOD(SetSaturation)(long lSat) = 0;
};
You can also define the interface with IDL and use the MIDL compiler to create the header file. Next,
implement the custom interface in the filter. This example uses "Get" and "Set" methods for the
filter's saturation value. Notice that both methods protect the m_lSaturation member with a critical
section.
class CGrayFilter : public ISaturation, /* Other inherited classes. */
private:
public:
CAutoLock lock(&m_csShared);
*plSat = m_lSaturation;
return S_OK;
CAutoLock lock(&m_csShared);
return E_INVALIDARG;
m_lSaturation = lSat;
return S_OK;
};
Of course, the details of your own implementation will differ from the example shown here.
Next, implement the ISpecifyPropertyPages interface in your filter. This interface has a single
method, GetPages, which returns an array of CLSIDs for the property pages that the filter supports.
In this example, the filter has a single property page. Start by generating the CLSID and declaring it
public ISpecifyPropertyPages,
public:
pPages->cElems = 1;
pPages->pElems = (GUID*)CoTaskMemAlloc(sizeof(GUID));
if (pPages->pElems == NULL)
return E_OUTOFMEMORY;
pPages->pElems[0] = CLSID_SaturationProp;
return S_OK;
};
/* ... */
Allocate memory for the array using CoTaskMemAlloc. The caller will release the memory.
Include the DECLARE_IUNKNOWN macro in the public declaration section of your filter:
public:
DECLARE_IUNKNOWN;
interfaces:
void **ppv)
{
if (riid == IID_ISpecifyPropertyPages)
{
return GetInterface(static_cast<ISpecifyPropertyPages*>(this),
ppv);
}
if (riid == IID_ISaturation)
{
}
}
At this point the filter supports everything that it needs for a property page. The next step is
implementing the property page itself. Start by deriving a new class from CBasePropertyPage. The
following example shows part of the declaration, including some private member variables that will be
private:
public:
/* ... */
};
Next, create a dialog resource in the resource editor, along with a string resource for the dialog title.
The string will appear in the tab for the property page. The two resource IDs are arguments to the
CBasePropertyPage constructor:
CGrayProp::CGrayProp(IUnknown *pUnk) :
m_pGray(0)
{ }
The following illustration shows the dialog resource for the example property page.
Now you are ready to implement the property page. Here are the methods in CBasePropertyPage to
override:
OnConnect is called when the client creates the property page. It sets the IUnknown
OnApplyChanges is called when the user commits the property changes by clicking the OK
or Apply button.
Override the CBasePropertyPage::OnConnect method to store a pointer to the filter. The following
example queries the pUnk parameter for the filter's custom ISaturation interface:
HRESULT CGrayProp::OnConnect(IUnknown *pUnk)
if (pUnk == NULL)
return E_POINTER;
ASSERT(m_pGray == NULL);
return pUnk->QueryInterface(IID_ISaturation,
reinterpret_cast<void**>(&m_pGray));
Override the CBasePropertyPage::OnActivate method to initialize the dialog. In this example, the
property page uses a slider control, so the first step in OnActivate is to initialize the common control
library. The method then initializes the slider control using the current value of the filter's saturation
property:
HRESULT CGrayProp::OnActivate(void)
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
icc.dwICC = ICC_BAR_CLASSES;
if (InitCommonControlsEx(&icc) == FALSE)
return E_FAIL;
}
ASSERT(m_pGray != NULL);
HRESULT hr = m_pGray->GetSaturation(&m_lVal);
if (SUCCEEDED(hr))
MAKELONG(SATURATION_MIN, SATURATION_MAX));
return hr;
response to user input. If you don't handle a particular message, call the OnReceiveMessage
method on the parent class. Whenever the user changes a property, do the following:
PROPPAGESTATUS_DIRTY flag. This flag informs the property frame that it should enable the
IPropertyPageSite interface.
To simplify this step, you can add the following helper function to your property page:
private:
void SetDirty()
m_bDirty = TRUE;
if (m_pPageSite)
m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
Call this private method inside OnReceiveMessage whenever a user action changes a property, as
switch (uMsg)
case WM_COMMAND:
if (LOWORD(wParam) == IDC_DEFAULT)
m_lNewVal = SATURATION_DEFAULT;
m_pGray->SetSaturation(m_lNewVal);
m_lNewVal);
SetDirty();
return (LRESULT) 1;
break;
case WM_HSCROLL:
switch(LOWORD(wParam))
case TB_PAGEDOWN:
case SB_THUMBTRACK:
case TB_PAGEUP:
TBM_GETPOS, 0, 0);
m_pGray->SetSaturation(m_lNewVal);
SetDirty();
return (LRESULT) 1;
} // Switch.
return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);
The property page in this example has two controls, a slider bar and a Revert to Default button. If
the user scrolls the slider bar, the property page sets the saturation value on the filter. If the user
clicks the button, the property page restores the filter's default saturation value. In each case,
m_lNewVal holds the current value and m_lVal holds the original value. The value of m_lVal is not
updated until the user commits the change, which happens when the user clicks the OK or Apply
this example, the m_lNewVal variable is updated whenever the user scrolls the slider bar. The
OnApplyChanges method copies this value into the m_lVal variable, overwriting the original value:
HRESULT CGrayProp::OnApplyChanges(void)
m_lVal = m_lNewVal;
return S_OK;
obtained in the OnConnect method. Also, if the user dismisses the property sheet without committing
the changes, you should restore the original values if they have changed. There is no "OnCancel"
method that gets called when the user cancels, so you need to keep track of whether the user has
called OnApplyChanges. This example uses the m_lVal variable, described earlier:
HRESULT CGrayProp::OnDisconnect(void)
if (m_pGray)
m_pGray->SetSaturation(m_lVal);
m_pGray->Release();
m_pGray = NULL;
return S_OK;
The last remaining task is to support COM registration, so that the property frame can create new
instances of your property page. Add another CFactoryTemplate entry to the global g_Templates
array, which is used to register all of the COM objects in your DLL. Do not include any filter set-up
CFactoryTemplate g_Templates[] =
wszName,
&CLSID_GrayFilter,
CGrayFilter::CreateInstance,
NULL,
&FilterSetupData
},
L"Saturation Props",
&CLSID_SaturationProp,
CGrayProp::CreateInstance,
NULL, NULL
};
If you declare g_cTemplates as shown in the following code, then it automatically has the correct
Also, add a static CreateInstance method to the property page class. You can name the method
anything that you prefer, but the signature must match the one shown the following example:
static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)
if (pNewObject == NULL)
*pHr = E_OUTOFMEMORY;
return pNewObject;
To test the property page, register the DLL and then load the filter in GraphEdit. Left-click the filter to