Interop antara C ++/WinRT dan ABI

Topik ini menunjukkan cara mengonversi antara antarmuka biner aplikasi SDK (ABI) dan objek C ++/WinRT . Anda dapat menggunakan teknik ini untuk interop antara kode yang menggunakan dua cara pemrograman dengan runtime Windows, atau Anda dapat menggunakannya saat Anda secara bertahap memindahkan kode Anda dari ABI ke C ++/ WinRT.

Secara umum, C++/WinRT mengekspos jenis ABI sebagai void*, sehingga Anda tidak perlu menyertakan file header platform.

Apa itu Windows Runtime ABI, dan apa saja jenis ABI?

Kelas runtime Windows (kelas runtime) benar-benar abstraksi. Abstraksi ini mendefinisikan antarmuka biner (Application Binary Interface, atau ABI) yang memungkinkan berbagai bahasa pemrograman untuk berinteraksi dengan suatu objek. Terlepas dari bahasa pemrograman, interaksi kode klien dengan objek runtime Windows terjadi pada tingkat terendah, dengan konstruksi bahasa klien diterjemahkan ke dalam panggilan ke abi objek.

Header SDK Windows di folder "%WindowsSdkDir%Include\10.0.17134.0\winrt" (sesuaikan nomor versi SDK untuk kasus Anda, jika perlu), adalah file header Runtime ABI Windows. Mereka diproduksi oleh kompiler MIDL. Berikut adalah contoh termasuk salah satu header ini.

#include <windows.foundation.h>

Dan berikut adalah contoh sederhana dari salah satu jenis ABI yang akan Anda temukan di header SDK tertentu. Perhatikan ruang nama ABI; Windows::Foundation, dan semua ruang nama Windows lainnya, dideklarasikan oleh header SDK dalam ruang nama 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 adalah antarmuka COM. Tetapi lebih dari itu — karena basisnya adalah IInspectableIUriRuntimeClass adalah antarmuka runtime Windows. Perhatikan jenis pengembalian HRESULT , bukan peningkatan pengecualian. Dan penggunaan artefak seperti pegangan HSTRING (ada baiknya untuk mengatur pegangan itu kembali nullptr ketika Anda selesai dengan itu). Ini memberi rasa seperti apa Windows Runtime di tingkat biner aplikasi; dengan kata lain, pada tingkat pemrograman COM.

Runtime Windows didasarkan pada API Component Object Model (COM). Anda dapat mengakses runtime Windows dengan cara itu, atau Anda dapat mengaksesnya melalui proyeksi bahasa. Proyeksi menyembunyikan detail COM, dan memberikan pengalaman pemrograman yang lebih alami untuk bahasa tertentu.

Misalnya, jika Anda melihat di folder "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (sekali lagi, sesuaikan nomor versi SDK untuk kasus Anda, jika perlu), maka Anda akan menemukan header proyeksi bahasa C++/WinRT. Ada header untuk setiap Windows namespace, sama seperti ada satu header ABI per Windows namespace. Berikut adalah contoh termasuk salah satu header C ++/WinRT.

#include <winrt/Windows.Foundation.h>

Dan, dari header itu, di sini (disederhanakan) adalah C ++/WinRT yang setara dengan tipe ABI yang baru saja kita lihat.

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

Antarmuka di sini modern, standar C ++. Ini menghilangkan HRESULTs (C ++/ WinRT menimbulkan pengecualian jika perlu). Dan fungsi aksesori mengembalikan objek string sederhana, yang dibersihkan di akhir cakupannya.

Topik ini adalah untuk kasus ketika Anda ingin interop dengan, atau port, kode yang bekerja pada lapisan Application Binary Interface (ABI).

Mengonversi ke dan dari jenis ABI dalam kode

Untuk keamanan dan kesederhanaan, untuk konversi di kedua arah Anda cukup menggunakan winrt::com_ptr, com_ptr::as, dan winrt::Windows::Foundation::IUnknown::as. Berikut adalah contoh kode (berdasarkan template proyek Aplikasi Konsol ), yang juga menggambarkan bagaimana Anda dapat menggunakan alias namespace untuk pulau yang berbeda untuk menangani tabrakan namespace potensial antara proyeksi C ++/ WinRT dan 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>() };
}

