Interoperabilidad entre C++/WinRT y la ABIInterop between C++/WinRT and the ABI

En este tema se muestra cómo convertir entre la interfaz binaria de aplicaciones del SDK (ABI) y objetos C++/WinRT.This topic shows how to convert between SDK application binary interface (ABI) and C++/WinRT objects. Puedes usar estas técnicas de interoperabilidad entre el código que use estas dos formas de programación con Windows Runtime o puedes usarlas a medida que muevas tu código de la ABI a C++/WinRT.You can use these techniques to interop between code that uses these two ways of programming with the Windows Runtime, or you can use them as you gradually move your code from the ABI to C++/WinRT.

En general, C++/WinRT expone los tipos ABI como void* , por lo que no necesitas incluir archivos de encabezado de plataforma.In general, C++/WinRT exposes ABI types as void*, so that you don't need to include platform header files.

¿Qué es la ABI de Windows Runtime y cuáles son los tipos de ABI?What is the Windows Runtime ABI, and what are ABI types?

Una clase de Windows Runtime (clase en tiempo de ejecución) realmente es una abstracción.A Windows Runtime class (runtime class) is really an abstraction. Esta abstracción define una interfaz binaria (la interfaz binaria de aplicaciones o ABI) que permite que varios lenguajes de programación interactúen con un objeto.This abstraction defines a binary interface (the Application Binary Interface, or ABI) that allows various programming languages to interact with an object. Independientemente del lenguaje de programación, la interacción de código de cliente con un objeto de Windows Runtime se produce en el nivel más bajo, con construcciones de lenguaje de cliente traducidas a llamadas en la ABI del objeto.Regardless of programming language, client code interaction with a Windows Runtime object happens at the lowest level, with client language constructs translated into calls into the object's ABI.

Los encabezados de Windows SDK en la carpeta "%WindowsSdkDir%Include\10.0.17134.0\winrt" (ajusta el número de versión SDK en tu caso, si es necesario) son los archivos de encabezado de la ABI de Windows Runtime.The Windows SDK headers in the folder "%WindowsSdkDir%Include\10.0.17134.0\winrt" (adjust the SDK version number for your case, if necessary), are the Windows Runtime ABI header files. Los ha producido el compilador MIDL.They were produced by the MIDL compiler. He aquí un ejemplo con uno de estos encabezados incluidos.Here's an example of including one of these headers.

#include <windows.foundation.h>

Y aquí tienes un ejemplo simplificado de uno de los tipos ABI que encontrarás en dicho encabezado de SDK concreto.And here's a simplified example of one of the ABI types that you'll find in that particular SDK header. Ten en cuenta que el espacio de nombres de ABI, Windows::Foundation y todos los demás espacios de nombres de Windows se declaran por los encabezados de SDK dentro del espacio de nombres de la ABI.Note the ABI namespace; Windows::Foundation, and all other Windows namespaces, are declared by the SDK headers within the ABI namespace.

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.IUriRuntimeClass is a COM interface. Pero más que eso—dado que su base es IInspectableIUriRuntimeClass, es una interfaz de Windows Runtime.But more than that—since its base is IInspectableIUriRuntimeClass is a Windows Runtime interface. Ten en cuenta el tipo devuelto HRESULT en lugar de generar excepciones.Note the HRESULT return type, rather than the raising of exceptions. Y el uso de los artefactos como el controlador HSTRING (se recomienda devolver dicho controlador a nullptr cuando hayas terminado con él).And the use of artifacts such as the HSTRING handle (it's good practice to set that handle back to nullptr when you're finished with it). Esto da una idea del aspecto de Windows Runtime en el nivel binario de la aplicación; en otras palabras, en el nivel de programación COM.This gives a taste of what the Windows Runtime looks like at the application binary level; in other words, at the COM programming level.

Windows Runtime se basa en las API de modelo de objetos componentes (COM).The Windows Runtime is based on Component Object Model (COM) APIs. Puedes acceder a Windows Runtime de esta forma, o bien a través de proyecciones de lenguaje.You can access the Windows Runtime that way, or you can access it through language projections. Una proyección oculta los detalles del COM y proporciona una experiencia de programación más natural para un lenguaje determinado.A projection hides the COM details, and provides a more natural programming experience for a given language.

