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

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

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

Примечание.

Среда выполнения Windows основана на модели COM. В рамках модели COM гибкий класс регистрируется с помощью ThreadingModel = Both. Подробные сведения о моделях потоков COM см. в статье Understanding and Using COM Threading Models (Общие сведения о потоковых моделях 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::implements реализует IAgileObject и IMarshal. Реализация IMarshal использует CoCreateFreeThreadedMarshaler для поддержки устаревшего кода, в котором нет данных об IAgileObject.

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

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 чтобы избежать реализации гибкости по умолчанию и самостоятельно реализовать интерфейс IMarshal например для поддержки семантики маршалирования по значению.

Гибкие ссылки (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