PLAN_02_バグ修正計画.md(連続撮影クラッシュ=撮影経路の修正)とは独立した別系統のバグを扱う.PLAN_02 は「撮影時」の不具合,本計画は「プレビュー表示中(撮影していなくても)長時間起動で落ちる」不具合を対象とする.[fix] タグを使用する.Cameras/・UI/)で記載する.プレビュー供給は Cameras/IScam.cs・Cameras/Lucam.cs,表示は UI/Form1.cs・UI/PreviewMonitor.cs に分かれている.v1.6)と現行コードの該当経路を比較した結果,プレビュー表示経路は v1.6 からほぼ同一で未修正であり,現行でも同じクラッシュが起こりうる.プレビューはカメラのフレームレート(30〜60 fps 相当)で連続的にフレームを供給し続ける.IScam では Cameras/IScam.cs の Retrieve(FrameQueueSink コールバック)が毎フレーム _form.ShowImage(imgt.ToBitmap()) を呼ぶ.この経路で Bitmap が確実に解放されず,時間とともに蓄積する.
Bitmap は内部に GDI ハンドル(HBITMAP)とネイティブ画素バッファ(IScam の 1840×2440×3 で約 13 MB/枚)を持つ.一方マネージドのラッパは小さいため GC がメモリ圧を感じにくく,回収が遅れる.結果として GDI ハンドルの枯渇(既定上限はプロセスあたり 1 万) か ワーキングセット肥大による OutOfMemory/GDI+ のエラーで,時間とともにクラッシュする.確率的に「たまに/いずれ」落ちる症状と整合する.
ShowImage のフルコピー Bitmap が毎フレームリークする(決定的・最優先)UI/Form1.cs の ShowImage(Bitmap bmp) は冒頭で var bmp2 = new Bitmap(bmp); とフルコピー Bitmap を毎フレーム生成する.bmp2 は _previewMonitor.IsShown が true のとき(副プレビューモニタ表示時)だけ PreviewMonitor.UpdateImage に渡される.通常運用(副モニタ非表示)では bmp2 はどこにも使われず,かつ Dispose されないため,1 フレームごとに 1 枚まるごとリークする.該当箇所
UI/Form1.cs の ShowImage(bmp2 = new Bitmap(bmp) と,IsShown 分岐).ShowImage は _dispBuf = bmp; で表示バッファ参照を差し替え,Invalidate() で再描画を要求する.実際の Dispose は Form1_Paint の picDisplay.Image?.Dispose(); で行われる.Invalidate() による WM_PAINT は合体(コアレス)されるため,Form1_Paint が走る前に次フレームが届くと _dispBuf が複数回上書きされる.IScam 経路は二重バッファを持たないため,Paint に到達しなかった ToBitmap() 結果は Dispose を通らずに捨てられ,リークする.該当箇所
UI/Form1.cs の ShowImage(_dispBuf 差し替え)/Form1_Paint(picDisplay.Image?.Dispose()).Cameras/IScam.cs の Retrieve(毎フレーム ToBitmap() を ShowImage へ渡す.二重バッファなし).Cameras/Lucam.cs の PreviewCallback は _bmps[2] の二重バッファで ToBitmap() 結果を 2 枚に抑えて Dispose しており,この点は安全.ただし原因1(ShowImage 内の bmp2)は Lucam 経路でも同様にリークする.PreviewMonitor.UpdateImage の上書き前バッファが Dispose されないUI/PreviewMonitor.cs の UpdateImage も単一バッファを上書きする構造で,差し替え前の旧バッファを Dispose していない(副プレビューモニタ表示時のみ顕在化).該当箇所
UI/PreviewMonitor.cs の UpdateImage(表示バッファ上書き).ShowImage のフルコピー Bitmap を必要時のみ生成するbmp2 = new Bitmap(bmp) を無条件生成から,_previewMonitor != null && _previewMonitor.IsShown が true のときだけ生成に変更する.未表示時のフルコピーリークを止める(効果が最も大きく,変更も小さい).Bitmap を確実に解放する.方針候補:
Lucam と同様の二重バッファ+確実な Dispose に表示経路を統一する,または_dispBuf を差し替える前に,まだ picDisplay.Image に反映されていない旧 _dispBuf を Dispose する(Paint を通らなかったフレームも必ず破棄する).picDisplay.Image への代入は「直前の Image を Dispose してから差し替え」を保証する.PreviewMonitor.UpdateImage の上書き前 DisposeUpdateImage で表示バッファを上書きする前に,旧バッファを Dispose する.MEMMONITOR ビルド(UI/MemoryMonitorForm)で,撮影せずプレビュー表示のまま放置し,メモリ(PrivateMemory/WorkingSet)が時間とともに増え続けないことを確認する.修正前後で対比する.PLAN_02 の「効果検証」・TEST_01)は撮影フロー対象であり,本件(プレビュー放置)はカバーしていない.プレビュー放置シナリオの観測は別途行う.GetGuiResources 等)の表示を追加して,メモリだけでなくハンドル数も時間で増えないことを確認することを推奨する(任意).| 順位 | 原因 | 該当ファイル | クラッシュとの関連 |
|---|---|---|---|
| 1 | ShowImage のフルコピー Bitmap が毎フレームリーク | UI/Form1.cs | 直接原因(アイドル時の主因・決定的リーク) |
| 2 | 表示バッファの取りこぼしフレーム未 Dispose | UI/Form1.cs / Cameras/IScam.cs | 直接原因(二重バッファなしで取りこぼし) |
| 3 | PreviewMonitor.UpdateImage の上書き前未 Dispose | UI/PreviewMonitor.cs | 副モニタ表示時のリーク |