You are on page 1of 36

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

How to Implement IUnknown

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
How IUnknown Works

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

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 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;

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.

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;

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

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.

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.

Class Factories and Factory Templates

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 IClassFactory::CreateInstance, the class factory calls the instantiation

function defined in the template.

The following illustration shows the sequence of method calls.

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

The factory template contains the following public member variables:


const WCHAR * m_Name; // Name

const CLSID * m_ClsID; // CLSID

LPFNNewCOMObject m_lpfnNew; // Function to create an instance

// of the component

LPFNInitRoutine m_lpfnInit; // Initialization function


(optional)

const AMOVIESETUP_FILTER * m_pAMovieSetup_Filter; // Set-up information (for filters)

The two function pointers, m_lpfnNew and m_lpfnInit, use the following type definitions:
typedef CUnknown *(CALLBACK *LPFNNewCOMObject)(LPUNKNOWN pUnkOuter, HRESULT *phr);

typedef void (CALLBACK *LPFNInitRoutine)(BOOL bLoading, const CLSID *rclsid);

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.

(The DLL entry-point function is discussed later in this article.)

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

template for CMyComponent.

 A global variable named g_cTemplates that specifies the size of the array.

The following example shows how to declare these items:


// Public method that returns a new instance.

CUnknown * WINAPI CMyComponent::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)

CMyComponent *pNewObject = new CMyComponent(NAME("My Component"), pUnk, pHr );

if (pNewObject == NULL) {

*pHr = E_OUTOFMEMORY;

return pNewObject;

CFactoryTemplate g_Templates[1] =

L"My Component", // Name

&CLSID_MyComponent, // CLSID

CMyComponent::CreateInstance, // Method to create an instance of MyComponent

NULL, // Initialization function


NULL // Set-up information (for filters)

};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

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

in the final version.

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

information, see the Platform SDK.

 DllGetClassObject: Creates a class factory instance. Described in the previous sections.

 DllCanUnloadNow: Queries whether the DLL can safely be unloaded.

 DllRegisterServer: Creates registry entries for the DLL.

 DllUnregisterServer: Removes registry entries for the DLL.

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

DllMain in the Platform SDK.

You must implement DllRegisterServer and DllUnregisterServer, but DirectShow provides a

function named AMovieDllRegisterServer2 that does the necessary work. Your component can

simply wrap this function, as shown in the following example:


STDAPI DllRegisterServer()

return AMovieDllRegisterServer2( TRUE );

}
STDAPI DllUnregisterServer()

return AMovieDllRegisterServer2( FALSE );

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

information, see How to Register DirectShow Filters.

In your module-definition (.def) file, export all the DLL functions except for the entry-point function.

The following is an example .def file:


EXPORTS

DllGetClassObject PRIVATE

DllCanUnloadNow PRIVATE

DllRegisterServer PRIVATE

DllUnregisterServer PRIVATE

You can register the DLL using the Regsvr32.exe utility.


Writing Transform Filters

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

recommended solution for encoders and decoders.

This section includes the following topics:

 Step 1. Choose a Base Class

 Step 2. Declare the Filter Class

 Step 3. Support Media Type Negotiation

 Step 4. Set Allocator Properties

 Step 5. Transform the Image

 Step 6. Add Support for COM

See Also

 Building DirectShow Filters

 DirectShow Base Classes

 Writing DirectShow Filters

Step 1. Choose a Base Class

Assuming that you decide to write a filter and not a DMO, the first step is choosing which base class to

use. The following classes are appropriate for transform filters:

 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

sample to the next filter.


 CTransInPlaceFilter is designed for filters that modify data in the original buffer, also called

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

always connect with matching media types.

 CVideoTransformFilter is designed primarily for video decoders. It derives from

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

requires the most work on your part.

Important   In-place video transforms can have a serious impact on rendering

performance. In-place transforms require read-modify-write operations on the buffer. If

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.

