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
- Interface IAgileObject
- Interface IMarshal
- Modelo de struct winrt::agile_ref
- modelo de struct winrt::implements
- Modelo de função winrt::make_agile
- Struct de marcador winrt::non_agile
- função winrt::Windows::Foundation::IUnknown::as
- Função winrt::Windows::Foundation::IUnknown::try_as