Interoperabilitas 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.

  • Migrasikan seluruh proyek dalam satu kali proses. Opsi paling sederhana untuk proyek yang tidak terlalu besar. Jika Anda memiliki proyek komponen Windows Runtime, maka strategi ini adalah satu-satunya pilihan 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 suatu waktu, tipe halaman XAML Anda harus berupa C++/WinRT seluruhnya atau C++/CX seluruhnya.

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 informasi dan contoh kode yang menunjukkan cara mendukung tugas PPL dan coroutine secara berdampingan dalam proyek yang sama (misalnya, memanggil coroutine dari rantai tugas), lihat topik yang lebih tingkat lanjut Asinkroni dan interoperabilitas 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 pembantu from_cx mengonversi objek C++/CX menjadi objek C++/WinRT yang setara. Fungsi ini mengonversi objek C++/CX menjadi penunjuk antarmuka IUnknown yang mendasarinya. 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++/CXsafe_cast. Dan, fungsi winrt::put_abi mengambil alamat penunjuk antarmuka IUnknown yang mendasari 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 mengembalikan pointer ke antarmuka IUnknown yang mendasari 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 proyekVersi Platform Target> diatur ke 10.0.17134.0 (Windows 10, versi 1803) atau lebih tinggi.

Instal paket C++/WinRT NuGet

Microsoft.Windows. Paket CppWinRT NuGet 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 project tersebut.

Important

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. Dalam properti proyek, C/C++>Umum>Gunakan Ekstensi Windows Runtime>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. Dalam properti proyek, Properti Umum>C++/WinRT>Bahasa Proyek>C++/CX. Melakukan itu akan menambahkan properti berikut ke file Anda .vcxproj .

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

Important

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

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

Sertakan berkas 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 terproyeksi C++/WinRT yang sesuai dengan namespace tersebut seperti ini (Anda tidak perlu secara eksplisit menyertakan winrt/base.h sekarang karena akan disertakan secara otomatis).

// 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 mengatasi potensi benturan namespace 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 pembantu from_cx dan to_cx 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. Dalam properti proyek, C/C++>Umum>Gunakan Ekstensi Windows Runtime>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 untuk bagian-bagian kode yang berbeda guna menghindari potensi bentrokan namespace antara proyeksi C++/WinRT dan proyeksi C++/CX.

  • Buat proyek Visual C++>Windows Universal>Core App (C++/WinRT).
  • Dalam properti proyek, C/C++>Umum>Gunakan Ekstensi Windows Runtime>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.
  • Buat 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