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 convertir entre la interfaz binaria de aplicaciones del SDK (ABI) y los objetos C++/WinRT . Puede usar estas técnicas para interoperabilidad entre código que usa estas dos formas de programación con la Windows Runtime, o bien puede usarlas a medida que mueva gradualmente el código de la ABI a C++/WinRT.
En general, C++/WinRT expone tipos de ABI como void*, por lo que no es necesario incluir archivos de encabezado de plataforma.
Note
En los ejemplos de código, usamos reinterpret_cast (en lugar de static_cast) para indicar que se trata de conversiones de tipo intrínsecamente no seguras.
¿Cuál es el Windows Runtime ABI y qué son los tipos de ABI?
Una clase Windows Runtime (clase en tiempo de ejecución) es realmente una abstracción. Esta abstracción define una interfaz binaria (la interfaz binaria de la aplicación o ABI) que permite que varios lenguajes de programación interactúen con un objeto. Independientemente del lenguaje de programación, la interacción del código de cliente con un objeto Windows Runtime se produce en el nivel más bajo, con construcciones de lenguaje cliente traducidas en llamadas a la ABI del objeto.
Los encabezados del SDK de Windows en la carpeta "%WindowsSdkDir%Include\10.0.17134.0\winrt" (ajuste el número de versión del SDK para su caso, si es necesario), son los archivos de encabezado Windows Runtime ABI. Fueron producidos por el compilador MIDL. Este es un ejemplo de incluir uno de estos encabezados.
#include <windows.foundation.h>
Y aquí tiene un ejemplo simplificado de uno de los tipos ABI que encontrará en ese archivo de encabezado concreto del SDK. Anote el espacio de nombres ABI; Windows::Foundation, y todos los demás espacios de nombres Windows, se declaran mediante los encabezados del SDK dentro del espacio de nombres abi.
namespace ABI::Windows::Foundation
{
IUriRuntimeClass : public IInspectable
{
public:
/* [propget] */ virtual HRESULT STDMETHODCALLTYPE get_AbsoluteUri(/* [retval, out] */__RPC__deref_out_opt HSTRING * value) = 0;
...
}
}
IUriRuntimeClass es una interfaz COM. Pero más que eso, ya que su base es IInspectable, IUriRuntimeClass es una interfaz Windows Runtime. Tenga en cuenta el tipo de valor devuelto HRESULT , en lugar de la generación de excepciones. Y el uso de artefactos como el descriptor HSTRING (es una buena práctica volver a establecer ese descriptor en nullptr cuando haya terminado de usarlo). Esto da un sabor al aspecto del Windows Runtime en el nivel binario de la aplicación; es decir, en el nivel de programación COM.
El Windows Runtime se basa en las API del modelo de objetos componentes (COM). Puede acceder a la Windows Runtime de esa manera, o puede acceder a ella a través de proyecciones de lenguaje. Una proyección oculta los detalles COM y proporciona una experiencia de programación más natural para un lenguaje determinado.
Por ejemplo, si busca en la carpeta "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (de nuevo, ajuste el número de versión del SDK para su caso, si es necesario), encontrará los encabezados de proyección del lenguaje C++/WinRT. Hay un encabezado para cada espacio de nombres de Windows, igual que hay un encabezado ABI por cada espacio de nombres de Windows. Este es un ejemplo de cómo incluir uno de los encabezados de C++/WinRT.
#include <winrt/Windows.Foundation.h>
Y, a partir de ese archivo de encabezado, aquí se muestra (de forma simplificada) el equivalente en C++/WinRT de ese tipo ABI que acabamos de ver.
namespace winrt::Windows::Foundation
{
struct Uri : IUriRuntimeClass, ...
{
winrt::hstring AbsoluteUri() const { ... }
...
};
}
La interfaz aquí es moderna y estándar de C++. Prescinde de los HRESULTs (C++/WinRT lanza excepciones si es necesario). Y la función de descriptor de acceso devuelve un objeto de cadena simple, que se limpia al final de su ámbito.
Este tema es para los casos en los que desea interopar o portar código que funciona en la capa de interfaz binaria de aplicaciones (ABI).
Conversión a y desde tipos ABI en el código
Por motivos de seguridad y simplicidad, para las conversiones en ambas direcciones, simplemente puedes usar winrt::com_ptr, com_ptr::as y winrt::Windows::Foundation::IUnknown::as. Aquí tiene un ejemplo de código (basado en la plantilla de proyecto Aplicación de consola) que también ilustra cómo se pueden usar alias de espacio de nombres para las distintas islas a fin de evitar conflictos de espacios de nombres que, de otro modo, podrían producirse entre la proyección de C++/WinRT y la ABI.
// pch.h
#pragma once
#include <windows.foundation.h>
#include <unknwn.h>
#include "winrt/Windows.Foundation.h"
// main.cpp
#include "pch.h"
namespace winrt
{
using namespace Windows::Foundation;
}
namespace abi
{
using namespace ABI::Windows::Foundation;
};
int main()
{
winrt::init_apartment();
winrt::Uri uri(L"http://aka.ms/cppwinrt");
// Convert to an ABI type.
winrt::com_ptr<abi::IStringable> ptr{ uri.as<abi::IStringable>() };
// Convert from an ABI type.
uri = ptr.as<winrt::Uri>();
winrt::IStringable uriAsIStringable{ ptr.as<winrt::IStringable>() };
}
Las implementaciones de las funciones as llaman a QueryInterface. Si desea conversiones de nivel inferior que solo llaman a AddRef, puede usar las funciones auxiliares winrt::copy_to_abi y winrt::copy_from_abi. En este ejemplo de código siguiente se agregan estas conversiones de nivel inferior al ejemplo de código anterior.
Important
Al interoperar con tipos ABI, es fundamental que el tipo abi usado se corresponda con la interfaz predeterminada del objeto C++/WinRT. De lo contrario, las invocaciones de métodos en el tipo ABI terminarán llamando a métodos en la misma ranura de vtable en la interfaz predeterminada con resultados muy inesperados. Tenga en cuenta que winrt::copy_to_abi no ofrece protección frente a esto en tiempo de compilación, ya que usa void* para todos los tipos ABI y supone que quien realiza la llamada ha tenido cuidado de no confundir los tipos. Esto es para evitar requerir que los encabezados de C++/WinRT hagan referencia a encabezados ABI cuando los tipos de ABI nunca se puedan usar.
int main()
{
// The code in main() already shown above remains here.
// Lower-level conversions that only call AddRef.
// Convert to an ABI type.
ptr = nullptr;
winrt::copy_to_abi(uriAsIStringable, *ptr.put_void());
// Convert from an ABI type.
uri = nullptr;
winrt::copy_from_abi(uriAsIStringable, ptr.get());
ptr = nullptr;
}
Aquí se muestran otras técnicas de conversión de bajo nivel similares, pero esta vez usando punteros sin formato a tipos de interfaz ABI (los definidos por los archivos de cabecera del SDK de Windows).
// The code in main() already shown above remains here.
// Copy to an owning raw ABI pointer with copy_to_abi.
abi::IStringable* owning{ nullptr };
winrt::copy_to_abi(uriAsIStringable, *reinterpret_cast<void**>(&owning));
// Copy from a raw ABI pointer.
uri = nullptr;
winrt::copy_from_abi(uriAsIStringable, owning);
owning->Release();
Para las conversiones de más bajo nivel, que solo copian direcciones, puedes usar las funciones auxiliares winrt::get_abi, winrt::detach_abi y winrt::attach_abi.
WINRT_ASSERT es una definición de macro y se expande a _ASSERTE.
// The code in main() already shown above remains here.
// Lowest-level conversions that only copy addresses
// Convert to a non-owning ABI object with get_abi.
abi::IStringable* non_owning{ reinterpret_cast<abi::IStringable*>(winrt::get_abi(uriAsIStringable)) };
WINRT_ASSERT(non_owning);
// Avoid interlocks this way.
owning = reinterpret_cast<abi::IStringable*>(winrt::detach_abi(uriAsIStringable));
WINRT_ASSERT(!uriAsIStringable);
winrt::attach_abi(uriAsIStringable, owning);
WINRT_ASSERT(uriAsIStringable);
función convert_from_abi
Esta función auxiliar convierte un puntero de interfaz ABI sin procesar a un objeto C++/WinRT equivalente, con una sobrecarga mínima.
template <typename T>
T convert_from_abi(::IUnknown* from)
{
T to{ nullptr }; // `T` is a projected type.
winrt::check_hresult(from->QueryInterface(winrt::guid_of<T>(),
winrt::put_abi(to)));
return to;
}
La función simplemente llama a QueryInterface para consultar la interfaz predeterminada del tipo C++/WinRT solicitado.
Como hemos visto, no se requiere una función auxiliar para convertir de un objeto C++/WinRT al puntero de interfaz ABI equivalente. Simplemente utilice la función miembro winrt::Windows::Foundation::IUnknown::as (o try_as) para consultar la interfaz requerida. Las funciones as y try_as devuelven un objeto winrt::com_ptr encapsulando el tipo de ABI solicitado.
Ejemplo de código con convert_from_abi
Este es un ejemplo de código que muestra esta función auxiliar en la práctica.
// pch.h
#pragma once
#include <windows.foundation.h>
#include <unknwn.h>
#include "winrt/Windows.Foundation.h"
// main.cpp
#include "pch.h"
#include <iostream>
using namespace winrt;
using namespace Windows::Foundation;
namespace winrt
{
using namespace Windows::Foundation;
}
namespace abi
{
using namespace ABI::Windows::Foundation;
};
namespace sample
{
template <typename T>
T convert_from_abi(::IUnknown* from)
{
T to{ nullptr }; // `T` is a projected type.
winrt::check_hresult(from->QueryInterface(winrt::guid_of<T>(),
winrt::put_abi(to)));
return to;
}
inline auto put_abi(winrt::hstring& object) noexcept
{
return reinterpret_cast<HSTRING*>(winrt::put_abi(object));
}
}
int main()
{
winrt::init_apartment();
winrt::Uri uri(L"http://aka.ms/cppwinrt");
std::wcout << "C++/WinRT: " << uri.Domain().c_str() << std::endl;
// Convert to an ABI type.
winrt::com_ptr<abi::IUriRuntimeClass> ptr = uri.as<abi::IUriRuntimeClass>();
winrt::hstring domain;
winrt::check_hresult(ptr->get_Domain(sample::put_abi(domain)));
std::wcout << "ABI: " << domain.c_str() << std::endl;
// Convert from an ABI type.
winrt::Uri uri_from_abi = sample::convert_from_abi<winrt::Uri>(ptr.get());
WINRT_ASSERT(uri.Domain() == uri_from_abi.Domain());
WINRT_ASSERT(uri == uri_from_abi);
}
Interoperación con punteros de interfaz COM abi
La siguiente plantilla de función auxiliar muestra cómo copiar un puntero a interfaz COM ABI de un tipo dado a su tipo equivalente de puntero inteligente proyectado de C++/WinRT.
template<typename To, typename From>
To to_winrt(From* ptr)
{
To result{ nullptr };
winrt::check_hresult(ptr->QueryInterface(winrt::guid_of<To>(), winrt::put_abi(result)));
return result;
}
...
ID2D1Factory1* com_ptr{ ... };
auto cppwinrt_ptr {to_winrt<winrt::com_ptr<ID2D1Factory1>>(com_ptr)};
La siguiente plantilla de función auxiliar es equivalente, salvo que copia a partir del tipo de puntero inteligente de las Bibliotecas de implementación de Windows (WIL).
template<typename To, typename From, typename ErrorPolicy>
To to_winrt(wil::com_ptr_t<From, ErrorPolicy> const& ptr)
{
To result{ nullptr };
if constexpr (std::is_same_v<typename ErrorPolicy::result, void>)
{
ptr.query_to(winrt::guid_of<To>(), winrt::put_abi(result));
}
else
{
winrt::check_result(ptr.query_to(winrt::guid_of<To>(), winrt::put_abi(result)));
}
return result;
}
Vea también Uso de componentes COM con C++/WinRT.
Interoperabilidad no segura con punteros ABI a interfaces COM
La tabla siguiente muestra (además de otras operaciones) conversiones no seguras entre un puntero de interfaz COM ABI de un tipo determinado y su tipo de puntero inteligente proyectado de C++/WinRT equivalente. Para el código de la tabla, suponga las siguientes declaraciones.
winrt::Sample s;
ISample* p;
void GetSample(_Out_ ISample** pp);
Supongamos más que ISample es la interfaz predeterminada para Sample.
Puede comprobarlo en tiempo de compilación con este código.
static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
| Operation | Cómo hacerlo | Notes |
|---|---|---|
| Extraer ISample* de winrt::Sample | p = reinterpret_cast<ISample*>(get_abi(s)); |
s sigue siendo el propietario del objeto. |
| Desasociar ISample* de winrt::Sample | p = reinterpret_cast<ISample*>(detach_abi(s)); |
s ya no posee el objeto. |
| Transferencia de ISample* al nuevo winrt::Sample | winrt::Sample s{ p, winrt::take_ownership_from_abi }; |
s toma posesión del objeto . |
| Establecer ISample* en winrt::Sample | *put_abi(s) = p; |
s toma posesión del objeto . Se filtra cualquier objeto que sea propiedad de s (se declarará en depuración). |
| Recibir ISample* en winrt::Sample | GetSample(reinterpret_cast<ISample**>(put_abi(s))); |
s toma posesión del objeto . Se produce una fuga de cualquier objeto que haya pertenecido previamente a s (se activará una aserción en depuración). |
| Reemplazar ISample* en winrt::Sample | attach_abi(s, p); |
s toma posesión del objeto . El objeto que anteriormente era propiedad de s se libera. |
| Copiar ISample* en winrt::Sample | copy_from_abi(s, p); |
s realiza una nueva referencia al objeto . El objeto que anteriormente era propiedad de s se libera. |
| Copiar winrt::Sample en ISample* | copy_to_abi(s, reinterpret_cast<void*&>(p)); |
p recibe una copia del objeto . Se filtra cualquier objeto que pertenezca previamente a p . |
Interoperación con la estructura GUID de la ABI
GUID (/previous-versions/aa373931(v%3Dvs.80)) se proyecta como winrt::guid. Para las API que implemente, debe usar winrt::guid para los parámetros GUID. De lo contrario, hay conversiones automáticas entre winrt::guid y GUID siempre y cuando incluyas unknwn.h (incluido implícitamente por <windows.h> y muchos otros archivos de encabezado) antes de incluir cualquier encabezado de C++/WinRT.
Si no lo haces, puedes hacerlo difícilmentereinterpret_cast entre ellos. Para la tabla que aparece a continuación, suponga las siguientes declaraciones.
winrt::guid winrtguid;
GUID abiguid;
| Conversion | Con #include <unknwn.h> |
Sin #include <unknwn.h> |
|---|---|---|
| Desde winrt::guid a GUID | abiguid = winrtguid; |
abiguid = reinterpret_cast<GUID&>(winrtguid); |
| De GUID a winrt::guid | winrtguid = abiguid; |
winrtguid = reinterpret_cast<winrt::guid&>(abiguid); |
Puedes construir un winrt::guid como este.
winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };
Para ver un gist que muestra cómo construir un winrt::guid a partir de una cadena, consulte make_guid.cpp.
Interoperación con el HSTRING de la ABI
En la tabla siguiente se muestran las conversiones entre winrt::hstring y HSTRING y otras operaciones. Para el código de la tabla, suponga las siguientes declaraciones.
winrt::hstring s;
HSTRING h;
void GetString(_Out_ HSTRING* value);
| Operation | Cómo hacerlo | Notes |
|---|---|---|
| Extracción de HSTRING de hstring | h = reinterpret_cast<HSTRING>(get_abi(s)); |
s sigue poseyendo la cadena. |
| Desacopla HSTRING de hstring | h = reinterpret_cast<HSTRING>(detach_abi(s)); |
s ya no posee la cadena. |
| Establecer HSTRING en hstring | *put_abi(s) = h; |
s adquiere la propiedad de la cadena. Cualquier cadena que antes pertenecía a s provoca una fuga de memoria (provocará una aserción en modo depuración). |
| Recibir HSTRING en hstring | GetString(reinterpret_cast<HSTRING*>(put_abi(s))); |
s asume la propiedad de la cadena de texto. Cualquier cadena que anteriormente perteneciera a s se pierde (provocará una aserción en modo depuración). |
| Reemplazar HSTRING en hstring | attach_abi(s, h); |
s asume la propiedad de la cadena. La cadena que antes pertenecía a s se libera. |
| Copia de HSTRING en hstring | copy_from_abi(s, h); |
s realiza una copia privada de la cadena. La cadena que anteriormente pertenecía a s se libera. |
| Copia de hstring en HSTRING | copy_to_abi(s, reinterpret_cast<void*&>(h)); |
h recibe una copia de la cadena. Se filtra cualquier cadena que pertenezca previamente a h . |
Además, los asistentes de cadenas de bibliotecas de implementación (WIL) de Windows realizan manipulaciones básicas de cadenas. Para usar los asistentes de cadenas WIL, incluya <wil/resource.h> y consulte la tabla siguiente. Siga los vínculos de la tabla para obtener detalles completos.
| Operation | Asistente de cadenas WIL para obtener más información |
|---|---|
| Proporcione un puntero a una cadena Unicode o ANSI sin procesar y una longitud opcional; obtenga un contenedor unique_any debidamente especializado | wil::make_something_string |
| Desencapsular un objeto inteligente hasta que se encuentre un puntero de cadena Unicode sin formato terminado en null | wil::str_raw_ptr |
Obtener la cadena contenida en un objeto de puntero inteligente; o la cadena vacía L"" si el puntero inteligente está vacío |
wil::string_get_not_null |
| Concatenar cualquier número de cadenas | wil::str_concat |
| Obtener una cadena a partir de una cadena de formato al estilo printf y la lista de parámetros correspondiente | wil::str_printf |
API importantes
- Función AddRef
- Función QueryInterface
- Función winrt::attach_abi
- Plantilla de estructura winrt::com_ptr
- Función winrt::copy_from_abi
- Función winrt::copy_to_abi
- función winrt::detach_abi
- Función winrt::get_abi
- función miembro winrt::Windows::Foundation::IUnknown::as
- función miembro winrt::Windows::Foundation::IUnknown::try_as