Skip to content

WinRT and COM wrappers

Alexander Sklar edited this page Jan 3, 2024 · 21 revisions

WIL WinRT and COM wrappers help you use COM pointers safely, just as though they were std smart pointers like std::unique_ptr.

Usage

The COM wrappers can be used by including the correct header file:

#include <wil/com.h>

wil::com_ptr_t

Similar to WIL's resource wrappers, the COM wrappers you are expected to use are based off of a template class, wil::com_ptr_t<T>, which serves as the basis for the WIL COM wrappers. You should not need to instantiate this class directly. Instead, use one of the Aliases defined below.

template <typename T,
          typename err_policy = err_exception_policy>
class com_ptr_t;

Template parameters

  • T
    The underlying type being managed by the com_ptr_t. This is usually the name of a COM interface (no trailing *). Note that the behavior of the wrapper class is slightly different if T is IAgileReference or IWeakReference. See com_agile_ref and com_weak_ref below for details.

  • err_policy
    The error handling policy. See the aliases below.

Aliases

The following aliases have been defined for the common error handling policies.

Alias Policy Description
com_ptr<T> err_exception_policy Throws exceptions on failure.
com_ptr_nothrow<T> err_returncode_policy Returns HRESULT values to indicate failure.
com_ptr_failfast<T> err_failfast_policy Terminates with a fast-fail on failure.

API Reference

Member types

  • result
    The function return result for the provided error policy. This is void for exceptions and fail-fast policies, and HRESULT for the return-code policy.

  • element_type
    The template type T being held by the com_ptr_t.

  • pointer
    A pointer to the template type T being held by the com_ptr_t. In other words, it is T*.

Constructors

  • com_ptr_t() (default constructor)
    Constructs an empty wrapper.

  • com_ptr_t(nullptr)
    Constructs an empty wrapper.

  • com_ptr_t(T* ptr)
    Constructs an additional reference to the COM object represented by the raw pointer. If ptr is nullptr, then the constructed wrapper is empty.

  • com_ptr_t(const com_ptr_t<...>& other) and
    com_ptr_t(const Microsoft::WRL::ComPtr<...>& other) (copy constructor)
    Constructs an additional reference to the COM object represented by other. If the other is empty, then the constructed wrapper is also empty. The other can be any kind of com_ptr_t or ComPtr, provided the underlying element of the source is compatible with the target. For example, you can construct a com_ptr_nothrow<IUnknown> from a com_ptr_failfast<IStream> because IStream* can be converted to IUnknown*. The error handling policies do not need to match.

  • com_ptr_t(com_ptr_t<...>&& other) and
    com_ptr_t(Microsoft::WRL::ComPtr<...>&& other) (move constructor)
    Transfers ownership of the COM object from other to the constructed wrapper. The other can be any kind of com_ptr_t or ComPtr, provided the underlying element of the source is compatible with the target.

Destructor

  • ~com_ptr_t
    Releases the wrapped COM object.

Object management methods

  • T* get() const
    Returns the wrapped COM object, or nullptr if the wrapper is empty.

  • void reset()
    Release the current COM object and leaves the wrapper empty.

  • void reset(nullptr)
    Release the current COM object and leaves the wrapper empty.

Note that there is no reset(T* ptr). Use the assignment operator instead.

  • void attach(T* ptr)
    Release the current COM object and takes ownership of the provided raw pointer. If ptr is nullptr, then the wrapper is left empty.

  • T* detach()
    Returns the current COM object and makes the wrapper empty. It is the caller's responsibility to release the returned raw pointer if non-null.

  • T** addressof()
    Returns the address of the internal pointer without releasing the current COM object. Do not use this for _Out_ parameters because it will leak the current COM object. For _Out_ parameters, use the & operator, which releases the current COM object before returning the address.

  • T* const* addressof() const
    Const version of above. The returned pointer cannot be modified.

  • T** put()
    Releases the current COM object and returns the address of the internal pointer. Use this method when passing the com_ptr_t as a _Out_ parameter.

  • T** operator&()
    Same as put().

  • void** put_void()
    Same as put(), but returns the address as a void**.

  • IUnknown** put_unknown()
    Same as put(), but returns the address as a IUnknown**.

  • swap(com_ptr_t<T, ...>& other) and
    swap(Microsoft::WRL::ComPtr<T>& other)
    Swap pointers with another wrapper. The wrappers must wrap the same COM interface, but they can have different error policies. Also available as a free function for ADL purposes.

  • swap(com_ptr_t<T, ...>&& other)and
    swap(Microsoft::WRL::ComPtr<T>&& other) (rvalue reference)
    Swap pointers with another wrapper. The wrappers must wrap the same COM interface, but they can have different error policies.

