Mengonsumsi komponen COM dengan C++/WinRT

Anda dapat menggunakan fasilitas pustaka C++/WinRT untuk mengonsumsi komponen COM, seperti grafik 2-D dan 3-D berkinerja tinggi dari API DirectX. C++/WinRT adalah cara paling sederhana untuk menggunakan DirectX tanpa mengorbankan performa. Topik ini menggunakan contoh kode Direct2D untuk menunjukkan cara menggunakan C++/WinRT untuk menggunakan kelas dan antarmuka COM. Anda tentu saja dapat mencampur pemrograman COM dan Windows Runtime dalam proyek C++/WinRT yang sama.

Di akhir topik ini, Anda akan menemukan daftar kode sumber lengkap aplikasi Direct2D minimal. Kami akan mengangkat kutipan dari kode tersebut dan menggunakannya untuk menggambarkan cara menggunakan komponen COM menggunakan C++/WinRT menggunakan berbagai fasilitas pustaka C++/WinRT.

COM smart pointers (winrt::com_ptr)

Ketika Anda memprogram dengan COM, Anda bekerja langsung dengan antarmuka daripada dengan objek (yang juga benar di belakang layar untuk WINDOWS Runtime API, yang merupakan evolusi COM). Untuk memanggil fungsi pada kelas COM, misalnya, Anda mengaktifkan kelas, mendapatkan antarmuka kembali, lalu Anda memanggil fungsi pada antarmuka tersebut. Untuk mengakses status objek, Anda tidak mengakses anggota datanya secara langsung; sebagai gantinya, Anda memanggil fungsi aksesor dan mutator pada antarmuka.

Untuk lebih spesifik, kita berbicara tentang berinteraksi dengan penunjuk antarmuka. Dan untuk itu, kami mendapat manfaat dari keberadaan jenis pointer pintar COM di C++/WinRT— jenis winrt::com_ptr .

#include <d2d1_1.h>
...
winrt::com_ptr<ID2D1Factory1> factory;

Kode di atas menunjukkan cara mendeklarasikan penunjuk pintar yang tidak diinisialisasi ke antarmuka COM ID2D1Factory1. Penunjuk pintar tidak diinisialisasi, jadi belum menunjuk ke antarmuka ID2D1Factory1 milik objek aktual apa pun (tidak menunjuk ke antarmuka sama sekali). Tetapi memiliki potensi untuk melakukannya; dan (menjadi penunjuk cerdas) ia memiliki kemampuan melalui penghitungan referensi COM untuk mengelola masa pakai objek pemilik antarmuka yang ditunjukkannya, dan menjadi media tempat Anda memanggil fungsi pada antarmuka tersebut.

Fungsi COM yang mengembalikan penunjuk antarmuka sebagai batal

Anda dapat memanggil fungsi com_ptr::p ut_void untuk menulis ke pointer mentah yang mendasar penunjuk pintar yang tidak diinisialisasi.

D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_NONE };
D2D1CreateFactory(
    D2D1_FACTORY_TYPE_SINGLE_THREADED,
    __uuidof(factory),
    &options,
    factory.put_void()
);

Kode di atas memanggil fungsi D2D1CreateFactory, yang mengembalikan pointer antarmuka ID2D1Factory1 melalui parameter terakhirnya, yang memiliki jenis void**. Banyak fungsi COM mengembalikan kekosongan **. Untuk fungsi tersebut, gunakan com_ptr::p ut_void seperti yang ditunjukkan.

Fungsi COM yang mengembalikan penunjuk antarmuka tertentu

Fungsi D3D11CreateDevice mengembalikan penunjuk antarmuka ID3D11Device melalui parameter ketiga dari terakhirnya, yang memiliki jenis ID3D11Device**. Untuk fungsi yang mengembalikan penunjuk antarmuka tertentu seperti itu, gunakan com_ptr::p ut.

winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
    ...
    device.put(),
    ...);

Contoh kode di bagian sebelum yang ini menunjukkan cara memanggil fungsi D2D1CreateFactory mentah. Tetapi pada kenyataannya, ketika contoh kode untuk topik ini memanggil D2D1CreateFactory, ia menggunakan templat fungsi pembantu yang membungkus API mentah, sehingga contoh kode benar-benar menggunakan com_ptr::p ut.