Implementasi fungsi as memanggil QueryInterface. Jika Anda menginginkan konversi tingkat rendah yang hanya memanggil AddRef, maka Anda dapat menggunakan fungsi winrt::copy_to_abi dan winrt::copy_from_abi helper. Contoh kode berikutnya ini menambahkan konversi tingkat rendah ini ke contoh kode di atas.

Penting

Saat beroperasi dengan jenis ABI, sangat penting bahwa jenis ABI yang digunakan sesuai dengan antarmuka default objek C ++/WinRT. Jika tidak, pemanggilan metode pada jenis ABI benar-benar akan berakhir dengan metode panggilan di slot vtable yang sama pada antarmuka default dengan hasil yang sangat tidak terduga. Perhatikan bahwa winrt::copy_to_abi tidak melindungi terhadap hal ini pada waktu kompilasi karena menggunakan void * untuk semua jenis ABI dan mengasumsikan bahwa penelepon telah berhati-hati untuk tidak salah mencocokkan jenis. Ini untuk menghindari memerlukan header C ++/WinRT untuk mereferensikan header ABI ketika jenis ABI mungkin tidak akan pernah digunakan.

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

Berikut adalah teknik konversi tingkat rendah lainnya tetapi menggunakan pointer mentah ke jenis antarmuka ABI (yang didefinisikan oleh header SDK Windows) kali ini.

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

Untuk konversi tingkat terendah, yang hanya menyalin alamat, Anda dapat menggunakan fungsi winrt::get_abi, winrt::d etach_abi, dan winrt::attach_abi helper.

WINRT_ASSERT adalah definisi makro, dan meluas ke _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);

convert_from_abi

Fungsi pembantu ini mengubah penunjuk antarmuka ABI mentah menjadi objek C ++/WinRT yang setara, dengan overhead minimal.

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

Fungsi ini hanya memanggil QueryInterface untuk kueri untuk antarmuka default dari jenis C ++/WinRT yang diminta.

Seperti yang telah kita lihat, fungsi pembantu tidak diperlukan untuk mengonversi dari objek C ++/WinRT ke penunjuk antarmuka ABI yang setara. Cukup gunakan fungsi anggota winrt::Windows::Foundation::IUnknown::as (atau try_as) untuk meminta antarmuka yang diminta. Fungsi as dan try_as mengembalikan objek winrt::com_ptr yang membungkus jenis ABI yang diminta.

Contoh kode menggunakan convert_from_abi

Berikut adalah contoh kode yang menunjukkan fungsi pembantu ini dalam praktiknya.

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

Interoperating dengan pointer antarmuka ABI COM

Template fungsi pembantu di bawah ini mengilustrasikan cara menyalin penunjuk antarmuka ABI COM dari jenis tertentu ke jenis pointer cerdas yang diproyeksikan C ++/WinRT yang setara.

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

Template fungsi pembantu berikutnya ini setara, kecuali bahwa itu disalin dari jenis pointer pintar dari 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;
}

Lihat juga Konsumsi komponen COM dengan C++/WinRT.

Interop yang tidak aman dengan penunjuk antarmuka ABI COM

Tabel berikut menunjukkan (selain operasi lain) konversi yang tidak aman antara penunjuk antarmuka ABI COM dari jenis tertentu dan jenis pointer pintar yang diproyeksikan C ++/WinRT yang setara. Untuk kode dalam tabel, asumsikan deklarasi ini.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Asumsikan lebih lanjut bahwa iSample adalah antarmuka default untuk Sampel.