COM type conversion methods

There are two families of conversion functions, query and copy.

The query methods require that the wrapper be non-empty. If the wrapper is empty, the behavior is undefined. (Usually, you will crash on a null pointer.)

The copy methods permit the wrapper to be empty, in which case the result is also empty.

The try_ prefix means that the function may fail. On failure, it produces an empty wrapper, and returns false (if applicable).

Both the query and copy methods optimize the case where the source COM type is the same as or can be converted to the destination COM type, in which case it just copies the pointer calling AddRef and avoids the call to QueryInterface.

All of the query and copy methods are const.

Direct Try
com_ptr_t<U> query<U>()
com_ptr_t<U> copy<U>()
see Notes 1 and 2 below
com_ptr_t<U> try_query<U>()
com_ptr_t<U> try_copy<U>()
see Note 2 below
result query_to(U** p)
result copy_to(U** p)
bool try_query_to(U** p)
bool try_copy_to(U** p)
result query_to(REFIID riid, void** ppv)
result copy_to(REFIID riid, void** ppv)
bool try_query_to(REFIID riid, void** ppv)
bool try_copy_to(REFIID riid, void** ppv)
Reports failure according to the error policy. Returns an empty wrapper or false on failure.
  • Note 1: query and copy cannot be used with return-code policy.
  • Note 2: (try_)query and (try_)copy produce a com_ptr_t with the same error policy as the original wrapper.

Typical usages for exception-based and fail-fast-based wrappers:

// throughout, com_ptr_failfast can be used in place of com_ptr

wil::com_ptr<IBaz> TryGetInnerBaz()
{
    wil::com_ptr<IBar> bar = ...;

    // Requires that bar be non-empty.
    auto foo = bar.query<IFoo>();

    auto baz = foo->TryGetBaz();

    return baz;
}

// The above could be simplified to

wil::com_ptr<IBaz> TryGetInnerBaz()
{
    wil::com_ptr<IBar> bar = ...;

    // Requires that bar be non-empty.
    return bar.query<IFoo>()->TryGetBaz();
}

void Example()
{
    wil::com_ptr<IBar> bar = ...;
    auto foo = bar.try_query<IFoo>();
    if (foo)
    {
        foo->Method();
    }
}

Typical usages for return-code-based wrappers:

HRESULT TryGetInnerBaz(_COM_Outptr_ IBaz** result)
{
    *result = nullptr;

    wil::com_ptr_nothrow<IBar> bar = ...;

    // Requires that bar be non-empty.
    wil::com_ptr_nothrow<IFoo> foo;
    RETURN_IF_FAILED(bar.query_to(&foo));

    wil::com_ptr_nothrow<IBaz> baz;
    RETURN_IF_FAILED(foo->TryGetBaz(&baz));

    // Use copy_to because baz might be empty.
    RETURN_IF_FAILED(baz.copy_to(result));

    return S_OK;
}

HRESULT Example()
{
    wil::com_ptr_nothrow<IBar> bar = ...;
    auto foo = bar.try_query<IFoo>();
    if (foo)
    {
        foo->Method();
    }

    wil::com_ptr_nothrow<IBaz> baz;
    RETURN_IF_FAILED(bar.try_query_to(&baz));
    baz->Method();

    return S_OK;   
}

