Implementing COM interfaces is an essential skill required for working with many of the Windows APIs. This article shows how easy it is to implement the IMFMediaEngineNotify interface, the callback interface for the IMFMediaEngine interface.

The IMFMediaEngineNotify interface have just a single function, EventNotify:

struct IMFMediaEngineNotify : public IUnknown
{
public:
    virtual HRESULT __stdcall EventNotify(  _In_  DWORD event, 
                                            _In_  DWORD_PTR param1, 
                                            _In_  DWORD param2) = 0;
};

There are 3 steps to implementing a COM object that implements a COM interface, and the first is quite possibly already provided by the library.

Step 1

The library requires a specialization of the Interface struct inside the Harlinn::Common::Core::Com::Interfaces namespace.

This Interface specialization is used to implement IUnknown::QueryInterface for the instantiated COM object.

The Harlinn.Common.Core library provides specializations for each interface with an entry in HCCComInterfaces.xm, and if it is, then it will be an error to create an additional specialization, and this step must be skipped.

Interfaces without a specialization matches:

template<typename ...InterfaceTypes>
struct Interface : std::false_type
{ };

The library already provides a specialization of Interface for IMFMediaEngineNotify, but if it did not, we would use the HCC_COM_IMPLEMENT_INTERFACE_SPECIALIZATION( Itf, BaseItf ) macro to implement the specialization. Itf is the interface we are specializing for, and BaseItf is the immediate base interface of the interface assigned to the Itf argument, in this case IUnknown:

#include <HCCComImpl.h>

namespace Harlinn::Common::Core::Com::Interfaces
{
    HCC_COM_IMPLEMENT_INTERFACE_SPECIALIZATION(IMFMediaEngineNotify,IUnknown)
}

Step 2

To implement the IMFMediaEngineNotify, we start out by deriving our implementation from the Harlinn::Common::Core::Com::ObjectBase template class:

template<typename DerivedT>
class MediaEngineNotifyImpl 
    : public Com::ObjectBase<DerivedT,IMFMediaEngineNotify>
{
    IMFNotify* callback_ = nullptr;
public:
    MediaEngineNotifyImpl( IMFNotify* callback ) noexcept
        : callback_( callback )
    { }

    HRESULT __stdcall EventNotify( DWORD meEvent, 
                                    DWORD_PTR param1, 
                                    DWORD param2)
    {
        HRESULT result = S_OK;
        try
        {
            callback_->OnMediaEngineEvent( meEvent, param1, param2 );
        }
        catch ( ... )
        {
            result = E_UNEXPECTED;
        }
        if ( meEvent == MF_MEDIA_ENGINE_EVENT_NOTIFYSTABLESTATE )
        {
            SetEvent( reinterpret_cast< HANDLE >( param1 ) );
        }
        return result;
    }
};

This creates a new template class, with an implementation of EventNotify, and nothing else.

Step 3

The library provides two templates, implementing the functions inherited from the IUnknown interface:

  1. Com::HeapObject - used to create COM objects allocated on the heap, with lifetime managed by their reference count.
  2. Com::StackObject - used to create COM objects allocated on the stack, with lifetime limited by their scope.

Com::StackObject avoids dynamic allocation, but can only be used when we are sure the COM object is not expected to exist beyond the scope of the object.

ObjectBase uses the Curiously Recurring Template Pattern where the type of the instantiated class implementing IUnknown is provided as the first template parameter, forming a complete COM object implementation:

class MediaEngineNotify 
    : public Com::HeapObject<MediaEngineNotifyImpl<MediaEngineNotify>>
{
public:
    using Base = Com::HeapObject<MediaEngineNotifyImpl<MediaEngineNotify>>;
    MediaEngineNotify( IMFNotify* callback ) noexcept
        : Base( callback )
    { }
};

Note that Com::HeapObject forwards the parameters passed to the constructor to the constructor for the MediaEngineNotifyImpl template, which is great for initialization, perhaps obvious, but still missing from other COM implementation libraries.

Done

The above provides a complete COM object implementation in C++, used to handle callbacks from the Microsoft Media Foundation MediaEngine to the app hosting the MediaEngine:

Media::MFMediaEngineNotify mediaEngineNotify( new MediaEngineNotify( this ) );

auto attributes = Media::MFAttributes::Create( 3 );
attributes.SetUnknown( MF_MEDIA_ENGINE_DXGI_MANAGER, dxgiManager );
attributes.SetUnknown( MF_MEDIA_ENGINE_CALLBACK, mediaEngineNotify );

Many of the Windows COM, or nano-COM, based APIs expects you to implement interfaces to facilitate notifications from the API to your app, and the Harlinn.Common.Core and Harlinn.Windows libraries provides one of the easiest ways to implement COM, or nano-COM, interfaces for your app.