日程Fit|「いつ空いてますか?」の往復はもう不要。候補日を選んでURLを送るだけ|登録不要|今すぐ無料で使う →

Win32/C++でXAML IslandsのTextBlockが表示されない時の原因と対処法|DesktopWindowXamlSourceとWindowsXamlManagerの正しい寿命管理

Win32/C++ の既存デスクトップアプリに XAML Islands を組み込み、「Hello World from Xaml Islands!」を表示したのに画面が空白のまま——この症状の90%は DesktopWindowXamlSource の寿命管理ミスです。SDK を追加する前に、ウィンドウメッセージ駆動のライフサイクルと STA/レイアウトの基本だけで解決できます。この記事では原因の切り分けと最小修正、実運用で効く設計パターンまで具体的に解説します。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

Win32 デスクトップアプリで XAML Islands のテキストが表示されない問題

質問の背景

C++/Win32 デスクトップアプリで DesktopWindowXamlSource を使い、XAML の TextBlock に「Hello World from Xaml Islands!」を表示させようとしたが、実行しても何も表示されない。
補助的な NuGet(Microsoft.Toolkit.Win32.UI.SDK など)を導入していないことが原因か知りたい。

結論(先に要点)

  • 最大の原因は寿命(ライフサイクル)DesktopWindowXamlSource を if ブロックなどのローカル変数で生成すると、ブロック終了時に破棄され、子 HWND と XAML ツリーも消えます。
  • SDK の追加は必須ではないMicrosoft.Toolkit.Win32.UI.SDK が無くても C++/WinRT だけで XAML Islands をホスト可能です。
  • 正しい保持スコープWindowsXamlManagerDesktopWindowXamlSource をウィンドウの寿命と同じスコープ(グローバル/メンバ/GWLP_USERDATA)で管理し、WM_CREATE で生成、WM_SIZE でレイアウト更新、WM_DESTROY で明示解放します。

原因と解決策(要約表)

主な原因解決策補足・ポイント
DesktopWindowXamlSource の寿命が短すぎる
if ブロック内のローカル変数として生成し、直後に破棄。
DesktopWindowXamlSourceWindowsXamlManager をウィンドウの存続期間と同じスコープで保持する(グローバル、ウィンドウクラスのメンバ、または GWLP_USERDATA に格納)。推奨フロー
1) WM_CREATEInitializeForCurrentThreadAttachToWindow
2) WM_SIZEMoveWindow/SetWindowPos
3) WM_DESTROYClose()
STA で初期化していない
COM アパートメントが MTA のまま。
winrt::init_apartment(winrt::apartment_type::single_threaded) を UI スレッドで一度だけ呼ぶ。XAML(WinUI)は STA 想定。二重初期化や別スレッドでの呼び出しは不具合の温床。
子 HWND の表示・サイズ更新をしていないIDesktopWindowXamlSourceNative::get_WindowHandle で取得した子 HWND に対し、SWP_SHOWWINDOW を付けて SetWindowPosWM_SIZEで追随。初期表示時は幅高さ 0 のことがある。Z オーダーや可視フラグも確認。
DPI/スケーリングで見切れているWM_DPICHANGED に応答し、提示の推奨矩形に MoveWindowPer-Monitor V2 を有効化(マニフェストまたは SetThreadDpiAwarenessContext)。
対象 OS 未満Windows 10 1903 (build 18362) 以降が必須。満たさない環境では処理をスキップし、ユーザーに通知。ApiInformation::IsApiContractPresent でランタイムチェックを入れておくと安全。
SDK を入れていないから動かない?いいえMicrosoft.Toolkit.Win32.UI.SDK は必須ではない。C++/WinRT のみでホストできる。ただし将来的に Windows App SDK/WinUI 3 へ移行するなら NuGet 管理が便利。

最小修正(コンセプト)

