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<winrt::Windows::UI::Composition::ContainerVisual>()
.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<T>()</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<T></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<T></code> は <em>投影型</em>を前提に設計されており、ABI 名を渡してはなりません。</p>
<h2>最短修正レシピ</h2>
<ol>
<li><strong>ABI 名をやめて投影名にする。</strong>
<pre><code class="language-cpp">// NG
auto visuals = root.as<abi::ContainerVisual>().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 <unknwn.h> // COM の基本定義(先に)
#include <winrt/base.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.Composition.Desktop.h> // DesktopWindowTarget 等を使うなら
// interop が必要なときだけ
#include <windows.ui.composition.interop.h>
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<ContainerVisual>())
{
for (Visual const& v : root.Children()) { /* ... */ }
}
投影型で統一しておけば、VisualCollection、Compositor、SpriteVisual 等のオブジェクトがすべて C++ の RAII で扱え、リソース解放や例外安全も自然に保たれます。
チェックリスト:ビルドが通らないときに確認する 7 項目
- ABI 名を使っていないか(
ABI::がソースに出てこないか)。 - ヘッダーの順序(
<unknwn.h>→<winrt/base.h>→<winrt/...>)。 - ネームスペースのエイリアスで ABI 名を引き込んでいないか。
as<T>のTは投影型か(winrt::Windows::...)。- interop が必要な場所を限定し、
get_abi/put_abiを使っているか。 - Desktop 拡張を使うなら
<winrt/Windows.UI.Composition.Desktop.h>を含めたか。 - 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::Compositor | winrt::Microsoft::UI::Composition::Compositor | 型名はほぼ同じ、名前空間だけ変化 |
Windows::UI::Composition::Desktop::DesktopWindowTarget | Microsoft::UI::Dispatching と併用 | デスクトップ統合の流儀がやや異なる |
ContainerVisual | ContainerVisual | 使い方は同一。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<ContainerVisual>().Children(); // ここで初めて有効
constexpr/テンプレート地獄でログが読めない
エラーメッセージの末尾近くに「最初のユーザー コード」が現れます。as<abi::...> の一箇所を直すだけで雪崩のようにエラーが消えることがほとんどです。
WRL/ATL のポインタから投影型へ移行したい
次のステップで安全に進められます。
- まず投影型で結果を受け取る関数形を用意(オーバーロードやラッパー)。
- 呼び出し側では
com_ptr<T>を使いwinrt::put_abi/winrt::get_abiで橋渡し。 - 呼び出し元を段階的に置換し、最終的に ABI 依存箇所を最小化。
実践 Tips:読みやすく安全なコードにするために
- エイリアスを活用:長い名前空間は
namespace WUC = winrt::Windows::UI::Composition;のように短縮。 - 投影型で統一:ローカル変数・プロパティ型・関数引数/戻り値いずれも
winrt::を使う。 - 例外/失敗パスの処理:
try_asを用いればキャスト失敗時に例外を投げず扱える。 - 色・座標は構造体で:
winrt::float2/float3やWindows::UI::Colorを活用。 - コレクション操作:
Children().InsertAtTopやInsertBottomなどの順序関数で意図を明確に。
ケーススタディ:既存コードの“1 行”を直すだけで通る
次は実際にありがちな断片です。左が NG、右が OK です。
| NG | OK | 解説 |
|---|---|---|
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::は渡さない。- ヘッダーは順序が重要。追加は原則不要。

コメント