Anda dapat menyatakan bahwa pada waktu kompilasi dengan kode ini.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
Operasi Cara melakukannya Catatan
Ekstrak ISample* dari winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s masih memiliki objek.
Lepaskan ISample* dari winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); Tidak lagi memiliki objek.
Transfer ISample* ke winrt baru::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s mengambil kepemilikan objek.
Atur ISample* ke winrt::Sample *put_abi(s) = p; s mengambil kepemilikan objek. Setiap objek yang sebelumnya dimiliki oleh s bocor (akan menegaskan dalam debug).
Terima ISample* ke winrt::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s mengambil kepemilikan objek. Setiap objek yang sebelumnya dimiliki oleh s bocor (akan menegaskan dalam debug).
Ganti ISample* di winrt::Sample attach_abi(s, p); s mengambil kepemilikan objek. Objek yang sebelumnya dimiliki oleh S dibebaskan.
Salin ISample* ke winrt::Sample copy_from_abi(s, p); s membuat referensi baru ke objek. Objek yang sebelumnya dimiliki oleh S dibebaskan.
Salin winrt::Sampel ke ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p menerima salinan objek. Setiap objek yang sebelumnya dimiliki oleh p bocor.

Interoperating dengan struct GUID ABI

GUID diproyeksikan sebagai winrt::guid. Untuk API yang Anda terapkan, Anda harus menggunakan winrt::guid untuk parameter GUID. Jika tidak, ada konversi otomatis antara winrt::guid dan GUID selama Anda menyertakan unknwn.h (secara implisit disertakan oleh <windows.h> dan banyak file header lainnya) sebelum Anda menyertakan header C ++/WinRT.

Jika Anda tidak melakukan itu, maka Anda bisa keras - direinterpret_cast antara mereka. Untuk tabel berikut, asumsikan deklarasi ini.

winrt::guid winrtguid;
GUID abiguid;
Konversi Dengan #include <unknwn.h> Tanpa #include <unknwn.h>
Dari winrt::guid ke GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
Dari GUID ke winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

Anda dapat membangun winrt::guid seperti ini.

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

Untuk intisari yang menunjukkan cara membuat winrt::guid dari string, lihat make_guid.cpp.

Interoperating dengan ABI HSTRING

Tabel berikut menunjukkan konversi antara winrt::hstring dan HSTRING, dan operasi lainnya. Untuk kode dalam tabel, asumsikan deklarasi ini.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
Operasi Cara melakukannya Catatan
Ekstrak HSTRING dari hstring h = static_cast<HSTRING>(get_abi(s)); s masih memiliki string.
Lepaskan HSTRING dari hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); Tidak lagi memiliki string.
Atur HSTRING ke hstring *put_abi(s) = h; s mengambil kepemilikan string. Setiap string yang sebelumnya dimiliki oleh s bocor (akan menegaskan dalam debug).
Terima HSTRING ke hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s mengambil kepemilikan string. Setiap string yang sebelumnya dimiliki oleh s bocor (akan menegaskan dalam debug).
Ganti HSTRING di hstring attach_abi(s, h); s mengambil kepemilikan string. String yang sebelumnya dimiliki oleh s dibebaskan.
Salin HSTRING ke hstring copy_from_abi(s, h); s membuat salinan pribadi dari string. String yang sebelumnya dimiliki oleh s dibebaskan.
Salin hstring ke HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h menerima salinan string. Setiap string yang sebelumnya dimiliki oleh h bocor.

Selain itu, pembantu string Windows Implementation Libraries (WIL) melakukan manipulasi string dasar. Untuk menggunakan pembantu string WIL, sertakan <wil/resource.h>, dan lihat tabel di bawah ini. Ikuti tautan dalam tabel untuk detail lengkap.

Operasi Pembantu string WIL untuk info lebih lanjut
Menyediakan penunjuk string Unicode atau ANSI mentah dan panjang opsional; mendapatkan pembungkus unique_any khusus yang sesuai wil::make_something_string
Membuka bungkus objek pintar sampai pointer string Unicode null-terminated mentah ditemukan wil::str_raw_ptr
Dapatkan string yang dibungkus oleh objek penunjuk cerdas; atau string L"" kosong jika penunjuk pintar kosong wil::string_get_not_null
Menggabungkan sejumlah string wil::str_concat
Mendapatkan string dari string format gaya printf dan daftar parameter yang sesuai wil::str_printf

API Penting