Utilizar APIs com C++/WinRT

Este tópico mostra como utilizar as APIs do C++/WinRT, sejam elas parte do Windows, implementadas por um fornecedor de componentes de terceiros, ou implementada por você mesmo.

Importante

Para que os exemplos de código deste tópico sejam curtos e fáceis de experimentar, você pode reproduzi-los criando um novo projeto de Aplicativo de Console do Windows (C++/WinRT) e copiando e colando o código. No entanto, você não pode consumir tipos personalizados (de terceiros) arbitrários de Windows Runtime de um aplicativo não empacotado como esse. Você pode consumir apenas os tipos do Windows dessa maneira.

Para consumir tipos personalizados (de terceiros) de Windows Runtime de um aplicativo de console, você precisará conceder ao aplicativo um identificador de pacote para que ele possa resolver o registro de tipos personalizados consumidos. Para obter mais informações, confira Projeto de Empacotamento de Aplicativo do Windows.

Como alternativa, crie um novo projeto com base nos modelos de projeto Aplicativo em Branco (C++/WinRT) , Aplicativo Principal (C++/WinRT) ou Componente do Windows Runtime (C++/WinRT) . Esses tipos de aplicativo já têm um identificador de pacote.

Se a API estiver em um namespace do Windows

Esse é o caso mais comum de utilização de uma API do Windows Runtime. Para cada tipo em um namespace do Windows definido nos metadados, C ++/WinRT define um equivalente C++ amigável (chamado de tipo projetado). Um tipo projetado tem o mesmo nome totalmente qualificado do tipo do Windows, mas é colocado no namespace C++ winrt usando a sintaxe do C++. Por exemplo, Windows::Foundation::Uri é projetado no C++/WinRT como winrt::Windows::Foundation::Uri.

A seguir apresentamos um exemplo de código simples. Se quiser copiar e colar os exemplos de código a seguir diretamente no arquivo de código-fonte principal de um projeto de Aplicativo de Console do Windows (C++/WinRT) , primeiro defina Não usar Cabeçalhos Pré-compilados nas propriedades do projeto.

// main.cpp
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

int main()
{
    winrt::init_apartment();
    Uri contosoUri{ L"http://www.contoso.com" };
    Uri combinedUri = contosoUri.CombineUri(L"products");
}

O cabeçalho winrt/Windows.Foundation.h incluído faz parte do SDK, e pode ser encontrado dentro da pasta %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\. Os cabeçalhos nessa pasta contêm tipos de namespace do Windows projetados em C++/WinRT. Neste exemplo, winrt/Windows.Foundation.h contém winrt::Windows::Foundation::Uri, que é o tipo projetado para a classe de runtime Windows::Foundation::Uri.

Dica

Sempre que desejar usar um tipo de namespace do Windows, inclua o cabeçalho do C++/WinRT correspondente a esse namespace. As diretivas using namespace são opcionais, mas convenientes.

No exemplo de código acima, após iniciar o C++/WinRT, alocamos em pilhas um valor do tipo projetado winrt::Windows::Foundation::Uri por meio de um de seus construtores documentados publicamente (Uri(String), neste exemplo). Este é o caso de uso mais comum, e normalmente é tudo o que você precisa fazer. Quando tiver um valor de tipo projetado do C++/WinRT, é possível tratá-lo como se fosse uma instância do tipo real do Windows Runtime, pois já tem todos os mesmos membros.

Na verdade, esse valor projetado é um proxy; essencialmente, é apenas um ponteiro inteligente de um objeto existente. A chamada de construtores do valor projetado RoActivateInstance para criar uma instância da classe existente do Windows Runtime (Windows.Foundation.Uri, neste caso) e armazenar a interface padrão do objeto no novo valor projetado. Como mostrado abaixo, suas chamadas para os membros do valor projetado na verdade delegam, por meio do ponteiro inteligente, para o objeto existente, que é onde ocorrem alterações de estado.

O tipo projetado Windows::Foundation::Uri

Quando o valor contosoUri não está mais no escopo, ele destrói e libera a referência à interface padrão. Se essa é a última referência ao objeto existente Windows.Foundation.Uri do Windows Runtime, o objeto existente também é destruído.

Dica

Um tipo projetado é um wrapper sobre um tipo Windows Runtime para fins de consumo de suas APIs. Por exemplo, uma interface projetada é um wrapper sobre uma interface do Windows Runtime.

Cabeçalhos de projeção do C++/WinRT

