Interoperabilidade entre C++/WinRT e ABI

Este tópico mostra como converter entre a interface binária de aplicação (ABI) do SDK e objetos C++/WinRT . Pode usar estas técnicas para interoperar entre código que utiliza estas duas formas de programação com o Windows Runtime, ou pode usá-las à medida que move gradualmente o seu código do ABI para C++/WinRT.

Em geral, o C++/WinRT expõe os tipos ABI como void*, para que não precises de incluir ficheiros de cabeçalho da plataforma.

Note

Nos exemplos de código, usamos reinterpret_cast (em vez de static_cast) numa tentativa de assinalar que se trata de conversões de tipo inerentemente inseguras.

O que é o Windows Runtime ABI e quais são os tipos de ABI?

Uma classe de Windows Runtime (classe de tempo de execução) é realmente uma abstração. Esta abstração define uma interface binária (a Interface Binária de Aplicação, ou ABI) que permite que várias linguagens de programação interajam com um objeto. Independentemente da linguagem de programação, a interação do código cliente com um objeto do Windows Runtime ocorre ao nível mais baixo, com as construções da linguagem cliente traduzidas em chamadas para a ABI do objeto.

Os ficheiros de cabeçalho do SDK do Windows na pasta "%WindowsSdkDir%Include\10.0.17134.0\winrt" (ajuste o número da versão do SDK ao seu caso, se necessário) são os ficheiros de cabeçalho da ABI do Windows Runtime. Foram produzidos pelo compilador MIDL. Aqui está um exemplo de inclusão de um destes cabeçalhos.

#include <windows.foundation.h>

E aqui está um exemplo simplificado de um dos tipos de ABI que encontrará nesse cabeçalho SDK em particular. Repare no espaço de nomes ABI; Windows::Foundation, e todos os outros espaços de nomes do Windows, são declarados pelos ficheiros de cabeçalho do SDK no espaço de nomes 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 é uma interface COM. Mas, mais do que isso — uma vez que a sua base é IInspectableIUriRuntimeClass é uma interface do Windows Runtime. Note o tipo de retorno HRESULT , em vez da criação de exceções. E a utilização de artefactos como o identificador HSTRING (é boa prática repor esse identificador para nullptr quando terminar de o utilizar). Isto dá uma ideia de como é o Windows Runtime ao nível binário da aplicação; ou seja, ao nível de programação COM.

O Windows Runtime baseia-se em APIs do Modelo de Objetos de Componente (COM). Pode aceder ao Windows Runtime dessa forma, ou pode aceder através de projeções de linguagem. Uma projeção oculta os detalhes do COM e fornece uma experiência de programação mais natural para uma determinada linguagem.

Por exemplo, se procurar na pasta "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (novamente, ajuste o número de versão do SDK para o seu caso, se necessário), então encontrará os cabeçalhos de projeção da linguagem C++/WinRT. Há um cabeçalho para cada namespace do Windows, tal como há um cabeçalho ABI por namespace do Windows. Aqui está um exemplo de inclusão de um dos cabeçalhos C++/WinRT.

#include <winrt/Windows.Foundation.h>

E, a partir desse cabeçalho, aqui (simplificado) está o equivalente em C++/WinRT desse tipo ABI que acabámos de ver.

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

A interface aqui é C++ moderna e padrão. Acaba com os HRESULTs (o C++/WinRT lança exceções, se necessário). E a função acessor devolve um objeto string simples, que é limpo no final do seu âmbito.

Este tópico destina-se a casos em que pretende interoperar ou portar código que funcione na camada da Interface Binária de Aplicação (ABI).

Conversão para e a partir de tipos ABI no código

Para segurança e simplicidade, para conversões em ambas as direções pode simplesmente usar winrt::com_ptr, com_ptr::as e winrt::Windows::Foundation::IUnknown::as. Aqui está um exemplo de código (baseado no modelo de projeto da Consola App ), que também ilustra como podes usar aliases de namespace para as diferentes ilhas para lidar com potenciais colisões de namespace entre a projeção C++/WinRT e a 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 QueryInterface. Se quiseres conversões de nível inferior que só chamem o AddRef, podes usar as funções auxiliares winrt::copy_to_abi e winrt::copy_from_abi . Este próximo exemplo de código adiciona estas conversões de nível inferior ao exemplo de código acima.

Important

Ao interoperar com tipos ABI, é fundamental que o tipo de ABI utilizado corresponda à interface padrão do objeto C++/WinRT. Caso contrário, as invocações de métodos no tipo ABI acabarão por chamar métodos no mesmo slot vtable na interface padrão, com resultados muito inesperados. Note que winrt::copy_to_abi não oferece proteção contra isto em tempo de compilação, uma vez que utiliza void* para todos os tipos ABI e assume que o chamador teve o cuidado de não fazer corresponder incorretamente os tipos. Isto serve para evitar que os cabeçalhos C++/WinRT referenciam cabeçalhos ABI quando os tipos ABI podem nunca ser usados.

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

Aqui estão outras técnicas de conversões de baixo nível semelhantes, mas desta vez usando ponteiros brutos para tipos de interface ABI (aqueles definidos pelos cabeçalhos do SDK do 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 as conversões de nível mais baixo, que apenas copiam endereços, pode usar as funções auxiliares winrt::get_abi, winrt::detach_abi e winrt::attach_abi.

WINRT_ASSERT é uma definição macro, e expande-se para _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);

função convert_from_abi

Esta função auxiliar converte um ponteiro bruto da interface ABI num objeto equivalente em C++/WinRT, com 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;
}

A função simplesmente chama o QueryInterface para consultar a interface padrão do tipo C++/WinRT solicitado.

