Objetos ágiles en C++/WinRT

En la mayoría de los casos, se puede acceder a una instancia de una clase en tiempo de ejecución de Windows desde cualquier subproceso (al igual que la mayoría de los objetos C++ estándar). Esta clase en tiempo de ejecución de Windows es ágil. Solo un número reducido de clases de Windows Runtime que se incluyen con Windows no son ágiles, pero cuando las consumas deberás tener en cuenta su modelo de subprocesos y el comportamiento de serialización (la serialización transmite datos a través de un límite de apartamento). Es una buena opción predeterminada de que todos los objetos Windows Runtime sean ágiles, por lo que tus propios tipos C++/WinRT son ágiles de manera predeterminada.

Sin embargo, puede optar por rechazarlos. Es posible que tenga un buen motivo para exigir que un objeto de su tipo resida, por ejemplo, en un determinado contenedor uniproceso. Por lo general, esto está relacionado con los requisitos de reentrada. Pero cada vez más, incluso las API de interfaces de usuarios, ofrecen objetos ágiles. En general, la agilidad es la opción más sencilla y aporta el mayor rendimiento. Además, cuando se implementa una fábrica de activaciones, debe ser ágil aunque tu correspondiente clase en tiempo de ejecución no lo sea.

Nota

Windows Runtime se basa en COM. En términos COM, una clase ágil se registra con ThreadingModel = both. Para más información sobre los modelos de subprocesos COM y los apartamentos, consulta Descripción y uso de los modelos de subprocesos COM.

Ejemplos de código

Usemos un ejemplo de implementación de una clase en tiempo de ejecución para mostrar cómo C++/WinRT admite la agilidad.

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

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

Dado que no lo hemos rechazado todavía, esta implementación es ágil. La estructura base winrt::implements implementa IAgileObject e IMarshal. La implementación IMarshal usa CoCreateFreeThreadedMarshaler para hacer lo correcto para el código heredado que no conoce IAgileObject.

Este código comprueba la agilidad de un objeto. La llamada a IUnknown::as lanza una excepción si myimpl no es ágil.

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

En lugar de controlar una excepción, puedes llamar a IUnknown::try_as en su lugar.

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

IAgileObject no tiene ningún método propio, por lo que no podrás hacer mucho con él. Por lo tanto, esta siguiente variante es más habitual.

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

IAgileObject es una interfaz de marcador. El mero éxito o error de consultar IAgileObject es la extensión de la información y la utilidad que obtienes a partir de él.

Rechazar la compatibilidad con objetos ágiles

Puedes optar por rechazar explícitamente la compatibilidad con objetos ágiles al pasar la estructura de marcador winrt::non_agile como un argumento de plantilla a tu clase base.

Si derivas directamente de winrt::implements.

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

Si vas a crear una clase en tiempo de ejecución.

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

No importa dónde aparezca la estructura del marcador dentro del paquete de parámetro variádicas.

Ya optes o no por la agilidad, puedes implementar IMarshal tú mismo. Por ejemplo, puede usar el marcador winrt::non_agile para evitar la implementación de agilidad predeterminada e implementar IMarshal usted mismo, para así admitir la semántica de cálculo de referencias por valor.

Referencias ágiles (winrt::agile_ref)

Si vas a consumir un objeto que no es ágil, pero necesitas pasarlo a algún contexto potencialmente ágil, una opción consiste en usar la plantilla de estructura winrt::agile_ref para obtener una referencia ágil a una instancia de un tipo que no sea ágil, o a una interfaz de un objeto que no sea ágil.

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

O bien, puedes usar la función auxiliar winrt::make_agile.

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

En cualquier caso, agile ya puede pasarse libremente a un subproceso en un apartamento diferente y usarse allí.

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

La llamada a agile_ref::get devuelve un proxy que puede usarse con seguridad dentro del contexto de subproceso donde se llame a get.

API importantes