winrt::com_ptr<ID2D1Factory1> factory;
D2D1CreateFactory(
    D2D1_FACTORY_TYPE_SINGLE_THREADED,
    options,
    factory.put());

Fungsi COM yang mengembalikan penunjuk antarmuka sebagai IUnknown

Fungsi DWriteCreateFactory mengembalikan penunjuk antarmuka pabrik DirectWrite melalui parameter terakhirnya, yang memiliki jenis IUnknown. Untuk fungsi seperti itu, gunakan com_ptr::p ut, tetapi masukkan kembali ke IUnknown.

DWriteCreateFactory(
    DWRITE_FACTORY_TYPE_SHARED,
    __uuidof(dwriteFactory2),
    reinterpret_cast<IUnknown**>(dwriteFactory2.put()));

Dudukkan kembali winrt ::com_ptr

Penting

Jika Anda memiliki winrt::com_ptr yang sudah duduk (pointer mentah internalnya sudah memiliki target) dan Anda ingin menempatkannya kembali untuk menunjuk ke objek yang berbeda, maka Anda harus menetapkannya nullptr terlebih dahulu—seperti yang ditunjukkan pada contoh kode di bawah ini. Jika tidak, maka com_ptr yang sudah duduk akan menarik masalah ke perhatian Anda (ketika Anda memanggil com_ptr::p ut atau com_ptr::p ut_void) dengan menegaskan bahwa penunjuk internalnya tidak null.

winrt::com_ptr<ID2D1SolidColorBrush> brush;
...
    brush.put()
...
brush = nullptr; // Important because we're about to re-seat
target->CreateSolidColorBrush(
    color_orange,
    D2D1::BrushProperties(0.8f),
    brush.put()));

Menangani kode kesalahan HRESULT

Untuk memeriksa nilai HRESULT yang dikembalikan dari fungsi COM, dan melemparkan pengecualian jika mewakili kode kesalahan, panggil winrt::check_hresult.

winrt::check_hresult(D2D1CreateFactory(
    D2D1_FACTORY_TYPE_SINGLE_THREADED,
    __uuidof(factory),
    options,
    factory.put_void()));

Fungsi COM yang mengambil penunjuk antarmuka tertentu

Anda dapat memanggil fungsi com_ptr::get untuk meneruskan com_ptr Anda ke fungsi yang mengambil penunjuk antarmuka tertentu dengan jenis yang sama.

... ExampleFunction(
    winrt::com_ptr<ID2D1Factory1> const& factory,
    winrt::com_ptr<IDXGIDevice> const& dxdevice)
{
    ...
    winrt::check_hresult(factory->CreateDevice(dxdevice.get(), ...));
    ...
}

Fungsi COM yang mengambil penunjuk antarmuka IUnknown

Anda dapat menggunakan com_ptr::get untuk meneruskan com_ptr Anda ke fungsi yang mengambil penunjuk antarmuka IUnknown.

Anda dapat menggunakan fungsi gratis winrt::get_unknown untuk mengembalikan alamat (dengan kata lain, pointer ke) antarmuka IUnknown mentah mendasar dari objek dari jenis yang diproyeksikan. Anda kemudian dapat meneruskan alamat tersebut ke fungsi yang mengambil penunjuk antarmuka IUnknown .

Untuk informasi tentang jenis yang diproyeksikan, lihat Menggunakan API dengan C++/WinRT.

Untuk contoh kode get_unknown, lihat winrt::get_unknown, atau Daftar kode sumber lengkap aplikasi Direct2D minimal dalam topik ini.

Meneruskan dan mengembalikan penunjuk pintar COM

Fungsi yang mengambil pointer pintar COM dalam bentuk winrt::com_ptr harus melakukannya dengan referensi konstanta, atau dengan referensi.

... GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device) ...

... CreateDevice(..., winrt::com_ptr<ID3D11Device>& device) ...

Fungsi yang mengembalikan winrt::com_ptr harus melakukannya berdasarkan nilai.

winrt::com_ptr<ID2D1Factory1> CreateFactory() ...

Mengkueri penunjuk cerdas COM untuk antarmuka yang berbeda

Anda dapat menggunakan fungsi com_ptr::as untuk mengkueri penunjuk cerdas COM untuk antarmuka yang berbeda. Fungsi ini melemparkan pengecualian jika kueri tidak berhasil.