Como vimos, não é necessária uma função auxiliar para converter de um objeto C++/WinRT para o ponteiro de interface ABI equivalente. Basta usar a função membro winrt::Windows::Foundation::IUnknown::as (ou try_as) para consultar a interface pretendida. As funções as e try_as retornam um objeto winrt::com_ptr que envolve o tipo de ABI solicitado.

Exemplo de código usando convert_from_abi

Aqui está um exemplo de código que mostra esta função auxiliar na prática.

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

Interoperar com ponteiros de interface COM ABI

O modelo de função auxiliar abaixo ilustra como copiar um ponteiro de interface COM ABI de um dado tipo para o seu tipo equivalente de ponteiro inteligente projetado em 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)};

Este próximo modelo de função auxiliar é equivalente, exceto que copia do tipo de apontador inteligente das Bibliotecas de Implementação do 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;
}

Veja também Consumo de componentes COM com C++/WinRT.

Interoperação insegura com ponteiros de interface ABI COM

A tabela que se segue mostra (além de outras operações) conversões inseguras entre um ponteiro de interface COM ABI de um dado tipo e o seu tipo equivalente de ponteiro inteligente projetado em C++/WinRT. Para o código na tabela, assuma estas declarações.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Assuma ainda que ISample é a interface padrão para o Sample.

Podes afirmar isso em tempo de compilação com este código.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
Operation Como fazê-lo Notes
Extraia ISample* de winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); S ainda é dono do objeto.
Separar ISample* do winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); S já não é dono do objeto.
Transferir ISample* para o novo winrt::Exemplo winrt::Sample s{ p, winrt::take_ownership_from_abi }; s assume a propriedade do objeto.
Defina ISample* em winrt::Sample *put_abi(s) = p; s assume a propriedade do objeto. Qualquer objeto anteriormente pertencente a s é divulgado (será afirmado em debug).
Receba ISample* em winrt::Exemplo GetSample(reinterpret_cast<ISample**>(put_abi(s))); s assume a propriedade do objeto. Qualquer objeto anteriormente pertencente a s é divulgado (será afirmado em debug).
Substituir ISample* em winrt::Sample attach_abi(s, p); s assume a propriedade do objeto. O objeto anteriormente pertencente a s é libertado.
Copiar ISample* para winrt::Sample copy_from_abi(s, p); s faz uma nova referência ao objeto. O objeto anteriormente pertencente a s é libertado.
Copiar winrt::Sample para ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p recebe uma cópia do objeto. Qualquer objeto anteriormente pertencente a p é divulgado.

Interoperar com a estrutura GUID da ABI

GUID (/previous-versions/aa373931(v%3Dvs.80)) é projetado como winrt::guid. Para as APIs que implementa, deve usar winrt::guid para os parâmetros do tipo GUID. Caso contrário, existem conversões automáticas entre winrt::guid e GUID desde que inclua unknwn.h (implicitamente incluído pelo <windows.h> e muitos outros ficheiros de cabeçalho) antes de incluir qualquer cabeçalho C++/WinRT.

Se não fizeres isso, então podes fazer hard-reinterpret_cast entre eles. Para a tabela que se segue, assuma estas declarações.

winrt::guid winrtguid;
GUID abiguid;
Conversion Com #include <unknwn.h> Sem #include <unknwn.h>
De winrt::guid para GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
De GUID para winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

Podes construir um winrt::guid assim.

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

Para uma ideia geral de como construir um winrt::guid a partir de uma cadeia, veja make_guid.cpp.

Interoperar com o HSTRING da ABI

A tabela que se segue mostra conversões entre winrt::hstring e HSTRING, e outras operações. Para o código na tabela, assuma estas declarações.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
Operation Como fazê-lo Notes
Extrair HSTRING de hstring h = reinterpret_cast<HSTRING>(get_abi(s)); s continua a ser proprietário da cadeia de caracteres.
Separar HSTRING de hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s já não detém a string.
Definir HSTRING em hstring *put_abi(s) = h; s assume a propriedade da corda. Qualquer cadeia de caracteres anteriormente pertencente a s causa uma fuga de memória (irá gerar uma asserção em modo de depuração).
Receber HSTRING em hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s assume a propriedade da corda. Qualquer string anteriormente detida por s fica perdida (irá gerar uma asserção em modo de depuração).
Substituir HSTRING por hstring attach_abi(s, h); s assume a propriedade da corda. A cadeia de caracteres anteriormente pertencente a s é libertada da memória.
Copiar HSTRING para hstring copy_from_abi(s, h); s faz uma cópia privada da cadeia de caracteres. A string que pertencia anteriormente a s é libertada.
Copiar hstring para HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h recebe uma cópia da cadeia de caracteres. Qualquer cadeia de caracteres anteriormente possuída por h fica perdida.

Além disso, os ajudantes de strings Windows Implementation Libraries (WIL) realizam manipulações básicas de strings. Para usar os auxiliares de string WIL, inclua <wil/resource.h> e consulte a tabela abaixo. Siga os links na tabela para detalhes completos.

Operation WIL string helper para mais informações
Forneça um ponteiro bruto para uma cadeia Unicode ou ANSI e um comprimento opcional; obtenha um contentor unique_any adequadamente especializado wil::make_something_string
Desdobrar um objeto inteligente até encontrar um ponteiro de string Unicode bruto terminado em null wil::str_raw_ptr
Obtenha a string contida num objeto ponteiro inteligente; ou a string vazia L"", se o ponteiro inteligente estiver vazio wil::string_get_not_null
Concatenar qualquer número de cadeias wil::str_concat
Obtenha uma cadeia de caracteres a partir de uma cadeia de formato estilo printf e uma lista de parâmetros correspondente wil::str_printf

APIs importantes