// グローバル or ウィンドウクラスのメンバで保持
winrt::WindowsXamlManager g_xamlManager{nullptr};
winrt::DesktopWindowXamlSource g_xamlSource{nullptr};

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
// STA(一度だけ)
winrt::init_apartment(winrt::apartment_type::single_threaded);


    // XAML ランタイム初期化
    g_xamlManager = winrt::WindowsXamlManager::InitializeForCurrentThread();

    // Islands を作成
    g_xamlSource = winrt::DesktopWindowXamlSource();

    // ネイティブ interop で Win32 ウィンドウにアタッチ
    auto interop = g_xamlSource.as<IDesktopWindowXamlSourceNative>();
    winrt::check_hresult(interop->AttachToWindow(hWnd));

    // 子 HWND を取得して初期表示
    HWND hIsland{};
    interop->get_WindowHandle(&hIsland);
    SetWindowPos(hIsland, nullptr, 0, 0, 800, 200, SWP_SHOWWINDOW);

    // 表示する XAML ツリー
    winrt::StackPanel panel{};
    panel.Background(winrt::SolidColorBrush{ winrt::Colors::LightGray() });

    winrt::TextBlock tb{};
    tb.Text(L"Hello World from XAML Islands!");
    tb.HorizontalAlignment(winrt::HorizontalAlignment::Center);
    tb.VerticalAlignment(winrt::VerticalAlignment::Center);
    tb.FontSize(64);

    panel.Children().Append(tb);
    g_xamlSource.Content(panel);
    return 0;
}

case WM_SIZE:
{
    // 親サイズに追随
    RECT rc{};
    GetClientRect(hWnd, &rc);
    HWND hIsland{};
    g_xamlSource.as<IDesktopWindowXamlSourceNative>()->get_WindowHandle(&hIsland);
    MoveWindow(hIsland, 0, 0, rc.right, rc.bottom, TRUE);
    return 0;
}

case WM_DESTROY:
    // 明示的にクローズ(先に XamlSource、その後 XamlManager)
    if (g_xamlSource) g_xamlSource.Close();
    if (g_xamlManager) g_xamlManager.Close();
    PostQuitMessage(0);
    return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);


} 

「寿命」で何が起きているのか(内部の見え方)

  • DesktopWindowXamlSource は内部的に「XAML ツリー」+「子 HWND(Island)」を管理します。
  • ローカル変数のままスコープを抜けると参照カウントが 0 になり解放、子 HWND も破棄されて 見えない(表示されない) 状態になります。
  • Win32 はメッセージ駆動なので、WM_CREATE で生成し、WM_DESTROY まで生かし続けるのが最も自然です。

実運用での保持パターン比較

保持先メリット注意点
グローバル変数サンプルを最短で動かせる。学習向け。多ウィンドウ対応やテスト容易性に劣る。
ウィンドウクラスのメンバ(C++ クラス)RAII と相性が良く、拡張しやすい。WndProc をメンバ化する設計が必要。
GWLP_USERDATA にポインタ格納無名関数型の WndProc でもメンバへアクセスしやすい。寿命管理(WM_NCDESTROY)とキャスト安全性に注意。

フルの最小サンプル(ビルドしやすい形)

以下は、単一ファイルでビルドできる最小構成(概念)。ヘッダーは Windows SDK 10.0.18362 以降を前提とします。

#include <windows.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <windows.ui.xaml.hosting.desktopwindowxamlsourcenative.h>

using namespace winrt;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Hosting;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Media;

WindowsXamlManager g_xamlManager{nullptr};
DesktopWindowXamlSource g_xamlSource{nullptr};

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

int APIENTRY wWinMain(HINSTANCE hInst, HINSTANCE, PWSTR, int nCmd)
{
init_apartment(apartment_type::single_threaded);


const wchar_t* kClass = L"XamlIslandsSample";
WNDCLASSEXW wc{ sizeof(wc) };
wc.lpfnWndProc   = WndProc;
wc.hInstance     = hInst;
wc.hCursor       = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = kClass;
RegisterClassExW(&wc);

HWND hWnd = CreateWindowExW(0, kClass, L"XAML Islands Sample",
    WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 1000, 600,
    nullptr, nullptr, hInst, nullptr);

ShowWindow(hWnd, nCmd);
UpdateWindow(hWnd);

MSG msg{};
while (GetMessageW(&msg, nullptr, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessageW(&msg);
}
return (int)msg.wParam;


}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
g_xamlManager = WindowsXamlManager::InitializeForCurrentThread();
g_xamlSource  = DesktopWindowXamlSource();


    auto interop = g_xamlSource.as<IDesktopWindowXamlSourceNative>();
    winrt::check_hresult(interop->AttachToWindow(hWnd));

    HWND hIsland{};
    interop->get_WindowHandle(&hIsland);

    // 初期レイアウト
    RECT rc{}; GetClientRect(hWnd, &rc);
    SetWindowPos(hIsland, nullptr, 0, 0, rc.right, rc.bottom, SWP_SHOWWINDOW);

    StackPanel panel{};
    panel.Background(SolidColorBrush{ Windows::UI::Colors::LightGray() });
    TextBlock tb{};
    tb.Text(L"Hello World from XAML Islands!");
    tb.HorizontalAlignment(HorizontalAlignment::Center);
    tb.VerticalAlignment(VerticalAlignment::Center);
    tb.FontSize(64);
    panel.Children().Append(tb);

    g_xamlSource.Content(panel);
    return 0;
}
case WM_SIZE:
{
    HWND hIsland{};
    g_xamlSource.as<IDesktopWindowXamlSourceNative>()->get_WindowHandle(&hIsland);
    MoveWindow(hIsland, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
    return 0;
}
case WM_DPICHANGED:
{
    // 推奨矩形に合わせると滲みが減る
    RECT* const prcNewWindow = reinterpret_cast<RECT*>(lParam);
    SetWindowPos(hWnd, nullptr,
        prcNewWindow->left, prcNewWindow->top,
        prcNewWindow->right - prcNewWindow->left,
        prcNewWindow->bottom - prcNewWindow->top,
        SWP_NOZORDER | SWP_NOACTIVATE);
    return 0;
}
case WM_DESTROY:
    if (g_xamlSource)  g_xamlSource.Close();
    if (g_xamlManager) g_xamlManager.Close();
    PostQuitMessage(0);
    return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);


} 

