墓場まで持っていく「Project VBA」のメモリ制御術:複数同時処理の極意
多くの開発者が、Project VBAにおける「プロジェクトの連続処理」で躓く。`Application.Projects.Open`を繰り返し、気がつけばメモリ使用量は肥大化し、最後は`Automation Error`や`不可解なクラッシュ`で幕を閉じる。
なぜか? 多くの者は`Project`オブジェクトを「単なる変数」としてしか見ていないからだ。VBAのガベージコレクションは、COMオブジェクトの参照カウントが0になるまで機能しない。そして、Projectの内部エンジンは非常に重い。
今日は、中級者の壁を越え、システムの生存率を極限まで高めるための「真のメモリ管理」について語る。
—
1. 参照の「断ち切り」は宣言とセットで行え
VBAにおいて`Set prj = Nothing`を呼び出すだけでは不十分なケースが多い。特にプロジェクト間の依存関係が複雑な場合、Project内部のキャッシュが解放されないことが往々にしてある。
以下のコードは、私が現場で必ず実装する「オブジェクトの完全なる破棄」のテンプレートだ。
Public Sub ProcessProjectSafely(ByVal filePath As String)
Dim prj As MSProject.Project
‘ 1. プロジェクトを開く(Read-Onlyで開くのが基本)
Set prj = Application.Projects.Open(filePath, ReadOnly:=True)
On Error GoTo Cleanup
‘ — ここにロジックを記述 —
Debug.Print “Processing: ” & prj.Name
‘ —————————
Cleanup:
‘ 2. エラー発生時でも必ずクローズし、参照を解放する
If Not prj Is Nothing Then
‘ 保存なしで閉じる(読み取り専用ならこれで十分)
prj.Close pjDoNotSave
‘ 参照カウントを強制的に叩き落とす
Set prj = Nothing
End If
‘ 3. GCを待たずにメモリを解放したい場合のTips
‘ DoEventsを挟むことで、バックグラウンドのCOM処理を完了させる
DoEvents
End Sub
2. 隠れた「ゾンビ・プロセス」を許すな
`Application.Projects.Open`を繰り返すと、見えないバックグラウンドでCOMコンポーネントが残存する。これを防ぐには、`MSProject.Application`を外部から制御する「アウトプロセス」での設計を推奨する。
社内システム管理者が大規模なバッチ処理を組む場合、ExcelからProjectを制御する構成が多いだろう。その際、必ず以下のAPIでプロセスを明示的に終了させる必要がある。
‘ プロセス終了のためのWindows API定義
Private Declare PtrSafe Function TerminateProcess Lib “kernel32” (ByVal hProcess As LongPtr, ByVal uExitCode As Long) As Long
‘ 現場で多用する終了シーケンス
Public Sub ForceKillProject(ByRef projApp As MSProject.Application)
If Not projApp Is Nothing Then
‘ まずは正攻法で閉じる
projApp.Quit
Set projApp = Nothing
‘ 極限環境ではここでDoEventsをループさせ、メモリの解放を待機する
‘ あるいは、タスクマネージャーから「WinProj.exe」を強制終了する
‘ APIを呼ぶのは最終手段だが、レガシー環境でのメモリリーク対策には最強の武器となる
End If
End Sub
3. なぜ「ベースライン」でメモリが爆発するのか
ベースラインの保存や設定は、Projectの内部テーブル(`TaskBaseline`等)を激しく書き換える。これをループ処理で行う際、メモリが枯渇する最大の理由は「再計算」だ。
解決策: 処理の開始前に`CalculationManual`へ切り替えること。これだけでメモリ使用量と処理速度は劇的に改善される。
‘ 大規模処理の定石:再計算を止めてから実行する
Application.Calculation = pjManual
‘ — 複雑なベースライン更新処理 —
‘ …
‘ 最後に再計算を戻す
Application.Calculation = pjAutomatic
Application.CalculateAll
4. チーフアーキテクトからの忠告
君たちが書いているコードは、単に「動く」だけではいけない。何百ものプロジェクトファイルを処理し、その後にProjectアプリを落とした際、タスクマネージャー上のメモリ使用量が綺麗に初期値まで戻るか? それを検証できて初めて「エンジニア」だ。
- 循環参照を避ける: `Project`オブジェクトをグローバル変数に保持するな。
- イベントの無効化: `Application.EnableEvents = False`を忘れずに。不要なイベント発火がメモリを食いつぶす。
- インデックスでアクセスしない: ループ処理では`For Each`を使い、メモリ上のオブジェクトアドレスを直接操作する感覚を持て。
プロジェクトファイルの管理は、結局のところ「リソースの管理」そのものだ。メモリという有限のリソースを制する者が、Project VBAという広大な戦場を支配する。
次は、プロジェクト間のデータ同期における「トランザクションの完全性」について語ろうか。準備ができたら、またここへ来るといい。
