July 22nd, 2026
0 reactions

Making an agile version of a Windows Runtime delegate in C++/WinRT, part 3

Last time, we made a small but significant optimization to making an agile version of a Windows Runtime delegate. But there’s another case we missed.

That case is an object that implements the INo­Marshal interface, which means “Do not marshal this object.” For these objects, the Ro­Get­Agile­Reference function fails to create an agile reference and returns CO_E_NOT_SUPPORTED. This function is what powers the agile_ref class, so if you ask for an agile_ref to an object that refuses to be marshaled, you get an CO_E_NOT_SUPPORTED exception.

The catch is that this error is produced at the creation of the agile reference. If in practice all your uses of the agile reference are from the original context, you never actually needed to marshal the object, but too bad, you get the error anyway.

So let’s teach our agile delegate wrapper about delegates that deny marshalability: If the wrapper is invoked on the same context that the original delegate belongs to, then everything is fine. But if you try to invoke the wrapper from another context, then you get the CO_E_NOT_SUPPORTED exception.

Here’s our first try. (Foreshadowing: Since I call this a “first try”, that suggests we’re going to have a second try.)

// Don't use this yet - read to the end of the series

template<typename Delegate>
Delegate make_agile_delegate(Delegate const& d)
{
    if (d.try_as<::IAgileObject>()) {
        return d;
    }

    if (d.try_as<::INoMarshal>()) {                                                                
        return [d, context = winrt::capture<IContextCallback>(CoGetObjectContext)](auto&&...args) {
            if (context == winrt::capture<IContextCallback>(CoGetObjectContext)) {                 
                d(std::forward<decltype(args)>(args)...);                                          
            } else {                                                                               
                throw winrt::hresult_error(CO_E_NOT_SUPPORTED);                                    
            }                                                                                      
        };                                                                                         
    }                                                                                              

    return [agile = winrt::agile_ref(d)](auto&&...args) {
        return agile.get()(std::forward<decltype(args)>(args)...);
    };
}

If the object has the INo­Marshal marker interface, then we capture the original context into our wrapper delegate. At invoke time, the wrapper checks whether the invoke context equals the captured context. If so, then all is good, and we call the original delegate. Otherwise, we throw the exception that Ro­Get­Agile­Reference uses to say “Sorry, I can’t marshal this object.”

If we take a universal reference to the Delegate, we gain the ability to std::move() out of the inbound delegate if it is an rvalue reference.

template<typename Delegate>
std::remove_reference_t<Delegate> make_agile_delegate(Delegate&& d)
{
    if (d.try_as<::IAgileObject>()) {
        return d;
    }

    if (d.try_as<::INoMarshal>()) {
        return [d = std::forward<Delegate>(d),
                context = winrt::capture<IContextCallback>(CoGetObjectContext)](auto&&...args) {
            if (context == winrt::capture<IContextCallback>(CoGetObjectContext)) {
                d(std::forward<decltype(args)>(args)...);
            } else {
                throw winrt::hresult_error(CO_E_NOT_SUPPORTED);
            }
        };
    }

    return [agile = winrt::agile_ref(d)](auto&&...args) {
        return agile.get()(std::forward<decltype(args)>(args)...);
    };
}

Next time, we’ll look at a small optimization we can make to this implementation to reduce the amount of work needed to check the context at invoke time.

Topics

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments