Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Note
Sampel kode dalam artikel ini menggunakan model aplikasi UWP Core App (IFrameworkView). Dalam aplikasi desktop WinUI 3, dapatkan kelas dari Microsoft::UI::Xaml::Application and call Application::Start(...) sebagai titik masuk aplikasi, dan gunakan DispatcherQueue alih-alih CoreDispatcher. Konsep dan pola COM/Direct2D yang ditampilkan di sini berlaku sama untuk aplikasi WinUI 3.
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 COM dan Windows Runtime pemrograman dalam proyek C++/WinRT yang sama.
Di akhir topik ini, Anda akan menemukan daftar kode sumber lengkap aplikasi Direct2D minimal. Kami akan mengambil cuplikan dari kode tersebut dan menggunakannya untuk menunjukkan cara memakai komponen COM dengan C++/WinRT melalui berbagai fasilitas dalam pustaka C++/WinRT.
COM smart pointers (winrt::com_ptr)
Saat Anda memprogram dengan COM, Anda bekerja langsung dengan antarmuka daripada dengan objek (yang juga benar di belakang layar untuk API Windows Runtime, yang merupakan evolusi COM). Untuk memanggil fungsi pada kelas COM, misalnya, Anda mengaktifkan kelas , mendapatkan antarmuka kembali, dan kemudian 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.
Lebih spesifiknya, yang kita bahas adalah interaksi dengan pointer 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 ia berpotensi untuk melakukannya; dan (sebagai pointer cerdas) ia memiliki kemampuan, melalui penghitungan referensi COM, untuk mengelola siklus hidup objek pemilik antarmuka yang ditunjuknya, serta menjadi perantara untuk memanggil fungsi melalui antarmuka tersebut.
Fungsi COM yang mengembalikan penunjuk antarmuka sebagai batal
Anda dapat memanggil fungsi com_ptr::put_void untuk menulis ke pointer mentah dasar dari smart pointer yang belum 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::put_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 pointer antarmuka tertentu seperti itu, gunakan com_ptr::put.
winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
...
device.put(),
...);
Contoh kode di bagian sebelum yang ini menunjukkan cara memanggil fungsi D2D1CreateFactory mentah. Namun, sebenarnya, ketika contoh kode untuk topik ini memanggil D2D1CreateFactory, contoh tersebut menggunakan templat fungsi pembantu yang membungkus API mentah, sehingga contoh kode itu sebenarnya menggunakan com_ptr::put.
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::put, tetapi lakukan reinterpret cast ke IUnknown.
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(dwriteFactory2),
reinterpret_cast<IUnknown**>(dwriteFactory2.put()));
Pasang kembali winrt::com_ptr
Important
Jika Anda memiliki winrt::com_ptr yang sudah terisi (pointer mentah internalnya sudah memiliki target) dan Anda ingin mengarahkannya ulang agar menunjuk ke objek lain, pertama-tama Anda perlu menetapkan nullptr kepadanya—seperti yang ditunjukkan dalam contoh kode di bawah ini. Jika tidak, com_ptr yang sudah terpasang akan menarik perhatian Anda pada masalah tersebut (saat Anda memanggil com_ptr::put atau com_ptr::put_void) dengan menegaskan bahwa pointer internalnya tidak bernilai 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 menerima 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 yang 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 pointer cerdas 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
Note
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 SDK Windows.
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> secara eksplisit setiap kali Anda menggunakan winrt::get_unknown, bahkan jika header tersebut telah disertakan oleh header lain.
Note
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 tipe COM, seperti BSTR dan VARIANT
Seperti yang Anda lihat, C++/WinRT menyediakan dukungan untuk menerapkan dan memanggil antarmuka COM. Untuk menggunakan jenis COM, seperti BSTR dan VARIAN, kami sarankan Anda menggunakan pembungkus yang disediakan oleh Windows Implementasi Pustaka (WIL), seperti wil::unique_bstr dan wil::unique_variant (yang mengelola masa pakai sumber daya).
WIL menggantikan kerangka kerja seperti Pustaka Templat Aktif (ATL) dan dukungan COM pada compiler Visual C++. Dan kami lebih menyarankannya daripada menulis wrapper Anda sendiri atau menggunakan tipe COM seperti BSTR dan VARIANT secara langsung dalam bentuk mentahnya (bersama API yang sesuai).
Menghindari benturan ruang nama
Ini adalah praktik umum di C++/WinRT—seperti yang ditunjukkan kode dalam topik ini—untuk menggunakan direktif penggunaan secara liberal. Namun, dalam beberapa kasus, hal itu dapat menyebabkan masalah berupa terjadinya benturan nama saat mengimpor ke dalam namespace global. Berikut ini contohnya.
C++/WinRT berisi jenis bernama winrt::Windows::Foundation::IUnknown; sedangkan 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 tanpa kualifikasi berkonflik di namespace global, sehingga terjadi kesalahan kompiler 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 menginginkan kemudahan dari using namespace winrt, Anda juga bisa. Anda hanya perlu menentukan versi global dari 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, hanya winrt::StorageFile.
API penting
Windows developer