Para utilizar APIs de namespace do Windows a partir do C++/WinRT, é preciso incluir os cabeçalhos da pasta %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt. Você deve incluir os cabeçalhos correspondentes a cada namespace usado.

Por exemplo, para o namespace Windows::Security::Cryptography::Certificates, as definições de tipo do C++/WinRT equivalentes estão em winrt/Windows.Security.Cryptography.Certificates.h. A inclusão desse cabeçalho fornece acesso a todos os tipos no namespace Windows::Security::Cryptography::Certificates .

Às vezes, um cabeçalho de namespace incluirá partes de cabeçalhos de namespace relacionados, mas você não deve confiar nesse detalhe de implementação. Inclua explicitamente os cabeçalhos para os namespaces que você usa.

Por exemplo, o método Certificate::GetCertificateBlob retorna uma interface Windows::Storage::Streams::IBuffer. Antes de chamar o método Certificate::GetCertificateBlob, você deve incluir o arquivo do cabeçalho de namespace winrt/Windows.Storage.Streams.h para garantir que você possa receber e operar o Windows::Storage::Streams::IBuffer retornado.

Esquecer de incluir os cabeçalhos de namespace necessários antes de usar tipos nesse namespace é uma fonte comum de erros de compilação.

Acessar membros pelo objeto, por uma interface ou pela ABI

Com a projeção do C++/WinRT, a representação do tempo de execução de uma classe do Windows Runtime é somente uma interface ABI subjacente. No entanto, para sua conveniência, é possível codificar em relação às classes da maneira que o autor pretendia. Por exemplo, chame o método ToString de um Uri como se fosse um método da classe (na verdade, por baixo dos panos, é um método na interface IStringable separada).

WINRT_ASSERT é uma definição de macro e se expande para _ASSERTE.

Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.

Essa conveniência é obtida por meio de uma consulta na interface apropriada. Entretanto, você está sempre no controle. É possível optar por trocar um pouco dessa conveniência por desempenho ao recuperar a interface IStringable por conta própria e usando-a diretamente. No exemplo de código abaixo, obtém-se um ponteiro de interface IStringable real no tempo de execução (por meio de uma consulta única). Depois disso, a chamada para ToString é direta e evita qualquer chamada subsequente para QueryInterface.

...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");

Escolha essa técnica se souber que chamará vários métodos na mesma interface.

A propósito, é possível acessar membros no nível da ABI, se quiser. O exemplo de código abaixo mostra como fazer isso, e é possível saber mais e obter exemplos de código em Interoperabilidade entre C++/WinRT e ABI.

#include <Windows.Foundation.h>
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
using namespace winrt::Windows::Foundation;

int main()
{
    winrt::init_apartment();
    Uri contosoUri{ L"http://www.contoso.com" };

    int port{ contosoUri.Port() }; // Access the Port "property" accessor via C++/WinRT.

    winrt::com_ptr<ABI::Windows::Foundation::IUriRuntimeClass> abiUri{
        contosoUri.as<ABI::Windows::Foundation::IUriRuntimeClass>() };
    HRESULT hr = abiUri->get_Port(&port); // Access the get_Port ABI function.
}

Inicialização atrasada

No C++/WinRT, cada tipo projetado tem um construtor std::nullptr_t especial do C++/WinRT. Com exceção desse, todos os construtores de tipo projetado – incluindo o construtor padrão – fazem com que um objeto de suporte do Windows Runtime seja criado e fornecem um ponteiro inteligente para ele. Portanto, essa regra se aplica em qualquer lugar em que o construtor padrão é usado, como variáveis locais não inicializadas, variáveis globais não inicializadas e variáveis de membro não inicializadas.

Se, por outro lado, você desejar construir uma variável de um tipo projetado sem construir um objeto do Windows Runtime (para atrasar esse trabalho até um momento posterior), poderá fazer isso. Declare a variável ou o campo usando o construtor std::nullptr_t especial do C++/WinRT (que a projeção do C++/WinRT injeta em toda classe de runtime). Usamos esse construtor especial com m_gamerPicBuffer no exemplo de código abaixo.

#include <winrt/Windows.Storage.Streams.h>
using namespace winrt::Windows::Storage::Streams;

#define MAX_IMAGE_SIZE 1024

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

int main()
{
    winrt::init_apartment();
    Sample s;
    // ...
    s.DelayedInit();
}

