Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este tópico mostra como consumir APIs C++/WinRT, sejam elas parte do Windows, implementadas por um fornecedor de componentes de terceiros ou implementadas por você mesmo.
Importante
Para que os exemplos de código neste tópico sejam curtos e fáceis de experimentar, você pode reproduzi-los criando um novo aplicativo de console do Windows (C++/WinRT) projeto e copiando e colando código. No entanto, não é possível consumir tipos arbitrários e personalizados de terceiros do Windows Runtime a partir de uma aplicação não empacotada como essa. Você pode consumir apenas tipos do Windows dessa forma.
Para consumir tipos personalizados (de terceiros) do Windows Runtime a partir de uma aplicação de consola, terá de atribuir à aplicação uma identidade de pacote de modo a que consiga resolver o registo dos tipos personalizados consumidos. Para obter mais informações, consulte Windows Application Packaging Project.
Como alternativa, crie um novo projeto com base nos modelos de projeto Aplicação em Branco (C++/WinRT), Aplicação Principal (C++/WinRT), ou Componente do Tempo de Execução do Windows (C++/WinRT). Esses tipos de aplicativo já têm uma identidade de pacote.
Se a API estiver em um namespace do Windows
Este é o caso mais comum em que se consome uma API Windows Runtime. Para cada tipo em um namespace do Windows definido em metadados, o C++/WinRT define um equivalente amigável ao C++ (chamado de tipo projetado). Um tipo projetado tem o mesmo nome totalmente qualificado que o tipo Windows, mas é colocado no namespace Winrt C++ usando sintaxe C++. Por exemplo, Windows::Foundation::Uri é projetado em C++/WinRT como winrt::Windows::Foundation::Uri.
Aqui está um exemplo de código simples. Se o utilizador quiser copiar e colar os exemplos de código seguintes diretamente no ficheiro de código-fonte principal de um projeto Aplicação de Consola do Windows (C++/WinRT), defina primeiro Não usando 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, 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 tempo de execução Windows::Foundation::Uri.
Sugestão
Sempre que desejar usar um tipo de um namespace do Windows, inclua o cabeçalho C++/WinRT correspondente a esse namespace. As using namespace diretivas são facultativas, mas convenientes.
No exemplo de código acima, depois de inicializar o C++/WinRT, alocamos um valor do winrt::Windows::Foundation::Uri tipo projetado por meio de um de seus construtores documentados publicamente (Uri(String), neste exemplo). Para isto, o caso de uso mais comum, normalmente é tudo o que precisa de fazer. Depois de ter um valor de tipo projetado em C++/WinRT, você pode tratá-lo como se fosse uma instância do tipo real do Tempo de Execução do Windows, já que ele tem todos os mesmos membros.
Na verdade, aquele valor projetado é um proxy; é essencialmente um ponteiro inteligente para um objeto subjacente. O construtor do valor projetado chama RoActivateInstance para criar uma instância da classe de apoio do Windows Runtime (Windows.Foundation.Uri, neste caso) e armazenar a interface padrão desse objeto dentro do novo valor projetado. Como ilustrado abaixo, as suas chamadas para os membros do valor projetado são realmente delegadas, através do ponteiro inteligente, para o objeto de suporte, que é onde as mudanças de estado ocorrem.
Quando o valor contosoUri sai do âmbito, é destruído e libera a sua referência à interface padrão. Se essa referência for a última referência ao objeto Windows.Foundation.Uri do Tempo de Execução do Windows
Sugestão
Um tipo projetado é um wrapper sobre um tipo do Windows Runtime para fins de consumo das suas APIs. Por exemplo, uma interface projetada é um envoltório sobre uma interface Windows Runtime.
Cabeçalhos de projeção C++/WinRT
Para utilizar as APIs de namespace do Windows a partir de C++/WinRT, insira 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 C++/WinRT equivalentes residem em winrt/Windows.Security.Cryptography.Certificates.h. A inclusão desse cabeçalho dá 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, deves incluir o arquivo de cabeçalho do winrt/Windows.Storage.Streams.h namespace para garantir que possas receber e operar no 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.
Acesso aos membros através do objeto, através de uma interface, ou através da ABI
Com a projeção C++/WinRT, a representação em tempo de execução de uma classe do Windows Runtime não passa das interfaces ABI subjacentes. Mas, para sua conveniência, você pode codificar contra classes da maneira que o autor pretendia. Por exemplo, você pode chamar o método
WINRT_ASSERT é uma definição 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.
Esta conveniência é conseguida através de uma consulta para a interface apropriada. Mas você está sempre no controle. Pode optar por sacrificar um pouco dessa conveniência em troca de algum desempenho, ao recuperar a interface IStringable por si mesmo e usá-la diretamente. No exemplo de código abaixo, obtém-se um ponteiro para a interface IStringable real em tempo de execução (através de uma consulta única). Depois disso, a sua chamada para ToString é direta e evita qualquer chamada adicional para QueryInterface.
...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");
Você pode escolher essa técnica se souber que chamará vários métodos na mesma interface.
Aliás, se você quiser acessar membros no nível ABI, então você pode. O exemplo de código abaixo mostra como fazer isso, e há mais detalhes e exemplos de código em Interoperabilidade entre C++/WinRT e o 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
Em C++/WinRT, cada tipo projetado tem um construtor especial C++/WinRT std::nullptr_t. Com exceção daquele, todos os construtores de tipo projetado, incluindo o construtor padrão, fazem com que um objeto 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 seja 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, quiser construir uma variável de um tipo projetado sem que, por sua vez, seja criado um objeto de Windows Runtime de suporte (para que possa atrasar esse trabalho para mais tarde), então pode fazer isso. Declare sua variável ou campo usando esse construtor especial C++/WinRT std::nullptr_t (que a projeção C++/WinRT injeta em cada classe de tempo de execução). 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 o construtor de std::nullptr_t, fazem com que seja criado um objeto de suporte do Windows Runtime. O construtor std::nullptr_t é essencialmente um no-op. Ele espera que o objeto projetado seja inicializado em um momento subsequente. Portanto, se uma classe de tempo de execução tem um construtor padrão ou não, você pode usar essa técnica para inicialização atrasada eficiente.
Essa consideração afeta outros locais onde você está invocando o construtor padrão, como em vetores e mapas. Considere este exemplo de código, para o qual você precisará de um aplicativo em branco (C++/WinRT) projeto.
std::map<int, TextBlock> lookup;
lookup[2] = value;
A atribuição cria um novo TextBlocke, em seguida, substitui-o imediatamente por value. Aqui está o remédio.
std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);
Consulte também Como o construtor padrão afeta as coleções.
Não atrase a inicialização por engano
Tenha cuidado para não invocar o construtor std::nullptr_t por engano. A resolução de conflitos do compilador favorece-o em relação aos construtores de fábrica. Por exemplo, considere essas duas definições de classe de tempo de execução.
// 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 Presente que não esteja dentro de uma caixa (um Presente que seja construído com um GiftBox não inicializado). Primeiro, vamos ver a maneira errada de fazer isso. Sabemos que existe um construtor Gift que aceita um GiftBox. Mas se formos tentados a passar um GiftBox
// 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 recebes aqui é um presentenão inicializado. Você não recebe um de Presente
// 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 de nullptr resolve a favor do construtor de inicialização atrasada. Para resolver a favor do fabricante, o tipo do parâmetro deve ser um GiftBox. Você ainda tem a opção de passar um GiftBox que inicializa com atraso explicitamente, como mostrado no exemplo correto.
Este próximo exemplo é igualmente correto, porque o parâmetro é do tipo GiftBox, não sendo std::nullptr_t.
GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.
Só quando se passa um nullptr literal é que surge a ambiguidade.
Não crie cópias por engano.
Esse cuidado é semelhante ao descrito na seção Não atrasar a inicialização por engano acima.
Além do construtor de inicialização tardia, a projeção C++/WinRT também injeta um construtor de cópia em cada classe de execução. É um construtor de parâmetro único que aceita o mesmo tipo que o objeto que está sendo construído. O ponteiro inteligente resultante aponta para o mesmo objeto subjacente do Windows Runtime a que o seu parâmetro de construtor aponta. O resultado são dois objetos de ponteiros inteligentes apontando para o mesmo objeto subjacente.
Aqui está uma definição de classe de tempo de execução que usaremos nos exemplos de código.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}
Digamos que queremos construir uma GiftBox dentro de uma maior GiftBox.
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 maneira correta 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 for implementada em um componente do Tempo de Execução do Windows
Esta seção se aplica se você mesmo criou o componente ou se ele veio 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 suporte a modelo de projeto e compilação), consulte suporte do Visual Studio para C++/WinRT.
Em seu projeto de aplicativo, faça referência ao arquivo de metadados do Tempo de Execução do Windows (.winmd) do componente do Tempo de Execução do Windows 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 para o componente. Em outras palavras, a biblioteca gerada contém os tipos projetados para o componente.
Em seguida, assim como para um tipo de namespace do Windows, você inclui um cabeçalho e constrói o tipo projetado por meio de um de seus construtores. O código de inicialização do projeto de aplicativo registra a classe runtime e o construtor do tipo projetado chama RoActivateInstance para ativar a classe runtime do componente referenciado.
#include <winrt/ThermometerWRC.h>
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
ThermometerWRC::Thermometer thermometer;
...
};
Para mais detalhes, código e um passo a passo sobre o consumo de APIs implementadas num componente do Tempo de Execução do Windows, consulte componentes do Tempo de Execução do Windows com C++/WinRT e crie eventos em C++/WinRT .
Se a API for implementada no projeto de consumo
O exemplo de código nesta seção é retirado do tópico controles XAML; vincular a uma propriedade C++/WinRT. Consulte esse tópico para obter mais detalhes, código e um passo a passo sobre como consumir uma classe de tempo de execução implementada no mesmo projeto que a consome.
Um tipo consumido pela interface de utilizador XAML deve ser uma classe em tempo de execução, mesmo que esteja no mesmo projeto que o XAML. Para este cenário, gera-se um tipo projetado a partir dos metadados do Windows Runtime da classe de Runtime (.winmd). Novamente, você inclui um cabeçalho, mas então você tem uma escolha entre as maneiras C++/WinRT versão 1.0 ou versão 2.0 de construir a instância da classe de tempo de execução. O método da versão 1.0 usa winrt::make; o método da versão 2.0 é conhecido como construção uniforme. Vejamos cada um por sua vez.
Construindo usando winrt::make
Vamos começar com o método padrão (C++/WinRT versão 1.0), porque é uma boa ideia estar pelo menos familiarizado com esse padrão. Você constrói o tipo projetado através do seu construtor std::nullptr_t. Esse construtor não executa nenhuma inicialização, então você deve atribuir um valor à instância por meio da função auxiliar winrt::make , passando todos os argumentos de construtor necessários. Uma classe de tempo de execução implementada no mesmo projeto que o código consumidor não precisa ser registada, nem instanciada por meio da ativação do Windows Runtime/COM.
Consulte os controlos XAML , ligue a uma propriedade C++/WinRT para um tutorial completo. Esta seção mostra trechos desse 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 otimizada de construção disponível para você, conhecida como de construção uniforme (consulte Notícias e alterações em C++/WinRT 2.0).
Consulte os controlos XAML , ligue a uma propriedade C++/WinRT para um tutorial completo. Esta seção mostra trechos desse passo a passo.
Para usar a construção uniforme em vez de winrt::make, precisarás de uma fábrica de ativação. Uma boa maneira de gerar um é adicionar um construtor ao seu ficheiro IDL.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
Em seguida, declare MainPage.h e inicialize m_mainViewModel em apenas uma etapa, conforme mostrado abaixo.
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel;
...
};
}
...
E então, no MainPage construtor em MainPage.cpp, não há necessidade do código m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.
Para obter mais informações sobre construção uniforme e exemplos de código, consulte Optar pela construção uniforme e acesso direto à implementação.
Instanciando e retornando tipos e interfaces projetados
Aqui está um exemplo de como os tipos e interfaces projetados podem parecer em seu projeto de consumo. Lembre-se de que um tipo projetado (como o deste exemplo) é gerado por ferramentas e não é algo que você mesmo criaria.
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 mostrou as diferentes maneiras pelas quais você pode instanciar um tipo projetado. Aqui está um lembrete e 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>();
- Você pode aceder aos membros de todas as interfaces de um tipo projetado.
- Você pode retornar um tipo projetado para um chamador.
- Os tipos e interfaces projetados derivam de winrt::Windows::Foundation::IUnknown. Assim, você pode chamar IUnknown::as num tipo ou interface projetado para consultar outras interfaces projetadas, que você também pode usar ou retornar para um solicitante. O como função membro funciona como QueryInterface.
void f(MyProject::MyRuntimeClass const& myrc)
{
myrc.ToString();
myrc.Close();
IClosable iclosable = myrc.as<IClosable>();
iclosable.Close();
}
Fábricas de ativação
A maneira conveniente e direta de criar um objeto C++/WinRT é a seguinte.
using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };
Mas pode haver momentos em que você mesmo queira criar a fábrica de ativação e, em seguida, criar objetos a partir dela conforme sua conveniência. Aqui estão alguns exemplos para lhe mostrar como, 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. Neste próximo exemplo, ThermometerWRC::Thermometer é um tipo personalizado implementado em um componente do Ambiente de Execução do Windows.
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 que um tipo, há ambiguidade. As regras que regem a pesquisa de nomes não qualificados em funções membro em C++ fazem com que a pesquisa seja feita na classe antes de procurar nos espaços de nomes. A falha de substituição para não é uma falha de regra (erro) SFINAE; não se aplica (aplica-se durante a resolução de sobrecarga de modelos de função). Então, se o nome dentro da classe não faz sentido, então o compilador não fica procurando uma correspondência melhor — ele simplesmente relata 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, em C++/WinRT, é uma função de 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 procura de nomes não qualificados tem uma exceção especial na situação de o nome ser seguido por ::, caso em que ignora funções, variáveis e valores de Enum. Isso permite que você faça coisas assim.
struct MyPage : Page
{
void DoSomething()
{
Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
}
}
A chamada para Visibility() resolve-se para o nome da função membro UIElement.Visibility. Mas o parâmetro Visibility::Collapsed segue a palavra Visibility com ::, e assim o nome do método é ignorado, e o compilador encontra a classe enum.
APIs importantes
- função QueryInterface
- função RoActivateInstance
- Windows::Foundation::Uri classe
- modelo de função winrt::get_activation_factory
- winrt::make modelo de função
- winrt::Windows::Fundação::IUnknown struct