Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Note
Los ejemplos de código de este artículo usan el modelo de aplicación de la aplicación principal para UWP (IFrameworkView). En una aplicación de escritorio winUI 3, derive una clase de Microsoft::UI::Xaml::Application y llame a Application::Start(...) como punto de entrada de la aplicación y use DispatcherQueue en lugar de CoreDispatcher. Los conceptos y patrones COM/Direct2D que se muestran aquí se aplican igualmente a las aplicaciones winUI 3.
Puede usar las instalaciones de la biblioteca de C++/WinRT para consumir componentes COM, como los gráficos 2D y 3D de alto rendimiento de las API de DirectX. C++/WinRT es la manera más sencilla de usar DirectX sin poner en peligro el rendimiento. En este tema se usa un ejemplo de código de Direct2D para mostrar cómo usar C++/WinRT para consumir clases e interfaces COM. Por supuesto, puede mezclar COM y programación de Windows Runtime en el mismo proyecto de C++/WinRT.
Al final de este tema, encontrará una lista de código fuente completa de una aplicación Direct2D mínima. Tomaremos fragmentos de ese código y los usaremos para ilustrar cómo consumir componentes COM con C++/WinRT, utilizando diversas funcionalidades de la biblioteca C++/WinRT.
Punteros inteligentes COM (winrt::com_ptr)
Al programar con COM, se trabaja directamente con interfaces en lugar de con objetos (esto también es cierto en segundo plano para las API de Windows Runtime, que son una evolución de COM). Para llamar a una función en una clase COM, por ejemplo, activa la clase , devuelve una interfaz y, a continuación, llama a funciones en esa interfaz. Para acceder al estado de un objeto, no se accede directamente a sus miembros de datos; en su lugar, se llama a funciones de acceso y modificación a través de una interfaz.
Para ser más concretos, estamos hablando de interactuar con punteros de interfaz. Y para ello, nos beneficiamos de la existencia del tipo de puntero inteligente COM en C++/WinRT, el tipo winrt::com_ptr .
#include <d2d1_1.h>
...
winrt::com_ptr<ID2D1Factory1> factory;
El código anterior muestra cómo declarar un puntero inteligente sin inicializar a una interfaz COM ID2D1Factory1 . El puntero inteligente no está inicializado, por lo que aún no apunta a una interfaz ID2D1Factory1 que pertenece a ningún objeto real (no apunta a una interfaz en absoluto). Pero puede hacerlo; y, al ser un puntero inteligente, tiene la capacidad, mediante el recuento de referencias de COM, de gestionar el ciclo de vida del objeto propietario de la interfaz a la que apunta, y de servir como medio a través del cual se invocan funciones en esa interfaz.
Funciones COM que devuelven un puntero de interfaz como void
Puede llamar a la función com_ptr::put_void para escribir en el puntero sin formato subyacente de un puntero inteligente no inicializado.
D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_NONE };
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
&options,
factory.put_void()
);
El código anterior llama a la función D2D1CreateFactory , que devuelve un puntero de interfaz ID2D1Factory1 a través de su último parámetro, que tiene el tipo void** . Muchas funciones COM devuelven un void**. Para estas funciones, use com_ptr::put_void como se muestra.
Funciones COM que devuelven un puntero de interfaz específico
La función D3D11CreateDevice devuelve un puntero de interfaz ID3D11Device a través de su tercer parámetro desde el último, que tiene el tipo ID3D11Device** . Para las funciones que devuelven un puntero a una interfaz específica de ese tipo, use com_ptr::put.
winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
...
device.put(),
...);
El ejemplo de código de la sección anterior muestra cómo llamar directamente a la función D2D1CreateFactory. Pero, de hecho, cuando el ejemplo de código de este tema llama a D2D1CreateFactory, usa una plantilla de función auxiliar que encapsula la API subyacente, por lo que el ejemplo de código realmente usa com_ptr::put.
winrt::com_ptr<ID2D1Factory1> factory;
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
options,
factory.put());
Funciones COM que devuelven un puntero de interfaz como IUnknown
La función DWriteCreateFactory devuelve un puntero de interfaz de fábrica de DirectWrite a través de su último parámetro, que tiene el tipo IUnknown . Para esa función, use com_ptr::put, pero reinterprete eso como IUnknown.
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(dwriteFactory2),
reinterpret_cast<IUnknown**>(dwriteFactory2.put()));
Vuelva a colocar un winrt::com_ptr
Important
Si tienes un winrt::com_ptr que ya está sentado (su puntero sin procesar interno ya tiene un destino) y quieres volver a sentarlo para que apunte a un objeto diferente, primero debes asignarlo nullptr , como se muestra en el ejemplo de código siguiente. Si no es así, un com_ptr ya inicializado le señalará el problema (cuando llame a com_ptr::put o com_ptr::put_void) mediante una aserción de que su puntero interno no es 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()));
Controlar códigos de error HRESULT
Para comprobar el valor de un HRESULT devuelto desde una función COM y producir una excepción en caso de que represente un código de error, llame a winrt::check_hresult.
winrt::check_hresult(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
options,
factory.put_void()));
Funciones COM que toman un puntero de interfaz específico
Puede llamar a la función com_ptr::get para pasar el com_ptr a una función que toma un puntero de interfaz específico del mismo tipo.
... ExampleFunction(
winrt::com_ptr<ID2D1Factory1> const& factory,
winrt::com_ptr<IDXGIDevice> const& dxdevice)
{
...
winrt::check_hresult(factory->CreateDevice(dxdevice.get(), ...));
...
}
Funciones COM que toman un puntero de interfaz IUnknown
Puede usar com_ptr::get para pasar su com_ptr a una función que acepta un puntero a la interfaz IUnknown.
Puede usar la función libre winrt::get_unknown para devolver la dirección de (es decir, un puntero a) la interfaz IUnknown subyacente de un objeto de un tipo proyectado. A continuación, puede pasar esa dirección a una función que toma un puntero de interfaz IUnknown .
Para obtener información sobre los tipos proyectados, consulta Consumir API con C++/WinRT.
Para obtener un ejemplo de código de get_unknown, consulta winrt::get_unknown o la lista de código fuente completo de una aplicación Direct2D mínima en este tema.
Paso y devolución de punteros inteligentes COM
Una función que recibe un puntero inteligente COM en forma de winrt::com_ptr debe hacerlo por referencia constante o por referencia.
... GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device) ...
... CreateDevice(..., winrt::com_ptr<ID3D11Device>& device) ...
Una función que devuelve un winrt::com_ptr debe hacerlo por valor.
winrt::com_ptr<ID2D1Factory1> CreateFactory() ...
Consultar un puntero inteligente COM para obtener una interfaz diferente
Puede usar la función com_ptr::as para consultar un puntero inteligente COM para una interfaz diferente. La función produce una excepción si la consulta no se realiza correctamente.
void ExampleFunction(winrt::com_ptr<ID3D11Device> const& device)
{
...
winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
...
}
Como alternativa, utilice com_ptr::try_as, que devuelve un valor que puede comprobar con nullptr para ver si la consulta tuvo éxito.
Lista de código fuente completo de una aplicación Direct2D mínima
Note
Para obtener información sobre cómo configurar Visual Studio para el desarrollo de C++/WinRT, incluida la instalación y el uso de la extensión de Visual Studio de C++/WinRT (VSIX) y el paquete NuGet (que juntos proporcionan compatibilidad con la plantilla de proyecto y la compilación), consulte Visual Studio compatibilidad con C++/WinRT.
Si desea compilar y ejecutar este ejemplo de código fuente, primero instale (o actualice) la versión más reciente de la extensión de Visual Studio de C++/WinRT (VSIX); vea la nota anterior. A continuación, en Visual Studio, cree una nueva aplicación principal (C++/WinRT).
Direct2D es un nombre razonable para el proyecto, pero puede asignarle el nombre que quiera. Tenga como destino la versión más reciente disponible con carácter general (es decir, no en versión preliminar) del SDK de Windows.
Paso 1. Editar pch.h
Abra pch.hy agregue #include <unknwn.h> inmediatamente después de incluir windows.h. Esto se debe a que estamos usando winrt::get_unknown. Es buena idea hacerlo #include <unknwn.h> explícitamente siempre que uses winrt::get_unknown, aunque ese encabezado ya esté incluido por otro encabezado.
Note
Si omite este paso, verá el error de compilación "get_unknown": identificador no encontrado.
Paso 2. Editar App.cpp
Abra App.cpp, elimine todo su contenido y pegue la lista siguiente.
El código siguiente usa la función winrt::com_ptr::capture siempre que sea posible.
WINRT_ASSERT es una definición de macro y se expande a _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>());
}
Trabajar con tipos COM, como BSTR y VARIANT
Como puede ver, C++/WinRT proporciona compatibilidad para implementar y llamar a interfaces COM. Para usar tipos COM, como BSTR y VARIANT, se recomienda usar contenedores proporcionados por las bibliotecas de implementación de Windows (WIL), como wil::unique_bstr y wil::unique_variant (que administran la duración de los recursos).
WIL sustituye a marcos como la biblioteca de plantillas activas (ATL) y la compatibilidad com del compilador de Visual C++. Y lo recomendamos antes que escribir sus propios envoltorios o usar tipos COM como BSTR y VARIANT en bruto (junto con las API correspondientes).
Evitar colisiones de espacios de nombres
Es habitual en C++/WinRT —como demuestra el fragmento de código de este tema— usar directivas using con liberalidad. Sin embargo, en algunos casos, esto puede provocar el problema de importar nombres en colisión en el espacio de nombres global. Este es un ejemplo.
C++/WinRT contiene un tipo denominado winrt::Windows::Foundation::IUnknown; mientras que COM define un tipo denominado ::IUnknown. Por lo tanto, tenga en cuenta el código siguiente, en un proyecto de C++/WinRT que consume encabezados COM.
using namespace winrt::Windows::Foundation;
...
void MyFunction(IUnknown*); // error C2872: 'IUnknown': ambiguous symbol
El nombre no calificado IUnknown colisiona en el espacio de nombres global, de ahí el error del compilador símbolo ambiguo. En su lugar, puedes aislar la versión de C++/WinRT del nombre en el espacio de nombres winrt , como este.
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
O bien, si quieres la comodidad de using namespace winrt, entonces puedes. Solo tienes que especificar la versión global de IUnknown, así.
using namespace winrt;
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(::IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Naturalmente, esto funciona con cualquier espacio de nombres de C++/WinRT.
namespace winrt
{
using namespace Windows::Storage;
using namespace Windows::System;
}
A continuación, puede hacer referencia a winrt::Windows::Storage::StorageFile, por ejemplo, como winrt::StorageFile.