This library is a header-only lightweight C++/20 wrapper for utilizing, declaring and implementing of Windows COM interfaces. It is adapted from Alexander Bessonov's moderncom
project, which was inspired by Kenny Kerr's moderncpp
work.
This is the code required to define MyObject
class that implements two COM interfaces, IFirstInterface
and ISecondInterface
:
#include <rtcsdk/interfaces.h>
class MyObject : public rtcsdk::object<MyObject, IFirstInterface, ISecondInterface>
{
// Implement methods of IFirstInterface
...
// Implement methods of ISecondInterface
...
public:
// MyObject may optionally have non-empty constructor that is
// allowed to throw exceptions
MyObject(int a, int b);
};
That's all! AddRef
, Release
and even QueryInterface
methods are automatically generated by the library. The objects of class MyObject
may be created on heap, on stack, as singleton or deeply integrated with COM to be automatically constructed via factory objects returned by DllGetClassObject
function.
Read further to find out the details!
- Supports declaration and implementation of COM interfaces with pure C++ code
- Provides automatic generation of
QueryInterface
,AddRef
andRelease
methods - Supports definition of native C++ classes that implement any number of COM interfaces either directly or through "implementation proxies"
- Supports aggregation, objects on stack and singleton objects
- Provides interoperability with ATL and serves as a natural upgrade path when upgrading legacy projects that use ATL
- Allows class that implements interfaces to have non-default constructors
- Provides the COM "smart pointer" class, which can also be used independently of the rest of the library
- Provides compile-time conversion of string GUIDs to
GUID
, which can be used independently of the rest of the library - Provides various customization points to simplify debugging or extend functionality of the library
- Has built-in leak detection mechanism (that can be opted-in per class) to automatically search for leaked COM object references
The library requires C++20 and has been tested on Microsoft Visual C++ compiler Version 14.34 (Visual Studio 2022 17.4.0).
See also FAQ section below for more information.
The library is header-only and does not require installation. Once brought into the project, the library's rtcsdk
folder should be made visible to the rest of the project.
Use the links for fast navigation:
- Guid Helpers
- Fetching Identifiers
- COM Interface Smart Pointer
- COM Interface Support
- Traits
- Object Customization Points
- Constructing Objects
- Implementing COM DLL Server
- Automatic Leak Detection
- FAQ
In order to support backward compatibility and to simplify migration, the library can fetch interface and class identifiers (GUIDs) attached to classes via Microsoft Visual C++ extension __declspec(uuid("..."))
.
However, it also provides functions that parse string GUID into GUID
structure at compile-time. Those functions are defined in rtcsdk/guid.h
header and may be used independently of the rest of the library. However, you don't need to include this header if you include any other library's header.
The following functions are defined in the header:
template<size_t N>
constexpr GUID rtcsdk::make_guid(const char(&str)[N]) { ... }
make_guid
converts a passed string ID to GUID
. Supports the following formats: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
or XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
.
The header also defines a user-defined literal (UDL) _guid
, allowing the following code:
constexpr const GUID id = "{AB9A7AF1-6792-4D0A-83BE-8252A8432B45}"_guid;
To be able to successfully work with interface types, the library must be able to fetch interface ID (as GUID
structure) from a type at compile time. By default, it looks for specialization of the following function:
template<typename Interface>
constexpr GUID get_guid(Interface *) { ... }
A specialization is found using unqualified-id grammar production (ADL). If specialization is not found, the default implementation tries to get an ID using Visual C++ extension uuidof(type)
.
get_guid
may also be declared as a public static constexpr member of a class:
class MyClass ...
{
public:
static constexpr GUID get_guid() { ... }
};
A specialization of get_guid
function is automatically added with RTCSDK_DEFINE_INTERFACE
, RTCSDK_DEFINE_CLASS
and other macros.
The following function can be used to get GUID from the interface:
template<typename Interface>
constexpr GUID get_interface_guid() noexcept;
#include <rtcsdk/com_ptr.h>
This header file provides the following template classes: rtcsdk::com_ptr
and rtcsdk::ref
. For convenience, they are also available in com
namespace as com::ptr
and com::ref
correspondingly.
template<typename Interface>
class com_ptr<Interface>
{
...
};
com_ptr
takes a single template argument, the interface class itself. The library must be able to fetch an interface ID (IID) from this type.
The following constructors are provided:
-
Default or null constructors
com_ptr(); com_ptr(std::nullptr_t) noexcept;
Default-construct or null-construct the smart pointer object. The object becomes empty.
-
Raw interface pointer constructor
com_ptr(Interface *) noexcept;
Constructs smart pointer object from a raw interface pointer. Successful construction increments object's usage counter with a call to
AddRef
method. -
Attaching constructor
com_ptr(rtcsdk::attach_t, Interface *);
Constructs smart pointer object from a raw interface pointer. DOES NOT call
AddRef
method. Use a constant objectrtcsdk::attach
as a first parameter to constructor. -
Other raw pointer constructor
template<typename OtherInterface> com_ptr(OtherInterface *punk) noexcept;
Constructs smart pointer object from a raw interface pointer to other interface. This constructor checks if
Interface
andOtherInterface
are related:- If
OtherInterface
is derived fromInterface
, a simplestatic_cast
is performed andAddRef
is called onInterface
. - Otherwise, an
Interface
pointer is obtained viaQueryInterface
.
- If
-
Reference constructor
com_ptr(ref<Interface> p) noexcept;
See the
com::ref
class below. -
Copy constructor
com_ptr(const com_ptr &) noexcept;
-
Move constructor
com_ptr(com_ptr &&) noexcept;
-
Other smart pointer copy constructor
template<typename OtherInterface> com_ptr(const com_ptr<OtherInterface> &punk) noexcept;
Copy constructor from other smart pointer type. This constructor checks if
Interface
andOtherInterface
are related:- If
OtherInterface
is derived fromInterface
, a simplestatic_cast
is performed andAddRef
is called onInterface
. - Otherwise, an
Interface
pointer is obtained viaQueryInterface
.
- If
-
Other smart pointer move constructor
template<typename OtherInterface> com_ptr(com_ptr<OtherInterface> &&punk) noexcept;
Move constructor from other smart pointer type. This constructor checks if
Interface
andOtherInterface
are related:- If
OtherInterface
is derived fromInterface
, no calls toAddRef
andRelease
are made. - Otherwise, an
Interface
pointer is obtained viaQueryInterface
.
- If
com_ptr
also provides a symmetric set of assignment operators.
The following methods are provided:
Method | Description |
---|---|
explicit operator bool() const noexcept |
Checks if the current smart pointer object not empty |
release() noexcept |
Releases a current interface and empties smart pointer object |
reset() noexcept |
Same asrelease |
Interface *operator ->() const noexcept |
Dereferences the current smart pointer object |
bool operator ==(const com_ptr &o) const noexcept |
Checks whether two smart pointer objects are equal |
bool operator !=(const com_ptr &o) const noexcept |
Checks whether two smart pointer objects are not equal |
bool operator <(const com_ptr &o) const noexcept |
Introduces ordering |
void attach(Interface *p) noexcept |
Attaches a raw interface pointer. Asserts if smart pointer object is not empty |
[[nodiscard]] Interface *detach() noexcept |
Detaches the currently stored raw interface pointer |
Interface *get() const noexcept |
Retrieves the currently stored raw interface pointer |
Interface **put() noexcept |
Provides a write access to the stored raw interface pointer. Asserts if the object is not empty |
template<class OtherInterface> auto as() const noexcept |
Constructs another smart pointer object with a given interface type |
template<class OtherInterface> HRESULT QueryInterface(OtherInterface **ppresult) const |
Calls QueryInterface to get raw result |
HRESULT CoCreateInstance(const GUID &clsid, IUnknown *pUnkOuter = nullptr, DWORD dwClsContext = CLSCTX_ALL) noexcept |
Calls::CoCreateInstance with provided parameters and stores the result in the current smart pointer object |
HRESULT create_instance(const GUID &clsid, IUnknown *pUnkOuter = nullptr, DWORD dwClsContext = CLSCTX_ALL) noexcept |
Same asCoCreateInstance |
static com_ptr create(const GUID &clsid, IUnknown *pUnkOuter = nullptr, DWORD dwClsContext = CLSCTX_ALL) |
Static method that calls::CoCreateInstance and returns a smart pointer object if successful. Otherwise, throws an instance of corsl::hresult_error . |
Operators ==
and !=
are also provided to any combination of Interface *
and const com_ptr<Interface> &
pairs.
This class is supposed to be used as a replacement for raw interface pointer in cases when interface pointer is used without adding a reference. Consider the following example:
void serialize(IStream *pStream)
{
// work with stream object and never store it
// therefore, we don't have to call AddRef and Release
...
}
void foo()
{
com::ptr<IStream> stream { construct_stream() };
serialize(stream.get()); // have to call get() here
}
serialize
function may be rewritten to take an instance of bcom::ref<IStream>
instead of a raw pointer. A benefit is additional lifetime checks in debug builds without any overhead in release builds:
void serialize(com::ref<IStream> pStream)
{
// note pStream is passed by value
// unmodified body of serialize function above
}
void foo()
{
com::ptr<IStream> stream { construct_stream() };
serialize(stream); // implicitly construct com::ref<IStream> here
}
template<typename Interface>
class ref { ... };
com::ref
takes a single template argument, the interface class itself. The library must be able to fetch an interface ID (IID) from this type.
The following constructors are provided:
-
Default and null constructors
ref() = default; ref(std::nullptr_t) noexcept;
Construct an empty object.
-
Constructor from a raw pointer
ref(Interface *p) noexcept;
-
Constructor from
com_ptr<Interface>
ref(const com_ptr<Interface> &o) noexcept;
-
Constructor from
com_ptr<Interface>
temporaryref(com_ptr<Interface> &&o) noexcept;
This constructor is allowed, however it introduces additional lifetime checks in debug builds, unless the
RTCSDK_NO_CHECKED_REFS
macro is defined before including thecom_ptr.h
header. -
Constructor from another smart pointer type:
template<typename OtherInterface> ref(const com_ptr<OtherInterface> &o) noexcept;
This constructor is allowed only if
OtherInterface
derives fromInterface
. Otherwise, the code is ill-formed. -
Constructor from another smart pointer type temporary:
template<typename OtherInterface> ref(com_ptr<OtherInterface> &&o) noexcept;
This constructor is allowed only if
OtherInterface
derives fromInterface
. Otherwise, the code is ill-formed. Additional lifetime checks are performed in debug builds, unless theRTCSDK_NO_CHECKED_REFS
macro is defined before including thecom_ptr.h
header. -
Copy-constructor
template<typename OtherInterface> ref(const ref<OtherInterface> &o) noexcept;
This constructor is allowed only if
OtherInterface
derives fromInterface
. Otherwise, the code is ill-formed.
Assignment operators are prohibited for ref
objects.
The same comparison operators are defined for ref
class as for com_ptr
class.
The following methods are available:
Method | Description |
---|---|
Interface *operator ->() const noexcept |
Dereferences the current smart pointer object |
Interface *get() const noexcept |
Retrieves the currently stored raw interface pointer |
template<class OtherInterface> auto as() const noexcept |
Constructs another smart pointer object with a given interface type |
A rtcsdk/interfaces.h
header provides infrastructure for working with COM interfaces in native C++ code.
The following macros may be used to declare interfaces: {#BELT_DEFINE_INTERFACE}
RTCSDK_DEFINE_INTERFACE(name, guid)
{
// declare interface members here as normal C++ abstract methods, for example
virtual int sum(int a, int b) = 0;
};
or
RTCSDK_DEFINE_INTERFACE_BASE(name, baseInterfaceName, guid)
{
// declare interface members here as normal C++ abstract methods, for example
virtual int sum(int a, int b) = 0;
};
First macro declares interface name
derived from IUnknown
and second macro declares interface name
derived from baseInterfaceName
.
The library is also capable of working with "legacy" interfaces. This basically means that any abstract class that is derived from another legacy interface or IUnknown
and for which library can fetch interface id can be directly used by the library.
Imagine you want to implement a class MyObject
that implements two interfaces, IFirstInterface
and ISecondInterface
. Here's the full class declaration:
#include <rtcsdk/interfaces.h>
class __declspec(novtable) MyObject : public rtcsdk::object<MyObject, IFirstInterface, ISecondInterface>
{
// Implement methods of IFirstInterface
...
// Implement methods of ISecondInterface
...
};
Constructing object on heap:
com_ptr<IFirstInterface> construct_object()
{
return MyObject::create_instance().to_ptr();
}
com_ptr<ISecondInterface> construct_object_second()
{
return MyObject::create_instance().to_ptr<ISecondInterface>();
}
object
is a variadic template class declared as
template<typename Derived, typename... Interfaces>
class object;
Derived
should be the name of the class that derives from object
.
Interfaces
is a non-empty list of interfaces the class implements. Every entry in the list must be one of the following:
- COM interface class, for which library is able to fetch IID (see above). Must not be
IUnknown
. also<SomeInterface>
. See below for more information.eats_all<Derived>
class. See below for more information.aggregates<Derived, OtherInterfaces...>
. See below for more information.- A class that derives from
intermediate
.
Important note: Derived
class declaration follows the so-called Curiously Recurring Template Pattern (CRTP) and causes the resulting class to effectively derive from all COM interfaces directly or indirectly listed.
object
has the following members:
-
using DefaultInterface = unspecified;
Returns the "default" (usually first) interface. Never equals
IUnknown
. -
IUnknown *GetUnknown() noexcept;
Can be used inside or outside derived class to obtain a direct pointer to
IUnknown
interface. Does not callAddRef
. -
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) noexcept override;
Implements
IUnknown::QueryInterface
. -
template<typename... Args> static object_holder<unspecified> create_instance(Args &&...args);
Static method that should be used to create an instance of
Derived
class on heap. Arguments, if passed are perfect-forwarded toDerived
's constructor. Returns a special wrapper object. Seeobject_holder
below.See also object customization points section below.
-
template<typename... Args> static com_ptr<IUnknown> create_aggregate(IUnknown *pOuterUnknown, Args &&...args);
Create new instance of
Derived
aggregatingpOuterUnknown
.Derived
must hassupports_aggregation
trait (see below).Arguments, if passed are perfect-forwarded to
Derived
's constructor.See also object customization points section below.
-
template<typename OtherInterface = FirstInterface> com_ptr<OtherInterface> create_copy() const;
Creates a copy of the current object (invoking
Derived
's copy constructor) and queries a copy for a given interface.Derived
must derive fromOtherInterface
orOtherInterface
must beIUnknown
. -
auto addref() noexcept;
This protected method may be called by
Derived
class if it needs to explicitly add an object reference. -
auto release() noexcept;
This protected method may be called by
Derived
class if it needs to explicitly release an object reference.
also
class template should be used whenever Derived
implements "legacy" interface that itself derives from another legacy interface. Legacy interfaces are those interfaces that are not declared with RTCSDK_DEFINE_INTERFACE
macro.
Consider the following example. IDispatchEx
interface is defined in Platform SDK and derives from IDispatch
interface. It is a legacy interface. We declare MyClass
the following way:
class MyClass : public rtcsdk::object<MyObject, IDispatchEx>
{
// Implement IDispatch
...
// Implement IDispatchEx
...
};
The generated QueryInterface
automatically supports querying for IDispatchEx
interface, but does not support querying for IDispatch
interface. This example must be fixed in the following way:
class MyClass : public RTCSDK::object<MyObject, IDispatchEx, rtcsdk::also<IDispatch>> // fix
{
// Implement IDispatch
...
// Implement IDispatchEx
...
};
If interface is declared using RTCSDK_DEFINE_INTERFACE
macro, this fix is not required, even if declared interface derives from "legacy" interface:
RTCSDK_DEFINE_INTERFACE_BASE(IMyDispatch, IDispatch, "{AB9A7AF1-6792-4D0A-83BE-8252A8432B45}")
{
...
};
class MyClass : public RTCSDK::object<MyObject, IMyDispatch>
{
// Implement IDispatch
...
// Implement IMyDispatch
...
};
One of the QueryInterface
customization points is an eats_all<Derived>
special entry in interface list. If the class contains this entry, it must implement the following public method:
void *on_eat_all(const IID &id) noexcept
{
...
}
If a class supports a given interface, it must obtain a pointer to this interface, call an AddRef
method through the obtained pointer and return it, cast to void *
. Otherwise, it must return nullptr
.
COM objects may support aggregation. That is, an object may advertise an interface that it does not directly implement. It then "forwards" the query for this particular interface to its class member variable, for example, or obtains the pointer using other ways.
The library supports this scenario with a aggregates<Derived, Interfaces...>
entry in interface list. For each aggregate interface listed, the Derived
class must implement the following public method:
void *on_query(rtcsdk::interface_wrapper<ISomeInterface>) noexcept
{
...
}
on_query
must obtain a pointer to requested interface, call an AddRef
method through the obtained pointer and return it, cast to void *
:
class MyClass : public rtcsdk::object<MyClass, IDirectlySupportedInterface, rtcsdk::aggregates<MyClass, IAggregateInterface>>
{
com::ptr<ISomeOtherInterface> member { initialize_member() };
// Implement IDirectlySupportedInterface
...
// Implement aggregation
void *on_query(rtcsdk::interface_wrapper<IAggregateInterface>) noexcept
{
IAggregateInterface *result{};
member->QueryInterface(&result);
return result;
}
};
It is often convenient to create classes or template classes that provide (partial) implementation of a given interface or interfaces and then use them when implementing final classes.
The library provides a machinery for such "implementation proxy" classes with a help of intermediate
class template:
template<typename ProxyClass, typename... Interfaces>
struct intermediate;
This class template looks similar to object
and should be used the same way. However, intermediate
does not implement members declared in object
.
RTCSDK_DEFINE_INTERFACE(IMyInterface, "{AB9A7AF1-6792-4D0A-83BE-8252A8432B45}")
{
virtual void Method1() =0;
virtual void Method2() =0;
};
// Provide a partial implementation of IMyInterface
class MyInterfaceImpl : public rtcsdk::intermediate<MyInterfaceImpl, IMyInterface>
{
// Partially implement Method1
virtual void Method1() override { ... }
};
// MyClass implements IMyInterface with a help of MyInterfaceImpl:
class MyClass : public rtcsdk::object<MyClass, MyInterfaceImpl>
{
// We still have to implement IMyInterface
virtual void Method2() override { ... }
};
object_holder
is a temporary object holder class template that is returned by object<Derived, ...>::create_instance
method:
template<typename T>
class object_holder;
Where T
is an unspecified class derived from Derived
.
The class has the following members:
-
com_ptr<Derived::DefaultInterface> to_ptr() && noexcept;
May only be invoked on temporary
object_holder
object and "converts" it tocom_ptr<Derived::DefaultInterface>
.The object is considered "moved-out" after this method returns.
-
template<typename Interface> com_ptr<Interface> to_ptr() && noexcept;
May only be invoked on temporary
object_holder
object and "converts" it tocom_ptr<Interface>
.Derived
must derive fromInterface
orInterface
must beIUnknown
.The object is considered "moved-out" after this method returns.
-
Derived *obj() const noexcept;
This special-purpose method is supposed to be used when additional initialization is required on constructed object:
class __declspec(novtable) MyObject : public rtcsdk::object<MyObject, IMyInterface> { public: void additional_initialization_method(...); }; com_ptr<IMyInterface> construct_object() { auto my_object = MyObject::create_instance(); my_object.obj()->additional_initialization_method(); return std::move(my_object).to_ptr(); }
A trait class is a special class that Derived
must directly derive from to change various defaults. The following trait classes are available:
singleton_factory
single_cached_instance
supports_aggregation
increments_module_count
enable_leak_detection
Some of these trait classes automatically "propagate" down on inheritance chain. That is, if an implementation proxy class or even an interface class specifies a trait, it will also be present in any derived final class.
When object is constructed using library's default construction mechanism, use the single global instance.
Singleton object is created at the time it is first requested and lives until the program is finished. There is no way to destroy the object before the program ends.
Access to object creation is thread-safe.
A special case of a singleton. An object is created at the time it is first requested and cached for all subsequent create requests. If the created object's reference count reaches zero, it is destroyed.
Access to object creation and destruction is thread-safe.
Marks the class as supporting aggregation. A class must be marked so if it is supposed to be used in aggregation (that is, constructed via create_aggregate
or via default construction mechanism with non-null pOuterUnknown
).
If the class is never to be used in aggregation, you can get more efficient implementation by not including this trait.
Increment global module reference count whenever this class object's is referenced. Can be used in conjunction with DllCanUnloadNow
implementation.
Turn on leak detection for this class. See Automatic Leak Detection section for more information.
Customization points allow the class to execute additional code at various object lifetime events. They are all completely optional.
A customization point is a public method declared in the Derived
class. The following customization points are supported:
When constructor of the Derived
class executes, reference-counting machinery is not yet initialized. Therefore, constructor cannot make any external calls that expect the current object to be a valid COM object.
For such cases, a class may provide the following public method:
template<typename... Args>
HRESULT final_construct(Args &&...args)
{
...
}
The final_construct
method is invoked when reference-counting machinery is fully initialized. If arguments are present, the user is supposed to pass rtcsdk::delayed
object as a first argument to create_instance
or create_aggregate
methods and Derived
constructor must take no parameters.
final_construct
is allowed to throw exceptions or return non-zero error codes. If final_construct
returns an error code, an instance of bad_hresult
holding this error code is thrown.
Correspondingly, there is a symmetric final_release
method. It must have one of the following signatures:
static void final_release(std::unique_ptr<Derived> ptr) noexcept
{
...
}
template<typename D>
static void final_release(std::unique_ptr<D> ptr) noexcept
{
...
}
Note that final_release
method is a static member, and it takes a unique pointer to the current object in the heap. By default, the object will be destroyed at the end of the final_release
method, but the customization is free to do anything it needs with a passed pointer.
The second variant is used with aggregated objects. An implementation may use if constexpr (std::is_same_v<D, Derived>)
to check if it is called with an object or aggregate value of the object. In the latter case, it can obtain a pointer to an object itself with by calling the pointed object's get()
method:
class MyObject : public rtcsdk::object<MyObject, ...>, public rtcsdk::supports_aggregation
{
void final_release()
{
...
}
public:
// customization point
template<typename D>
static void final_release(std::unique_ptr<D> ptr) noexcept
{
if constexpr (std::is_same_v<D, MyObject>) {
ptr->final_release();
} else {
ptr->get()->final_release();
}
// Object will auto-destruct here
}
};
This customization point is invoked each time an object's reference counter is incremented.
void on_add_ref(int new_counter_value);
This customization point is invoked each time an object's reference counter is decremented.
void on_release(int new_counter_value);
This customization point is invoked before standard QueryInterface
machinery:
HRESULT pre_query_interface(REFIID iid, void **ppresult) noexcept;
If method is capable of producing result, it must store it at *ppresult
and return S_OK
. Otherwise, it must return E_NOINTERFACE
. If this method returns any other value, QueryInterface
processing immediately stops and the given error code is returned to the caller. In this case, the method must store nullptr at *ppresult
.
If successful result is produced, implementation must call AddRef
on obtained interface.
This customization point is invoked after standard QueryInterface
machinery was unable to find a requested interface:
HRESULT post_query_interface(REFIID iid, void **ppresult) noexcept;
If method is capable of producing result, it must store it at *ppresult
and return S_OK
. Otherwise, it must store nullptr at *ppresult
and return E_NOINTERFACE
.
If successful result is produced, implementation must call AddRef
on obtained interface.
The library provides several ways to construct COM objects:
To construct an object of a given class Derived
in the heap, call the static method Derived::create_instance
, passing any number of arguments for class's constructor.
If the first argument is rtcsdk::delayed
, then the default constructor is used instead and the rest of arguments are passed to the final_construct
customization point.
If object construction succeeds, the method returns a proxy object. This proxy object is usually kept temporary, and you will immediately invoke its to_ptr()
method, optionally passing an interface you want to query from the created object.
Advanced usages of a proxy object are described above in object_holder
section.
Note that the class's constructor or final_construct
customization point are allowed to throw exceptions.
For short-lived COM objects or for COM objects for which lifetime can be synchronized with a specific scope, you can use stack-based construction:
class MyClass : public rtcsdk::object<MyClass, IMyInterface> {...};
void bar(com::ref<IMyInterface> p)
{
...
}
void foo()
{
rtcsdk::value_on_stack<MyClass> obj{/* arguments to constructor or final_construct */};
bar(&obj);
}
AddRef
and Release
methods for objects constructed on stack are no-op, however, in debug builds the object's destructor will assert if there were unmatched number of calls to AddRef
and Release
.
This generic mechanism for constructing COM objects can be used in cases when the calling code does not know specific implementation class, or for runtime object construction.
First of all, a class that wants to participate in default construction must register itself using one of the following macros:
RTCSDK_OBJ_ENTRY_AUTO(classname)
RTCSDK_OBJ_ENTRY_AUTO2(classguid, classname)
The RTCSDK_OBJ_ENTRY_AUTO
macro registers classname
class for which library can automatically fetch class ID.
The RTCSDK_OBJ_ENTRY_AUTO2
macro registers classname
with a given CLSID:
RTCSDK_OBJ_ENTRY_AUTO2("{FFDBB4B7-8ECB-42FE-BF68-163B1E0829A2}"_guid, MyClass);
As described above, an ability to automatically fetch the class ID means that either get_guid
was specialized for the class, or __declspec(uuid("..."))
was added to class's declaration.
The library also provides a macro to attach an ID to a class: {#BELT_DEFINE_CLASS}
RTCSDK_DEFINE_CLASS(classGuidName, guid)
or inside a class
class MyClass ...
{
public:
RTCSDK_CLASS_GUID(guid)
};
After the class is registered, instances of this class may be created using one of the following functions:
-
template<typename Interface> HRESULT create_object(const GUID &clsid, const GUID &iid, void **ppv, IUnknown *pOuterUnknown = nullptr) noexcept;
Create an instance of a class with a given
CLSID
and query an interface with a givenIID
. Pass a non-nullpOuterUnknown
if you want a created object to be aggregated.This function never throws. It returns a non-zero error code. If object creation throws an instance of
corsl::hresult_error
exception, exception's error code is returned. If object creation throws any other exception,E_FAIL
is returned. -
template<typename Interface> HRESULT create_object(const GUID &clsid, com::ptr<Interface> &result, IUnknown *pOuterUnknown = nullptr) noexcept;
The same as above, but automatically takes
IID
fromInterface
and fillsresult
on success. -
template<typename Interface> com::ptr<Interface> create_object(const GUID &clsid, IUnknown *pOuterUnknown = nullptr);
Directly returns a smart pointer to a given
Interface
or throws an instance ofbad_hresult
when object creation fails.
create_object
respects the singleton and single cached instance traits when creating objects.
create_object
function described above serves as a foundation for implementing DllGetClassObject
.
All you need to do to implement DLL COM server is to add the following code to one of your CPP files:
#include <rtcsdk/factory.h>
HRESULT_export CALLBACK DllCanUnloadNow()
{
return rtcsdk::DllCanUnloadNow();
}
HRESULT_export CALLBACK DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID * ppvObj)
{
return rtcsdk::DllGetClassObject(rclsid, riid, ppvObj);
}
The library provides a built-in mechanism to search for leaked object references. It not only shows you which objects were leaked, but also provides detailed stack traces for the corresponding leaked AddRef
calls!
Leak detection is only enabled in debug builds.
The following pre-requisites must be done in order for the leak detection to work:
-
Boost.StackTrace
library is a dependency. If you cannot useboost
, you must disable automatic leak detection by defining the following macro before including any library header:#define RTCSDK_COM_NO_LEAK_DETECTION
-
rtcsdk::init_leak_detection()
function must be called once before usingcom_ptr
class or creating objects.Note that this function must be called no matter if you actually use automatic leak detection, unless you completely disable leak detection as described above.
This function is empty in release builds.
-
Classes that you want to participate in leak detection must opt in by including
rtcsdk::enable_leak_detection
trait.The recommendation is to only enable leak detection for those classes whose object references are found to be leaking.
Once all pre-requisites are met, you should run your program under debugger. Currently, this mechanism does not detect leaked objects, you should use other facilities to detect leaked objects. For example, you can use tools built into Visual Studio or use tracing to find leaked objects. Alternatively, you can combine automatic leak detection with objects constructed on stack, because in debug builds library automatically asserts when destructor for such object is called with mismatched number of calls to AddRef
and Release
.
Once leaked objects are found, add them to the Watch window in Visual Studio and expand until you find umb_usages
member. It will contain a list of stack traces of calls to AddRef
that were not matched with corresponding calls to Release
.
- Leak detection is built into
com_ptr
andobject
classes and therefore is unable to track calls toAddRef
andRelease
made by other components. In other words, it always assumes that onlycom_ptr
class makes calls toAddRef
andRelease
. - Leak detection does not currently find leaked objects. Once a leaked object is found by other means, it can be viewed in the debugger to see a list of stack traces.
-
Why Windows and MSVC only?
COM is a Windows technology that is continued to be used even in modern OS components. COM can be considered a technology to provide binary interconnectivity between native components with a stable C++ ABI and therefore, may theoretically be used on other platforms. It can also be used as a way to establish Inversion of Control principles in code.
There are a few Windows bindings in the code that may be relatively easy decoupled from the rest of the code. After that, the library may be used on other platforms to establish a solid connectivity between application components.
-
What served as inspiration for this library?
The library was initially created as a way to "renovate" old code base that used ATL for COM support. It was inspired by early works by Kenny Kerr on his
moderncpp
project (which later became C++/WinRT library). The library may share some common ideas (but not implementation) with C++/WinRT. It also does not have dependency on WinRT and does not require Windows 10.