Todos os construtores no tipo projetado, exceto pelo construtor std::nullptr_t, resultam na criação do objeto existente do Windows Runtime. O construtor std::nullptr_t é, essencialmente, inoperante. Ele espera que o objeto projetado seja iniciado posteriormente. Portanto, se uma classe de runtime tem ou não um construtor padrão, use essa técnica para uma inicialização atrasada eficiente.

Essa consideração afeta outros lugares em que se chama o construtor padrão, como em vetores e mapas. Considere este exemplo de código, para o qual será necessário um projeto Aplicativo em Branco (C++/WinRT) .

std::map<int, TextBlock> lookup;
lookup[2] = value;

A atribuição cria um novo TextBlock e o substitui imediatamente por value. A solução é a seguinte.

std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);

Confira também Como o construtor padrão afeta as coleções.

Não inicializar com atraso por engano

Tenha cuidado para não invocar o construtor std::nullptr_t por engano. A resolução de conflitos do compilador o favorece em detrimento dos construtores de fábrica. Por exemplo, considere estas duas definições de classe de runtime.

// GiftBox.idl
runtimeclass GiftBox
{
    GiftBox();
}

// Gift.idl
runtimeclass Gift
{
    Gift(GiftBox giftBox); // You can create a gift inside a box.
}

Digamos que queremos construir um Gift que não está dentro de uma caixa (um Gift construído com um GiftBox não inicializado). Primeiro, vamos ver a maneira errada de fazer isso. Sabemos que há um construtor Gift que usa um GiftBox. Mas, se estivermos tentados a passar um GiftBox nulo (invocando o construtor Gift por meio da inicialização uniforme, como fazemos abaixo), não teremos o resultado desejado.

// These are *not* what you intended. Doing it in one of these two ways
// actually *doesn't* create the intended backing Windows Runtime Gift object;
// only an empty smart pointer.

Gift gift{ nullptr };
auto gift{ Gift(nullptr) };

O que você terá aqui é um Gift não inicializado. Você não recebe um Gift com um GiftBox não inicializado. Esta é a forma correta de fazer isso.

// Doing it in one of these two ways creates an initialized
// Gift with an uninitialized GiftBox.

Gift gift{ GiftBox{ nullptr } };
auto gift{ Gift(GiftBox{ nullptr }) };

No exemplo incorreto, passar um literal nullptr resolve em favor do construtor de inicialização com atraso. Para resolver em favor do construtor de fábrica, o tipo do parâmetro deve ser GiftBox. Você ainda terá a opção de passar explicitamente um GiftBox com inicialização com atraso, conforme mostrado no exemplo correto.

O exemplo a seguir também está correto, pois o parâmetro tem o tipo GiftBox e não std::nullptr_t.

GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.

A ambiguidade surge apenas quando você passa um literal nullptr.

Não construir por cópia por engano.

Este aviso é semelhante ao descrito na seção Não inicializar com atraso por engano acima.

Além do construtor de inicialização com atraso, a projeção de C++/WinRT também injeta um construtor de cópia em cada classe de runtime. Trata-se de um construtor de parâmetro único que aceita o mesmo tipo que o objeto que está sendo construído. Os ponteiro inteligente resultante aponta para o mesmo objeto do Windows Runtime que é apontado pelo seu parâmetro de construtor. O resultado são dois objetos de ponteiro inteligente apontando para o mesmo objeto.

Esta é uma definição de classe de runtime que usaremos nos exemplos de código.

// GiftBox.idl
runtimeclass GiftBox
{
    GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}

Vamos supor que queremos construir um GiftBox dentro de um GiftBox maior.

GiftBox bigBox{ ... };

// These are *not* what you intended. Doing it in one of these two ways
// copies bigBox's backing-object-pointer into smallBox.
// The result is that smallBox == bigBox.

GiftBox smallBox{ bigBox };
auto smallBox{ GiftBox(bigBox) };

A forma correta de fazer isso é chamar a fábrica de ativação explicitamente.

GiftBox bigBox{ ... };

// These two ways call the activation factory explicitly.