Operators

  • operator=(nullptr)
    Releases the current COM object and leaves the wrapper empty.

  • operator=(T* ptr)
    Releases the current COM object and creates an additional reference to the COM object represented by the raw pointer.

  • operator=(const com_ptr_t<...>& other) and
    operator=(const Microsoft::WRL::ComPtr<...>& other)
    Releases the current COM object and creates an additional reference to the COM object represented by other. The other can be any kind of com_ptr_t or ComPtr, provided the underlying element of the source is compatible with the target. For example, you can construct a com_ptr_nothrow<IUnknown> from a com_ptr_failfast<IStream> because IStream* can be converted to IUnknown*. The error handling policies do not need to match.

  • operator=(com_ptr_t<...>&& other) and
    operator=(Microsoft::WRL::ComPtr<...>&& other) (move assignment)
    Releases the current COM object and transfers ownership of the COM object from other to the wrapper. The other can be any kind of com_ptr_t or ComPtr, provided the underlying element of the source is compatible with the target.

  • T** operator&()
    Releases the current COM object and returns the address of the internal pointer. To obtain the address of the internal pointer without releasing the COM object (in the rare case of an _Inout_ parameter), use the addressof method.

  • operator bool()
    Returns true if there is a current COM object, or false if the wrapper is empty.

  • T* operator->() const
    Returns the wrapped COM object pointer. This crashes if the wrapper is empty.

  • T& operator*() const
    Dereferences the wrapped COM object. This crashes if the wrapper is empty.

  • ==, !=, <, <=, >, >= comparison operators
    These perform comparison on the underlying raw pointers. The left and right sides can have different error policies, and the underlying pointers need only be convertible. For example, you can compare a com_ptr<IUnknown> with a ComPtr<IStream>. You can also compare for equality and inequality with raw pointers and with nullptr.

Makers and class template argument deduction

Starting in C++20, you can use class template argument deduction for alias templates to construct a com_ptr_t from a raw pointer without having to repeat the type name.

For earlier versions of C++, WIL provides the functions wil::make_com_ptr, wil::make_com_ptr_nothrow and wil::make_com_ptr_failfast which deduce the type name from the argument.

// Pre-C++20 without helper
void OnThingCreated(ILongNamedThing* rawThing)
{
    auto thing = wil::com_ptr<ILongNamedThing>(rawThing);

    wil::com_ptr<ILongNamedThing> thing(rawThing);

    RunAsync([thing = wil::com_ptr<ILongNamedThing>(rawThing)]
      { ... });
}

// Pre-C++20 with helper
void OnThingCreated(ILongNamedThing* rawThing)
{
    auto thing = wil::make_com_ptr(rawThing);

    RunAsync([thing = wil::make_com_ptr(rawThing)]
      { ... });
}

// C++20 and onward
void OnThingCreated(ILongNamedThing* rawThing)
{
    auto thing = wil::com_ptr(rawThing);

    wil::com_ptr thing(rawThing);

    RunAsync([thing = wil::com_ptr(rawThing)]
      { ... });
}

wil::com_ptr_t vs Microsoft::WRL::ComPtr

wil::com_ptr_t supports exceptions and failfast that enables concise use like this.

someObj.query<ISomeInterface>()->CallMethod();

ComPtr only supports error codes. If you are using exceptions or desire failfast behavior, the choice is clear. If you are converting to exceptions use wil::com_ptr. If you are HRESULT-based and the code already uses WRL ComPtr, you can continue to use it. If you are HRESULT-based and the code is new prefer using wil::com_ptr_nothrow in new code.

Free functions with com_ptr-like behavior

  • T* com_raw_ptr(x)
    Extracts the underlying raw pointer. x can be a raw pointer, a com_ptr_t<T>, a WRL::ComPtr<T>, or a C++/CX T^ hat pointer. (For C++/CX hat pointers, the raw pointer is always IInspectable*.)
