Objetos Agile em C++/WinRT

Na grande maioria dos casos, uma instância de uma classe Windows Runtime pode ser acessada de qualquer thread (assim como a maioria dos objetos C++ padrão pode). Essa classe 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). É uma boa configuração padrão que todo objeto do Windows Runtime seja ágil, portanto seus próprios tipos C++/WinRT também sã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 tem a ver com os requisitos de reentração. Mas cada vez mais, até mesmo as APIs de interface do usuário (UI) oferecem objetos ágeis. Em geral, a agilidade é a opção mais simples e com maior desempenho. 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.

Note

O Windows Runtime é baseado em COM. Em termos COM, uma classe agile é registrada com ThreadingModel = Both. 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 uma implementação de exemplo de uma classe de runtime para 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 optamos por não participar, esta implementação é ágil. A estrutura base winrt::implements implementa IAgileObject e IMarshal. A implementação IMarshal usa CoCreateFreeThreadedMarshaler para fazer a coisa certa para o código herdado que não sabe sobre IAgileObject.

Esse código verifica um objeto quanto à 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 lidar com uma exceção, você pode chamar IUnknown::try_as em vez disso.

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

IAgileObject não tem métodos próprios, portanto, você não pode fazer muito com ele. Essa próxima variante, então, é mais típica.

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

IAgileObject é uma interface de marcador. O simples sucesso ou fracasso ao consultar IAgileObject é tudo o que você obtém disso em termos de informação e utilidade.

Recusar o suporte a objetos agile

Você pode optar explicitamente por desabilitar o suporte a objetos ágeis passando a estrutura marcadora winrt::non_agile como parâmetro de modelo para 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.

Independentemente de você desativar ou não a agilidade, você mesmo pode implementar IMarshal. Por exemplo, você pode usar o marcador winrt::non_agile para evitar a implementação de agilidade padrão e implementar o IMarshal por conta própria, talvez para dar suporte à semântica marshal-by-value.

Referências ágeis (winrt::agile_ref)

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

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

Ou você pode usar a função auxiliar 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 usado com segurança dentro do contexto de thread no qual get é chamado.

APIs importantes