C++/WinRTでABI::ContainerVisual未定義エラーを解消する方法|Windows.UI.Compositionと投影型の正しい使い方

Windows.UI.Composition を Win32 アプリに導入したときに「ABI::Windows::UI::Composition::ContainerVisual が未定義」や __is_base_of 関連のテンプレート エラーに悩まされる――この症状は C++/WinRT の“投影型と ABI 型”の境界をまたいだときに起きがちです。本記事では原因を構造的に解きほぐし、再現/修正/運用の各観点から、今後迷わないための実践的な対処法を詳説します。

目次

症状の再現と状況

次の 1 行がきっかけでビルドが止まるケースがよくあります。

auto visuals = m_DesktopWindowTarget.Root().as<abi::ContainerVisual>().Children();

代表的なエラーは以下のとおりです。

  • error C2653: 'ABI::Windows::UI::Composition::ContainerVisual' が未定義
  • __is_base_of<...> を含むテンプレート メタ関数のインスタンス化に失敗
  • 大量のテンプレート スタックトレース(winrt::impl まわり)が噴出

Visual Studio で C++/WinRT を使い、Win32(非 XAML)アプリに Windows.UI.Composition を組み込んだときに発生します。ヘッダーの追加や using 宣言の調整では解決しません。原因は ABI 型を投影型(projection)として扱おうとしたことにあります。

結論:投影型(projection)を使う

C++/WinRT では「まず winrt:: の投影型を使う」が大原則です。該当行は次のように修正します。

auto visuals = m_DesktopWindowTarget.Root()
                 .as&lt;winrt::Windows::UI::Composition::ContainerVisual&gt;()
                 .Children();

あるいは、いったん変数に受ける書き方でも同じです。

using namespace winrt;
using namespace Windows::UI::Composition;

ContainerVisual root = m_DesktopWindowTarget.Root().as<ContainerVisual>();
VisualCollection children = root.Children(); </code></pre>

<p>ポイントは <code>as&lt;T&gt;()</code> の <code>T</code> に <strong>投影型</strong>(例:<code>winrt::Windows::UI::Composition::ContainerVisual</code>)を渡すこと。ABI 名(<code>ABI::...</code>)を渡すとテンプレート制約の検査で型が不完全となり失敗します。</p>

<h2>なぜ「未定義」になるのか ― ABI と投影型の違い</h2>
<p>C++/WinRT のヘッダーは「投影層(C++ らしい API)」と「ABI 層(生の COM インターフェイス)」の二層に分かれています。図式化すると次のとおりです。</p>
<table>
  <thead>
    <tr><th>層</th><th>例</th><th>役割</th><th>開発者が通常触るか</th></tr>
  </thead>
  <tbody>
    <tr>
      <td>投影層(projection)</td>
      <td><code>winrt::Windows::UI::Composition::ContainerVisual</code></td>
      <td>C++ の型として快適に使えるラッパー。メソッド、プロパティ、<code>as&lt;T&gt;</code> などを提供。</td>
      <td><strong>触る(推奨)</strong></td>
    </tr>
    <tr>
      <td>ABI 層</td>
      <td><code>ABI::Windows::UI::Composition::IContainerVisual</code> など</td>
      <td>COM vtable に近い低レベルなインターフェイス。SDK 内部で定義。</td>
      <td>原則触らない(interop が必要な場合のみ)</td>
    </tr>
  </tbody>
</table>
<p><code>ABI::Windows::UI::Composition::ContainerVisual</code> という<strong>クラス型そのものは ABI 層に存在しない</strong>(存在するとしても未完全型としてのみ宣言されている)ため、C++ の完全型が必要になるテンプレート(<code>__is_base_of</code> 等)の評価で破綻します。C++/WinRT の <code>as&lt;T&gt;</code> は <em>投影型</em>を前提に設計されており、ABI 名を渡してはなりません。</p>

<h2>最短修正レシピ</h2>
<ol>
  <li><strong>ABI 名をやめて投影名にする。</strong>
    <pre><code class="language-cpp">// NG
auto visuals = root.as&lt;abi::ContainerVisual&gt;().Children();

// OK
auto visuals = root.as<winrt::Windows::UI::Composition::ContainerVisual>().Children(); </code></pre>

  </li>
  <li><strong>ヘッダーの順序を確認。</strong> <code>unknwn.h</code> を <code>winrt/base.h</code> より前に include していれば十分です。追加で何かを入れる必要は通常ありません。
    <pre><code class="language-cpp">#include &lt;unknwn.h&gt; // COM の基本定義(先に)