Por ejemplo, si buscas en la carpeta "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (de nuevo, ajusta el número de versión SDK en tu caso, si es necesario), encontrarás los encabezados de proyección del lenguaje C++/WinRT.For example, if you look in the folder "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (again, adjust the SDK version number for your case, if necessary), then you'll find the C++/WinRT language projection headers. Hay un encabezado para cada espacio de nombres de Windows, al igual que hay un encabezado ABI por espacio de nombres de Windows.There's a header for each Windows namespace, just like there's one ABI header per Windows namespace. He aquí un ejemplo con uno de los encabezados C++/WinRT incluidos.Here's an example of including one of the C++/WinRT headers.

#include <winrt/Windows.Foundation.h>

Y, a partir de dicho encabezado, aquí tienes de forma simplificada el equivalente C++/WinRT de este tipo ABI que acabamos de ver.And, from that header, here (simplified) is the C++/WinRT equivalent of that ABI type we just saw.

namespace winrt::Windows::Foundation
{
    struct Uri : IUriRuntimeClass, ...
    {
        winrt::hstring AbsoluteUri() const { ... }
        ...
    };
}

La interfaz es C++ moderno y estándar.The interface here is modern, standard C++. Acaba con los HRESULT (C++/WinRT genera excepciones si es necesario).It does away with HRESULTs (C++/WinRT raises exceptions if necessary). Y la función de descriptor de acceso devuelve un objeto de cadena simple, que se ha limpiado al final de su ámbito.And the accessor function returns a simple string object, which is cleaned up at the end of its scope.

Este tema es para casos en los que quieres interoperar con código que funciona en la capa de la interfaz binaria de aplicaciones (ABI), o portarlo.This topic is for cases when you want to interop with, or port, code that works at the Application Binary Interface (ABI) layer.

Convertir a tipos ABI en código y desde ellosConverting to and from ABI types in code

Por seguridad y sencillez, para las conversiones en ambas direcciones puedes limitarte a usar winrt::com_ptr, com_ptr::as y winrt::Windows::Foundation::IUnknown::as.For safety and simplicity, for conversions in both directions you can simply use winrt::com_ptr, com_ptr::as, and winrt::Windows::Foundation::IUnknown::as. He aquí un ejemplo de código (basado en la plantilla del proyecto Console App), que también muestra cómo puedes usar los alias de espacio de nombre para las diferentes islas para abordar las posibles colisiones de espacio de nombres entre la proyección de C++/WinRT y la ABI.Here's a code example (based on the Console App project template), which also illustrates how you can use namespace aliases for the different islands to deal with otherwise potential namespace collisions between the C++/WinRT projection and the 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.The implementations of the as functions call QueryInterface. Si quieres que las conversiones de nivel más bajo solo llamen a AddRef, puedes usar las funciones auxiliares winrt::copy_to_abi y winrt::copy_from_abi.If you want lower-level conversions that only call AddRef, then you can use the winrt::copy_to_abi and winrt::copy_from_abi helper functions. El siguiente ejemplo de código agrega estas conversiones de nivel más bajo al ejemplo de código anterior.This next code example adds these lower-level conversions to the code example above.

Importante

Al interoperar con tipos de ABI, es fundamental que el tipo de ABI usado se corresponda con la interfaz predeterminada del objeto de C++/WinRT.When interoperating with ABI types it's critical that the ABI type used corresponds to the default interface of the C++/WinRT object. De lo contrario, las invocaciones de métodos del tipo de ABI acabarán por llamar a métodos del mismo espacio vtable de la interfaz predeterminada con resultados muy inesperados.Otherwise, invocations of methods on the ABI type will actually end up calling methods in the same vtable slot on the default interface with very unexpected results. Tenga en cuenta que winrt::copy_to_abi no protege contra esta situación en tiempo de compilación, ya que usa void* para todos los tipos de ABI y supone que el autor de la llamada se ha encargado de que los tipos no tengan errores de coincidencia.Note that winrt::copy_to_abi does not protect against this at compile time since it uses void* for all ABI types and assumes that the caller has been careful not to mis-match the types. De este modo, se evita que los encabezados de C++/WinRT tengan que hacer referencia a los encabezados de ABI, cuando es posible que los tipos de ABI nunca se usen.This is to avoid requiring C++/WinRT headers to reference ABI headers when ABI types may never be used.

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;
}

Estas son otras técnicas de conversiones de bajo nivel de forma similar, pero usando punteros sin procesar a tipos de interfaz ABI (los definidos por los encabezados de Windows SDK) esta vez.Here are other similarly low-level conversions techniques but using raw pointers to ABI interface types (those defined by the Windows SDK headers) this time.

    // 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 nivel más bajo, que solo copian direcciones, puedes usar las funciones auxiliares winrt::get_abi, winrt::detach_abi y winrt::attach_abi.For the lowest-level conversions, which only copy addresses, you can use the winrt::get_abi, winrt::detach_abi, and winrt::attach_abi helper functions.

