Interop antara C++/WinRT dan C++/CX

Sebelum membaca topik ini, Anda memerlukan info dalam topik Pindahkan ke C++/WinRT dari C++/CX. Topik itu memperkenalkan dua opsi strategi utama untuk memindahkan proyek C++/CX Anda ke C++/WinRT.

  • Port seluruh proyek dalam satu pass. Opsi paling sederhana untuk proyek yang tidak terlalu besar. Jika Anda memiliki proyek komponen Windows Runtime, maka strategi ini adalah satu-satunya opsi Anda.
  • Port proyek secara bertahap (ukuran atau kompleksitas basis kode Anda mungkin membuat ini diperlukan). Tetapi strategi ini meminta Anda untuk mengikuti proses porting di mana untuk waktu kode C++/CX dan C++/WinRT ada berdampingan dalam proyek yang sama. Untuk proyek XAML, pada waktu tertentu, jenis halaman XAML Anda harus semua C++/WinRT atau semua C++/CX.

Topik interop ini relevan untuk strategi kedua tersebut—untuk kasus ketika Anda perlu memindahkan proyek Anda secara bertahap. Topik ini menunjukkan kepada Anda berbagai bentuk pasangan fungsi pembantu yang dapat Anda gunakan untuk mengonversi objek C++/CX (dan jenis lainnya) menjadi objek C++/WinRT (dan sebaliknya) dalam proyek yang sama.

Fungsi pembantu ini akan sangat berguna saat Anda mem-port kode Anda secara bertahap dari C++/CX ke C++/WinRT. Atau Anda mungkin hanya memilih untuk menggunakan proyeksi bahasa C++/WinRT dan C++/CX dalam proyek yang sama, baik Anda melakukan porting atau tidak, dan menggunakan fungsi pembantu ini untuk mengoperasikan antara keduanya.

Setelah membaca topik ini, untuk contoh info dan kode yang menunjukkan cara mendukung tugas PPL dan koroutin berdampingan dalam proyek yang sama (misalnya, memanggil koroutine dari rantai tugas), lihat topik Asinkron yang lebih canggih, dan interop antara C++/WinRT dan C++/CX.

Fungsi from_cx dan to_cx

Berikut adalah daftar kode sumber file header bernama interop_helpers.h, yang berisi berbagai fungsi pembantu konversi. Saat Anda secara bertahap memindahkan proyek Anda, akan ada bagian yang masih di C++/CX, dan bagian yang telah Anda port ke C++/WinRT. Anda dapat menggunakan fungsi pembantu ini untuk mengonversi objek (dan jenis lainnya) ke dan dari C++/CX dan C++/WinRT dalam proyek Anda di titik batas antara kedua bagian tersebut.

Bagian yang mengikuti daftar kode menjelaskan fungsi pembantu, dan cara membuat dan menggunakan file header di proyek Anda.

// interop_helpers.h
#pragma once

template <typename T>
T from_cx(Platform::Object^ from)
{
    T to{ nullptr };

    if (from != nullptr)
    {
        winrt::check_hresult(reinterpret_cast<::IUnknown*>(from)
            ->QueryInterface(winrt::guid_of<T>(), winrt::put_abi(to)));
    }

    return to;
}

template <typename T>
T^ to_cx(winrt::Windows::Foundation::IUnknown const& from)
{
    return safe_cast<T^>(reinterpret_cast<Platform::Object^>(winrt::get_abi(from)));
}

inline winrt::hstring from_cx(Platform::String^ const& from)
{
    return reinterpret_cast<winrt::hstring&>(const_cast<Platform::String^&>(from));
}

inline Platform::String^ to_cx(winrt::hstring const& from)
{
    return reinterpret_cast<Platform::String^&>(const_cast<winrt::hstring&>(from));
}

inline winrt::guid from_cx(Platform::Guid const& from)
{
    return reinterpret_cast<winrt::guid&>(const_cast<Platform::Guid&>(from));
}

inline Platform::Guid to_cx(winrt::guid const& from)
{
    return reinterpret_cast<Platform::Guid&>(const_cast<winrt::guid&>(from));
}

Fungsi from_cx

