C++/WinRT で COM コンポーネントを使用する

Note

この記事のコード サンプルでは、UWP Core アプリ (IFrameworkView) アプリケーション モデルを使用します。 WinUI 3 デスクトップ アプリで、Microsoft::UI::Xaml::Application からクラスを派生させ、アプリケーションエントリ ポイントとして Application::Start(...) を呼び出し、CoreDispatcher の代わりに DispatcherQueue を使用します。 ここで示す COM/Direct2D の概念とパターンは、WinUI 3 アプリにも同様に適用されます。

C++/WinRT ライブラリの機能を使用して、DirectX API の高性能な 2-D および 3-D グラフィックスなどの COM コンポーネントを使用できます。 C++/WinRT は、パフォーマンスを損なうことなく DirectX を使用する最も簡単な方法です。 このトピックでは、Direct2D コード例を使用して、C++/WinRT を使用して COM クラスとインターフェイスを使用する方法を示します。 もちろん、同じ C++/WinRT プロジェクト内で COM とWindows ランタイムプログラミングを混在させることができます。

このトピックの最後には、最小限の Direct2D アプリケーションの完全なソース コードの一覧が表示されます。 そのコードからの抜粋を取り上げ、C++/WinRT ライブラリのさまざまな機能を使用して C++/WinRT を使用して COM コンポーネントを使用する方法を示します。

COM スマート ポインター (winrt::com_ptr)

COM を使用してプログラミングする場合は、オブジェクトではなくインターフェイスを直接操作します (これは、COM の進化である Windows ランタイム API のバックグラウンドでも同様です)。 たとえば、COM クラスで関数を呼び出すには、クラスをアクティブ化し、インターフェイスを取得してから、そのインターフェイスで関数を呼び出します。 オブジェクトの状態にアクセスするには、そのデータ メンバーに直接アクセスしません。代わりに、インターフェイスでアクセサー関数とミューテーター関数を呼び出します。

具体的には、インターフェイス ポインターとの対話について説明 します。 そのため、C++/WinRT に COM スマート ポインター型 ( winrt::com_ptr 型) が存在する利点があります。

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

上記のコードは、 ID2D1Factory1 COM インターフェイスへの初期化されていないスマート ポインターを宣言する方法を示しています。 スマート ポインターは初期化されていないため、実際のオブジェクトに属する ID2D1Factory1 インターフェイスをまだ指していません (インターフェイスをまったく指していません)。 しかし、そのようになる可能性はあります。さらに、(スマート ポインターであるため)それが指すインターフェイスの所有元オブジェクトの存続期間を COM の参照カウントによって管理し、またそのインターフェイスの関数を呼び出す手段となることができます。

インターフェイス ポインターを void として返す COM 関数

com_ptr::put_void 関数を呼び出すと、初期化されていないスマートポインターの内部の生ポインターに書き込めます。

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

上記のコードは D2D1CreateFactory 関数を呼び出します。この関数は、void** 型を持つ最後のパラメーターを介して ID2D1Factory1 インターフェイス ポインターを返します。 多くの COM 関数は void を返 します**。 このような関数では、以下に示すように com_ptr::put_void を使用します。

特定のインターフェイス ポインターを返す COM 関数

D3D11CreateDevice 関数は、ID3D11Device** 型を持つ最後から 3 番目のパラメーターを介して ID3D11Device インターフェイス ポインターを返します。 このような特定のインターフェイス ポインターを返す関数の場合は、 com_ptr::p ut を使用します。

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

この前のセクションのコード例は、生 の D2D1CreateFactory 関数を呼び出す方法を示しています。 ただし、実際には、このトピックのコード例で D2D1CreateFactory を呼び出すと、生の API をラップするヘルパー関数テンプレートが使用されるため、コード例では実際には com_ptr::p ut を使用します。

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

インターフェイス ポインターを IUnknown として返す COM 関数

DWriteCreateFactory 関数は、IUnknown 型の最後のパラメーターを介して DirectWrite ファクトリ インターフェイス ポインターを返します。 このような関数の場合は 、com_ptr::p ut を使用しますが、 IUnknown にキャストを再解釈します。

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

winrt::com_ptr を再設定する

Von Bedeutung

既に座っている winrt::com_ptr (内部生ポインターに既にターゲットがある) があり、別のオブジェクトを指すように再座させる場合は、次のコード例に示すように、最初に nullptr を割り当てる必要があります。 そうしないと、すでに値が設定されている com_ptr が、内部ポインターが null でないことをアサートして、問題に気付かせます(com_ptr::put または com_ptr::put_void を呼び出したとき)。

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()));

HRESULT エラー コードの処理

COM 関数から返された HRESULT の値を確認し、エラー コードを表す場合に例外をスローするには、 winrt::check_hresult を呼び出します。

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

特定のインターフェイス ポインターを受け取る COM 関数