Direct Try
com_ptr<U> com_query<U>(x)
com_ptr_failfast<U> com_query_failfast<U>(x)
(no nothrow version)
com_ptr<U> try_com_query<U>(x)
com_ptr_failfast<U> try_com_query_failfast<U>(x)
com_ptr_nothrow<U> try_com_query_nothrow<U>(x)
com_ptr<U> com_copy<U>(x)
com_ptr_failfast<U> com_copy_failfast<U>(x)
(no nothrow version)
com_ptr<U> try_com_copy<U>(x)
com_ptr_failfast<U> try_com_copy_failfast<U>(x)
com_ptr_nothrow<U> try_com_copy_nothrow<U>(x)
void com_query_to(x, U** p)
void com_query_to_failfast(x, U** p)
HRESULT com_query_to_nothrow(x, U** p)
bool try_com_query_to(x, U** p)
bool try_com_query_to_failfast(x, U** p)
bool try_com_query_to_nothrow(x, U** p)
void com_copy_to(x, U** p)
void com_copy_to_failfast(x, U** p)
HRESULT com_copy_to_nothrow(x, U** p)
bool try_com_copy_to(x, U** p)
(no fastfail version)
(no nothrow version)
void com_query_to(x, REFIID riid, void** ppv)
void com_query_to_failfast(x, REFIID riid, void** ppv)
HRESULT com_query_to_nothrow(x, REFIID riid, void** ppv)
bool try_com_query_to(x, REFIID riid, void** ppv)
bool try_com_query_to_failfast(x, REFIID riid, void** ppv)
bool try_com_query_to_nothrow(x, REFIID riid, void** ppv)
void com_copy_to(x, REFIID riid, void** ppv)
void com_copy_to_failfast(x, REFIID riid, void** ppv)
HRESULT com_copy_to_nothrow(x, REFIID riid, void** ppv)
bool try_com_copy_to(x, REFIID riid, void** ppv)
(no fastfail version)
(no nothrow version)
auto com_multi_query(IUnknown* obj)
auto try_com_multi_query(IUnknown* obj)

These are free function versions of the query and copy methods. They take anything that com_raw_ptr accepts as the first parameter and behave the same as the corresponding query and copy methods. These functions are handy if you have a raw pointer in hand and want to perform a COM operation on it.

C++/CX helpers

  • Object^ wil::cx_object_from_abi(x)
    Takes anything that com_raw_ptr accepts, converts it to IInspectable*, and then to Object^.

  • U^ wil::cx_safe_cast<U>(x)
    Takes anything that com_raw_ptr accepts, converts it to IInspectable*, and then to Object^, and then safe_casts it to U^.

  • U^ wil::cx_dynamic_cast<U>(x)
    Takes anything that com_raw_ptr accepts, converts it to IInspectable*, and then to Object^, and then dynamic_casts it to U^.

wil::com_agile_ref

The wil::com_agile_ref family of classes is an alias for com_ptr<IAgileReference>, with differences noted below.

Class Equivalent to Description
com_agile_ref com_ptr<IAgileReference> IAgileReference wrapper which throws on error.
com_agile_ref_failfast com_ptr_failfast<IAgileReference> IAgileReference wrapper which fails fast on error.
com_agile_ref_nothrow com_ptr_nothrow<IAgileReference> IAgileReference wrapper which returns error codes.

The com_agile_ref wrapper class differs from a regular com_ptr in that the query and copy methods operate on the target of the agile reference, rather than on the agile reference itself. In other words, they use IAgileReference::Resolve rather than IUnknown::QueryInterface. Every query and copy operation results in a resolve operation; there is no short-circuit like there is for regular com_ptrs.

Additional free function helpers to assist with using com_agile_ref objects:

Query family Copy family Description
com_agile_ref com_agile_query(anything) com_agile_ref com_agile_copy(anything) Returns a throwing com_agile_ref.
com_agile_ref_failfast com_agile_query_failfast(anything) com_agile_ref_failfast com_agile_copy_failfast(anything) Returns a fail-fast com_agile_ref_failfast.
HRESULT com_agile_query_nothrow(anything, IAgileReference** result) HRESULT com_agile_copy_nothrow(anything, IAgileReference** result) Returns an error-code based com_agile_ref_nothrow.
anything must not be a null pointer or empty wrapper. anything can be a null pointer or empty wrapper.

The anything parameter can be anything that com_raw_ptr accepts, and the agile reference wraps the COM interface that anything represents.

In the case of the copy functions, it is legal to pass a null pointer or empty wrapper, in which case the function succeeds with an empty agile reference (for com_agile_copy and com_agile_copy_failfast) or succeeds with a null pointer (for com_agile_copy_nothrow).

All of the above functions accept an optional AgileReferenceOptions final parameter. If omitted, it defaults to AGILEREFERENCE_DEFAULT. One final free function:

  • bool is_agile(anything)
    Determines whether the object is agile, by querying it for IAgileObject. The anything parameter can be anything that com_raw_ptr accepts, and it may not be a null pointer or empty wrapper.

wil::com_weak_ref

The wil::com_weak_ref family of classes is an alias for com_ptr<IWeakReference>, with differences noted below.