Fungsi from_cx helper mengonversi objek C++/CX menjadi objek C++/WinRT yang setara. Fungsi ini melemparkan objek C++/CX ke penunjuk antarmuka IUnknown yang mendasarnya. Kemudian memanggil QueryInterface pada penunjuk tersebut untuk mengkueri antarmuka default objek C++/WinRT. QueryInterface adalah antarmuka biner aplikasi Windows Runtime (ABI) yang setara dengan ekstensi C++/CX safe_cast . Dan, fungsi winrt::p ut_abi mengambil alamat penunjuk antarmuka IUnknown yang mendasar objek C++/WinRT sehingga dapat diatur ke nilai lain.

Fungsi to_cx

Fungsi pembantu to_cx mengonversi objek C++/WinRT menjadi objek C++/CX yang setara. Fungsi winrt::get_abi mengambil pointer ke antarmuka IUnknown yang mendasar objek C++/WinRT. Fungsi ini mentransmisikan penunjuk tersebut ke objek C++/CX sebelum menggunakan ekstensi C++/CX safe_cast untuk mengkueri jenis C++/CX yang diminta.

File interop_helpers.h header

Untuk menggunakan fungsi pembantu dalam proyek Anda, ikuti langkah-langkah berikut.

  • Tambahkan item File Header (.h) baru ke proyek Anda, dan beri nama interop_helpers.h.
  • Ganti konten interop_helpers.h dengan daftar kode di atas.
  • Tambahkan ini termasuk ke pch.h.
// pch.h
...
#include <unknwn.h>
// Include C++/WinRT projected Windows API headers here.
...
#include <interop_helpers.h>

Mengambil proyek C++/CX dan menambahkan dukungan C++/WinRT

Bagian ini menjelaskan apa yang harus dilakukan jika Anda telah memutuskan untuk mengambil proyek C++/CX yang ada, menambahkan dukungan C++/WinRT ke sana, dan melakukan pekerjaan porting Anda di sana. Lihat juga dukungan Visual Studio untuk C++/WinRT.

Untuk mencampur C++/CX dan C++/WinRT dalam proyek C++/CX—termasuk menggunakan fungsi pembantu from_cx dan to_cx dalam proyek—Anda harus menambahkan dukungan C++/WinRT secara manual ke proyek.

Pertama, buka proyek C++/CX Anda di Visual Studio dan konfirmasikan bahwa properti proyek Versi Platform Target Umum>diatur ke 10.0.17134.0 (Windows 10, versi 1803) atau lebih besar.

Instal paket C++/WinRT NuGet

Paket NuGet Microsoft.Windows.CppWinRT menyediakan dukungan build C++/WinRT (properti dan target MSBuild). Untuk menginstalnya, klik item menu Project>Kelola Paket NuGet...>Telusuri, ketik, atau tempel Microsoft.Windows.CppWinRT di kotak pencarian, pilih item di hasil pencarian, lalu klik Instal untuk menginstal paket untuk proyek tersebut.

Penting

Menginstal paket C++/WinRT NuGet menyebabkan dukungan untuk C++/CX dinonaktifkan dalam proyek. Jika Anda akan melakukan port dalam satu pass, sebaiknya biarkan dukungan tersebut dinonaktifkan sehingga pesan build akan membantu Anda menemukan (dan port) semua dependensi Anda pada C++/CX (akhirnya mengubah apa yang merupakan proyek C++/CX murni menjadi proyek C++/WinRT murni). Tapi lihat bagian berikutnya untuk info tentang mengaktifkannya kembali.

Mengaktifkan kembali dukungan C++/CX

Jika Anda melakukan port dalam satu pass, maka Anda tidak perlu melakukan ini. Tetapi jika Anda perlu melakukan port secara bertahap, maka pada titik ini Anda harus mengaktifkan kembali dukungan C++/CX di proyek Anda. Di properti proyek, C/C++>General>Gunakan Ekstensi>Runtime Windows Ya (/ZW)).

Atau (atau, untuk proyek XAML, selain itu), Anda dapat menambahkan dukungan C++/CX dengan menggunakan halaman properti proyek C++/WinRT di Visual Studio. Di properti proyek, Properti>Umum C++/WinRT>Project Language>C++/CX. Melakukan itu akan menambahkan properti berikut ke file Anda .vcxproj .

  <PropertyGroup Label="Globals">
    <CppWinRTProjectLanguage>C++/CX</CppWinRTProjectLanguage>
  </PropertyGroup>