com_ptr::get 関数を呼び出して、同じ型の特定のインターフェイス ポインターを受け取る関数にcom_ptrを渡すことができます。

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

IUnknown インターフェイス ポインターを受け取る COM 関数

com_ptr::get を使用して、IUnknown インターフェイス ポインターを受け取る関数に com_ptr を渡すことができます。

winrt::get_unknown free 関数を使用すると、投影された型のオブジェクトの基になる生の IUnknown インターフェイスのアドレス (つまり、ポインター) を返すことができます。 その後、そのアドレスを IUnknown インターフェイス ポインターを受け取る関数に渡すことができます。

投影される型の詳細については、「C++/WinRT を使用した API の使用」を参照してください。

get_unknownのコード例については、winrt::get_unknown、またはこのトピックの最小 Direct2D アプリケーションの完全なソース コードの一覧を参照してください。

COM スマート ポインターの受け渡しと返し

com スマート ポインターを winrt::com_ptr の形式で受け取る関数は、定数参照または参照によって実行する必要があります。

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

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

winrt::com_ptr を返す関数は、値によってこれを行う必要があります。

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

別のインターフェイスの COM スマート ポインターのクエリを実行する

com_ptr::as 関数を使用して、COM スマート ポインターに対して別のインターフェイスのクエリを実行できます。 クエリが成功しない場合、関数は例外をスローします。

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

または、 com_ptr::try_as を使用します。この値は、 nullptr に対して確認してクエリが成功したかどうかを確認できる値を返します。

最小限の Direct2D アプリケーションの完全なソース コードの一覧

Note

C++/WinRT 開発用のVisual Studio (C++/WinRT Visual Studio拡張機能 (VSIX) のインストールと使用、NuGet パッケージ (プロジェクト テンプレートとビルド サポートを提供) など) の設定については、C++/WinRT のサポートVisual Studio参照してください。

このソース コードの例をビルドして実行する場合は、最初に最新バージョンの C++/WinRT Visual Studio Extension (VSIX) をインストール (または更新) します。上記の注を参照してください。 次に、Visual Studioで、新しいコア アプリ (C++/WinRT) を作成します。 Direct2D はプロジェクトの適切な名前ですが、任意の名前を付けることができます。 Windows SDK の最新の一般公開バージョン (プレビュー版ではない) をターゲットにします。

ステップ 1. 編集 pch.h

pch.hを開き、#include <unknwn.h>を含めた直後にwindows.hを追加します。 これは、 winrt::get_unknown を使用しているためです。 #include <unknwn.h> を使用するときは、そのヘッダーが別のヘッダーに含まれている場合でも、明示的にすることをお勧めします。

Note

この手順を省略すると、ビルド エラー 'get_unknown': 識別子が見つかりません

ステップ 2. 編集App.cpp

App.cppを開き、その内容全体を削除して、以下の一覧に貼り付けます。

次のコードでは、可能な場合は winrt::com_ptr::capture 関数 を使用します。 WINRT_ASSERT はマクロ定義であり、 _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>());
}

BSTR や VARIANT などの COM 型を扱う

ご覧のように、C++/WinRT では COM インターフェイスの実装と呼び出しの両方がサポートされています。 BSTR や VARIANT などの COM 型を使用する場合は、wil::unique_bstr や wil::unique_variant (リソースの有効期間を管理する) など、Windows実装ライブラリ (WIL) によって提供されるラッパーを使用することをお勧めします。

WIL は、Active Template Library (ATL) や Visual C++ コンパイラの COM サポートなどのフレームワークよりも優先されます。 また、独自のラッパーを作成したり、BSTR や VARIANT などの COM 型を(適切な API と併せて)生のまま使用したりするよりも、それを推奨します。

名前空間の競合の回避

このトピックのコードリストで示されているように、C++/WinRT では using ディレクティブを自由に使用するのが一般的です。 ただし、場合によっては、グローバル名前空間に衝突した名前をインポートする問題が発生する可能性があります。 次に例を示します。

C++/WinRT には winrt::Windows::Foundation::IUnknown という名前の型が含まれていますが、COM では ::IUnknown という名前の型が定義されています。 そのため、COM ヘッダーを使用する C++/WinRT プロジェクトでは、次のコードを検討してください。

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

修飾されていない名前 IUnknown はグローバル名前空間で競合するため、 あいまいなシンボル コンパイラ エラーが発生します。 代わりに、次のように、名前の C++/WinRT バージョンを winrt 名前空間に分離できます。

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

または、 using namespace winrtの利便性が必要な場合は、可能です。 次のように、 IUnknown のグローバル バージョンを修飾するだけです。

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

当然ながら、これは任意の C++/WinRT 名前空間で動作します。

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

その後、winrt::Windows::Storage::StorageFilewinrt::StorageFile のように参照できます。

重要な API