OS 条件チェックの入れ方

開発機では動くのに検証機で表示されない場合、OS 条件を満たしていない可能性があります。ランタイムチェックを入れておきましょう。

#include <winrt/Windows.Foundation.Metadata.h>
using namespace Windows::Foundation::Metadata;

// 例: UniversalApiContract の存在で早期判定
if (!ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8))
{
MessageBoxW(hWnd, L"XAML Islands は Windows 10 1903 以降が必要です。", L"環境要件", MB_OK | MB_ICONWARNING);
return -1; // WM_CREATE 失敗でクラシック UI にフォールバックなど
} 

レイアウトと DPI(見えない=実は極小サイズ問題のことも)

  • 初期化直後の子 HWND は サイズ 0×0 のことがあります。必ず SetWindowPosMoveWindow を呼びます。
  • 最初の表示時は SWP_SHOWWINDOW を付けて可視化。Z オーダーを崩したくないときは SWP_NOZORDER を併用。
  • 高 DPI 環境では WM_DPICHANGED を処理し、推奨矩形を採用することでボケや欠けを防げます。

スレッドモデル(STA は必須)

WindowsXamlManager::InitializeForCurrentThread() の実行スレッドは STA である必要があります。
以下を満たさないと、表示されない/時々落ちる/描画が止まるなどの不定挙動を招きます。

  • UI を扱うスレッドで winrt::init_apartment(winrt::apartment_type::single_threaded) を一度だけ。
  • 別スレッドから XAML ツリーを触らない(必要ならディスパッチ)。

診断のコツ(開発者の実務ワークフロー)

  • Spy++/WinDbg で子 HWND が存在するかを確認。無ければ寿命か Attach ミス。
  • ログAttachToWindowget_WindowHandle戻り値 を必ず検証(check_hresult)。
  • 可視状態IsWindowVisible(hIsland)、矩形は GetWindowRect で確認。
  • スケーリング… 200% 以上のディスプレイで領域が 0 になっていないか。

よくある落とし穴

  • コンストラクタ内で Islands を作って すぐ 破棄してしまう(メンバ化忘れ)。
  • WM_CREATE で作ったあと、WM_SIZE を実装しておらず、裏に貼り付いた極小ウィンドウ のまま。
  • Close() の順序が逆(先に WindowsXamlManager を閉じる)で例外や未定義動作。
  • ハンドル漏れ・二重初期化(InitializeForCurrentThread を同一スレッドで複数回)。

設計パターン:メンバ保持と GWLP_USERDATA

WndProc スタイルの既存コードに最小侵襲で組み込むなら、GWLP_USERDATA に this ポインタを入れ、メンバへディスパッチするのが定番です。

struct MainWindow {
    HWND hwnd{};
    winrt::WindowsXamlManager xamlManager{nullptr};
    winrt::DesktopWindowXamlSource xamlSource{nullptr};


void OnCreate() { /* ... 上述の初期化 ... */ }
void OnSize(WORD w, WORD h) { /* ... MoveWindow ... */ }
void OnDestroy() { xamlSource.Close(); xamlManager.Close(); }


};

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM w, LPARAM l)
{
MainWindow* self = reinterpret_cast(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
if (msg == WM_NCCREATE) {
self = new MainWindow{};
self->hwnd = hWnd;
SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(self));
}
if (!self) return DefWindowProcW(hWnd, msg, w, l);


switch (msg) {
case WM_CREATE:   self->OnCreate();  return 0;
case WM_SIZE:     self->OnSize(LOWORD(l), HIWORD(l)); return 0;
case WM_DESTROY:  self->OnDestroy(); delete self; SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); PostQuitMessage(0); return 0;
}
return DefWindowProcW(hWnd, msg, w, l);


} 

