Гибкие объекты в C++/WinRT

В большинстве случаев доступ к экземпляру класса среда выполнения Windows можно получить из любого потока (как и большинство стандартных объектов C++). Такой класс среда выполнения Windows является гибким. Лишь небольшое число классов среда выполнения Windows, поставляемых с Windows, не являются agile-объектами, но при работе с ними необходимо учитывать их модель потоков и поведение при маршалинге (маршалинг — это передача данных через границу апартамента). Для каждого объекта среда выполнения Windows хорошо, если он по умолчанию является agile-объектом, поэтому ваши собственные типы C++/WinRT также по умолчанию являются agile-объектами.

Но вы можете отказаться от этого. У вас могут быть убедительные основания требовать, чтобы объект вашего типа находился, например, в определённом однопоточном апартаменте. Обычно это связано с требованиями к повторному входу. Но все чаще даже API пользовательского интерфейса предлагают гибкие объекты. Как правило, гибкость является самым простым и наиболее производительным вариантом. Кроме того, при реализации фабрики активации необходимо, чтобы она была agile, даже если соответствующий класс среды выполнения agile не является.

Note

среда выполнения Windows основана на COM. В терминах COM агильный класс регистрируется как ThreadingModel = Both. Дополнительные сведения о моделях потоков COM и апартаментах см. в статье Основы и использование моделей потоков COM.

Примеры кода

Давайте рассмотрим пример реализации класса среды выполнения, чтобы показать, как C++/WinRT поддерживает гибкость.

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

struct MyType : winrt::implements<MyType, IStringable>
{
    winrt::hstring ToString(){ ... }
};

Поскольку мы не отказались от этого, это решение остается гибким. Winrt::реализует базовую структуру, реализуя IAgileObject и IMarshal. Реализация IMarshal использует CoCreateFreeThreadedMarshaler, чтобы корректно работать с устаревшим кодом, который не поддерживает IAgileObject.

Этот код проверяет объект для гибкости. Вызов IUnknown::as вызывает исключение, если myimpl не является agile-объектом.

winrt::com_ptr<MyType> myimpl{ winrt::make_self<MyType>() };
winrt::com_ptr<IAgileObject> iagileobject{ myimpl.as<IAgileObject>() };

Вместо того чтобы обрабатывать исключение, вместо этого можно вызвать IUnknown::try_as .

winrt::com_ptr<IAgileObject> iagileobject{ myimpl.try_as<IAgileObject>() };
if (iagileobject) { /* myimpl is agile. */ }

IAgileObject не имеет собственных методов, поэтому вы не можете сделать много с ним. Следующий вариант, в таком случае, более типичен.

if (myimpl.try_as<IAgileObject>()) { /* myimpl is agile. */ }

IAgileObject — это интерфейс маркера. Сам факт успешного или неуспешного выполнения запроса к IAgileObject — это всё, что можно из этого узнать и какую практическую пользу извлечь.

Отказ от поддержки гибких объектов

Вы можете явно отказаться от поддержки гибких объектов, передав структуру маркера winrt::non_agile в качестве аргумента шаблона в базовый класс.

Если вы наследуете непосредственно от winrt::implements.

struct MyImplementation: implements<MyImplementation, IStringable, winrt::non_agile>
{
    ...
}

Если вы создаете класс среды выполнения.

struct MyRuntimeClass: MyRuntimeClassT<MyRuntimeClass, winrt::non_agile>
{
    ...
}

Не имеет значения, где именно в вариативном пакете параметров находится структура-маркер.

Независимо от того, вы отказываетесь от гибкости, вы можете реализовать IMarshal самостоятельно. Например, вы можете использовать маркер winrt::non_agile, чтобы избежать стандартной реализации agile-объекта и самостоятельно реализовать IMarshal — возможно, для поддержки семантики маршалинга по значению.

Agile-ссылки (winrt::agile_ref)

Если вы используете объект, который не является гибким, но вам нужно передать его в некотором потенциально гибком контексте, то один из вариантов — использовать шаблон структуры winrt::agile_ref , чтобы получить гибкую ссылку на экземпляр негибкого типа или интерфейс не гибкого объекта.

NonAgileType nonagile_obj;
winrt::agile_ref<NonAgileType> agile{ nonagile_obj };

Кроме того, можно использовать функцию winrt::make_agile вспомогательной функции.

NonAgileType nonagile_obj;
auto agile{ winrt::make_agile(nonagile_obj) };

В любом случае agile теперь может быть свободно передан потоку в другой квартире и использоваться там.

co_await resume_background();
NonAgileType nonagile_obj_again{ agile.get() };
winrt::hstring message{ nonagile_obj_again.Message() };

Вызов agile_ref::get возвращает прокси-сервер, который может безопасно использоваться в контексте потока, в котором вызывается get.

Важные API