Penting

Setiap kali Anda perlu membangun untuk memproses konten File Midl (.idl) menjadi file stub, Anda harus mengubah Bahasa Proyek kembali ke C++/WinRT. Setelah build menghasilkan stub tersebut, ubah Bahasa Proyek kembali ke C++/CX.

Untuk daftar opsi kustomisasi serupa (yang menyempurnakan perilaku cppwinrt.exe alat), lihat readme paket NuGet Microsoft.Windows.CppWinRT.

Sertakan file header C++/WinRT

Paling tidak yang harus Anda lakukan adalah, dalam file header yang telah dikommpilasikan sebelumnya (biasanya pch.h), sertakan winrt/base.h seperti yang ditunjukkan di bawah ini.

// pch.h
...
#include <winrt/base.h>
...

Tetapi Anda hampir pasti membutuhkan jenis di namespace winrt::Windows::Foundation . Dan Anda mungkin sudah mengetahui namespace lain yang akan Anda butuhkan. Jadi sertakan header WINDOWS API yang diproyeksikan C++/WinRT yang sesuai dengan namespace seperti ini (Anda tidak perlu secara eksplisit menyertakan winrt/base.h sekarang karena akan disertakan secara otomatis untuk Anda).

// pch.h
...
#include <winrt/Windows.Foundation.h>
// Include any other C++/WinRT projected Windows API headers here.
...

Lihat juga contoh kode di bagian berikut (Mengambil proyek C++/WinRT dan menambahkan dukungan C++/CX) untuk teknik menggunakan alias namespace cx namespace dan namespace winrt. Teknik itu memungkinkan Anda menangani potensi tabrakan namespace layanan antara proyeksi C++/WinRT dan proyeksi C++/CX.

Tambahkan interop_helpers.h ke proyek

Anda sekarang dapat menambahkan fungsi from_cx dan to_cx ke proyek C++/CX Anda. Untuk petunjuk tentang melakukannya, lihat bagian fungsi from_cx dan to_cx di atas.

Mengambil proyek C++/WinRT dan menambahkan dukungan C++/CX

Bagian ini menjelaskan apa yang harus dilakukan jika Anda telah memutuskan untuk membuat proyek C++/WinRT baru, dan melakukan pekerjaan porting Anda di sana.

Untuk mencampur C++/WinRT dan C++/CX dalam proyek C++/WinRT—termasuk menggunakan fungsi from_cx dan to_cx pembantu dalam proyek—Anda harus menambahkan dukungan C++/CX secara manual ke proyek.

  • Buat proyek C++/WinRT baru di Visual Studio menggunakan salah satu templat proyek C++/WinRT (lihat dukungan Visual Studio untuk C++/WinRT).
  • Aktifkan dukungan proyek untuk C++/CX. Di properti proyek, C/C++>General>Gunakan Ekstensi>Runtime Windows Ya (/ZW).

Contoh proyek C++/WinRT yang memperlihatkan dua fungsi pembantu yang digunakan

Di bagian ini, Anda dapat membuat contoh proyek C++/WinRT yang menunjukkan cara menggunakan from_cx dan to_cx. Ini juga menggambarkan bagaimana Anda dapat menggunakan alias namespace layanan untuk berbagai pulau kode, untuk menangani potensi tabrakan namespace layanan antara proyeksi C++/WinRT dan proyeksi C++/CX.

  • Buat proyek Visual C++>Windows Universal>Core App (C++/WinRT).
  • Di properti proyek, C/C++>General>Gunakan Ekstensi>Runtime Windows Ya (/ZW).
  • Tambahkan interop_helpers.h ke proyek. Untuk petunjuk tentang melakukannya, lihat bagian fungsi from_cx dan to_cx di atas.
  • Ganti konten App.cpp dengan daftar kode di bawah ini.
  • Bangun dan jalankan.

WINRT_ASSERT adalah definisi makro, dan meluas ke _ASSERTE.

// App.cpp
#include "pch.h"
#include <sstream>

namespace cx
{
    using namespace Windows::Foundation;
}

namespace winrt
{
    using namespace Windows;
    using namespace Windows::ApplicationModel::Core;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Numerics;
    using namespace Windows::UI;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::Composition;
}