WPF/WinForms との違い(混同しがちなポイント)

  • WPF/WinForms の ホストコントロール は生存がフレームワークにより保証されますが、Win32 直叩きでは 開発者が寿命を保証 する必要があります。
  • メッセージ駆動でレイアウトが変わるたびに MoveWindow を呼ぶ点もフレームワークと発想が異なります。

Windows App SDK / WinUI 3 への見通し

現行の XAML Islands(UWP XAML ベース)でも問題なく動作しますが、将来的に WinUI 3(Windows App SDK)へ移行する場合は NuGet で依存を一元管理しやすくなります。とはいえ、「表示されない」根本原因は同じく寿命とレイアウトです。まずは本記事のチェックリストで健全化し、その後に移行を検討すると安全です。

チェックリスト(そのまま現場で使える)

  • UI スレッドで init_apartment(STA) を一度だけ呼んでいる
  • WindowsXamlManagerDesktopWindowXamlSource をウィンドウ寿命で保持している
  • AttachToWindow の戻り値を検証している
  • 子 HWND 取得後、初回に SWP_SHOWWINDOW 付きでサイズ設定している
  • WM_SIZEWM_DPICHANGED を処理している
  • Close() の順序(XamlSourceXamlManager)を守っている
  • OS 条件未満では処理をスキップしユーザーへ通知している

FAQ

  • Q: Microsoft.Toolkit.Win32.UI.SDK を入れないと表示できませんか?
    A: いいえ。必須ではありません。C++/WinRT だけで十分です。
  • Q: たまに表示され、リサイズすると消えます。
    A: 子 HWND のレイアウト未更新が原因です。WM_SIZEMoveWindow を必ず実行してください。
  • Q: スレッドを分けたい。
    A: UI を跨ぐ操作は非推奨。UI スレッドにディスパッチする設計にしてください。

まとめ

  • 表示されない最大の原因はライフサイクルDesktopWindowXamlSourceWindowsXamlManager をウィンドウ寿命と同じスコープで保持し、WM_CREATE/WM_SIZE/WM_DESTROY で制御します。
  • 追加 SDK は不要。C++/WinRT ベースで問題なくホスト可能です。
  • 実装のコツ:初回表示に SWP_SHOWWINDOW、DPI 変化に追随、Close() の順序を厳守。

参考:トラブル対処フローチャート(簡易)

  1. STA 初期化済みか? → 未実施なら init_apartment(STA)
  2. InitializeForCurrentThreadAttachToWindow の戻り値 OK? → 失敗ならデバッグログ確認
  3. 子 HWND 取得できたか? → できていなければ寿命・権限・ハンドルを再点検
  4. 初期レイアウト(SWP_SHOWWINDOW 付き)を実施したか?
  5. WM_SIZE/WM_DPICHANGED 対応はあるか?
  6. OS 条件を満たしているか?

チェック用コード断片(差分採用しやすい版)

// 1) グローバル or メンバ
winrt::WindowsXamlManager g_mgr{ nullptr };
winrt::DesktopWindowXamlSource g_src{ nullptr };

// 2) WM_CREATE
g_mgr = winrt::WindowsXamlManager::InitializeForCurrentThread();
g_src = winrt::DesktopWindowXamlSource();
auto interop = g_src.as();
winrt::check_hresult(interop->AttachToWindow(hWnd));
HWND hIsland{}; interop->get_WindowHandle(&hIsland);
RECT rc{}; GetClientRect(hWnd, &rc);
SetWindowPos(hIsland, nullptr, 0, 0, rc.right, rc.bottom, SWP_SHOWWINDOW);

// 3) コンテンツ
winrt::TextBlock tb{}; tb.Text(L"Hello World from XAML Islands!"); tb.FontSize(64);
winrt::StackPanel panel{}; panel.Children().Append(tb);
g_src.Content(panel);

// 4) WM_SIZE
g_src.as()->get_WindowHandle(&hIsland);
MoveWindow(hIsland, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);

// 5) WM_DESTROY(順序に注意)
g_src.Close();
g_mgr.Close(); 

この記事を書いた人

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

コメント

コメントする

目次