GiftBox smallBox{
    winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
auto smallBox{
    winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };

Se a API é implementada em um componente do Windows Runtime

Esta seção se aplica se você criou o componente por conta própria ou se ele veio por meio de um fornecedor.

Observação

Para obter informações sobre como instalar e usar o C++/WinRT Visual Studio Extension (VSIX) e o pacote NuGet (que juntos fornecem um modelo de projeto e suporte ao build), confira as informações de suporte do Visual Studio para C++/WinRT.

No seu projeto de aplicativo, faça referência ao arquivo de metadados do Windows Runtime do componente do Windows Runtime (.winmd) e compile. Durante a compilação, a ferramenta cppwinrt.exe gera uma biblioteca C++ padrão que descreve completamente – ou projeta – a superfície da API do componente. Em outras palavras, a biblioteca gerada contém os tipos projetados para o componente.

Em seguida, assim como acontece com um tipo de namespace do Windows, inclua um cabeçalho e construa o tipo projetado por meio de um de seus construtores. O código de inicialização do seu projeto de aplicativo registra a classe de runtime, e o construtor do tipo projetado chama RoActivateInstance para ativar a classe de runtime do componente mencionado.

#include <winrt/ThermometerWRC.h>

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    ThermometerWRC::Thermometer thermometer;
    ...
};

Para saber mais, obter códigos e ver um passo a passo do consumo de APIs implementadas em um componente do Windows Runtime, confira Componentes do Windows Runtime com C++/WinRT e Criar eventos em C++/WinRT.

Se a API é implementada no projeto utilizado

O exemplo de código nesta seção é extraído do tópico Controles XAML; fazer a associação com uma propriedade C++/WinRT. Confira esse tópico para obter mais detalhes, código e um guia passo a passo de como consumir uma classe de runtime implementada no mesmo projeto que a consome.

Um tipo utilizado a partir da interface de usuário do XAML deve ser uma classe de runtime, mesmo que esteja no mesmo projeto do XAML. Para esse cenário, gere um tipo projetado a partir de metadados do Windows Runtime da classe de tempo de execução (.winmd). Novamente, você incluirá um cabeçalho, mas terá uma opção entre as maneiras do C++/WinRT versão 1.0 ou 2.0 de construir a instância da classe de runtime. O método da versão 1.0 usa winrt::make; o método da versão 2.0 é conhecido como construção uniforme. Vamos examinar cada um deles separadamente.

Como construir usando winrt::make

Vamos começar com o método padrão (C++/WinRT versão 1.0), pois é uma boa ideia ter, pelo menos, familiaridade com esse padrão. Construa o tipo projetado por meio do construtor std::nullptr_t. Esse construtor não realiza qualquer inicialização, portanto, em seguida é preciso atribuir um valor para a instância por meio da função auxiliar winrt::make, passando quaisquer argumentos de construtor necessários. Uma classe de tempo de execução implementada no mesmo projeto que o código utilizado não precisa ser registrada, nem instanciada por meio de ativação do Windows Runtime/COM.

Para obter um guia passo a passo completo, confira Controles XAML; fazer a associação com uma propriedade C++/WinRT. Esta seção mostra os extratos desse guia passo a passo.

// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        BookstoreViewModel MainViewModel{ get; };
    }
}

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
    ...
    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
};
...

// MainPage.cpp
...
#include "BookstoreViewModel.h"

MainPage::MainPage()
{
    m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
    ...
}

Construção uniforme

Com o C++/WinRT versão 2.0 e posterior, há uma forma de construção otimizada disponível para você, conhecida como construção uniforme (confira Novidades e alterações em C++ /WinRT 2.0).

Para obter um guia passo a passo completo, confira Controles XAML; fazer a associação com uma propriedade C++/WinRT. Esta seção mostra os extratos desse guia passo a passo.

Para usar a construção uniforme em vez de winrt::make, você precisará de uma fábrica de ativação. Uma boa maneira de gerar uma é adicionar um construtor à sua IDL.

// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

Em seguida, em MainPage.h, declare e inicialize m_mainViewModel em apenas uma etapa, conforme mostrado abaixo.

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
    ...
    private:
        Bookstore::BookstoreViewModel m_mainViewModel;
        ...
    };
}
...

E, em seguida, no construtor MainPage no MainPage.cpp, não há necessidade do código m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Para obter mais informações sobre a construção uniforme, confira Aceitar a construção uniforme e o acesso direto de implementação.

Instanciar e retornar tipos e interfaces projetados

Veja um exemplo da aparência dos tipos projetados e interfaces ao utilizar seu projeto. Lembre-se de que um tipo projetado (como o deste exemplo), é gerado por ferramenta e não é algo que você pode criar por conta própria.

struct MyRuntimeClass : MyProject::IMyRuntimeClass, impl::require<MyRuntimeClass,
    Windows::Foundation::IStringable, Windows::Foundation::IClosable>