struct App : winrt::implements<App, winrt::IFrameworkViewSource, winrt::IFrameworkView>
{
    winrt::CompositionTarget m_target{ nullptr };
    winrt::VisualCollection m_visuals{ nullptr };
    winrt::Visual m_selected{ nullptr };
    winrt::float2 m_offset{};

    winrt::IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(winrt::CoreApplicationView const &)
    {
    }

    void Load(winrt::hstring const&)
    {
    }

    void Uninitialize()
    {
    }

    void Run()
    {
        winrt::CoreWindow window = winrt::CoreWindow::GetForCurrentThread();
        window.Activate();

        winrt::CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(winrt::CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(winrt::CoreWindow const & window)
    {
        winrt::Compositor compositor;
        winrt::ContainerVisual root = compositor.CreateContainerVisual();
        m_target = compositor.CreateTargetForCurrentView();
        m_target.Root(root);
        m_visuals = root.Children();

        window.PointerPressed({ this, &App::OnPointerPressed });
        window.PointerMoved({ this, &App::OnPointerMoved });

        window.PointerReleased([&](auto && ...)
        {
            m_selected = nullptr;
        });
    }

    void OnPointerPressed(IInspectable const &, winrt::PointerEventArgs const & args)
    {
        winrt::float2 const point = args.CurrentPoint().Position();

        for (winrt::Visual visual : m_visuals)
        {
            winrt::float3 const offset = visual.Offset();
            winrt::float2 const size = visual.Size();

            if (point.x >= offset.x &&
                point.x < offset.x + size.x &&
                point.y >= offset.y &&
                point.y < offset.y + size.y)
            {
                m_selected = visual;
                m_offset.x = offset.x - point.x;
                m_offset.y = offset.y - point.y;
            }
        }

        if (m_selected)
        {
            m_visuals.Remove(m_selected);
            m_visuals.InsertAtTop(m_selected);
        }
        else
        {
            AddVisual(point);
        }
    }

    void OnPointerMoved(IInspectable const &, winrt::PointerEventArgs const & args)
    {
        if (m_selected)
        {
            winrt::float2 const point = args.CurrentPoint().Position();

            m_selected.Offset(
            {
                point.x + m_offset.x,
                point.y + m_offset.y,
                0.0f
            });
        }
    }

    void AddVisual(winrt::float2 const point)
    {
        winrt::Compositor compositor = m_visuals.Compositor();
        winrt::SpriteVisual visual = compositor.CreateSpriteVisual();

        static winrt::Color colors[] =
        {
            { 0xDC, 0x5B, 0x9B, 0xD5 },
            { 0xDC, 0xED, 0x7D, 0x31 },
            { 0xDC, 0x70, 0xAD, 0x47 },
            { 0xDC, 0xFF, 0xC0, 0x00 }
        };

        static unsigned last = 0;
        unsigned const next = ++last % _countof(colors);
        visual.Brush(compositor.CreateColorBrush(colors[next]));

        float const BlockSize = 100.0f;

        visual.Size(
        {
            BlockSize,
            BlockSize
        });

        visual.Offset(
        {
            point.x - BlockSize / 2.0f,
            point.y - BlockSize / 2.0f,
            0.0f,
        });

        m_visuals.InsertAtTop(visual);

        m_selected = visual;
        m_offset.x = -BlockSize / 2.0f;
        m_offset.y = -BlockSize / 2.0f;
    }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    winrt::init_apartment();

    winrt::Uri uri(L"http://aka.ms/cppwinrt");
    std::wstringstream wstringstream;
    wstringstream << L"C++/WinRT: " << uri.Domain().c_str() << std::endl;

    // Convert from a C++/WinRT type to a C++/CX type.
    cx::Uri^ cx = to_cx<cx::Uri>(uri);
    wstringstream << L"C++/CX: " << cx->Domain->Data() << std::endl;
    ::OutputDebugString(wstringstream.str().c_str());

    // Convert from a C++/CX type to a C++/WinRT type.
    winrt::Uri uri_from_cx = from_cx<winrt::Uri>(cx);
    WINRT_ASSERT(uri.Domain() == uri_from_cx.Domain());
    WINRT_ASSERT(uri == uri_from_cx);

    winrt::CoreApplication::Run(winrt::make<App>());
}

API penting