Step 2. Declare the Filter Class

Start by declaring a C++ class that inherits the base class:


class CRleFilter : public CTransformFilter

/* Declarations will go here. */

};

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"

Next, write a constructor method for the filter:


CRleFilter::CRleFilter()

: CTransformFilter(NAME("My RLE Encoder"), 0, CLSID_RLEFilter)

/* Initialize any private variables here. */

Notice that one of the parameters to the CTransformFilter constructor is the CLSID defined earlier.

Step 3. Support Media Type Negotiation

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

to have another filter treat it as something else.

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

proposes media types, although it does not have to.

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

CBasePin::GetMediaType on the input pin.

 When the upstream filter proposes a media type, the input pin calls the

CTransformFilter::CheckInputType method, which accepts or rejects the type.

 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

downstream filter. It calls the CTransformFilter::GetMediaType method to generate this list.

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

calls the CTransformFilter::CheckTransform method.


The three CTransformFilter methods listed previously are pure virtual methods, so your derived

class must implement them. None of these methods belongs to a COM interface; they are simply part

of the implementation provided by the base classes.

The following sections describe each method in more detail:

 Step 3A. Implement the CheckInputType Method

 Step 3B. Implement the GetMediaType Method

 Step 3C. Implement the CheckTransform Method

Step 3A. Implement the CheckInputType Method

The CTransformFilter::CheckInputType method is called when the upstream filter proposes a

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) ||

(mtIn->cbFormat < sizeof(VIDEOINFOHEADER)))

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.

if (pVih->bmiHeader.biClrUsed > PALETTE_ENTRIES(pVih))

return VFW_E_TYPE_NOT_ACCEPTED;

DWORD cbPalette = pVih->bmiHeader.biClrUsed * sizeof(RGBQUAD);

if (mtIn->cbFormat < sizeof(VIDEOINFOHEADER) + cbPalette)

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)

before casting the pointer.

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

method returns S_OK.

Step 3B. Implement the GetMediaType Method

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

preferred output types. 

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

to specify RLE8 compression:


HRESULT CRleFilter::GetMediaType(int iPosition, CMediaType *pMediaType)

ASSERT(m_pInput->IsConnected());

if (iPosition < 0)

return E_INVALIDARG;

if (iPosition == 0)

HRESULT hr = m_pInput->ConnectionMediaType(pMediaType);

if (FAILED(hr))

return hr;

FOURCCMap fccMap = FCC('MRLE');

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

the FOURCCMap class.

 It calls the CMediaType::SetVariableSize method, which sets the bFixedSizeSamples

flag to FALSE and the lSampleSize member to zero, indicating variable-sized samples.

 It calls the CMediaType::SetTemporalCompression method with the value FALSE,

indicating that every frame is a key frame. (This field is informational only, so you could safely

ignore it.)

 It sets the biCompression field to BI_RLE8.


 It sets the biSizeImage field to the image size. 

Step 3C. Implement the CheckTransform Method

Note   This step is not required for filters that derive from CTransInPlaceFilter.

The CTransformFilter::CheckTransform method checks if a proposed output type is compatible

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

not match the image size.


HRESULT CRleFilter::CheckTransform(

const CMediaType *mtIn, const CMediaType *mtOut)

// Check the major type.

if (mtOut->majortype != MEDIATYPE_Video)

return VFW_E_TYPE_NOT_ACCEPTED;

// Check the subtype and format type.

FOURCCMap fccMap = FCC('MRLE');

if (mtOut->subtype != static_cast<GUID>(fccMap))

return VFW_E_TYPE_NOT_ACCEPTED;

if ((mtOut->formattype != FORMAT_VideoInfo) ||

(mtOut->cbFormat < sizeof(VIDEOINFOHEADER)))

return VFW_E_TYPE_NOT_ACCEPTED;

// Compare the bitmap information against the input type.

ASSERT(mtIn->formattype == FORMAT_VideoInfo);