Class Equivalent to Description
com_weak_ref com_ptr<IWeakReference> IWeakReference wrapper which throws on error.
com_weak_ref_failfast com_ptr_failfast<IWeakReference> IWeakReference wrapper which fails fast on error.
com_weak_ref_nothrow com_ptr_nothrow<IWeakReference> IWeakReference wrapper which returns error codes.

The com_weak_ref wrapper class differs from a regular com_ptr in that the query and copy methods operate on the target of the weak reference, rather than on the weak reference itself. In other words, they use IWeakReference::Resolve rather than IUnknown::QueryInterface. Every query and copy operation results in a resolve operation; there is no short-circuit like there is for regular com_ptrs.

If the target of the weak reference has been destroyed, then the operation fails with E_NOT_SET. Note that this is different from how COM itself reports failure, which is RPC_E_DISCONNECTED.

The object from which you obtain a weak reference must implement IInspectable.

These free function helpers assist with creating com_weak_ref objects:

Query family Copy family Description
com_weak_ref com_weak_query(anything) com_weak_ref com_weak_copy(anything) Returns a throwing com_weak_ref.
com_weak_ref_failfast com_weak_query_failfast(anything) com_weak_ref_failfast com_weak_copy_failfast(anything) Returns a fail-fast com_weak_ref_failfast.
HRESULT com_weak_query_nothrow(anything, IAgileReference** result) HRESULT com_weak_copy_nothrow(anything, IAgileReference** result) Returns an error-code based com_weak_ref_nothrow.
anything must not be a null pointer or empty wrapper. anything can be a null pointer or empty wrapper.

The anything parameter can be anything that com_raw_ptr accepts.

In the case of the copy functions, it is legal to pass a null pointer or empty wrapper, in which case the function succeeds with an empty weak reference (for com_weak_copy and com_weak_copy_failfast) or succeeds with a null pointer (for com_weak_copy_nothrow).

Object creation helpers

The object creation helpers follow these patterns:

  • com_ptr<Interface> CoCreateInstance<Class, Interface>(CLSCTX clsCtx = CLSCTX_LOCAL_SERVER)
  • com_ptr<Interface> CoCreateInstance<Interface>(REFCLSID rclsid, CLSCTX clsCtx = CLSCTX_LOCAL_SERVER)
  • com_ptr<Interface> CoCreateInstance<Interface>(REFCLSID rclsid, CLSCTX clsCtx = CLSCTX_LOCAL_SERVER)
  • com_ptr<Interface> CoGetClassObject<Class, Interface>(CLSCTX clsCtx = CLSCTX_LOCAL_SERVER)
  • com_ptr<Interface> CoGetClassObject<Interface>(REFCLSID rclsid, CLSCTX clsCtx = CLSCTX_LOCAL_SERVER)
  • auto CoCreateInstanceEx(REFCLSID clsid, CLSCTX clsCtx = CLSCTX_LOCAL_SERVER)
  • auto TryCoCreateInstanceEx<Interface>(REFCLSID rclsid, CLSCTX clsCtx = CLSCTX_LOCAL_SERVER)

Notes:

  • You can specify a Class (which must support __uuidof) as a template parameter or a REFCLSID as a function parameter.
  • The Interface template parameter is optional and defaults to IUnknown.
  • The dwClsContext function parameter is optional and defaults to CLSCTX_INPROC_SERVER.

The following table demonstrates all the error policies.

Error policy Functions
err_exception_policy
Throws on failure.
com_ptr<Interface> CoCreateInstance(...)
com_ptr<Interface> CoGetClassObject(...)
err_failfast_policy
Fails fast on failure.
com_ptr_failfast<Interface> CoCreateInstanceFailFast(...)
com_ptr_failfast<Interface> CoGetClassObjectFailFast(...)
err_returncode_policy
Returns empty on failure.
com_ptr_nothrow<Interface> CoCreateInstanceNoThrow(...)
com_ptr_nothrow<Interface> CoGetClassObjectNoThrow(...)

Stream helpers

Parameters are typed as follows:

  • ISequentialStream* stream, must not be null.
  • void* dest, must be size bytes.
  • const void* source, must be size bytes.
  • unsigned long size, size of dest or source buffer in bytes.
  • long long distance, relative position in the stream.
  • unsigned long long position, absolute position in the stream (relative to stream start).
  • unsigned long origin, A STREAM_SEEK value which describes how the distance should be interpreted.
  • ISequentialStream* sourceStream and ISequentialStream* targetStream for the stream copying methods.
  • unsigned long long amount, number of bytes to copy.

