「デバッグでは動くのに、リリース (.NET Native) にするとスプラッシュ直後に即死」。UWP では珍しくない落とし穴です。特に SkiaSharp を含む一部の NuGet を更新した直後から再現するケースが多く、ログも出ず原因が掴みにくいのが厄介。本記事では、再現条件の整理から最短の復旧策、恒久対策、そしてクラッシュダンプを使った深掘り解析まで、実務でそのまま役立つ手順を一気通貫で解説します。
症状と前提:エラー「The target process aborted before activation completed」で即時クラッシュ
長年ストア公開してきた UWP アプリにて、NuGet(特に SkiaSharp)を更新したあとに以下の挙動が発生します。
- デバッグ構成:正常起動
- リリース構成(.NET Native AOT):スプラッシュ画面直後にプロセスが終了し、Visual Studio では The target process aborted before activation completed と表示される
App()やOnLaunched()を空にしても再現し、デバッグ出力にも手掛かりが出ない
なぜ「デバッグは動く」のに「リリースで落ちる」のか(.NET Native の特性)
UWP のリリースビルドでは .NET Native ツールチェーンにより、アプリは AOT(Ahead-of-Time) でネイティブコードへ変換されます。この過程では以下のような差分が生まれます。
- IL トリミング:未参照と判断された型やメンバーが削除され、動的アクセス(リフレクション、シリアライザ、XAML バインディングの一部など)が失敗しうる。
- 最適化とインライン化:例外の表面化タイミングが変わり、デバッグ構成では生き残るコードがリリースでは即死に変わることがある。
- API コントラクト差:ターゲット/最小バージョンより新しい API を間接参照した結果、AOT で早期に失敗するケースがある。
SkiaSharp v3 系は .NET 6+/WinUI 3/Maui へのシフトが進んだ経緯があり、UWP + .NET Native では相性問題が出やすいのが実情です。
最短で復旧するための「優先手順」
| 優先度 | アクション | 狙い | ポイント |
|---|---|---|---|
| 高 | SkiaSharp を v2.88.9(= 3.0 未満)に固定 | 既知の安定ラインへ戻し、AOT との相性起因を除去 | SkiaSharp 本体だけでなく SkiaSharp.Views.UWP など関連パッケージも 同系の 2.88.x に揃える |
| 高 | 旧ビルド・キャッシュの一掃 | ローカル残骸によるダーティビルドや競合を排除 | アプリをアンインストール → Clean → Rebuild。別 PC/VM でも再現チェック |
| 中 | .NET Native を一時的に無効化して切り分け | AOT/最適化由来かを 1 手で判定 | .csproj に <UseDotNetNativeToolchain>false</UseDotNetNativeToolchain> を追加(公開前に戻す) |
| 中 | WER(Windows Error Reporting)でフルダンプ採取 | 「どのモジュールで死んでいるか」を客観的に把握 | Visual Studio/WinDbg で ExceptionCode と Faulting Module を確認 |
| 中 | 依存パッケージと API コントラクトの整合性を点検 | 最小バージョンを超える API 要求や、暗黙の依存を炙り出す | 必要に応じて機能ガード(ApiInformation.IsApiContractPresent)を入れる |
具体的な復旧・解析手順(コピペで進められる実務ガイド)
1. SkiaSharp を 2.88.9(3.0 未満)に固定する
- ソリューションを閉じる前に アプリをアンインストール(設定 > アプリ から確実に削除)。
- Visual Studio でプロジェクトの Manage NuGet Packages を開き、以下を 2.88.x に揃える。
SkiaSharpSkiaSharp.Views.UWP- (入っていれば)
SkiaSharp.HarfBuzz、SkiaSharp.NativeAssets.*
- Clean Solution → Rebuild → Release で実行。
これで起動できるなら、クラッシュの直接原因は SkiaSharp v3 以降と .NET Native の相性だった可能性が高いです。
2. 旧ビルドの残骸を完全除去する
再現が消えたり、PC を変えると動く場合はビルド残骸が濃厚です。以下を徹底します。
- 対象アプリを確実にアンインストール(ストア版が残っていないか確認)。
bin/とobj/を手動で削除してから再ビルド。- PowerShell で Appx を念のため削除:
Get-AppxPackage -Name <PackageFamilyName> | Remove-AppxPackage - 別ユーザー/別 PC/クリーンな VM で .appx または .msix を新規インストールし検証。
3. .NET Native を一時的にオフにして AOT 起因か判定
プロジェクトの .csproj に次を追記して、リリースでも JIT で動かします。
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<UseDotNetNativeToolchain>false</UseDotNetNativeToolchain>
</PropertyGroup>
この状態で起動できる場合、AOT 最適化/トリミング起因の可能性が高くなります(本番公開前には必ず true に戻してください)。
4. WER でクラッシュダンプ(フル)を採取し、客観的に突き止める
管理者権限のコマンド プロンプトで以下を実行し、C:\Dumps にフルダンプを保存します(YourApp.exe は実行ファイル名に置換)。
mkdir C:\Dumps
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\YourApp.exe" /v DumpType /t REG_DWORD /d 2 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\YourApp.exe" /v DumpCount /t REG_DWORD /d 10 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\YourApp.exe" /v DumpFolder /t REG_EXPAND_SZ /d C:\Dumps /f
再現させたら Visual Studio の Debug > Open Dump File… で開き、以下を確認します。
- Exception Code(
0xC0000005など) - Faulting Module(
SkiaSharp.dll、vcruntime*.dllなど) - Call Stack(.NET Native でもシンボルがあれば関数名が一定程度見える)
Symbol の読み込みは Tools > Options > Debugging > Symbols で「Microsoft Symbol Servers」を有効化して行うと解析精度が上がります。不要になったらレジストリの LocalDumps 設定は削除しておきましょう。
5. API コントラクトと最小バージョンの整合性を点検する
NuGet 更新で暗黙に新しい API を間接参照している場合、.NET Native で早期に落ちることがあります。必要に応じて機能ガードを入れます。
using Windows.Foundation.Metadata;
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
{
// 新しめの API をここでだけ使う
}
マニフェスト(最小バージョン/ターゲットバージョン)と、パッケージが要求する API の整合性を確認してください。
より深い恒久対策:.NET Native のトリミングと動的アクセスを味方にする
RD.XML でリフレクション/シリアライザ/XAML の動的アクセスを確保
リフレクションや JSON シリアライザ、XAML バインディングで動的に型へアクセスする箇所がある場合、.NET Native ではトリミングにより型が削除され、実行時に見つからず落ちることがあります。RD.XML を使って保持を宣言しておきましょう。
<Directives>
<Application>
<!-- アプリ自身の型はすべて動的アクセス可に(解析中の暫定策) -->
<Assembly Name="*Application*" Dynamic="Required All" />
</Application>
最初は広めに指定し、ダンプ解析やログを見ながら徐々に絞っていくのが実務では安全です。
XAML 起動直後クラッシュの見つけ方
アプリのコンストラクタより前に XAML が評価されると、ResourceDictionary のキー重複や型解決ミスなどで .NET Native では即死になりがちです。次の観点をチェックします。
App.xamlのApplication.Resourcesで、存在しない型を参照していないか。- カスタムマークアップ拡張やコンバータが 内部型のままになっていないか(
internalは解決できず失敗することがある)。 BindingのPathが存在しないプロパティ名になっていないか(デバッグでは Warning だが、リリースでは致命傷化することがある)。
ネイティブ資産(NativeAssets)の整合性
SkiaSharp では SkiaSharp.NativeAssets.* がランタイムに必要です。UWP であれば UWP 向けの NativeAssets に揃え、x86/x64/ARM/ARM64 それぞれのビルドに適切に含まれるかを Appx/MSIX のコンテンツ で確認してください。
構成別(x86/x64/ARM64)・SDK 別の起動確認
構成を変えるだけでクラッシュ箇所が変容します。以下の表を元に網羅的に確認すると再現範囲が絞れます。
| 軸 | 観点 | チェックポイント |
|---|---|---|
| アーキテクチャ | x86 / x64 / ARM64 | NativeAssets が想定どおり配置されるか、特定アーキでのみ落ちないか |
| ビルド構成 | Debug / Release | Release + UseDotNetNativeToolchain=true のときだけ落ちるか |
| Windows SDK | 10.0.x の違い | ビルドマシン・検証マシンの SDK 差で再現が変わらないか |
エラーパターン別の対処チャート
| 症状 | 想定原因 | 対処 |
|---|---|---|
| スプラッシュ直後に即死、ログ空 | .NET Native によるトリミング/NativeAssets 配置不整合 | SkiaSharp を 2.88.x に戻す → RD.XML で保持 → WER で Faulting Module を確認 |
| 特定デバイス(ARM64)のみ再現 | ネイティブ DLL の不足/不整合 | パッケージ展開物を確認し、該当アーキの DLL を含める |
| デバッグで Warning の XAML がリリースで落ちる | 型解決失敗・内部型参照・Binding ミス | 型を public 化、キー重複解消、プロパティ名の正当性を見直す |
| 更新前は動作、更新後だけ落ちる | NuGet のメジャー移行(SkiaSharp v3 など) | 旧系に固定、または WinUI 3 への移行計画を立てる |
「回答・解決策」まとめ(再掲+実務補足)
| 解決策 | 内容 | 補足・ポイント |
|---|---|---|
| ① SkiaSharp を旧バージョンに戻す | UWP で安定しているのは v2.88.9 など 3.0 未満。v3 以降は .NET 6+/WinUI 3/Maui 志向が強く、.NET Native でクラッシュが発生しやすい。 | NuGet を一つずつロールバックして SkiaSharp が原因であることを確証する。関連パッケージも同系に揃える。 |
| ② 不要キャッシュ/旧ビルドの一掃 | 旧版アプリを完全アンインストール → Clean & Rebuild。別 PC だと動く例もあり、ローカルの残骸が影響する。 | ストア版が残っていると競合。設定 > アプリから確実に削除し、bin/obj も物理削除。 |
| ③ クラッシュダンプ取得(WER) | 管理者コマンドで WER を有効化し C:\Dumps に DumpType=2(フル)で保存、Visual Studio で解析。 | 環境依存クラッシュの根本原因確認に有効。解析後は LocalDumps を戻しておく。 |
| ④ .NET Native を一時無効化 | .csproj に <UseDotNetNativeToolchain>false</UseDotNetNativeToolchain> を追加し、AOT 由来か切り分け。 | 本番公開時は必ず再有効化。AOT 無効のままストア審査に出さない。 |
| ⑤ 依存パッケージのバージョン確認 | 更新した NuGet が ターゲット最小バージョンを超える API を要求していないか、依存関係まで含めて点検。 | 小さなマイナー差でも .NET Native で落ちるケースがあるため、動いていたセットを記録し再現性を担保する。 |
トラブルが長引いたときの「安全弁」:観測可能性を高める
アプリ全体の未処理例外ログを拾う
完全には拾えませんが、早期のヒントを得るために App() でイベントを購読します。
public App()
{
this.InitializeComponent();
this.UnhandledException += (s, e) =>
{
// ここで e.Message / e.Exception をローカルファイルに書き出すなど
};
}
起動初期の XAML パース例外を表面化
デバッグ時限定ですが、Binding のトレースを有効化すると「どのキー/型で失敗しているか」の手掛かりが増えます。
Application.Current.DebugSettings.IsBindingTracingEnabled = true;
Application.Current.DebugSettings.BindingFailed += (s, e) => {
System.Diagnostics.Debug.WriteLine(e.Message);
};
再発防止:変更管理と移行計画
- 依存のロック:CI では packages.lock.json を有効化し、ビルドごとのバージョン揺れを防ぐ。
- 最小再現プロジェクト:Blank UWP に問題の NuGet だけ入れて再現するミニマムを常備し、検証を短サイクル化。
- 移行の地ならし:将来的に WinUI 3 へ移す見込みがあるなら、描画面の依存(SkiaSharp)や JSON シリアライザの使い方を移行しやすい形に整える。
- アーキ別の自動検証:x86/x64/ARM64 それぞれのパッケージ生成と起動スモークテストをパイプラインに組み込む。
ケーススタディ:最小手順で「原因の切り分け」を完走する
- SkiaSharp を 2.88.9 に固定して Release 実行。起動できた → そのまま 2.88 系で運用。
- なお再現する → .NET Native を無効にして動くか判定。動いた → RD.XML で保持を広めに設定。
- それでも落ちる → WER ダンプを採取。Faulting Module が
SkiaSharp.dllならネイティブ資産/バージョン不整合、Windows.UI.Xaml.dllなら XAML 起因を第一候補とする。 - 別 PC/VM で新規インストールし、環境依存(SDK/ランタイム差)を切る。
よくある質問(抜粋)
| 質問 | 回答 |
|---|---|
| v3 系の SkiaSharp を UWP で使ってもいい? | UWP + .NET Native では相性問題が出やすい。現行 UWP の安定運用を重視するなら 2.88.x に固定が無難。 |
| デバッグにだけログが出て、リリースでは何も出ない… | .NET Native による最適化で「静かに死ぬ」ことがある。WER のフルダンプで客観データを取るのが近道。 |
| RD.XML は最初から絞るべき? | まずは広め(*Application* や問題のライブラリの Namespace 単位)に設定し、解析しながら段階的に縮小するのが安全。 |
| API コントラクト差によるクラッシュはどう検知? | ApiInformation.IsApiContractPresent で機能ガードを入れる。最小/ターゲット バージョンの見直しも併せて行う。 |
まとめ
- UWP の「リリースだけ即死」多発の背景には、.NET Native の AOT/トリミング がある。
- SkiaSharp v3 以降の導入後に再現したなら、まずは 2.88.9 へロールバックし、関連パッケージも 2.88 系に揃える。
- 解消しない場合は .NET Native を一時的に無効化→ WER でフルダンプ → RD.XML で保持 → API コントラクト整合の順で切り分ける。
- 再発防止には、依存のロック、最小再現プロジェクト、アーキ別の自動検証が有効。
上記の手順と観点をチームの標準運用に落とし込めば、同系統のクラッシュは確実に「可視化」され、最短手数で復旧できます。
付録:現場で使えるスクリプト集
WER LocalDumps 設定(追加・削除)
| 目的 | コマンド | 補足 |
|---|---|---|
| 追加(フルダンプ) | mkdir C:\Dumps reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /f reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\YourApp.exe" /v DumpType /t REG_DWORD /d 2 /f reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\YourApp.exe" /v DumpCount /t REG_DWORD /d 10 /f reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\YourApp.exe" /v DumpFolder /t REG_EXPAND_SZ /d C:\Dumps /f | DumpType=2 はフル |
| 削除 | reg delete "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\YourApp.exe" /f | 解析後は戻す |
csproj による .NET Native 切替(切り分け用)
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<UseDotNetNativeToolchain>false</UseDotNetNativeToolchain>
</PropertyGroup>
RD.XML のスターター
<Directives>
<Application>
<Assembly Name="*Application*" Dynamic="Required All" />
</Application>
<Library Name="SkiaSharp">
<Namespace Name="SkiaSharp" Dynamic="Required All" />
</Library>
</Directives>
実践的チェックリスト(保存版)
- SkiaSharp(本体/Views/NativeAssets)は 2.88.x に揃っているか。
- アプリは一度アンインストールし、Clean & Rebuild 済みか。
- Release + .NET Native 有効 で再現、無効で解消するか。
- WER フルダンプで Faulting Module を特定したか。
- RD.XML で必要な型/名前空間を 保持しているか。
- API コントラクトと最小/ターゲット バージョンの整合が取れているか。
- x86/x64/ARM64 すべてで 起動スモークを通したか。
結論:今回の直接原因は高確率で SkiaSharp の新バージョンと .NET Native の相性不良です。UWP を継続運用する間は v3 未満に固定し、WER ダンプ・.NET Native オフ・RD.XML といった Release 専用の切り分け手法をツールボックスとして常備しておくと、同様のトラブルを短時間で収束できます。

コメント