void ExampleFunction(winrt::com_ptr<ID3D11Device> const& device)
{
    ...
    winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
    ...
}

Atau, gunakan com_ptr::try_as, yang mengembalikan nilai yang dapat Anda periksa nullptr untuk melihat apakah kueri berhasil.

Daftar kode sumber lengkap aplikasi Direct2D minimal

Catatan

Untuk informasi tentang menyiapkan Visual Studio untuk pengembangan C++/WinRT—termasuk menginstal dan menggunakan C++/WinRT Visual Studio Extension (VSIX) dan paket NuGet (yang bersama-sama menyediakan templat proyek dan dukungan build)—lihat Dukungan Visual Studio untuk C++/WinRT.

Jika Anda ingin membangun dan menjalankan contoh kode sumber ini, pertama-tama instal (atau perbarui ke) versi terbaru C++/WinRT Visual Studio Extension (VSIX); lihat catatan di atas. Kemudian, di Visual Studio, buat Aplikasi Inti baru (C++/WinRT). Direct2D adalah nama yang masuk akal untuk proyek, tetapi Anda dapat menamainya apa pun yang Anda suka. Targetkan versi terbaru yang tersedia secara umum (yaitu, bukan pratinjau) dari Windows SDK.

Langkah 1. Mengedit pch.h

Buka pch.h, dan tambahkan #include <unknwn.h> segera setelah menyertakan windows.h. Ini karena kita menggunakan winrt::get_unknown. Ada baiknya untuk #include <unknwn.h> eksplisititas setiap kali Anda menggunakan winrt::get_unknown, bahkan jika header tersebut telah disertakan oleh header lain.

Catatan

Jika Anda menghilangkan langkah ini maka Anda akan melihat kesalahan build 'get_unknown': pengidentifikasi tidak ditemukan.

Langkah 2. Mengedit App.cpp

Buka App.cpp, hapus seluruh kontennya, dan tempelkan daftar di bawah ini.

Kode di bawah ini menggunakan fungsi winrt::com_ptr::capture jika memungkinkan. WINRT_ASSERT adalah definisi makro, dan meluas ke _ASSERTE.

#include "pch.h"
#include <d2d1_1.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <winrt/Windows.Graphics.Display.h>

using namespace winrt;

using namespace Windows;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::UI;
using namespace Windows::UI::Core;
using namespace Windows::Graphics::Display;

namespace
{
    winrt::com_ptr<ID2D1Factory1> CreateFactory()
    {
        D2D1_FACTORY_OPTIONS options{};

#ifdef _DEBUG
        options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

        winrt::com_ptr<ID2D1Factory1> factory;

        winrt::check_hresult(D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            options,
            factory.put()));

