はじめに
Windows App SDK(WinUI 3)を用いて高パフォーマンスなデスクトップアプリを開発し、Microsoft Storeへの申請に向けてMSIXパッケージ化を施す際、一つの大きな壁に直面することがあります。それは 「外部バイナリ(ffmpeg.exe等)の実行制限」 です。
動画処理などで重宝する FFmpeg などの外部実行ファイルをパッケージ(MSIX)の内部から直接起動しようとすると、OSのセキュリティ保護(サンドボックス)が働き、「E_ACCESSDENIED (アクセス拒否: 0x80070005)」 でプロセス生成が阻害されてしまいます。
この記事では、このサンドボックスによる実行制限を安全に回避しつつ、開発環境(非パッケージ・デバッグ起動)と製品環境(Store配布パッケージ)の双方で共通して動作するバイナリ配置・実行戦略について解説します。
直面する課題と解決戦略
1. MSIXサンドボックス内からの直接起動の拒否
MSIXのインストール先(C:\Program Files\WindowsApps 配下など)は高度に保護されているため、そこに内包した .exe を直接プロセス(Process.Start)として起動することは、権限エラーによって拒否されます。
2. ApplicationData の非パッケージ時動作エラー
この制限を回避するため、パッケージ実行時に割り当てられる安全な書き込み・実行可能領域である Windows.Storage.ApplicationData.Current.LocalFolder(ローカルアプリフォルダ)に一度バイナリを複製してそこから起動するという手法が有効です。
しかし、開発中に Visual Studio から「非パッケージデスクトップアプリ」として直接F5デバッグ実行しようとすると、今度は 「パッケージコンテキストが存在しない」 ために ApplicationData.Current へのアクセス自体がシステム例外をスローしてしまいます。
解決へのアプローチ
このジレンマを解決するため、以下のフローを実装します。
- パッケージ状態の動的検知: アプリがMSIXとして実行されているか、デバッグ中(非パッケージ)かを起動時に判定する
-
安全なローカルパスの自動解決: パッケージ時は
ApplicationDataのパス、非パッケージ時は標準のAppData\Local\YourAppのパスへフォールバックする安全なプロパティ(SafeLocalFolderPath)を仲介させる -
起動時の高速バイナリ配置: 起動時に Assets フォルダへえ同梱した
ffmpeg.exe等のサイズ変化を検知し、安全なローカルパスにコピー(キャッシュ)する -
ライブラリのバインド: コピー先の物理フォルダを、C#用のFFmpegラッパー(
FFMpegCore等)にバインドしてプロセスを起動させる
具体的な実装コード (C#)
以下は、App.xaml.cs や共通ヘルパークラスに実装する具体的な実装パターンです。
1. パッケージ判定と安全なパス解決ラッパー
まずは、実行時エラーを引き起こさずに適切なフォルダパスを解決するためのプロパティを定義します。
using System;
using System.IO;
public static class AppUtility
{
/// <summary>
/// アプリがMSIXパッケージとして実行されているかを動的に判定します
/// </summary>
public static bool IsPackaged()
{
try
{
return Windows.ApplicationModel.Package.Current.Id != null;
}
catch
{
// パッケージコンテキストが存在しない(非パッケージ実行)場合は例外がスローされるため、falseを返す
return false;
}
}
/// <summary>
/// パッケージ環境・非パッケージ環境を問わず、安全にアクセス・実行可能なAppDataのフォルダパスを返します
/// </summary>
public static string SafeLocalFolderPath
{
get
{
try
{
if (IsPackaged())
{
// MSIXパッケージ時は、サンドボックス化された安全なローカルストレージパスを使用
return Windows.Storage.ApplicationData.Current.LocalFolder.Path;
}
}
catch { }
// 非パッケージデバッグ起動時は、通常のユーザーAppDataローカル領域を使用
string fallbackPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"YourAppName"
);
if (!Directory.Exists(fallbackPath))
{
Directory.CreateDirectory(fallbackPath);
}
return fallbackPath;
}
}
}
2. 起動時のバイナリ配置とバインド処理
次に、アプリ起動時に実行ファイルを安全なディレクトリへ複製し、FFmpeg制御ライブラリへパスを通します。ここでは、サイズが異なっている場合(アプリ本体のアップデート時など)のみ上書きコピーを行うことで、起動のオーバーヘッドを最小限に抑えています。
using System.Diagnostics;
using FFMpegCore; // FFMpegCore (NuGet) を使用したバインド例
public partial class App : Microsoft.UI.Xaml.Application
{
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
// 起動時にFFmpegバイナリの配置とバインドを行う
EnsureFFmpegInstalled();
// ウィンドウの初期化とアクティベートなど
}
private void EnsureFFmpegInstalled()
{
try
{
string localFolder = AppUtility.SafeLocalFolderPath;
string ffmpegDir = Path.Combine(localFolder, "Bin");
if (!Directory.Exists(ffmpegDir))
{
Directory.CreateDirectory(ffmpegDir);
}
// コピー対象となるバイナリ(ffmpeg.exe / ffprobe.exe)のパスを設定
string destFfmpeg = Path.Combine(ffmpegDir, "ffmpeg.exe");
string sourceFfmpeg = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "ffmpeg.exe");
if (File.Exists(sourceFfmpeg))
{
// 初回、またはファイルサイズが更新されている場合のみ高速コピーを実行
if (!File.Exists(destFfmpeg) || new FileInfo(sourceFfmpeg).Length != new FileInfo(destFfmpeg).Length)
{
File.Copy(sourceFfmpeg, destFfmpeg, true);
}
}
string destFfprobe = Path.Combine(ffmpegDir, "ffprobe.exe");
string sourceFfprobe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "ffprobe.exe");
if (File.Exists(sourceFfprobe))
{
if (!File.Exists(destFfprobe) || new FileInfo(sourceFfprobe).Length != new FileInfo(destFfprobe).Length)
{
File.Copy(sourceFfprobe, destFfprobe, true);
}
}
// FFMpegCoreに対して、サンドボックス制限外へ退避させたバイナリのパスを指定
GlobalFFOptions.Configure(new FFOptions
{
BinaryFolder = ffmpegDir
});
}
catch (Exception ex)
{
Debug.WriteLine($"FFmpegの初期化に失敗しました: {ex.Message}");
}
}
}
メリット
- デバッグ効率の維持: パッケージプロジェクトを毎回ビルド・配置することなく、軽量な非パッケージデスクトップアプリとして直接F5実行・デバッグを素早く行えます
-
ストア要件の遵守:
%LocalAppData%のパッケージ固有フォルダはMSIXサンドボックス内のプロセスから明示的に実行権限が許可されているため、Microsoft Storeのセキュリティ要件を完全に遵守した状態で安全にプロセスを起動できます
開発アプリ「ScripTim」のご紹介
この動的なバイナリ退避およびローカル実行パス解決の設計パターンを取り入れ、現在 Micorsoft Store で公開中のWindows用ソフトウェアが、AI自動文字起こし&動画編集アプリ 『ScripTim(スクリプティム)』 です。
ScripTim:文字起こし編集・動画生成アプリ
https://apps.microsoft.com/detail/9NX813FLMFNG
ScripTim とは?
『ScripTim』は、高精度な音声認識AI「Whisper」を100%ローカルに統合した、テキストベースの動画・音声編集デスクトップアプリです。
- 安全なローカルAI処理: 音声データの抽出やAIによる文字起こし処理はすべて自身のPC上で行われ、機密データが外部へ送信されるリスクはありません
- シンクロカット(チェックボックス連動カット): 字幕テキストに紐づいたチェックボックスをON/OFFするだけで、不要な沈黙や言い淀みのある区間を動画本編から自動的にトリミング除去して書き出すことができます
- 高機能な主字幕・副字幕デザイン: 2系統の字幕トラックにそれぞれ異なるフォント、境界線、ドロップシャドウ、進行に合わせたカラオケ風ワイプ効果を付与してリアルタイムにプレビュー・書き出しが行えます
Windows App SDK(WinUI 3)のモダンなMicaインターフェイスと、Win2Dグラフィックス、そして安全なFFmpegインフラに支えられたデスクトップアプリ開発の事例として、少しでも参考になれば幸いです。