Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Note
Os exemplos de código neste artigo utilizam o modelo de aplicação UWP Core App (IFrameworkView). Numa aplicação de desktop WinUI 3, derive uma classe de Microsoft::UI::Xaml::Application e chame Application::Start(...) como ponto de entrada da aplicação, usando DispatcherQueue em vez de CoreDispatcher. Os conceitos e padrões COM/Direct2D apresentados aqui aplicam-se igualmente às aplicações WinUI 3.
Pode usar as funcionalidades da biblioteca C++/WinRT para consumir componentes COM, como os gráficos 2D e 3D de alto desempenho das APIs DirectX. C++/WinRT é a forma mais simples de usar DirectX sem comprometer o desempenho. Este tópico utiliza um exemplo de código Direct2D para mostrar como usar C++/WinRT para consumir classes e interfaces COM. Pode, claro, misturar programação COM e Windows Runtime dentro do mesmo projeto C++/WinRT.
No final deste tópico, encontrará uma lista completa do código-fonte de uma aplicação mínima Direct2D. Vamos recolher excertos desse código e usá-los para ilustrar como consumir componentes COM usando C++/WinRT usando várias funcionalidades da biblioteca C++/WinRT.
Apontadores inteligentes COM (winrt::com_ptr)
Quando programas com COM, trabalhas diretamente com interfaces em vez de com objetos (isso também é verdade nos bastidores para APIs do Windows Runtime, que são uma evolução do COM). Para chamar uma função numa classe COM, por exemplo, ativas a classe, recebes uma interface de volta e depois chamas funções nessa interface. Para aceder ao estado de um objeto, não se acede diretamente aos seus membros de dados; em vez disso, chamas as funções de acessório e mutador numa interface.
Para ser mais específico, estamos a falar de interagir com apontadores de interface. E para isso, beneficiamos da existência do tipo de apontador inteligente COM em C++/WinRT — o tipo winrt::com_ptr .
#include <d2d1_1.h>
...
winrt::com_ptr<ID2D1Factory1> factory;
O código acima mostra como declarar um apontador inteligente não inicializado para uma interface COM ID2D1Factory1 . O apontador inteligente não foi inicializado, pelo que ainda não aponta para uma interface ID2D1Factory1 que pertença a qualquer objeto real (na verdade, não aponta para interface alguma). Mas tem potencial para isso; e (sendo um apontador inteligente) tem a capacidade, através da contagem de referências COM, de gerir a vida útil do objeto proprietário da interface para onde aponta, e de ser o meio pelo qual se chamam funções nessa interface.
Funções COM que retornam um ponteiro de interface como void
Podes chamar a função com_ptr::p ut_void para escrever no ponteiro bruto subjacente de um apontador inteligente não inicializado.
D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_NONE };
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
&options,
factory.put_void()
);
O código acima chama a função D2D1CreateFactory, que devolve um ponteiro para a interface ID2D1Factory1 através do último parâmetro, que é do tipo void**. Muitas funções COM devolvem um vazio**. Para essas funções, use com_ptr::put_void conforme mostrado.
Funções COM que retornam um ponteiro de interface específico
A função D3D11CreateDevice retorna um ponteiro de interface ID3D11Device através do seu antepenúltimo parâmetro, que é do tipo ID3D11Device**. Para funções que devolvem um ponteiro de interface específico como esse, use com_ptr::put.
winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
...
device.put(),
...);
O exemplo de código na secção anterior mostra como chamar a função bruta D2D1CreateFactory . Mas, na verdade, quando o exemplo de código deste tópico chama D2D1CreateFactory, utiliza um modelo de função auxiliar que encapsula a API de baixo nível e, por isso, o exemplo de código utiliza efetivamente com_ptr::put.
winrt::com_ptr<ID2D1Factory1> factory;
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
options,
factory.put());
Funções COM que retornam um ponteiro de interface como IUnknown
A função DWriteCreateFactory devolve um ponteiro para a interface de fábrica do DirectWrite no último parâmetro, que é do tipo IUnknown. Para essa função, utilize com_ptr::put, mas faça uma conversão reinterpretada para IUnknown.
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(dwriteFactory2),
reinterpret_cast<IUnknown**>(dwriteFactory2.put()));
Reinicializar um winrt::com_ptr
Important
Se tiver um winrt::com_ptr que já estiver associado (o respetivo ponteiro interno já aponta para um destino) e quiser fazer com que passe a apontar para outro objeto, tem primeiro de lhe atribuir nullptr — como se mostra no exemplo de código abaixo. Se não o fizeres, um com_ptr já inicializado alertar-te-á para o problema (quando chamares com_ptr::put ou com_ptr::put_void), ao emitir uma asserção de que o seu ponteiro interno não é nulo.
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()));
Lidar com códigos de erro HRESULT
Para verificar o valor de um HRESULT devolvido de uma função COM, e lançar uma exceção caso este represente um código de erro, chame winrt::check_hresult.
winrt::check_hresult(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
options,
factory.put_void()));
Funções COM que recebem um ponteiro para uma interface específica
Podes chamar a função com_ptr::get para passar a tua com_ptr a uma função que aceite um ponteiro de interface específico do mesmo tipo.
... ExampleFunction(
winrt::com_ptr<ID2D1Factory1> const& factory,
winrt::com_ptr<IDXGIDevice> const& dxdevice)
{
...
winrt::check_hresult(factory->CreateDevice(dxdevice.get(), ...));
...
}
Funções COM que recebem um ponteiro para a interface IUnknown
Pode usar com_ptr::get para passar o seu com_ptr a uma função que aceita um ponteiro para a interface IUnknown.
Pode usar a função winrt::get_unknown free para devolver o endereço de (ou seja, um ponteiro para) a interface IUnknown crua subjacente de um objeto de um tipo projetado. Depois pode passar esse endereço para uma função que recebe um ponteiro para a interface IUnknown.
Para obter informações sobre tipos projetados, consulte Consumir APIs com C++/WinRT.
Para um exemplo de código de get_unknown, veja winrt::get_unknown, ou a listagem completa do código-fonte de uma aplicação mínima Direct2D neste tópico.
Passagem e devolução de apontadores inteligentes COM
Uma função que recebe um ponteiro inteligente COM sob a forma de winrt::com_ptr deve fazê-lo por referência constante ou por referência.
... GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device) ...
... CreateDevice(..., winrt::com_ptr<ID3D11Device>& device) ...
Uma função que retorna um winrt::com_ptr deve fazê-lo por valor.
winrt::com_ptr<ID2D1Factory1> CreateFactory() ...
Consultar um smart pointer COM para obter uma interface diferente
Podes usar a função com_ptr::as para consultar um ponteiro inteligente COM para uma interface diferente. A função lança uma exceção se a consulta não for bem-sucedida.
void ExampleFunction(winrt::com_ptr<ID3D11Device> const& device)
{
...
winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
...
}
Em alternativa, use com_ptr::try_as, que devolve um valor que pode comparar com nullptr para verificar se a consulta foi bem-sucedida.
Listagem completa do código-fonte de uma aplicação mínima Direct2D
Note
Para informações sobre como configurar o Visual Studio para desenvolvimento em C++/WinRT — incluindo a instalação e utilização da Extensão Visual Studio (VSIX) em C++/WinRT e do pacote NuGet (que juntos fornecem suporte para modelos de projeto e compilação) — consulte suporte ao Visual Studio para C++/WinRT.
Se quiser compilar e executar este exemplo de código-fonte, então primeiro instale (ou atualize) a versão mais recente da Extensão Visual Studio (VSIX) em C++/WinRT; veja a nota acima. Depois, no Visual Studio, crie uma nova Aplicação Core (C++/WinRT).
Direct2D é um nome razoável para o projeto, mas podes dar-lhe o nome que quiseres. Utilize a versão mais recente do SDK do Windows disponível ao público (ou seja, não uma versão de pré-visualização).
Passo 1. Editar pch.h
Abra pch.h e adicione #include <unknwn.h> imediatamente a seguir à inclusão de windows.h. Isto porque estamos a usar winrt::get_unknown. É uma boa ideia fazer explicitamente #include <unknwn.h> sempre que usar winrt::get_unknown, mesmo que esse cabeçalho tenha sido incluído por outro cabeçalho.
Note
Se omitires este passo, vais ver o erro de compilação 'get_unknown': identificador não encontrado.
Passo 2. Editar App.cpp
Abra App.cpp, elimine todo o respetivo conteúdo e cole a listagem abaixo.
O código abaixo utiliza a função winrt::com_ptr::capture sempre que possível.
WINRT_ASSERT é uma definição macro, e expande-se para _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>());
}
Trabalhar com tipos COM, como BSTR e VARIANT
Como pode ver, o C++/WinRT oferece suporte tanto para implementar como para chamar interfaces COM. Para utilizar tipos COM, como BSTR e VARIANT, recomendamos que utilize wrappers fornecidos pelas Windows Implementation Libraries (WIL), como wil::unique_bstr e wil::unique_variant (que gerem a vida útil dos recursos).
O WIL substitui frameworks como a Active Template Library (ATL) e o suporte COM do compilador Visual C++. E recomendamos essa abordagem em vez de escrever os seus próprios encapsulamentos ou de usar tipos COM como BSTR e VARIANT na sua forma nativa (juntamente com as APIs adequadas).
Evitar colisões em espaços de nomes
É prática comum em C++/WinRT — como demonstra a listagem de código neste tópico — usar using-directives de forma generosa. Em alguns casos, isso pode levar ao problema de importar nomes em colisão para o espaço global de nomes. Eis um exemplo.
C++/WinRT contém um tipo chamado winrt::Windows::Foundation::IUnknown; enquanto COM define um tipo chamado ::IUnknown. Portanto, considere o seguinte código, num projeto C++/WinRT que consome cabeçalhos COM.
using namespace winrt::Windows::Foundation;
...
void MyFunction(IUnknown*); // error C2872: 'IUnknown': ambiguous symbol
O nome não qualificado IUnknown entra em conflito no espaço de nomes global, daí resultar o erro do compilador símbolo ambíguo. Em vez disso, pode isolar a versão C++/WinRT do nome no espaço de nomes winrt, desta forma.
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Ou, se quiseres a conveniência de using namespace winrt, então podes. Basta qualificar a versão global de IUnknown, desta forma.
using namespace winrt;
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(::IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Naturalmente, isto funciona com qualquer namespace C++/WinRT.
namespace winrt
{
using namespace Windows::Storage;
using namespace Windows::System;
}
Pode então referir-se a winrt::Windows::Storage::StorageFile, por exemplo, simplesmente como winrt::StorageFile.
APIs importantes
Windows developer