BITMAPINFOHEADER *pBmiOut = HEADER(mtOut->pbFormat);

BITMAPINFOHEADER *pBmiIn = HEADER(mtIn->pbFormat);

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;

// Compare source and target rectangles.

RECT rcImg;

SetRect(&rcImg, 0, 0, pBmiIn->biWidth, pBmiIn->biHeight);

RECT *prcSrc = &((VIDEOINFOHEADER*)(mtIn->pbFormat))->rcSource;

RECT *prcTarget = &((VIDEOINFOHEADER*)(mtOut->pbFormat))->rcTarget;

if (!IsRectEmpty(prcSrc) && !EqualRect(prcSrc, &rcImg))

return VFW_E_INVALIDMEDIATYPE;

if (!IsRectEmpty(prcTarget) && !EqualRect(prcTarget, &rcImg))

return VFW_E_INVALIDMEDIATYPE;

// Check the palette table.

if (pBmiOut->biClrUsed != pBmiIn->biClrUsed)

return VFW_E_TYPE_NOT_ACCEPTED;

DWORD cbPalette = pBmiOut->biClrUsed * sizeof(RGBQUAD);

if (mtOut->cbFormat < sizeof(VIDEOINFOHEADER) + cbPalette)

return VFW_E_TYPE_NOT_ACCEPTED;

if (0 != memcmp(pBmiOut + 1, pBmiIn + 1, cbPalette))

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

 Source and Target Rectangles in Video Renderers

Step 4. Set Allocator Properties

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

need to modify this behavior, override the CBaseInputPin::NotifyAllocator method.

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(

IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp)

AM_MEDIA_TYPE mt;

HRESULT hr = m_pOutput->ConnectionMediaType(&mt);

if (FAILED(hr))

return hr;

}
ASSERT(mt.formattype == FORMAT_VideoInfo);

BITMAPINFOHEADER *pbmi = HEADER(mt.pbFormat);

pProp->cbBuffer = DIBSIZE(*pbmi) * 2;

if (pProp->cbAlign == 0)

pProp->cbAlign = 1;

if (pProp->cBuffers == 0)

pProp->cBuffers = 1;

// Release the format block.

FreeMediaType(mt);

// Set allocator properties.

ALLOCATOR_PROPERTIES Actual;

hr = pAlloc->SetProperties(pProp, &Actual);

if (FAILED(hr))

return hr;

// Even when it succeeds, check the actual result.

if (pProp->cbBuffer > Actual.cbBuffer)

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

sure they meet your filter's minimum requirements.

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:

 Input pin: CBaseInputPin::GetAllocator and CBaseInputPin::NotifyAllocator.


 Output pin: CBaseOutputPin::DecideAllocator.

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.

Step 5. Transform the Image

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

CTransInPlaceFilter classes use two different versions of this method:

 CTransformFilter::Transform takes a pointer to the input sample and a pointer to the

output sample. Before the filter calls the method, it copies the sample properties from the input

sample to the output sample, including the time stamps.

 CTransInPlaceFilter::Transform takes a pointer to the input sample. The filter modifies

the data in place.

If the Transform method returns S_OK, the filter delivers the sample downstream. To skip a frame,

return S_FALSE. If there is a streaming error, return a failure code.

The following example shows how the RLE encoder might implement this method. Your own

implementation might differ considerably, depending on what your filter does.


HRESULT CRleFilter::Transform(IMediaSample *pSource, IMediaSample *pDest)

// Get pointers to the underlying buffers.

BYTE *pBufferIn, *pBufferOut;

hr = pSource->GetPointer(&pBufferIn);

if (FAILED(hr))

return hr;

hr = pDest->GetPointer(&pBufferOut);

if (FAILED(hr))

return hr;

// Process the data.

DWORD cbDest = EncodeFrame(pBufferIn, pBufferOut);

KASSERT((long)cbDest <= pDest->GetSize());


