Interoperabilidade entre C++/WinRT e ABIInterop between C++/WinRT and the ABI

Este tópico mostra como realizar a conversão entre a interface binária do aplicativo (ABI) do SDK e objetos do C++/WinRT.This topic shows how to convert between SDK application binary interface (ABI) and C++/WinRT objects. Utilize essas técnicas para favorecer a interoperabilidade entre o código que usa essas duas maneiras de programação com o Windows Runtime ou à medida que migra gradativamente o código da ABI para o 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.

De modo geral, C++/WinRT expõe tipos de ABI como void* , de modo que você não precisa incluir os arquivos de cabeçalho da plataforma.In general, C++/WinRT exposes ABI types as void*, so that you don't need to include platform header files.

O que é a ABI do Windows Runtime e o que são tipos da ABI?What is the Windows Runtime ABI, and what are ABI types?

Uma classe do Windows Runtime (classe de tempo de execução) é uma abstração.A Windows Runtime class (runtime class) is really an abstraction. Essa abstração define uma interface binária (a interface binária do aplicativo, ou ABI), que permite a interação entre várias linguagens de programação com um objeto.This abstraction defines a binary interface (the Application Binary Interface, or ABI) that allows various programming languages to interact with an object. Independentemente da linguagem de programação, a interação do código de cliente com um objeto do Windows Runtime acontece no nível mais baixo, com construções de linguagem do cliente convertidas em chamadas na ABI do 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.

Os cabeçalhos do SDK do Windows na pasta "%DiretorioSDKWindows%Include\10.0.17134.0\winrt" (ajuste o número de versão do SDK para o seu caso, se necessário) são os arquivos de cabeçalho da ABI do 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. Eles foram gerados pelo compilador MIDL.They were produced by the MIDL compiler. Este é um exemplo da inclusão de um desses cabeçalhos.Here's an example of including one of these headers.

#include <windows.foundation.h>

Veja um exemplo simplificado de um dos tipos de ABI que você encontrará nesse cabeçalho de SDK específico.And here's a simplified example of one of the ABI types that you'll find in that particular SDK header. Observe o namespace ABI; Windows::Foundation e os demais namespaces do Windows são declarados pelos cabeçalhos do SDK no namespace 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 é uma interface COM.IUriRuntimeClass is a COM interface. Porém, mais do que isso, —considerando que sua base é IInspectableIUriRuntimeClass, é uma interface do Windows Runtime.But more than that—since its base is IInspectableIUriRuntimeClass is a Windows Runtime interface. Observe o tipo de retorno HRESULT, em vez da geração de exceções.Note the HRESULT return type, rather than the raising of exceptions. E o uso de artefatos como o identificador HSTRING (essa é uma boa prática para definir esse identificador novamente como nullptr quando tiver terminado de trabalhar com ele).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). Isso dá uma ideia da aparência do Windows Runtime no nível binário do aplicativo. Em outras palavras, no nível de programação 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.

O Windows Runtime baseia-se nas APIs de COM (Component Object Model).The Windows Runtime is based on Component Object Model (COM) APIs. Acesse o Windows Runtime dessa forma ou por meio de projeções de linguagem.You can access the Windows Runtime that way, or you can access it through language projections. Uma projeção oculta os detalhes de COM e oferece uma experiência de programação mais natural para determinada linguagem.A projection hides the COM details, and provides a more natural programming experience for a given language.

Por exemplo, se você analisar a pasta "%DiretorioSDKWindows%Include\10.0.17134.0\cppwinrt\winrt" (novamente, ajuste o número de versão do SDK para o seu caso, se necessário), encontrará os cabeçalhos de projeção da linguagem 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. Há um cabeçalho para cada namespace do Windows, assim como há um cabeçalho ABI por namespace do Windows.There's a header for each Windows namespace, just like there's one ABI header per Windows namespace. Este é um exemplo da inclusão de um dos cabeçalhos de C++/WinRT.Here's an example of including one of the C++/WinRT headers.

#include <winrt/Windows.Foundation.h>

A partir desse cabeçalho, obtemos o equivalente (simplificado) do C++/WinRT desse tipo de 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 { ... }
        ...
    };
}

A interface aqui é o C++ padrão moderno.The interface here is modern, standard C++. Ele dispensa os HRESULTs (o C++/WinRT gera exceções, se necessário).It does away with HRESULTs (C++/WinRT raises exceptions if necessary). A função do acessador retorna um objeto de cadeia de caracteres simples, que é removido no final do seu escopo.And the accessor function returns a simple string object, which is cleaned up at the end of its scope.

Este tópico serve nos casos em que se deseja fazer a portabilidade ou interoperabilidade com um código que funciona na camada da Interface binária do aplicativo (ABI).This topic is for cases when you want to interop with, or port, code that works at the Application Binary Interface (ABI) layer.