        return factory;
    }

    HRESULT CreateDevice(D3D_DRIVER_TYPE const type, winrt::com_ptr<ID3D11Device>& device)
    {
        WINRT_ASSERT(!device);

        return D3D11CreateDevice(
            nullptr,
            type,
            nullptr,
            D3D11_CREATE_DEVICE_BGRA_SUPPORT,
            nullptr, 0,
            D3D11_SDK_VERSION,
            device.put(),
            nullptr,
            nullptr);
    }

    winrt::com_ptr<ID3D11Device> CreateDevice()
    {
        winrt::com_ptr<ID3D11Device> device;
        HRESULT hr{ CreateDevice(D3D_DRIVER_TYPE_HARDWARE, device) };

        if (DXGI_ERROR_UNSUPPORTED == hr)
        {
            hr = CreateDevice(D3D_DRIVER_TYPE_WARP, device);
        }

        winrt::check_hresult(hr);
        return device;
    }

    winrt::com_ptr<ID2D1DeviceContext> CreateRenderTarget(
        winrt::com_ptr<ID2D1Factory1> const& factory,
        winrt::com_ptr<ID3D11Device> const& device)
    {
        WINRT_ASSERT(factory);
        WINRT_ASSERT(device);

        winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };

        winrt::com_ptr<ID2D1Device> d2device;
        winrt::check_hresult(factory->CreateDevice(dxdevice.get(), d2device.put()));

        winrt::com_ptr<ID2D1DeviceContext> target;
        winrt::check_hresult(d2device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, target.put()));
        return target;
    }

    winrt::com_ptr<IDXGIFactory2> GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device)
    {
        WINRT_ASSERT(device);

        winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };

        winrt::com_ptr<IDXGIAdapter> adapter;
        winrt::check_hresult(dxdevice->GetAdapter(adapter.put()));

        winrt::com_ptr<IDXGIFactory2> factory;
        factory.capture(adapter, &IDXGIAdapter::GetParent);
        return factory;
    }

    void CreateDeviceSwapChainBitmap(
        winrt::com_ptr<IDXGISwapChain1> const& swapchain,
        winrt::com_ptr<ID2D1DeviceContext> const& target)
    {
        WINRT_ASSERT(swapchain);
        WINRT_ASSERT(target);

        winrt::com_ptr<IDXGISurface> surface;
        surface.capture(swapchain, &IDXGISwapChain1::GetBuffer, 0);

        D2D1_BITMAP_PROPERTIES1 const props{ D2D1::BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)) };

        winrt::com_ptr<ID2D1Bitmap1> bitmap;

        winrt::check_hresult(target->CreateBitmapFromDxgiSurface(surface.get(),
            props,
            bitmap.put()));

        target->SetTarget(bitmap.get());
    }

    winrt::com_ptr<IDXGISwapChain1> CreateSwapChainForCoreWindow(winrt::com_ptr<ID3D11Device> const& device)
    {
        WINRT_ASSERT(device);

        winrt::com_ptr<IDXGIFactory2> const factory{ GetDxgiFactory(device) };

        DXGI_SWAP_CHAIN_DESC1 props{};
        props.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        props.SampleDesc.Count = 1;
        props.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        props.BufferCount = 2;
        props.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;

        winrt::com_ptr<IDXGISwapChain1> swapChain;

        winrt::check_hresult(factory->CreateSwapChainForCoreWindow(
            device.get(),
            winrt::get_unknown(CoreWindow::GetForCurrentThread()),
            &props,
            nullptr, // all or nothing
            swapChain.put()));

        return swapChain;
    }

    constexpr D2D1_COLOR_F color_white{ 1.0f,  1.0f,  1.0f,  1.0f };
    constexpr D2D1_COLOR_F color_orange{ 0.92f,  0.38f,  0.208f,  1.0f };
}

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    winrt::com_ptr<ID2D1Factory1> m_factory;
    winrt::com_ptr<ID2D1DeviceContext> m_target;
    winrt::com_ptr<IDXGISwapChain1> m_swapChain;
    winrt::com_ptr<ID2D1SolidColorBrush> m_brush;
    float m_dpi{};

    IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(CoreApplicationView const&)
    {
    }

    void Load(hstring const&)
    {
        CoreWindow const window{ CoreWindow::GetForCurrentThread() };

        window.SizeChanged([&](auto&&...)
        {
            if (m_target)
            {
                ResizeSwapChainBitmap();
                Render();
            }
        });

        DisplayInformation const display{ DisplayInformation::GetForCurrentView() };
        m_dpi = display.LogicalDpi();

        display.DpiChanged([&](DisplayInformation const& display, IInspectable const&)
        {
            if (m_target)
            {
                m_dpi = display.LogicalDpi();
                m_target->SetDpi(m_dpi, m_dpi);
                CreateDeviceSizeResources();
                Render();
            }
        });

        m_factory = CreateFactory();
        CreateDeviceIndependentResources();
    }

    void Uninitialize()
    {
    }

    void Run()
    {
        CoreWindow const window{ CoreWindow::GetForCurrentThread() };
        window.Activate();

        Render();
        CoreDispatcher const dispatcher{ window.Dispatcher() };
        dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(CoreWindow const&) {}

    void Draw()
    {
        m_target->Clear(color_white);

        D2D1_SIZE_F const size{ m_target->GetSize() };
        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
        m_target->DrawRectangle(rect, m_brush.get(), 100.0f);

        char buffer[1024];
        (void)snprintf(buffer, sizeof(buffer), "Draw %.2f x %.2f @ %.2f\n", size.width, size.height, m_dpi);
        ::OutputDebugStringA(buffer);
    }

    void Render()
    {
        if (!m_target)
        {
            winrt::com_ptr<ID3D11Device> const device{ CreateDevice() };
            m_target = CreateRenderTarget(m_factory, device);
            m_swapChain = CreateSwapChainForCoreWindow(device);

            CreateDeviceSwapChainBitmap(m_swapChain, m_target);

            m_target->SetDpi(m_dpi, m_dpi);

            CreateDeviceResources();
            CreateDeviceSizeResources();
        }

        m_target->BeginDraw();
        Draw();
        m_target->EndDraw();

        HRESULT const hr{ m_swapChain->Present(1, 0) };

        if (S_OK != hr && DXGI_STATUS_OCCLUDED != hr)
        {
            ReleaseDevice();
        }
    }

    void ReleaseDevice()
    {
        m_target = nullptr;
        m_swapChain = nullptr;

        ReleaseDeviceResources();
    }

    void ResizeSwapChainBitmap()
    {
        WINRT_ASSERT(m_target);
        WINRT_ASSERT(m_swapChain);

        m_target->SetTarget(nullptr);

        if (S_OK == m_swapChain->ResizeBuffers(0, // all buffers
            0, 0, // client area
            DXGI_FORMAT_UNKNOWN, // preserve format
            0)) // flags
        {
            CreateDeviceSwapChainBitmap(m_swapChain, m_target);
            CreateDeviceSizeResources();
        }
        else
        {
            ReleaseDevice();
        }
    }

    void CreateDeviceIndependentResources()
    {
    }

    void CreateDeviceResources()
    {
        winrt::check_hresult(m_target->CreateSolidColorBrush(
            color_orange,
            D2D1::BrushProperties(0.8f),
            m_brush.put()));
    }

    void CreateDeviceSizeResources()
    {
    }

    void ReleaseDeviceResources()
    {
        m_brush = nullptr;
    }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    CoreApplication::Run(winrt::make<App>());
}

Bekerja dengan jenis COM, seperti BSTR dan VARIAN

Seperti yang Anda lihat, C++/WinRT menyediakan dukungan untuk menerapkan dan memanggil antarmuka COM. Untuk menggunakan jenis COM, seperti BSTR dan VARIANT, kami sarankan Anda menggunakan pembungkus yang disediakan oleh Windows Implementation Libraries (WIL), seperti wil::unique_bstr dan wil::unique_variant (yang mengelola masa pakai sumber daya).

Kerangka kerja pengganti WIL seperti Pustaka Templat Aktif (ATL), dan Dukungan COM kompilator Visual C++. Dan kami merekomendasikannya melalui penulisan pembungkus Anda sendiri, atau menggunakan jenis COM seperti BSTR dan VARIAN dalam bentuk mentah mereka (bersama dengan API yang sesuai).

Menghindari tabrakan namespace

Ini adalah praktik umum di C++/WinRT—seperti yang ditunjukkan kode dalam topik ini—untuk menggunakan direktif penggunaan secara liberal. Namun, dalam beberapa kasus, itu dapat menyebabkan masalah mengimpor nama yang bertabrakan ke dalam namespace layanan global. Berikut adalah contoh.

C++/WinRT berisi jenis bernama winrt::Windows::Foundation::IUnknown; sementara COM mendefinisikan jenis bernama ::IUnknown. Jadi pertimbangkan kode berikut, dalam proyek C++/WinRT yang menggunakan header COM.

using namespace winrt::Windows::Foundation;
...
void MyFunction(IUnknown*); // error C2872:  'IUnknown': ambiguous symbol

Nama IUnknown yang tidak memenuhi syarat bertabrakan di namespace layanan global, oleh karena itu kesalahan pengompilasi simbol ambigu. Sebagai gantinya, Anda dapat mengisolasi versi C++/WinRT nama ke dalam namespace winrt , seperti ini.

namespace winrt
{
    using namespace Windows::Foundation;
}
...
void MyFunctionA(IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.

Atau, jika Anda ingin kenyamanan using namespace winrt, maka Anda bisa. Anda hanya perlu memenuhi syarat versi global IUnknown, seperti ini.

using namespace winrt;
namespace winrt
{
    using namespace Windows::Foundation;
}
...
void MyFunctionA(::IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.

Secara alami, ini berfungsi dengan namespace C++/WinRT apa pun.

namespace winrt
{
    using namespace Windows::Storage;
    using namespace Windows::System;
}

Anda kemudian dapat merujuk ke winrt::Windows::Storage::StorageFile, misalnya, sebagai winrt ::StorageFile.

API penting