pDest->SetActualDataLength(cbDest);

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

Platform SDK documentation.

First, the example calls IMediaSample::GetPointer to retrieve the addresses of the underlying

buffers. It passes these to the private EncoderFrame method. Then it calls

IMediaSample::SetActualDataLength to specify the length of the encoded data. The downstream

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.

Other issues that you must consider include:

 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

CTransformFilter class, this results in a call to CheckInputType followed by

CheckTransform. The downstream filter can also change media types, using the same

mechanism. In your own filter, there are two things to watch for:

 Make sure that QueryAccept does not return false acceptances.

 If your filter does accept format changes, check for them inside the Transform

method by calling IMediaSample::GetMediaType. If that method returns S_OK, your

filter must respond to the format change.

For more information, see Dynamic Format Changes.

 Threads. In both CTransformFilter and CTransInPlaceFilter, the transform filter delivers

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.

Step 6. Add Support for COM

The final step is adding support for COM.


Reference Counting

You do not have to implement AddRef or Release. All of the filter and pin classes derive from

CUnknown, which handles reference counting.

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

expose any additional interfaces, you do not have to do anything else.

To expose additional interfaces, override the CUnknown::NonDelegatingQueryInterface method.

For example, suppose your filter implements a custom interface named IMyCustomInterface. To

expose this interface to clients, do the following:

 Derive your filter class from that interface.

 Put the DECLARE_IUNKNOWN macro in the public declaration section.

 Override NonDelegatingQueryInterface to check for the IID of your interface and return a

pointer to your filter.

The following code shows these steps:


CMyFilter : public CBaseFilter, public IMyCustomInterface

public:

DECLARE_IUNKNOWN

STDMETHODIMP NonDelegatingQueryInterface(REFIID iid, void **ppv);

};

STDMETHODIMP CMyFilter::NonDelegatingQueryInterface(REFIID iid, void **ppv)

if (riid == IID_IMyCustomInterface) {

return GetInterface(static_cast<IMyCustomInterface*>(this), ppv);

return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);

For more information, see How to Implement IUnknown.

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

what to do. For details, see How to Create a DLL.

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)

CRleFilter *pFilter = new CRleFilter();

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

objects, such as property pages.


static WCHAR g_wszName[] = L"My RLE Encoder";

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

implementation for these functions: 


STDAPI DllRegisterServer()

return AMovieDllRegisterServer2( TRUE );

STDAPI DllUnregisterServer()

return AMovieDllRegisterServer2( FALSE );

Filter Registry Entries

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,

which describes the recommended practices for filter registration.


// Declare media type information.

FOURCCMap fccMap = FCC('MRLE');

REGPINTYPES sudInputTypes = { &MEDIATYPE_Video, &GUID_NULL };

REGPINTYPES sudOutputTypes = { &MEDIATYPE_Video, (GUID*)&fccMap };

// Declare pin information.

REGFILTERPINS sudPinReg[] = {

// Input pin.

{ 0, FALSE, // Rendered?

FALSE, // Output?

FALSE, // Zero?

FALSE, // Many?

0, 0,

1, &sudInputTypes // Media types.

},

// Output pin.

{ 0, FALSE, // Rendered?

TRUE, // Output?

FALSE, // Zero?

FALSE, // Many?

0, 0,

1, &sudOutputTypes // Media types.

};

// Declare filter information.

REGFILTER2 rf2FilterReg = {

1, // Version number.

MERIT_DO_NOT_USE, // Merit.

2, // Number of pins.

sudPinReg // Pointer to pin information.

};
STDAPI DllRegisterServer(void)

HRESULT hr = AMovieDllRegisterServer2(TRUE);

if (FAILED(hr))

return hr;

IFilterMapper2 *pFM2 = NULL;

hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,

IID_IFilterMapper2, (void **)&pFM2);

if (SUCCEEDED(hr))