Convertendo de e para tipos de ABI no códigoConverting to and from ABI types in code

Para fins de segurança e praticidade, nas conversões em ambas as direções, é possível simplesmente usar winrt::com_ptr, com_ptr::as e 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. Veja um exemplo de código (com base no modelo de projeto Aplicativo de console), que também ilustra como usar aliases de namespace para as diferentes ilhas a fim de solucionar possíveis conflitos de namespace entre a projeção do C++/WinRT e a 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>() };
}

As implementações das funções as chamam a QueryInterface.The implementations of the as functions call QueryInterface. Se precisar de conversões de nível inferior que chamem apenas AddRef, use as funções auxiliares winrt::copy_to_abi e 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. O exemplo de código a seguir adiciona essas conversões de nível inferior ao exemplo de código anterior.This next code example adds these lower-level conversions to the code example above.

Importante

Ao interoperar com os tipos de ABI, é essencial que o tipo de ABI usado corresponda à interface padrão do objeto 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. Caso contrário, invocações de métodos no tipo de ABI, na verdade, chamarão os métodos no mesmo slot vtable na interface padrão, obtendo resultados muito 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. Observe que winrt::copy_to_abi não protege contra isso no tempo de compilação, pois ele usa void* para todos os tipos de ABI e pressupõe que o chamador tenha sido cuidadoso para não ter tipos incompatíveis.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. Isso ocorre para evitar a necessidade de usar cabeçalhos de C++/WinRT para referenciar cabeçalhos de ABI quando tipos de API talvez nunca sejam usados.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;
}

Apresentamos a seguir outras técnicas de conversão de nível inferior similares, mas que usam ponteiros brutos para tipos de interface ABI (aqueles definidos pelos cabeçalhos do SDK do Windows) no momento.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();

Nas conversões de nível inferior, que apenas copiam endereços, use as funções auxiliares winrt::get_abi, winrt::detach_abi e 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 é uma definição de macro e se expande para _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);

Função convert_from_abiconvert_from_abi function

Essa função auxiliar converte um ponteiro de interface ABI bruto em um objeto equivalente do C++/WinRT, com o mínimo de sobrecarga.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;
}

A função simplesmente chama QueryInterface para consultar a interface padrão do tipo C++/WinRT solicitado.The function simply calls QueryInterface to query for the default interface of the requested C++/WinRT type.

Como vimos, não é necessário ter uma função auxiliar para converter um objeto C++/WinRT em um ponteiro de interface 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. Basta usar a função de membro winrt::Windows::Foundation::IUnknown::as (ou try_as) para consultar a interface solicitada.Simply use the winrt::Windows::Foundation::IUnknown::as (or try_as) member function to query for the requested interface. As funções as e try_as retornam um objeto winrt::com_ptr que encapsula o tipo de ABI solicitada.The as and try_as functions return a winrt::com_ptr object wrapping the requested ABI type.

Exemplo de código que usa convert_from_abiCode example using convert_from_abi

Este é um exemplo de código que mostra essa função auxiliar na prática.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);
}

Interoperação com ponteiros de interface COM da ABIInteroperating with ABI COM interface pointers

O modelo de função auxiliar a seguir ilustra como copiar um ponteiro de interface COM da ABI de um determinado tipo no tipo de ponteiro inteligente equivalente projetado em C++/WinRT.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)};

Esse próximo modelo de função auxiliar é equivalente, exceto pelo fato de copiar do tipo de ponteiro inteligente da Biblioteca de Implementação do 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;
}

Confira também Consumir componentes COM com C++/WinRT.Also see Consume COM components with C++/WinRT.

Interoperação não segura com ponteiros de interface COM da ABIUnsafe interop with ABI COM interface pointers

A tabela a seguir mostra (além de outras operações) conversões não seguras entre um ponteiro de interface COM da ABI de um determinado tipo no tipo de ponteiro inteligente equivalente projetado em C++/WinRT.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 o código na tabela, suponha essas declarações.For the code in the table, assume these declarations.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Além disso, suponha que ISample é a interface padrão para Sample.Assume further that ISample is the default interface for Sample.