WINRT_ASSERT es una definición de macro y se expande a _ASSERTE.WINRT_ASSERT is a macro definition, and it expands to _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{ static_cast<abi::IStringable*>(winrt::get_abi(uriAsIStringable)) };
    WINRT_ASSERT(non_owning);

    // Avoid interlocks this way.
    owning = static_cast<abi::IStringable*>(winrt::detach_abi(uriAsIStringable));
    WINRT_ASSERT(!uriAsIStringable);
    winrt::attach_abi(uriAsIStringable, owning);
    WINRT_ASSERT(uriAsIStringable);

Función convert_from_abiconvert_from_abi function

Esta función auxiliar convierte un puntero de interfaz ABI sin procesar en un objeto equivalente C++/WinRT con una sobrecarga mínima.This helper function converts a raw ABI interface pointer to an equivalent C++/WinRT object, with minimal overhead.

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.The function simply calls QueryInterface to query for the default interface of the requested C++/WinRT type.

Como hemos visto, no es necesaria la función auxiliar para convertir de un objeto C++/WinRT al puntero de interfaz ABI equivalente.As we've seen, a helper function is not required to convert from a C++/WinRT object to the equivalent ABI interface pointer. Solo tienes que usar la función miembro winrt::Windows::Foundation::IUnknown::as (o try_as) para consultar la interfaz solicitada.Simply use the winrt::Windows::Foundation::IUnknown::as (or try_as) member function to query for the requested interface. Las funciones as y try_as devuelven un objeto winrt::com_ptr que encapsula el tipo ABI solicitado.The as and try_as functions return a winrt::com_ptr object wrapping the requested ABI type.

Ejemplo de código con convert_from_abiCode example using convert_from_abi

Aquí tienes un ejemplo de código que muestra esta función auxiliar en práctica.Here's a code example showing this helper function in practice.

// 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);
}

Interoperaciones con los punteros de la interfaz ABI COMInteroperating with ABI COM interface pointers

La plantilla de la función auxiliar que tienes a continuación ilustra cómo copiar un puntero de interfaz COM ABI de un tipo determinado a tu tipo de puntero inteligente y proyectado C++/WinRT equivalente.The helper function template below illustrates how to copy an ABI COM interface pointer of a given type to its equivalent C++/WinRT projected smart pointer type.

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, excepto que se copia desde el tipo de puntero inteligente que se encuentra en Bibliotecas de implementación de Windows (WIL).This next helper function template is equivalent, except that it copies from the smart pointer type from the Windows Implementation Libraries (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;
}

También, consulta Consumir componentes COM con C++/WinRT.Also see Consume COM components with C++/WinRT.

Interoperabilidad insegura con punteros de interfaz ABI COMUnsafe interop with ABI COM interface pointers

En la siguiente tabla se muestra (además de otras operaciones) las conversiones inseguras entre un puntero de interfaz ABI COM de un tipo determinado y su tipo de puntero inteligente y proyectado C++/WinRT equivalente.The table that follows shows (in addition to other operations) unsafe conversions between an ABI COM interface pointer of a given type and its equivalent C++/WinRT projected smart pointer type. Para el código en la tabla, se presentan estas declaraciones.For the code in the table, assume these declarations.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Supongamos, además, que ISample es la interfaz predeterminada de Sample.Assume further that ISample is the default interface for Sample.