All of the helper functions wrap a single call to the underlying COM stream. If a read, write, or copy operation produces a partial result, they do not loop internally to read, write, or copy the remaining data.

The _nothrow version is the same as the throwing version, except that it returns HRESULT and the return value (if any) of the throwing version is an explicit final output parameter of the same type as the return value of the throwing version.

Throwing version Nonthrowing version Note
void something(...) HRESULT something(...)
ReturnType something(...) HRESULT something_nothrow(..., ReturnType* result)
HRESULT something_nothrow(..., ReturnType* result = nullptr) Final parameter is optional.
Function Description
unsigned long stream_read_partial(stream, dest, size)
HRESULT stream_read_partial_nothrow(stream, dest, size, unsigned long* result)
void stream_read(stream, dest, size)
HRESULT stream_read_nothrow(stream, dest, size)
void stream_read(stream, T* dest)
HRESULT stream_read_nothrow(stream, T* dest)
void stream_write(stream, source, size)
HRESULT stream_write_nothrow(stream, source, size)
void stream_write(stream, const T* source)
HRESULT stream_write_nothrow(stream, const T* source)
unsigned long long stream_size(stream)
HRESULT stream_size_nothrow(stream, unsigned long long* result)
unsigned long long stream_seek(stream, distance, origin)
HRESULT stream_seek_nothrow(stream, distance, origin, unsigned long long* result = nullptr)
unsigned long long stream_set_position(stream, position)
HRESULT stream_set_position_nothrow(stream, position, unsigned long long* result = nullptr)
unsigned long long stream_seek_from_current_position(stream, distance)
HRESULT stream_seek_from_current_position_nothrow(stream, distance, unsigned long long* result = nullptr)
unsigned long long stream_get_position(stream)
HRESULT stream_get_position_nothrow(stream, unsigned long long* result)
void stream_reset(stream)
HRESULT stream_reset_nothrow(stream)
unsigned long long stream_copy_bytes(sourceStream, targetStream, amount)
HRESULT stream_copy_bytes_nothrow(sourceStream, targetStream, amount, unsigned long long* result = nullptr)
unsigned long long stream_copy_all(sourceStream, targetStream)
HRESULT stream_copy_all_nothrow(sourceStream, targetStream, unsigned long long* result = nullptr)
void stream_copy_exact(sourceStream, targetStream, amount)
HRESULT stream_copy_exact_nothrow(sourceStream, targetStream, amount)

Note 1: Fails with HRESULT_FROM_WIN32(ERROR_INVALID_DATA) if the number of bytes transferred is not equal to the number of bytes requested. Note that this failure code is thrown even for incomplete writes, which is misleading because there is nothing wrong with the data being written; the problem is that the stream is full.

String stream helpers

Return value Function
void stream_write_string(ISequentialStream* stream, const wchar_t sourceString, size_t characterCount)
HRESULT stream_write_string_nothrow(ISequentialStream* stream, const wchar_t* sourceString, size_t characterCount)
unique_cotaskmem_string stream_read_string(ISequentialStream* stream, options = returns_empty)
HRESULT stream_read_string_nothrow(ISequentialStream* stream, PWSTR* result, options = returns_empty)

The stream_write_string functions write a counted string to the stream. The format is a 16-bit character count, followed by that many 16-bit values. No null terminator is appended. This is the same format used by the IStream_WriteStr function.

The characterCount is typically wcslen(sourceString), but can be less if you need to write only a portion of the string.

If the characterCount exceeds 65535, a fail-fast exception is raised, even for the _nothrow version.

The stream_read_string functions read a string written by stream_write_string. The returned string must be freed with CoTaskFreeMemory.

The optional options parameter controls how zero-length strings are returned.

Option Description
wil::empty_string_options::returns_empty (default) zero-length strings are returned as an allocated empty string.
wil::empty_string_options::returns_null zero-length strings are returned as nullptr.

stream_position_saver

The stream_position_saver class remembers the position of the optional stream at construction and restores the stream position when it is destructed.

The stream_position_saver is said to be empty if it does not have a captured position. An empty stream_position_saver does nothing when destructed.

Example usage:

// On error, restore the stream position to where it was when we started.
// That way, somebody else can try to read the stream.
auto saver = wil::stream_position_saver(stream);

auto header = wil::stream_read<MY_HEADER>(stream);
if (header.signature != MY_HEADER_SIGNATURE)
{
    // Reject the stream. The "saver" will restore the position.
    THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
}

// We are happy with the stream.
// Cancel the restoration of the stream position.
saver.dismiss(); 

The stream_position_saver can be moved but not copied. Moving a stream_position_saver transfers the obligation to restore the stream position to the moved-to object. The moved-from object becomes empty.

Constructor

  • stream_position_saver(IStream* stream)
    Captures the position of the stream for future restoration. Throws if the position of the stream cannot be obtained. The stream can be nullptr, in which case no position is saved.

Destructor

  • ~stream_position_saver
    Restores the saved position of the stream, if nonempty. If the attempt to restore the stream position fails, an error is logged but execution continues.

Methods

  • void update()
    Update the saved position to match the current position of the stream. Throws if the position cannot be obtained. Crashes if the stream_position_saver is empty.

  • unsigned long long position()
    Returns the captured position. Returns a garbage value if the stream_position_saver is empty.

  • void reset()
    Restores the saved position of the stream. Throws if the position cannot be restored. The stream_position_saver continues to remember this position and will restore the position again at destruction.

  • void reset(IStream* newStream)
    Restores the saved position of the stream and captures the position of newStream for future restoration. Throws if the position cannot be restored or if the position of the new stream cannot be obtained. (There is a bug in the current implementation: If the position of the new stream cannot be obtained, we will incorrectly set its position to the position captured from the original stream.) The newStream may not be nullptr.

  • void dismiss()
    Cancels the restoration of the saved stream position and makes the stream_position_saver empty.

Site helpers

wil::unique_set_site_null_call

wil::unique_set_site_null_call is an RAII type that calls IObjectWithSite::SetSite(nullptr) on destruction. it follows the unique_com_call pattern; see unique_com_call for further details.

This is usually not constructed explicitly, but is rather returned by com_set_site so that you can set a site, perform an operation, and then clear the site.

Explicit usage:

// For demonstration purposes only. This pattern is encapsulated in the com_set_site method,
// which you should use instead of writing out these three lines manually.

auto objectWithSite = someObject.query<IObjectWithSite>();
objectWithSite->SetSite(someSite);
auto cleanup = wil::unique_set_site_null_call(objectWithSite.get());

someObject->Execute();

// when cleanup goes out of scope, it will set the site back to nullptr.

wil::com_set_site

The wil::com_set_site helper method sets an optional site on an optional object which optionally implements IObjectWithSite. It returns a unique_set_site_null_call which sets the site back to null upon destruction. This is the typical way of using unique_set_site_null_call.

Gotcha: If you forget to save the result of com_set_site, then the site is reset immediately, and it looks like your com_set_site didn't do anything.

unique_set_site_null_call com_set_site(_In_opt_ IUnknown* obj, _In_opt_ IUnknown* site);

If obj is null, or if it does not implement IObjectWithSite, then nothing happens, and the resulting unique_set_site_null_call is empty.

If obj implements IObjectWithSite, then its site is set to site, and the resulting unique_set_site_null_call sets obj's site back to null upon destruction.

Example usage:

auto obj = ...;
auto cleanup = wil::com_set_site(obj.get(), site.get());

someObject->Execute();

// when cleanup goes out of scope, it will set the site back to nullptr.

If you want to set the site and are not interested in restoring it afterward, then use

auto obj = ...;
wil::com_set_site(obj.get(), site.get()).release();

wil::for_each_site

The for_each_site function invokes its callback once for each site in the site chain. All errors are ignored and cause the site chain walk to terminate. it is typically used for debugging.

template<typename TLambda>
void for_each_site(_In_opt_ IUnknown* obj, TLambda&& callback);

Example usage:

void OutputDebugSiteChainWatchWindowText(IUnknown* site)
{
    OutputDebugStringW(L"Copy and paste these entries into the Visual Studio Watch Window\n");
    wil::for_each_site(site, [](IUnknown* site)
    {
        wchar_t msg[64];
        StringCchPrintfW(msg, ARRAYSIZE(msg), L"((IUnknown*)0x%p)->__vfptr[0]\n", site);
        OutputDebugStringW(msg);
    });
}