#include &lt;winrt/base.h&gt;
#include &lt;winrt/Windows.UI.Composition.h&gt;
#include &lt;winrt/Windows.UI.Composition.Desktop.h&gt; // DesktopWindowTarget 等を使うなら
// interop が必要なときだけ
#include &lt;windows.ui.composition.interop.h&gt;

using namespace abi; は書かない。 無意識に ABI 名を引き込む温床です。

「ABI をどうしても使いたい」場合の正しいやり方

低レベルの interop(例:ICompositorDesktopInterop による DesktopWindowTarget 作成)では ABI ポインタが必要になります。このときも、投影型で受けて必要な瞬間だけ ABI ポインタを取り出すという流儀を守ります。

using namespace winrt;
using namespace Windows::UI::Composition;
using namespace Windows::UI::Composition::Desktop;

Compositor compositor;

// 投影型(C++/WinRT)のオブジェクトを作る
DesktopWindowTarget target{ nullptr };

// ABI インターフェイス(生 COM)を明示的に使うのはここだけ
com_ptr interop = compositor.as();

// put_abi で「受け取り用の**void****」を渡す
check_hresult(interop->CreateDesktopWindowTarget(
hwnd, true, reinterpret_cast(put_abi(target))));

// 以降は投影型にもどる
ContainerVisual root = compositor.CreateContainerVisual();
target.Root(root); 
  • winrt::get_abi(obj): 投影オブジェクトから void* 形式の ABI ポインタを取り出す。
  • winrt::put_abi(obj): 関数の out 引数に ABI ポインタを書き込ませ、その結果で投影オブジェクトを初期化する。

直接 as<abi::XXX> と書いてはいけません。 投影と ABI は明確に分離し、「必要なときだけ get_abi/put_abi」が鉄則です。

最小動作サンプル(Win32 + Composition)

以下は「コンパイルが通る」「ルートを ContainerVisual にして Children を触れる」ことに焦点を当てた最小サンプルです。ウィンドウ生成・メッセージ ループは既にある前提です。

#include <windows.h>
#include <unknwn.h>                          // ※ winrt/base.h より前
#include <winrt/base.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <windows.ui.composition.interop.h>

using namespace winrt;
using namespace Windows::UI::Composition;
using namespace Windows::UI::Composition::Desktop;

struct CompositionHost
{
Compositor                 m_compositor{ nullptr };
DesktopWindowTarget        m_target{ nullptr };
ContainerVisual            m_root{ nullptr };


void Initialize(HWND hwnd)
{
    m_compositor = Compositor{};

    // Create DesktopWindowTarget via interop (ABI はこの一箇所だけ)
    com_ptr<ICompositorDesktopInterop> interop = m_compositor.as<ICompositorDesktopInterop>();
    check_hresult(interop->CreateDesktopWindowTarget(
        hwnd, true, reinterpret_cast<IDesktopWindowTarget**>(put_abi(m_target))));

    // Root を ContainerVisual にする(投影型)
    m_root = m_compositor.CreateContainerVisual();
    m_target.Root(m_root);

    // 子を一個置いて動作確認
    SpriteVisual sprite = m_compositor.CreateSpriteVisual();
    sprite.Size({ 200,100 });
    sprite.Offset({ 20,20,0 });
    sprite.Brush(m_compositor.CreateColorBrush({ 0xFF,0x33,0x99,0xFF })); // 目立つ色
    m_root.Children().InsertAtTop(sprite);

    // ★ 質問の行に相当:投影型で OK
    auto children = m_target.Root()
                      .as<ContainerVisual>()
                      .Children();
    (void)children; // 未使用警告抑制
}


}; 

このコードは ABI と投影を正しく切り分け、as<ContainerVisual>() で想定どおりにダウンキャストできます。

ヘッダーは「順序」が命:追加は原則不要

多くのケースで、以下の 4 枚で十分です。足りないのはヘッダーの数ではなく順序です。

ヘッダー目的いつ必要か
<unknwn.h>COM の基本型(IUnknown 等)常に先頭付近(<winrt/base.h> より前)
<winrt/base.h>C++/WinRT の基盤(com_ptr, check_hresult, ABI ヘルパ)常に
<winrt/Windows.UI.Composition.h>Composition の投影型Composition を使うなら
<winrt/Windows.UI.Composition.Desktop.h>DesktopWindowTarget など Desktop 拡張Win32 でターゲットを作るなら
<windows.ui.composition.interop.h>ICompositorDesktopInterop 等 ABI インターフェイスinterop が必要なときだけ

