Skip to content

アーキテクチャ

技術スタック

  • .NET 10 / C#
  • Windows Forms (WinForms)
  • Win32 API (P/Invoke)
  • GitHub Releases API(自動更新機能)

モジュール構成

text
src/Launcher/
├── Core/           ドメインモデル・ビジネスロジック
├── Infrastructure/ 基盤ユーティリティ
├── UI/             WinFormsフォーム群
├── Win32/          Win32 API連携
└── Updater/        自動更新機能

モジュール分割の設計意図

  • Core — UIフレームワーク(WinForms)に依存しない純粋なドメインモデルとロジック。テスト容易性と関心の分離が目的
  • Infrastructure — アプリケーション基盤。シリアライズ・パス操作・ファイル操作など、ドメインやUIに属さない横断的関心事を集約。PathHelperはパス文字列操作のみ、FileHelperはファイル・ディレクトリ操作と役割を分離している
  • UI — WinFormsに依存するフォーム群。ロジックはPresenterに委譲し、フォーム自体は表示と入力の橋渡しに徹する
  • Win32 — P/Invoke呼び出しを隔離するモジュール。Win32 APIの複雑さ(マーシャリング、リソース管理)をアプリケーション本体から遮断する
  • Updater — 自動更新機能。GitHub Releases APIとの通信、ZIPの展開、バッチスクリプトによる自己置換など、更新特有の処理を分離

主要な設計パターン

Presenterパターン

MainFormPresenter / ButtonLauncherPresenter / SchedulerPresenterがUIロジックを担当する。WinFormsのフォームクラスはイベントハンドラとコントロール操作のみを持ち、判断ロジックはPresenterに委譲する。これにより、UIロジックをWinFormsから分離してテスト可能にしている。

ConfigStore継承による永続化

Config、CommandList、ButtonLauncherData(Data)はすべてConfigStoreを継承し、XMLシリアライズで永続化される。ConfigStoreは原子的なファイル保存(一時ファイルに書き込み後File.Moveで置換)を提供し、保存中のクラッシュによるデータ破損を防止する。

制約: XMLシリアライズ対象プロパティのコレクション初期化子は変更禁止。XmlSerializerはデシリアライズ時に既存インスタンスへAddするため、初期化子で値を入れるとデシリアライズ結果と重複する。

DummyFormによるIPCハブ

DummyFormは不可視の常駐フォームで、アプリケーション全体のハブとして機能する。WM_APPMSGによるプロセス間通信(/close、/restart等のコマンドライン引数の処理)を受け付ける。加えて、子フォーム(MainForm、ButtonLauncherForm)のライフサイクルを管理する。WinFormsのメッセージループを維持するために常駐フォームが必要であり、メインウィンドウ(MainForm)は表示/非表示を繰り返すため、この役割を分離している。また、スケジューラーのタイマー(30秒間隔)を管理し、スケジュール条件に合致したタスクの自動実行も制御する。

スケジューラータスクの種類

スケジューラーはファイル実行に加え、メッセージ表示タスクをサポートする。

種類説明
ExecuteShellExecuteExでプログラムを起動する
BalloonTipタスクトレイのバルーン通知でメッセージを表示する(自動消去)
MessageBoxメッセージボックスでメッセージを表示する(OKボタンで手動消去)

Core層(SchedulerPresenter)はUI依存を持たない。 BalloonTip/MessageBoxの表示はデリゲート経由でUI層(DummyForm)に委譲する。 MessageBoxはInvoke(同期呼び出し)でダイアログが閉じるまで後続タスクをブロックする。 BalloonTipはBeginInvoke(非同期)で実行する。

設定ファイル

すべてXMLシリアライズで、アプリケーションと同じディレクトリに保存される。 基底クラスConfigStoreがシリアライズ/デシリアライズを提供。

*.cfgファイルはユーザーが設定変更したときのみ書き換わる静的な設定ファイル。*.datファイルはアプリケーション動作中に頻繁に更新されるランタイムデータ(ウィンドウハンドル、スケジューラーの最終チェック時刻など)。この分離により、大容量になりうる設定ファイルの頻繁な書き込みを避けている。

ファイル内容
らんちゃ.cfgアプリケーション設定(Config)
らんちゃ.cmd.cfgコマンド一覧(CommandList)
らんちゃ.btns.cfgボタン型ランチャーのデータ(ButtonLauncherData)
らんちゃ.sch.cfgスケジューラー設定(SchedulerData)
らんちゃ.datランタイムデータ(Data)

スレッディングモデル

スレッド用途備考
UIスレッド (STA)WinFormsメッセージループ、全UI操作Application.Run(DummyForm)
コマンド実行スレッド (STA)Command.Execute()の実行MainForm.ExecuteCommandで生成
ディレクトリ展開スレッド (STA)Command.OpenDirectory()の実行MainForm.OpenDirectoryで生成
アイコン読込スレッド (STAx8)AsyncIconLoaderによる非同期アイコン取得固定8本STAワーカー + リトライ(最大2回)
環境変数置換スレッドReplaceEnvListのコマンド名置換MainForm.ApplyConfigで生成
スケジューラー実行スレッド (STA)SchedulerPresenter.ExecuteItemTasksタイマーTick時に生成、アイテムごとに1本
フックコールバックキーボード/マウスフックのイベント通知BeginInvokeでUIスレッドへディスパッチ

STA制約

コマンド実行・ディレクトリ展開・アイコン読込はすべてSTAスレッドで行う。ShellExecuteExやSHGetFileInfo等のShell APIはCOMのSTA(Single-Threaded Apartment)を前提としている。そのため、Task.Run(ThreadPool/MTA)では正常に動作しない。専用のSTAスレッドを生成して実行する必要がある。

アイコンローダーの並行度制限

AsyncIconLoaderのワーカー数は8本固定。SHGetFileInfo(Shell API)は高並行度で不安定になるため、ProcessorCount等の動的な値は使用せず固定値とする。ButtonLauncherFormのHandle作成は、アイコン非同期読み込み(BuildTabs→iconLoader.Load)より前に行うこと。Handle未作成時にIconLoadedイベントが到着すると、BeginInvokeの失敗によりアイコンは破棄される。アイコン読み込み完了時のInvalidateはbtn.Parent?.Invalidate(true)で親パネル全体を対象にすること。btn.Invalidate()では非選択タブのボタンが再描画されない。

フック管理

HookManagerがグローバルキーボード/マウスフックの状態管理を一元的に担当する。

  • ホットキー検知: SetWindowsHookExで登録したキーボードフックのコールバックで、KEYDOWN時に仮想キーコードと修飾キーを照合。一致したらKEYUPを抑制しつつ、BeginInvokeでUIスレッドへShowHideメッセージを送信
  • ボタンランチャー起動: マウスフックでボタン押下状態(lbuttonDown/rbuttonDown)を追跡し、設定されたトリガー(左右同時押し等)を検知。トリガー発動時はUPイベントを抑制して誤操作を防止
  • UP抑制フラグ: suppressNextLButtonUp/suppressNextRButtonUp/suppressKeyUpVKで、トリガー発動後の不要なUPイベントをフック内で消費する。フックコールバック内でUPを消費しないと、トリガー操作の直後にボタンクリックやキー入力として誤検知される

設計上の制約・選択

  • cfg/dat分離の徹底: *.cfgは設定変更時のみ、*.datは頻繁に書き換わるデータ。混在させない
  • STAスレッド必須: Shell API呼び出しはすべてSTAスレッドから。ThreadPool/MTAからの呼び出しは禁止
  • アイコンローダー8本固定: SHGetFileInfoの安定性のため動的調整しない