hr = pFM2->RegisterFilter(

CLSID_RLEFilter, // Filter CLSID.

g_wszName, // Filter name.

NULL, // Device moniker.

&CLSID_VideoCompressorCategory, // Video compressor category.

g_wszName, // Instance data.

&rf2FilterReg // Filter information.

);

pFM2->Release();

return hr;

STDAPI DllUnregisterServer()

HRESULT hr = AMovieDllRegisterServer2(FALSE);

if (FAILED(hr))

return hr;

IFilterMapper2 *pFM2 = NULL;

hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,

IID_IFilterMapper2, (void **)&pFM2);

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:

#include "MyFilter.h" // Header file that declares the filter class.

// Compile and link MyFilter.cpp.

int main()

IBaseFilter *pFilter = 0;

// Scope to hide pF.

CMyFilter* pF = new MyFilter();

if (!pF)

printf("Could not create MyFilter.\n");

return 1;

pF->QueryInterface(IID_IBaseFilter,

reinterpret_cast<void**>(&pFilter));

/* Now use pFilter as normal. */

pFilter->Release(); // Deletes the filter.

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.

This section contains the following topics:

 Step 1. Define a Mechanism for Setting the Property

 Step 2. Implement ISpecifyPropertyPages

 Step 3. Support QueryInterface

 Step 4. Create the Property Page

 Step 5. Store a Pointer to the Filter

 Step 6. Initialize the Dialog

 Step 7. Handle Window Messages

 Step 8. Apply Property Changes

 Step 9. Disconnect the Property Page

 Step 10. Support COM Registration

Step 1. Define a Mechanism for Setting the Property

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:

 Expose a custom COM interface.

 Support Automation properties, through IDispatch.

 Expose the IPropertyBag interface and define a set of named properties.

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

with the interface identifier (IID):


// Always create new GUIDs! Never copy a GUID from an example.

DEFINE_GUID(IID_ISaturation, 0x19412d6e, 0x6401,

0x475c, 0xb0, 0x48, 0x7a, 0xd2, 0x96, 0xe1, 0x6a, 0x19);

interface ISaturation : public IUnknown

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:

CCritSec m_csShared; // Protects shared data.

long m_lSaturation; // Saturation level.

public:

STDMETHODIMP GetSaturation(long *plSat)

if (!plSat) return E_POINTER;

CAutoLock lock(&m_csShared);

*plSat = m_lSaturation;

return S_OK;

STDMETHODIMP SetSaturation(long lSat)

CAutoLock lock(&m_csShared);

if (lSat < SATURATION_MIN || lSat > SATURATION_MAX)

return E_INVALIDARG;

m_lSaturation = lSat;

return S_OK;

};

Of course, the details of your own implementation will differ from the example shown here.

Step 2. Implement ISpecifyPropertyPages

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

in your header file:


// Always create new GUIDs! Never copy a GUID from an example.

DEFINE_GUID(CLSID_SaturationProp, 0xa9bd4eb, 0xded5,

0x4df0, 0xba, 0xf6, 0x2c, 0xea, 0x23, 0xf5, 0x72, 0x61);

Now implement the GetPages method:


class CGrayFilter : public ISaturation,

public ISpecifyPropertyPages,

/* Other inherited classes. */


{

public:

STDMETHODIMP GetPages(CAUUID *pPages)

if (pPages == NULL) return E_POINTER;

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.

Step 3. Support QueryInterface

To expose the filter's new interfaces to clients, do the following: 

 Include the DECLARE_IUNKNOWN macro in the public declaration section of your filter:

 public:

 DECLARE_IUNKNOWN;

 Override CUnknown::NonDelegatingQueryInterface to check for the IIDs of the two

interfaces:

 STDMETHODIMP CGrayFilter::NonDelegatingQueryInterface(REFIID riid,

 void **ppv)

 {

 if (riid == IID_ISpecifyPropertyPages)

 {

 return GetInterface(static_cast<ISpecifyPropertyPages*>(this),

 ppv);

 }
 if (riid == IID_ISaturation)

 {

 return GetInterface(static_cast<ISaturation*>(this), ppv);

 }

 return CBaseFilter::NonDelegatingQueryInterface(riid, ppv);

 }

Step 4. Create the Property Page

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

used later in the example:


class CGrayProp : public CBasePropertyPage

private:

ISaturation *m_pGray; // Pointer to the filter's custom interface.

long m_lVal // Store the old value, so we can revert.

long m_lNewVal; // New value.

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) :

