C++/WinRT: Fun with Agility

Previous: Working with Implementations

For the most part, every COM and WinRT object should be agile. This is a good default unless you have a compelling reason to require an object to reside in a given single-threaded apartment. This typically has to do with reentrancy requirements. Increasingly, APIs that provide services for building user experiences are offering objects that are agile. This tends to provides the best performance and avoids all kinds of problems. Certainly, if you’re implementing an activation factory you must ensure that it is agile even if the runtime class is not. So how does C++/WinRT help or support working with agility?

Here’s a simple example of an implementation:

using namespace Windows::Foundation;

struct Sample : implements<Sample, IStringable>
{
    hstring ToString()
    {
        return L"Sample";
    }
};

This implementation is agile by default. What that means is that unless you say otherwise, the implements class template will implement both IAgileObject and IMarshal. The latter simply uses CoCreateFreeThreadedMarshaler to do the right thing for legacy code that doesn’t know about IAgileObject. So how does one check for agility?

com_ptr<IAgileObject> agile = sample.as<IAgileObject>();

The as method will throw an exception if the sample object is not agile (the query for IAgileObject failed). To avoid the exception, you can use try_as instead:

com_ptr<IAgileObject> maybe = sample.try_as<IAgileObject>();

if (maybe)
{
    // yep, sample is agile!
}

Of course, the IAgileObject interface has no methods beyond those inherited from IUnknown. So, it’s more typical that you might write code like this:

if (sample.try_as<IAgileObject>())
{
    // yep, sample is agile!
}

You see, the IAgileObject interface is just a “marker” interface. The success or failure of the query tells you something. The interface itself is useless. What if you really don’t want an agile implementation? No problem, you can simply request that when you define your implementation:

struct Sample : implements<Sample, non_agile, IStringable>
{
    ...
};

Now, if you run the following check it will report that the object is not agile:

if (sample.try_as<IAgileObject>())
{
    printf("I'm agile :)\n");
}
else
{
    printf("I'm not agile :(\n");
}

And it doesn’t matter where in the variadic parameter pack non_agile appears:

struct Sample : implements<Sample, IStringable, IClosable, non_agile>
{
    ...
};

Of course, this doesn’t preclude you from implementing IMarshal yourself. You might use non_agile to avoid the default agility implementation and then go and implement IMarshal yourself, perhaps to support marhal-by-value semantics.

Now what if you have some poor object that’s not agile but you need to pass it around in some potentially agile context? No problem, the agile_ref helpers comes to the rescue:

IStringable sample = make<Sample>();

You can wrap it inside an agile_ref as follows:

agile_ref<IStringable> ref = sample;

The ref object may be freely passed to a thread in a different apartment:

co_await resume_background();

IStringable local = ref.get();

hstring value = local.ToString();

The get method returns a proxy that may safely be used within the thread context in which the get method was called.

I hope that helps. Join me next time as we continue to explore C++/WinRT. Give C++/WinRT a try today. Got a question? Post it here and we’ll do our best to help.

Next: Optimizing Activation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s