Implementing COM objects and interfaces in C++
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:
- Com::HeapObject - used to create COM objects allocated on the heap, with lifetime managed by their reference count.
- 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.