Objetos ágeis em C++/WinRT

Na maior parte dos casos, uma instância da classe do Windows Runtime pode ser acessada de qualquer thread (da mesma forma que a maioria dos objetos C++ podem). Essa classe do Windows Runtime é ágil. Apenas poucas classes do Windows Runtime que acompanham o Windows não são ágeis, mas ao consumi-las, é necessário levar em consideração o modelo de threading e o comportamento de marshaling (transmissão de dados por um limite de apartment). É um padrão conveniente que todos os objetos do Windows Runtime sejam ágeis, portanto, os seus próprios tipos do C++/WinRT serão ágeis por padrão.

No entanto, é possível recusá-los. Você pode ter um motivo convincente para exigir que um objeto do seu tipo resida, por exemplo, em um determinado single-threaded apartment. Normalmente, isso envolve os requisitos de reentrância. Entretanto, cada vez mais, até mesmo as APIs da interface do usuário oferecem objetos ágeis. Em geral, a agilidade é a opção mais simples e eficiente. Além disso, quando você implementa um alocador de ativação, ele deve ser ágil mesmo que a classe de runtime correspondente não seja.

Observação

O Windows Runtime se baseia em COM. Em termos COM, uma classe ágil é registrada com ThreadingModel = Ambos. Para saber mais sobre os modelos de threading COM e apartments, confira as noções básicas e uso de modelos de threading COM.

Exemplos de código

Vamos usar um exemplo de implementação de uma classe de runtime par mostrar como o C++/WinRT dá suporte à agilidade.

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

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

Como não recusamos a opção, essa implementação é ágil. O struct base winrt::implements implementa IAgileObject e IMarshal. A implementação IMarshal usa CoCreateFreeThreadedMarshaler para fazer a coisa certa com relação ao código herdado que não sabe sobre IAgileObject.

Esse código verifica um objeto em busca de agilidade. A chamada para IUnknown::as gera uma exceção se myimpl não for ágil.

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

Em vez de manipular uma exceção, você pode chamar IUnknown::try_as.

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

O IAgileObject não possui métodos próprios, por isso você não pode fazer muito com ele. A próxima variante, então, é mais comum.

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

O IAgileObject é uma interface de marcador. O mero êxito ou falha da consulta por IAgileObject é a extensão da informação e utilidade que você obtém dela.

Recusar o suporte ao objeto ágil

Você pode optar por recusar explicitamente o suporte ao objeto ágil, passando o struct de marcador winrt::non_agile como um argumento de modelo para a sua classe base.

Se você derivar diretamente de winrt::implements.

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

Se você estiver criando uma classe de runtime.

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

Não importa onde o struct de marcador apareça no pacote de parâmetros variadic.

Recusando agilidade ou não, você poderá implementar IMarshal por conta própria. Por exemplo, você pode usar o marcador winrt::non_agile para evitar a implementação de agilidade padrão e implementar IMarshal por conta própria para, talvez, oferecer suporte à semântica de marshal-por-valor.

Referências ágeis (winrt::agile_ref)

Se você estiver consumindo um objeto que não seja ágil, mas precisa passá-lo em algum contexto potencialmente ágil, uma opção será usar o modelo de struct winrt::agile_ref a fim de obter uma referência ágil para uma instância de tipo não ágil, ou para a interface de um objeto não ágil.

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

Ou você poderá usar a função auxiliar do winrt::make_agile.

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

Em ambos os casos, agora agile pode ser livremente passado a um thread em um apartment diferente e ser usado lá.

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

A chamada agile_ref::get retorna um proxy que pode ser utilizado com segurança dentro do contexto de thread no qual get é chamado.

APIs importantes