MyRuntimeClass é um tipo projetado; as interfaces projetadas incluem IMyRuntimeClass, IStringable e IClosable. Este tópico mostra as diferentes maneiras de como instanciar um tipo projetado. Este é um lembrete e um resumo, usando MyRuntimeClass como exemplo.

// The runtime class is implemented in another compilation unit (it's either a Windows API,
// or it's implemented in a second- or third-party component).
MyProject::MyRuntimeClass myrc1;

// The runtime class is implemented in the same compilation unit.
MyProject::MyRuntimeClass myrc2{ nullptr };
myrc2 = winrt::make<MyProject::implementation::MyRuntimeClass>();
  • É possível acessar os membros de todas as interfaces de um tipo projetado.
  • E retornar um tipo projetado para um chamador.
  • Tipos projetados e interfaces derivam de winrt::Windows::Foundation::IUnknown. Assim, chame IUnknown::as em um tipo projetado ou interface para consultar outras interfaces projetadas, que também podem ser usadas ou retornadas para um chamador. A função de membro as funciona como QueryInterface.
void f(MyProject::MyRuntimeClass const& myrc)
{
    myrc.ToString();
    myrc.Close();
    IClosable iclosable = myrc.as<IClosable>();
    iclosable.Close();
}

Alocadores de ativação

A maneira conveniente e direta de criar um objeto de C++/WinRT é a seguinte.

using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };

Pode haver momentos em que se deseja criar o alocador de ativação por conta própria e, em seguida, criar objetos a partir dele para sua conveniência. Veja alguns exemplos mostrando como fazer isso usando o modelo de função winrt::get_activation_factory.

using namespace winrt::Windows::Globalization::NumberFormatting;
...
auto factory = winrt::get_activation_factory<CurrencyFormatter, ICurrencyFormatterFactory>();
CurrencyFormatter currency = factory.CreateCurrencyFormatterCode(L"USD");
using namespace winrt::Windows::Foundation;
...
auto factory = winrt::get_activation_factory<Uri, IUriRuntimeClassFactory>();
Uri uri = factory.CreateUri(L"http://www.contoso.com");

As classes nos dois exemplos acima são tipos de um namespace do Windows. No exemplo a seguir, ThermometerWRC::Thermometer é um tipo personalizado implementado em um componente do Windows Runtime.

auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();

Ambiguidades de membro/tipo

Quando uma função de membro tem o mesmo nome de um tipo, há ambiguidade. As regras para a pesquisa de nome não qualificado do C++ em funções de membro fazem com que ele pesquise a classe antes de fazer a pesquisa em namespaces. A regra a falha de substituição não é um erro (SFINAE) não se aplica (ela se aplica durante a resolução de sobrecarga dos modelos de função). Portanto, se o nome dentro da classe não fizer sentido, o compilador não continuará procurando uma correspondência melhor, ele simplesmente relatará um erro.

struct MyPage : Page
{
    void DoWork()
    {
        // This doesn't compile. You get the error
        // "'winrt::Windows::Foundation::IUnknown::as':
        // no matching overloaded function found".
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Style>() };
    }
}

Acima, o compilador pensa que você está passando FrameworkElement.Style() (que, no C++/WinRT, é uma função membro) como o parâmetro de modelo para IUnknown::as. A solução é forçar o nome Style a ser interpretado como o tipo Windows::UI::Xaml::Style.

struct MyPage : Page
{
    void DoWork()
    {
        // One option is to fully-qualify it.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Windows::UI::Xaml::Style>() };

        // Another is to force it to be interpreted as a struct name.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<struct Style>() };

        // If you have "using namespace Windows::UI;", then this is sufficient.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Xaml::Style>() };

        // Or you can force it to be resolved in the global namespace (into which
        // you imported the Windows::UI::Xaml namespace when you did
        // "using namespace Windows::UI::Xaml;".
        auto style = Application::Current().Resources().
            Lookup(L"MyStyle").as<::Style>();
    }
}

A pesquisa de nome não qualificado tem uma exceção especial no caso de o nome ser seguido por ::; nesse caso, ele ignora funções, variáveis e valores de enumeração. Isso permite realizar ações semelhantes às mostradas a seguir.

struct MyPage : Page
{
    void DoSomething()
    {
        Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
    }
}

A chamada a Visibility() é resolvida para o nome da função de membro UIElement.Visibility. Mas o parâmetro Visibility::Collapsed segue a palavra Visibility com :: e, portanto, o nome do método é ignorado e o compilador localiza a classe de enumeração.

APIs importantes