Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tema se muestra cómo consumir API de de C++/WinRT, ya sea que formen parte de Windows, implementadas por un proveedor de componentes de terceros o implementadas por usted mismo.
Importante
Para que los ejemplos de código de este tema sean breves y fáciles de probar, puede reproducirlos creando un nuevo proyecto de aplicación de consola de Windows (C++/WinRT) y copiando y pegando el código. Sin embargo, no puedes consumir tipos arbitrarios personalizados (de terceros) de Windows Runtime desde una aplicación desempaquetada como esa. Solo puedes consumir tipos de Windows de esa manera.
Para consumir tipos personalizados (de terceros) de Windows Runtime desde una aplicación de consola, tendrás que proporcionar a la aplicación una identidad de paquete para que pueda resolver el registro de los tipos personalizados consumidos. Para obtener más información, consulta Proyecto de empaquetado de aplicaciones de Windows.
Como alternativa, cree un nuevo proyecto a partir de los Aplicación en blanco (C++/WinRT)Core App (C++/WinRT)o Componente de Windows Runtime (C++/WinRT) plantillas de proyectos. Esos tipos de aplicación ya tienen una identidad de paquete .
Si la API está en un espacio de nombres de Windows
Este es el caso más común en el que consumirás una API de Windows Runtime. Para cada tipo de un espacio de nombres de Windows definido en metadatos, C++/WinRT define un equivalente de C++ (denominado tipo proyectado
Este es un ejemplo de código sencillo. Si desea copiar y pegar los ejemplos de código siguientes directamente en el archivo de código fuente principal de una aplicación de consola de Windows (C++/WinRT) proyecto, establezca primero Encabezados precompilados en las propiedades del proyecto.
// 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");
}
El encabezado incluido winrt/Windows.Foundation.h
forma parte del SDK, que se encuentra dentro de la carpeta %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\
. Los encabezados de esa carpeta contienen tipos del espacio de nombres de Windows que se proyectan en C++/WinRT. En este ejemplo, winrt/Windows.Foundation.h
contiene winrt::Windows::Foundation::Uri, que es el tipo proyectado para la clase en tiempo de ejecución Windows::Foundation::Uri.
Sugerencia
Siempre que quiera usar un tipo de un espacio de nombres de Windows, incluya el encabezado C++/WinRT correspondiente a ese espacio de nombres. Las directivas using namespace
son opcionales, pero convenientes.
En el ejemplo de código anterior, después de inicializar C++/WinRT, reservamos un valor del tipo proyectado winrt::Windows::Foundation::Uri utilizando uno de sus constructores documentados públicamente (en este ejemplo,Uri(String)). Para ello, en el caso de uso más común, suele ser todo lo que necesita hacer. Una vez que tengas un valor de tipo proyectado de C++/WinRT, puedes tratarlo como si fuera una instancia del tipo real de Windows Runtime, ya que tiene todos los mismos miembros.
De hecho, ese valor proyectado es un proxy; es básicamente un puntero inteligente a un objeto subyacente. Los constructores del valor proyectado llaman a RoActivateInstance para crear una instancia de la clase de Windows Runtime de respaldo (Windows.Foundation.Uri, en este caso) y almacenar la interfaz predeterminada del objeto dentro del nuevo valor proyectado. Como se muestra a continuación, las llamadas a los miembros del valor proyectado realmente delegan, a través del puntero inteligente, al objeto de respaldo; donde se producen cambios de estado.
Cuando el valor de contosoUri
queda fuera del ámbito, se destructiza y libera su referencia a la interfaz predeterminada. Si esa referencia es la última referencia al objeto subyacente de Windows Runtime Windows.Foundation.Uri, el objeto subyacente también se destruye.
Sugerencia
Un tipo proyectado es un contenedor sobre un tipo de Windows Runtime con el fin de consumir sus API. Por ejemplo, una interfaz proyectada es una envoltura sobre una interfaz del Windows Runtime.
Encabezados de proyección de C++/WinRT
Para consumir las API del espacio de nombres de Windows en el contexto de C++/WinRT, debes incluir encabezados de la carpeta %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt
. Debe incluir los encabezados correspondientes a cada espacio de nombres que use.
Por ejemplo, para el espacio de nombres Windows::Security::Cryptography::Certificates, las definiciones de tipos de C++/WinRT equivalentes residen en winrt/Windows.Security.Cryptography.Certificates.h
. Incluir ese encabezado le proporciona acceso a todos los tipos del espacio de nombres Windows::Security::Cryptography::Certificates .
A veces, un encabezado de espacio de nombres incluirá partes de encabezados de espacio de nombres relacionados, pero no debe basarse en este detalle de implementación. Incluya explícitamente los encabezados de los espacios de nombres que use.
Por ejemplo, el método Certificate::GetCertificateBlob devuelve una interfaz Windows::Storage::Streams::IBuffer .
Antes de llamar al método Certificate::GetCertificateBlob, debe incluir el archivo de encabezado del winrt/Windows.Storage.Streams.h
espacio de nombres para asegurarse de poder recibir y operar con el Windows::Storage::Streams::IBuffer devuelto.
Olvidar incluir los encabezados de espacio de nombres necesarios antes de usar tipos en ese espacio de nombres es un origen común de errores de compilación.
Acceso a los miembros a través del objeto, a través de una interfaz o a través de la ABI
Con la proyección de C++/WinRT, la representación en tiempo de ejecución de una clase de Windows Runtime no es más que las interfaces ABI subyacentes. Pero, para su comodidad, puede codificar contra las clases de la manera en que su autor pretendía. Por ejemplo, puede llamar al método ToString de un Uri como si fuera un método de la clase (de hecho, internamente, es un método en la interfaz IStringable separada).
WINRT_ASSERT
es una definición de macro y se expande a _ASSERTE.
Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.
Esta comodidad se logra a través de una consulta para la interfaz adecuada. Pero siempre estás en control. Puede optar por sacrificar un poco de esa comodidad para ganar un poco de rendimiento al recuperar la interfaz IStringable usted mismo y usarla directamente. En el ejemplo de código siguiente, obtendrá un puntero de interfaz IStringable real en tiempo de ejecución (a través de una consulta única). Después, la llamada a ToString es directa y evita cualquier llamada adicional a QueryInterface.
...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");
Puede elegir esta técnica si sabe que va a llamar a varios métodos en la misma interfaz.
Por cierto, si quieres acceder a los miembros en el nivel de ABI, puedes hacerlo. En el ejemplo de código siguiente se muestra cómo hacerlo, y hay más detalles y ejemplos de código en Interoperabilidad entre C++/WinRT y la 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.
}
Inicialización retrasada
En C++/WinRT, cada tipo proyectado tiene un constructor especial de C++/WinRT std::nullptr_t. Con la excepción de ese en particular, todos los constructores de tipo proyectado (incluido el constructor predeterminado) generan un objeto de Windows Runtime de apoyo y le proporcionan un puntero inteligente. Por lo tanto, esa regla se aplica en cualquier lugar en el que se use el constructor predeterminado, como variables locales sin inicializar, variables globales sin inicializar y variables de miembro no inicializadas.
Por otro lado, si quieres construir una variable de un tipo proyectado sin él a su vez construyendo un objeto de Windows Runtime de respaldo (para que puedas retrasar ese trabajo hasta más adelante), puedes hacerlo. Declare la variable o el campo utilizando ese constructor especial std::nullptr_t de C++/WinRT (que la proyección de C++/WinRT inserta en cada clase en tiempo de ejecución). Usamos ese constructor especial con m_gamerPicBuffer en el ejemplo de código siguiente.
#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 los constructores del tipo proyectado , excepto el constructor std::nullptr_t, provocan que se cree un objeto de respaldo de Windows Runtime. El constructor del tipo std::nullptr_t es esencialmente un no-op. Espera que el objeto proyectado se inicialice en un momento posterior. Por lo tanto, si una clase en tiempo de ejecución tiene un constructor predeterminado o no, puede usar esta técnica para una inicialización retrasada eficaz.
Esta consideración afecta a otros lugares en los que se invoca el constructor predeterminado, como en vectores y mapas. Para este ejemplo de código, necesitarás un proyecto de
std::map<int, TextBlock> lookup;
lookup[2] = value;
La asignación crea un nuevo TextBlocky luego lo sobrescribe inmediatamente con value
. Este es el remedio.
std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);
Consulte también Cómo afecta el constructor predeterminado a las colecciones.
No retrasar la inicialización por error
Ten cuidado de no invocar por error el constructor std::nullptr_t. La resolución de conflictos del compilador lo favorece sobre los constructores de fábrica. Por ejemplo, considere estas dos definiciones de clase en tiempo de ejecución.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox();
}
// Gift.idl
runtimeclass Gift
{
Gift(GiftBox giftBox); // You can create a gift inside a box.
}
Supongamos que deseamos construir un Gift que no esté dentro de una caja (un Gift que se construya utilizando un GiftBox no inicializado). En primer lugar, echemos un vistazo a la manera incorrecta de hacerlo. Sabemos que hay un constructor Gift que acepta un GiftBox. Pero si estamos tentados a pasar un GiftBox nulo (invocando el constructor Gift a través de la inicialización uniforme, como hacemos a continuación), entonces no obtendremos el resultado que queremos.
// 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) };
Lo que obtienes aquí es un Regalo no inicializado. No se obtiene un regalo con una caja de regalo sin inicializar. Esta es la correcta manera de hacerlo.
// 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 }) };
En el ejemplo incorrecto, pasar un literal de nullptr
resulta en favor del constructor de inicialización retrasada. Para resolver en favor del constructor de fábrica, el tipo del parámetro debe ser un GiftBox. Todavía tiene la opción de pasar un GiftBoxcon inicialización diferida, como se muestra en el ejemplo correcto.
En este siguiente ejemplo también es correcto, ya que el parámetro tiene el tipo GiftBox y no std::nullptr_t.
GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.
Es solo cuando pasas un nullptr
literal que surge la ambigüedad.
No construya copias por error.
Esta precaución es similar a la descrita en la sección anterior No retrasar la inicialización por error.
Además del constructor de inicialización diferida, la proyección de C++/WinRT también inserta un constructor de copia en cada clase de tiempo de ejecución. Es un constructor de un solo parámetro que acepta el mismo tipo que el objeto que se va a construir. El puntero inteligente resultante apunta al mismo objeto subyacente de Windows Runtime al que apunta el parámetro de su constructor. El resultado son dos objetos de puntero inteligente que apuntan al mismo objeto de respaldo.
Esta es una definición de clase en tiempo de ejecución que usaremos en los ejemplos de código.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}
Supongamos que queremos construir un GiftBox dentro de un GiftBox más grande.
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) };
La correcta manera de hacerlo es llamar explícitamente a la fábrica de activación.
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) };
Si la API se implementa en un componente de Windows Runtime
Esta sección se aplica tanto si ha creado el componente usted mismo como si procede de un proveedor.
Nota:
Para obtener información sobre cómo instalar y usar la extensión de Visual Studio (VSIX) de C++/WinRT y el paquete NuGet (que juntos proporcionan soporte para plantillas de proyecto y compilación), consulte compatibilidad de Visual Studio con C++/WinRT.
En el proyecto de aplicación, haga referencia al archivo de metadatos de Windows Runtime (.winmd
) del componente y luego compílelo. Durante la compilación, la herramienta cppwinrt.exe
genera una biblioteca estándar de C++ que describe completamente (o proyectos) la superficie de API del componente. En otras palabras, la biblioteca generada contiene los tipos proyectados para el componente.
A continuación, al igual que para un tipo de espacio de nombres de Windows, se incluye un encabezado y se construye el tipo proyectado a través de uno de sus constructores. El código de inicio del proyecto de aplicación registra la clase en tiempo de ejecución y el constructor del tipo proyectado llama a RoActivateInstance para activar la clase en tiempo de ejecución desde el componente al que se hace referencia.
#include <winrt/ThermometerWRC.h>
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
ThermometerWRC::Thermometer thermometer;
...
};
Para obtener más información, código y un tutorial sobre el uso de API implementadas en un componente de Windows Runtime, consulta Componentes de Windows Runtime con C++/WinRT y Autorizar eventos en C++/WinRT.
Si la API se implementa en el proyecto de consumo
El ejemplo de código de esta sección se toma del tema controles XAML; enlazar a una propiedad de C++/WinRT. Consulte ese tema para obtener más detalles, código y un tutorial sobre cómo consumir una clase en tiempo de ejecución que se implementa en el mismo proyecto que lo consume.
Un tipo que se consume desde la interfaz de usuario XAML debe ser una clase en tiempo de ejecución, incluso si está en el mismo proyecto que el XAML. En este escenario, se genera un tipo proyectado a partir de los metadatos de Windows Runtime de la clase en tiempo de ejecución (.winmd
). De nuevo, incluye un encabezado, pero después tiene una opción entre las formas de construcción de la instancia de la clase en tiempo de ejecución de C++/WinRT versión 1.0 o la versión 2.0. El método version 1.0 usa winrt::make; El método versión 2.0 se conoce como construcción uniforme. Echemos un vistazo a cada uno a su vez.
Construcción mediante winrt::make
Comencemos con el método predeterminado (C++/WinRT versión 1.0), ya que es una buena idea estar al menos familiarizado con ese patrón. El tipo proyectado se construye a través de su constructor std::nullptr_t. Ese constructor no realiza ninguna inicialización, por lo que debe asignar un valor a la instancia a través del winrt::make función auxiliar, pasando los argumentos de constructor necesarios. No es necesario registrar ni crear instancias de una clase de tiempo de ejecución implementada en el mismo proyecto que el código que la utiliza, ni activarla mediante Windows Runtime/COM.
Consulta los controles XAML y vincula una propiedad de C++/WinRT para ver un tutorial completo. En esta sección se muestran los extractos de ese tutorial.
// 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>();
...
}
Construcción uniforme
Con C++/WinRT versión 2.0 y versiones posteriores, hay una forma optimizada de construcción disponible para usted conocida como de construcción uniforme (consulte Noticias y cambios, en C++/WinRT 2.0).
Consulta los controles XAML y vincula una propiedad de C++/WinRT para ver un tutorial completo. En esta sección se muestran los extractos de ese tutorial.
Para usar la construcción uniforme en lugar de winrt::make, necesitará un generador de activación. Una buena manera de generar una consiste en agregar un constructor a su IDL.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
A continuación, en MainPage.h
declarar e inicializar m_mainViewModel en un solo paso, como se muestra a continuación.
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel;
...
};
}
...
Y luego, en el constructor MainPage en
Para obtener más información sobre la construcción uniforme y ejemplos de código, consulta Participación en la construcción uniforme y acceso directo a la implementación.
Instanciación y devolución de tipos e interfaces proyectados
Este es un ejemplo de cómo podrían verse los tipos e interfaces proyectados en tu proyecto de uso. Recuerde que un tipo proyectado, como el de este ejemplo, es generado por una herramienta y no es algo que haya creado usted mismo.
struct MyRuntimeClass : MyProject::IMyRuntimeClass, impl::require<MyRuntimeClass,
Windows::Foundation::IStringable, Windows::Foundation::IClosable>
MyRuntimeClass es un tipo proyectado; las interfaces proyectadas incluyen IMyRuntimeClass, IStringable, y IClosable. En este tema se muestran las distintas formas en las que se puede crear una instancia de un tipo proyectado. Este es un recordatorio y un resumen, con MyRuntimeClass como ejemplo.
// 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>();
- Puede acceder a los miembros de todas las interfaces de un tipo proyectado.
- Puede devolver un tipo proyectado a quien llama.
- Los tipos e interfaces proyectados derivan de winrt::Windows::Foundation::IUnknown. Por lo tanto, puede llamar a IUnknown::as en un tipo o interfaz proyectados para consultar otras interfaces proyectadas, que también puede usar o devolver al llamante. El como función miembro de funciona como QueryInterface.
void f(MyProject::MyRuntimeClass const& myrc)
{
myrc.ToString();
myrc.Close();
IClosable iclosable = myrc.as<IClosable>();
iclosable.Close();
}
Fábricas de activación
La forma cómoda y directa de crear un objeto de C++/WinRT es la siguiente.
using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };
Pero puede haber ocasiones en las que desee crear la fábrica de activación usted mismo y, a continuación, crear objetos a partir de ella cuando le sea conveniente. Estos son algunos ejemplos que muestran cómo, usando la plantilla de función 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");
Las clases de los dos ejemplos anteriores son tipos de un espacio de nombres de Windows. En el siguiente ejemplo, TermómetroWRC::Termómetro es un tipo personalizado implementado en un componente de Windows Runtime.
auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();
Ambigüedades en miembros o tipos
Cuando una función miembro tiene el mismo nombre que un tipo, hay ambigüedad. Las reglas para la búsqueda de nombres sin calificar de C++ en las funciones miembro hacen que busque la clase antes de buscar en espacios de nombres. El error de sustitución de no es un error regla (SFINAE) no se aplica (se aplica durante la resolución de sobrecarga de plantillas de función). Por lo tanto, si el nombre dentro de la clase no tiene sentido, el compilador no sigue buscando una mejor coincidencia, simplemente notifica un error.
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>() };
}
}
Arriba, el compilador piensa que está pasando FrameworkElement.Style() (que, en C++/WinRT, es una función miembro) como parámetro de plantilla para IUnknown::as. La solución consiste en forzar que el nombre Style
se interprete como el 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>();
}
}
La búsqueda de nombres sin calificar tiene una excepción especial en el caso de que el nombre vaya seguido de ::
, en cuyo caso omite las funciones, las variables y los valores de enumeración. Esto le permite hacer cosas como esta.
struct MyPage : Page
{
void DoSomething()
{
Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
}
}
La llamada a Visibility()
se resuelve como el nombre de la función miembro de UIElement.Visibility. Pero el parámetro Visibility::Collapsed
sigue la palabra Visibility
con ::
, por lo que se omite el nombre del método y el compilador encuentra la clase enum.
API importantes
- función QueryInterface
- función RoActivateInstance
- clase Windows::Foundation::Uri
- plantilla de función winrt::get_activation_factory
- plantilla de función winrt::make
- winrt::Windows::Foundation::IUnknown estructura