wil::apartment_variable

COM apartment variables are stored in the current COM apartment, every apartment has unique storage. When the apartment runs down the variables are released. This avoids the problems (undefined behavior, crashes) of using COM objects in global variables and enables caching of information relative to the apartment. To get the same effect as a global variable, that is one instance per DLL, make sure use is always from the MTA.

template<typename T> struct apartment_variable

Template parameters

  • T - The thing to be stored, a COM object (e.g. wil::com_ptr) or an aggregate that contains COM objects.

API Reference

Destructor

  • ~apartment_variable()
    Releases T and the COM objects it represents. When necessary, starts and async rundown of the variables stored in other apartments.

Methods

  • T& get_existing()
    Get current value or throw if no value has been set.

  • T& get_or_create()
    Get current value or default-construct one on demand.

  • template<typename F> T& get_or_create(F&& f)
    Get current value or custom-construct one on demand by invoking f to get the value.

  • T* get_if()
    get pointer to current value or nullptr if no value has been set

  • template<typename V> void set(V&& value)
    Replace or create the current value, fail fasts if the value is not already stored. Use this when the stored value is invalid and needs to be replaced with a new one.

  • void clear()
    Clear the value in the current apartment.

  • winrt::IAsyncAction clear_all_apartments_async()
    Asynchronously clear the value in all apartments.

Module lifetime managment

Apartment variables hosted in a COM DLL need to integrate with the DllCanUnloadNow() function to include the ref counts contributed by C++ WinRT objects that wil::apartment_varaible is based on. This is automatic for DLLs that host C++ WinRT objects but WRL projects will need to be updated to use winrt::get_module_lock() in the DllCanUnloadNow() implementation.

Example usage:

wil::apartment_variable<std::wstring> loggerName;

std::wstring GetNewLoggerName() { /* ... */ }

void LogThings() {
    auto& name = loggerName.get_or_create(&GetNewLoggerName);
    log_to_file(name);
    if (log_file_getting_big())
    {
        name = GetNewLoggerName(); // roll over to new log file
        loggerName.set(name);
    }
}

void UseLogIfPresent() {
   auto name = loggerName.get_if();
   if (name) log_to_file(*name);
}

wil::apartment_variable<int> widgetCount;

void UpdateWidgetCounter()
{
    ++widgetCount.get_or_create();
}

IEnum-style iterators

There is a pattern that is followed by many COM interfaces known as IEnum. These interfaces expose a Next(ULONG cElemsToFetch, T* output, ULONG* cFetched) API, for some type T. Examples of these:

WIL provides helpers for using these interfaces as iterators:

wil::com_ptr<IEnumAssocHandlers> enumAssocHandlers;
wil::verify_hresult(SHAssocEnumHandlers(L".jpg", ASSOC_FILTER_RECOMMENDED, &enumAssocHandlers));
for (const wil::com_ptr<IAssocHandler>& assocHandler : wil::make_range(enumAssocHandlers.get()))
{
    // do something with each assocHandler
}

These iterators can also be used with algorithms:

wil::com_ptr<IEnumAssocHandlers> enumAssocHandlers;
wil::verify_hresult(SHAssocEnumHandlers(L".jpg", ASSOC_FILTER_RECOMMENDED, &enumAssocHandlers));
auto iterator = wil::make_range(enumAssocHandlers.get());
const auto it = std::find_if(iterator.begin(), iterator.end(), [](const wil::com_ptr<IAssocHandler>& assocHandler)
    {
        // some criteria on the assocHandler
    });

If the type that the enumerator enumerates is IUnknown-derived, you can simply invoke wil::make_range(foo). Otherwise, you must pass a type template parameter for what "smart wrapper" type will be used to hold the enumerated value, e.g. a unique_any<T, ...> for something like PCIDLSIT_CHILD:

using unique_idlist = wil::unique_any<LPITEMIDLIST, decltype(&ILFree), ILFree>;
for (const auto& pidl : wil::make_range<unique_idlist>(enumIDList.get()))
{
    // ...
}

See also

Clone this wiki locally