WIL を併用するなら <wil/cppwinrt.h> を <winrt/base.h> の後ろに置きます(WIL が C++/WinRT のヘルパに依存するため)。

エラーの本質をもう少しだけ

  • テンプレートの制約に失敗:as<T> は T が Windows::Foundation::IInspectable(C++/WinRT では winrt::Windows::Foundation::IInspectable)に「変換可能」であることなどを静的に検証します。ABI 名を渡すと不完全型のままで、__is_base_of 等が評価できず爆発します。
  • ABI では「クラス型」がない:ABI 層で実体があるのは インターフェイス(例:IContainerVisual)であって、ランタイム クラス名(ContainerVisual)を C++ の型として扱うのは筋が悪い、という設計です。

よくある NG パターンと回避法

  • using namespace ABI;:無自覚に ABI 名を可視化してしまいます。やめましょう。
  • 古い C++/CX サンプルの流用:C++/CX では ContainerVisual^ などランタイム クラス名を直接扱えました。C++/WinRT は別物です。CX の感覚を持ち込まない。
  • WRL/ATL のヘッダーを大量に include:名前が衝突しやすく、未定義/曖昧化の温床に。必要最小限に抑え、順序を守る。
  • as<abi::IContainerVisual> としてしまう:ABI インターフェイスも as の対象ではありません。ABI が必要なら get_abi/put_abi を使う。

投影型でもっと書きやすく:as と try_as

安全性や見通しを高めるために、例外を出さない try_as も活用できます。

if (auto root = m_target.Root().try_as&lt;ContainerVisual&gt;())
{
    for (Visual const&amp; v : root.Children()) { /* ... */ }
}

投影型で統一しておけば、VisualCollection、Compositor、SpriteVisual 等のオブジェクトがすべて C++ の RAII で扱え、リソース解放や例外安全も自然に保たれます。

チェックリスト:ビルドが通らないときに確認する 7 項目

  1. ABI 名を使っていないか(ABI:: がソースに出てこないか)。
  2. ヘッダーの順序(<unknwn.h> → <winrt/base.h> → <winrt/...>)。
  3. ネームスペースのエイリアスで ABI 名を引き込んでいないか。
  4. as<T> の T は投影型か(winrt::Windows::...)。
  5. interop が必要な場所を限定し、get_abi/put_abi を使っているか。
  6. Desktop 拡張を使うなら <winrt/Windows.UI.Composition.Desktop.h> を含めたか。
  7. WIL を使う場合の include 順(<winrt/base.h> の後)。

補足:Windows.UI.Composition と Microsoft.UI.Composition(WinUI 3)

Windows App SDK(WinUI 3)では名前空間が Microsoft.UI.Composition に変わりますが、「投影型を使う」「ABI は最後」という原則は同じです。移行時の置き換え例を示します。

Windows 10/11(OS の Composition)Windows App SDK(WinUI 3)メモ
winrt::Windows::UI::Composition::Compositorwinrt::Microsoft::UI::Composition::Compositor型名はほぼ同じ、名前空間だけ変化
Windows::UI::Composition::Desktop::DesktopWindowTargetMicrosoft::UI::Dispatching と併用デスクトップ統合の流儀がやや異なる
ContainerVisualContainerVisual使い方は同一。as<T> の T を投影型に

トラブルシューティング集

Root() が null で Children() に触れない

DesktopWindowTarget::Root() は既定では null を返します。まず Compositor::CreateContainerVisual() でルートを作り、target.Root(root) を呼びます。

ContainerVisual root = compositor.CreateContainerVisual();
m_target.Root(root);
auto children = m_target.Root().as&lt;ContainerVisual&gt;().Children(); // ここで初めて有効

constexpr/テンプレート地獄でログが読めない

エラーメッセージの末尾近くに「最初のユーザー コード」が現れます。as<abi::...> の一箇所を直すだけで雪崩のようにエラーが消えることがほとんどです。

WRL/ATL のポインタから投影型へ移行したい

次のステップで安全に進められます。

  1. まず投影型で結果を受け取る関数形を用意(オーバーロードやラッパー)。
  2. 呼び出し側では com_ptr<T> を使い winrt::put_abi/winrt::get_abi で橋渡し。
  3. 呼び出し元を段階的に置換し、最終的に ABI 依存箇所を最小化。

実践 Tips:読みやすく安全なコードにするために

  • エイリアスを活用:長い名前空間は namespace WUC = winrt::Windows::UI::Composition; のように短縮。
  • 投影型で統一:ローカル変数・プロパティ型・関数引数/戻り値いずれも winrt:: を使う。
  • 例外/失敗パスの処理:try_as を用いればキャスト失敗時に例外を投げず扱える。
  • 色・座標は構造体で:winrt::float2/float3 や Windows::UI::Color を活用。
  • コレクション操作:Children().InsertAtTop や InsertBottom などの順序関数で意図を明確に。