Puedes establecer el tiempo de compilación con este código.You can assert that at compile time with this code.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
OperaciónOperation Cómo hacerloHow to do it NotasNotes
Extraer ISample* de winrt::SampleExtract ISample* from winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s todavía posee el objeto.s still owns the object.
Desasociar ISample* de winrt::SampleDetach ISample* from winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s ya no posee el objeto.s no longer owns the object.
Transferir ISample* al nuevo winrt::SampleTransfer ISample* to new winrt::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s toma posesión del objeto.s takes ownership of the object.
Establecer ISample* al nuevo winrt::SampleSet ISample* into winrt::Sample *put_abi(s) = p; s toma posesión del objeto.s takes ownership of the object. Cualquier objeto que anteriormente era propiedad de s se filtra (se confirmará en la depuración).Any object previously owned by s is leaked (will assert in debug).
Establecer ISample* en el nuevo winrt::SampleReceive ISample* into winrt::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s toma posesión del objeto.s takes ownership of the object. Cualquier objeto que anteriormente era propiedad de s se filtra (se confirmará en la depuración).Any object previously owned by s is leaked (will assert in debug).
Establecer ISample* en winrt::SampleReplace ISample* in winrt::Sample attach_abi(s, p); s toma posesión del objeto.s takes ownership of the object. El objeto que anteriormente pertenecía a s se libera.The object previously owned by s is freed.
Copiar ISample* en winrt::SampleCopy ISample* to winrt::Sample copy_from_abi(s, p); s hace referencia al objeto.s makes a new reference to the object. El objeto que anteriormente pertenecía a s se libera.The object previously owned by s is freed.
Copiar winrt::Sample a ISample*Copy winrt::Sample to ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p recibe una copia del objeto.p receives a copy of the object. Cualquier objeto que anteriormente pertenecía a p se libera.Any object previously owned by p is leaked.

Interoperaciones con la estructura GUID de ABIInteroperating with the ABI's GUID struct

GUID se proyecta como winrt::guid.GUID is projected as winrt::guid. Para las API que implementes, debes usar winrt::guid para los parámetros GUID.For APIs that you implement, you must use winrt::guid for GUID parameters. En caso contrario, hay conversiones automáticas entre winrt::guid y GUID siempre y cuando incluyas unknwn.h (incluido de forma implícita mediante < windows.h > y muchos otros archivos de encabezado) antes de incluir cualquier encabezado de C++/WinRT.Otherwise, there are automatic conversions between winrt::guid and GUID as long as you include unknwn.h (implicitly included by <windows.h> and many other header files) before you include any C++/WinRT headers.

Si no lo haces, entonces puedes forzar el valor reinterpret_cast entre ellos.If you don't do that, then you can hard-reinterpret_cast between them. Para la tabla siguiente, se presentan estas declaraciones.For the table that follows, assume these declarations.

winrt::guid winrtguid;
GUID abiguid;
ConversiónConversion Con #include <unknwn.h>With #include <unknwn.h> Sin #include <unknwn.h>Without #include <unknwn.h>
Desde winrt::guid a GUIDFrom winrt::guid to GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
Desde GUID a winrt::guidFrom GUID to winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

Puede construir un winrt::guid como este.You can construct a winrt::guid like this.

winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };

Para ver la esencia de cómo construir un winrt::guid a partir de una cadena, vea make_guid.cpp.For a gist showing how to construct a winrt::guid from a string, see make_guid.cpp.

Interoperaciones con el valor HSTRING de ABIInteroperating with the ABI's HSTRING

La tabla siguiente muestra las conversiones entre winrt::hstring, HSTRING y otras operaciones.The table that follows shows conversions between winrt::hstring and HSTRING, and other operations. Para el código en la tabla, se presentan estas declaraciones.For the code in the table, assume these declarations.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
OperaciónOperation Cómo hacerloHow to do it NotasNotes
Extraer HSTRING desde hstringExtract HSTRING from hstring h = static_cast<HSTRING>(get_abi(s)); s todavía posee la cadena.s still owns the string.
Desasociar HSTRING desde hstringDetach HSTRING from hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s ya no posee la cadena.s no longer owns the string.
Establecer HSTRING desde hstringSet HSTRING into hstring *put_abi(s) = h; s toma posesión de la cadena.s takes ownership of string. Cualquier cadena que anteriormente era propiedad de s se filtra (se confirmará en la depuración).Any string previously owned by s is leaked (will assert in debug).
Recibir HSTRING en hstringReceive HSTRING into hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s toma posesión de la cadena.s takes ownership of string. Cualquier cadena que anteriormente era propiedad de s se filtra (se confirmará en la depuración).Any string previously owned by s is leaked (will assert in debug).
Reemplazar HSTRING en hstringReplace HSTRING in hstring attach_abi(s, h); s toma posesión de la cadena.s takes ownership of string. La cadena que anteriormente pertenecía a s se libera.The string previously owned by s is freed.
Copiar HSTRING en hstringCopy HSTRING to hstring copy_from_abi(s, h); s realiza una copia privada de la cadena.s makes a private copy of the string. La cadena que anteriormente pertenecía a s se libera.The string previously owned by s is freed.
Copiar hstring en HSTRINGCopy hstring to HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h recibe una copia de la cadena.h receives a copy of the string. Cualquier cadena que anteriormente pertenecía a h se libera.Any string previously owned by h is leaked.

API importantesImportant APIs