Você pode declarar isso em tempo de compilação com esse 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>);
OperaçãoOperation Como fazerHow to do it ObservaçõesNotes
Extrair ISample* de winrt::SampleExtract ISample* from winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s ainda tem o objeto.s still owns the object.
Desanexar ISample* de winrt::SampleDetach ISample* from winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s não tem mais o objeto.s no longer owns the object.
Transferir ISample* para um novo winrt::SampleTransfer ISample* to new winrt::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s assume a propriedade do objeto.s takes ownership of the object.
Definir ISample* em winrt::SampleSet ISample* into winrt::Sample *put_abi(s) = p; s assume a propriedade do objeto.s takes ownership of the object. Qualquer objeto anteriormente pertencente a s é vazado (será declarado na depuração).Any object previously owned by s is leaked (will assert in debug).
Receber ISample* em winrt::SampleReceive ISample* into winrt::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s assume a propriedade do objeto.s takes ownership of the object. Qualquer objeto anteriormente pertencente a s é vazado (será declarado na depuração).Any object previously owned by s is leaked (will assert in debug).
Substituir ISample* em winrt::SampleReplace ISample* in winrt::Sample attach_abi(s, p); s assume a propriedade do objeto.s takes ownership of the object. O objeto anteriormente pertencente a s é liberado.The object previously owned by s is freed.
Copiar ISample* para winrt::SampleCopy ISample* to winrt::Sample copy_from_abi(s, p); s faz uma nova referência ao objeto.s makes a new reference to the object. O objeto anteriormente pertencente a s é liberado.The object previously owned by s is freed.
Copiar winrt::Sample para ISample*Copy winrt::Sample to ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p recebe uma cópia do objeto.p receives a copy of the object. Qualquer objeto anteriormente pertencente a s é vazado.Any object previously owned by p is leaked.

Interoperação com o struct de GUID da ABIInteroperating with the ABI's GUID struct

GUID agora é projetado como winrt::guid.GUID is projected as winrt::guid. Para as APIs que você implementa, você deve usar winrt::guid para parâmetros de GUID.For APIs that you implement, you must use winrt::guid for GUID parameters. Caso contrário, há conversões automáticas entre winrt::guid e GUID, desde que você inclua unknwn.h (incluído implicitamente por <windows.h> e por muitos outros arquivos de cabeçalho) antes de incluir qualquer cabeçalho 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.

Se não fizer isso, você poderá embutir reinterpret_cast entre eles.If you don't do that, then you can hard-reinterpret_cast between them. Para a tabela a seguir, suponha essas declarações.For the table that follows, assume these declarations.

winrt::guid winrtguid;
GUID abiguid;
ConversãoConversion Com #include <unknwn.h>With #include <unknwn.h> Sem #include <unknwn.h>Without #include <unknwn.h>
De winrt::guid em GUIDFrom winrt::guid to GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
De GUID em winrt::guidFrom GUID to winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

Você pode construir um winrt::guid assim.You can construct a winrt::guid like this.

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

Para um gist mostrando como construir um winrt::guid de uma cadeia de caracteres, confira make_guid.cpp.For a gist showing how to construct a winrt::guid from a string, see make_guid.cpp.

Interoperação com a HSTRING da ABIInteroperating with the ABI's HSTRING

A tabela a seguir mostra conversões entre winrt::hstring e HSTRING, bem como outras operações.The table that follows shows conversions between winrt::hstring and HSTRING, and other operations. Para o código na tabela, suponha essas declarações.For the code in the table, assume these declarations.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
OperaçãoOperation Como fazerHow to do it ObservaçõesNotes
Extrair HSTRING de hstringExtract HSTRING from hstring h = static_cast<HSTRING>(get_abi(s)); s ainda tem a cadeia de caracteres.s still owns the string.
Desanexar HSTRING de hstringDetach HSTRING from hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s não tem mais a cadeia de caracteres.s no longer owns the string.
Definir HSTRING em hstringSet HSTRING into hstring *put_abi(s) = h; s assume a propriedade da cadeia de caracteres.s takes ownership of string. Qualquer cadeia de caracteres anteriormente pertencente a s é vazada (será declarada na depuração).Any string previously owned by s is leaked (will assert in debug).
Receber HSTRING em hstringReceive HSTRING into hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s assume a propriedade da cadeia de caracteres.s takes ownership of string. Qualquer cadeia de caracteres anteriormente pertencente a s é vazada (será declarada na depuração).Any string previously owned by s is leaked (will assert in debug).
Substituir HSTRING em hstringReplace HSTRING in hstring attach_abi(s, h); s assume a propriedade da cadeia de caracteres.s takes ownership of string. A cadeia de caracteres anteriormente pertencente a s é liberada.The string previously owned by s is freed.
Copiar HSTRING para hstringCopy HSTRING to hstring copy_from_abi(s, h); s faz uma cópia particular da cadeia de caracteres.s makes a private copy of the string. A cadeia de caracteres anteriormente pertencente a s é liberada.The string previously owned by s is freed.
Copiar hstring para HSTRINGCopy hstring to HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h recebe uma cópia da cadeia de caracteres.h receives a copy of the string. Qualquer cadeia de caracteres anteriormente pertencente a h é vazada.Any string previously owned by h is leaked.

APIs importantesImportant APIs