CBasePropertyPage(NAME("GrayProp"), pUnk, IDD_PROPPAGE, IDS_PROPPAGE_TITLE),

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

pointer to the filter.

 OnActivate is called when the dialog is created.

 OnReceiveMessage is called when the dialog receives a window message.

 OnApplyChanges is called when the user commits the property changes by clicking the OK

or Apply button.

 OnDisconnect is called when the user dismisses the property sheet.

The remainder of this tutorial describes each of these methods.

Step 5. Store a Pointer to the Filter

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));

Step 6. Initialize the Dialog

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))

SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETRANGE, 0,

MAKELONG(SATURATION_MIN, SATURATION_MAX));

SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETTICFREQ,

(SATURATION_MAX - SATURATION_MIN) / 10, 0);

SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETPOS, 1, m_lVal);

return hr;

Step 7. Handle Window Messages

Override the CBasePropertyPage::OnReceiveMessage method to update the dialog controls in

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:

 Set the m_bDirty variable of the property page to TRUE.

 Call the IPropertyPageSite::OnStatusChange method of the property frame with the

PROPPAGESTATUS_DIRTY flag. This flag informs the property frame that it should enable the

Apply button. The CBasePropertyPage::m_pPageSite member holds a pointer to 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

shown in the following example:


BOOL CGrayProp::OnReceiveMessage(HWND hwnd,

UINT uMsg, WPARAM wParam, LPARAM lParam)


{

switch (uMsg)

case WM_COMMAND:

if (LOWORD(wParam) == IDC_DEFAULT)

// User clicked the 'Revert to Default' button.

m_lNewVal = SATURATION_DEFAULT;

m_pGray->SetSaturation(m_lNewVal);

// Update the slider control.

SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETPOS, 1,

m_lNewVal);

SetDirty();

return (LRESULT) 1;

break;

case WM_HSCROLL:

// User moved the slider.

switch(LOWORD(wParam))

case TB_PAGEDOWN:

case SB_THUMBTRACK:

case TB_PAGEUP:

m_lNewVal = SendDlgItemMessage(m_Dlg, IDC_SLIDER1,

TBM_GETPOS, 0, 0);

m_pGray->SetSaturation(m_lNewVal);

SetDirty();

return (LRESULT) 1;

} // Switch.

// Let the parent class handle the message.

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

button on the property frame.

Step 8. Apply Property Changes

Override the CBasePropertyPage::OnApplyChanges method to commit any property changes. In

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;

Step 9. Disconnect the Property Page

Override the CBasePropertyPage::OnDisconnect method to release any interfaces that you

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)

// If the user clicked OK, m_lVal holds the new value.

// Otherwise, if the user clicked Cancel, m_lVal is the old value.

m_pGray->SetSaturation(m_lVal);

m_pGray->Release();

m_pGray = NULL;

return S_OK;

Step 10. Support COM Registration

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

information for the property page.


const AMOVIESETUP_FILTER FilterSetupData =

/* Not shown ... */


};

CFactoryTemplate g_Templates[] =

// This entry is for the filter.

wszName,

&CLSID_GrayFilter,

CGrayFilter::CreateInstance,

NULL,

&FilterSetupData

},

// This entry is for the property page.

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

value based on the array size:


int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);

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)

CGrayProp *pNewObject = new CGrayProp(pUnk);

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

view the property page.

You might also like