ケーススタディ:既存コードの“1 行”を直すだけで通る

次は実際にありがちな断片です。左が NG、右が OK です。

NGOK解説
auto children = m_target.Root() .as<abi::ContainerVisual>() .Children();auto children = m_target.Root() .as<winrt::Windows::UI::Composition::ContainerVisual>() .Children();as<T> は 投影型を渡す
ABI::Windows::UI::Composition:: ContainerVisual* cv; cv = ...; // 直接扱うwinrt::Windows::UI::Composition:: ContainerVisual cv = ...; // 投影型で扱うABI ポインタは interop の境界でのみ取り出す
#include <winrt/base.h> #include <unknwn.h> // 後ろにある#include <unknwn.h> // 先 #include <winrt/base.h>include の順序を守る

運用の心得:「まず winrt::、ABI は最後」

ルール: 投影型(winrt::)で組み立て、interop の直前だけ get_abi/put_abi を使う。ABI 名(ABI::)をソースに出さない。

  • 投影型で統一すれば、IntelliSense が正しく働き、Children() などの API を迷いなく辿れる。
  • ABI の知識は必要なときにだけ持ち出す(例:ICompositorDesktopInterop、IGraphicsEffectD2D1Interop 等)。
  • コードレビューの観点からも、ABI 名が出た行は「interop 境界」であると一目で分かり、バグの温床を局所化できる。

まとめ

  • エラーの原因:ABI 名(ABI::...)を as<T> に渡し、テンプレート制約の評価に必要な完全型が存在せず破綻。
  • 解決策:as<winrt::Windows::UI::Composition::ContainerVisual>() のように投影型を使う。ヘッダーは <unknwn.h> → <winrt/base.h> → <winrt/Windows.UI.Composition*.h> の順で十分。
  • ABI を使う場合:winrt::get_abi/winrt::put_abi でピンポイントに。直接 as<abi::XXX> は行わない。

この原則だけで、「ABI::ContainerVisual が未定義」問題はもちろん、Composition を巡る多くの型まわりの混乱を未然に防げます。明日以降のメンテナンスのためにも、まず winrt::、ABI は最後を習慣にしましょう。

付録:完全なサンプル(関数分割版)

最後に、初期化コードを関数に分割した少し丁寧な例を載せておきます。既存プロジェクトに貼り付け、該当部分だけ読み替えれば動きます。

#include <windows.h>
#include <unknwn.h>
#include <winrt/base.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <windows.ui.composition.interop.h>

namespace WUC  = winrt::Windows::UI::Composition;
namespace WUCD = winrt::Windows::UI::Composition::Desktop;

struct CompositionApp
{
WUC::Compositor          m_compositor{ nullptr };
WUCD::DesktopWindowTarget m_target{ nullptr };
WUC::ContainerVisual     m_root{ nullptr };


void Initialize(HWND hwnd)
{
    m_compositor = WUC::Compositor{};
    create_target(hwnd);
    create_root_and_scene();
}

void create_target(HWND hwnd)
{
    winrt::com_ptr<ICompositorDesktopInterop> interop = m_compositor.as<ICompositorDesktopInterop>();
    winrt::check_hresult(interop->CreateDesktopWindowTarget(
        hwnd, true, reinterpret_cast<IDesktopWindowTarget**>(winrt::put_abi(m_target))));
}

void create_root_and_scene()
{
    m_root = m_compositor.CreateContainerVisual();
    m_target.Root(m_root);

    auto red = m_compositor.CreateColorBrush(winrt::Windows::UI::Color{ 0xFF, 0xE6, 0x4A, 0x19 });
    auto sprite = m_compositor.CreateSpriteVisual();
    sprite.Brush(red);
    sprite.Size({ 240, 120 });
    sprite.Offset({ 40, 40, 0 });

    // 子のコレクションを投影型で取得(★ 本記事の要点)
    m_root.Children().InsertAtTop(sprite);
    auto children = m_target.Root().as<WUC::ContainerVisual>().Children();
    // children を使って列挙や整列などのロジックへ…
}


}; 

この記事の要点(再掲)

  • 投影型(winrt::)で書く。ABI は最後の手段。
  • as<T> に渡す T は投影型。ABI:: は渡さない。
  • ヘッダーは順序が重要。追加は原則不要。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次