【入門編】【上級者向け】大規模プロジェクトのサブプロジェクト一括更新におけるメモリリーク対策 – Project VBA解析バイブル

スポンサーリンク

こんにちは!プロジェクト管理の現場で、日々数え切れないほどのタスクやリソースと格闘お疲れ様です。

「マクロの記録」ボタンを押すだけの自動化から一歩抜け出して、本格的なVBAのコードを書き始めたあなた。素晴らしいステップアップですね!
今回は、MS Project(Microsoft Project)のVBAにおける最難関にして最重要テーマの一つ「大規模プロジェクトのサブプロジェクト一括更新におけるメモリリーク対策」について、徹底的に解説していきます。

「サブプロジェクトを何十個も開いて一括でデータを更新するバッチ処理を作ったはいいものの、途中で動作が重くなり、最終的にフリーズしてしまう……」
そんな絶望を味わったことはありませんか?

大丈夫。ここをクリアすれば、あなたも立派なProject VBAのアーキテクトです。メモリの機微を理解した「ワンランク上のエンジニア」への扉を、一緒に開いていきましょう!

1. なぜ、サブプロジェクトの連続処理でメモリが爆発するのか?

まず、敵を知ることから始めましょう。
MS Projectは、Excelなどと比べて圧倒的にメモリを喰らう巨大なアプリケーションです。1つのプロジェクトファイルを開くだけでも、タスク、リソース、カレンダー、そして複雑なネットワーク構造(先行・後続関係)の計算エンジンがバックグラウンドでフル稼働します。

ここで、VBAを使って次のようなループ処理を書いたとします。

‘ 【やってはいけないアンチパターンの例】
Sub BadSubProjectUpdate()
Dim prjName As Variant
Dim targetPrj As Project

For Each prjName In Array(“SubA.mpp”, “SubB.mpp”, “SubC.mpp”)
‘ プロジェクトを開く
Set targetPrj = Application.FileOpenEx(“C:\Data\” & prjName)

‘ 何かしらの更新処理
targetPrj.ProjectSummaryInfo.Company = “New Company”

‘ 保存して閉じる
targetPrj.Save
targetPrj.Close pjDoNotSave
Next prjName
End Sub

一見、何の問題もない綺麗なコードに見えますよね?
しかし、これを数十個、数百個のサブプロジェクトで回すと、PCのメモリがジワジワと削られ、やがてVBAが「メモリ不足(Out of memory)」エラーを吐いてクラッシュします。

原因:VBAの「参照」がメモリにしがみついている

VBAの `Set targetPrj = …` という記述は、単にファイルを開くだけでなく、「オブジェクトへの参照(ポインタ)」をVBAのメモリ空間に保持させます。
ループが次の周回に進むとき、変数 `targetPrj` には「新しいプロジェクト」が上書きされますが、古いプロジェクトのオブジェクトがWindowsのメモリ(COMオブジェクトの参照カウント)上に残存してしまう現象が起きるのです。これが、Project VBAにおける「メモリリーク」の正体です。

2. メモリリークを防ぐための「3種の神器」

この厄介なメモリリークを防ぎ、何時間でも安定稼働するバッチ処理を書くためには、以下の3つの鉄則を守る必要があります。

1. 変数を確実に「解放(Nothing代入)」する
2. バックグラウンド処理(Visible := False)を徹底する
3. ガベージコレクションを促すインターバルを挟む

これらをすべて組み込んだ、実務でそのまま使える【極限まで最適化されたマスターコード】を授けましょう。

3. 【実践】完全安定版:サブプロジェクト一括更新プロシージャ

以下のコードは、数多くの修羅場を潜り抜けてきたエンジニアたちが辿り着いた、極めて堅牢なバッチ処理のテンプレートです。

Option Explicit

‘ ==============================================================================
‘ 【上級者向け】サブプロジェクト一括更新・メモリリーク完全対策マクロ
‘ ==============================================================================
Sub MasterUpdateSubProjects()
Dim subProjFiles As Variant
Dim i As Long
Dim targetPrj As Project
Dim startTime As Double

startTime = Timer

‘ 更新対象のサブプロジェクトファイル名のリスト(フルパス)
subProjFiles = Array( _
“C:\Projects\SubProject_Alpha.mpp”, _
“C:\Projects\SubProject_Beta.mpp”, _
“C:\Projects\SubProject_Gamma.mpp” _
)

‘ 【重要】画面描画と警告をオフにし、処理速度と安定性を劇的に向上させる
Application.ScreenUpdating False
Application.DisplayAlerts = False

On Error GoTo ErrorHandler

For i = LBound(subProjFiles) To UBound(subProjFiles)
Debug.Print “処理中: ” & subProjFiles(i)

‘ 1. プロジェクトをバックグラウンド(非表示)で開く
‘ ※余計なUIを描画させないことがメモリ節約の最大のコツです
Application.FileOpenEx Name:=subProjFiles(i), ReadOnly:=False, ReadOnlyRecommended:=False

‘ 開いたアクティブプロジェクトを変数に格納
Set targetPrj = ActiveProject

‘ ====================================================================
‘ 2. ここに実際の更新処理を記述する(例:カスタムフィールドの一括書き換えなど)
‘ ====================================================================
Call UpdateSingleProjectLogic(targetPrj)

‘ 3. 変更を保存して閉じる(PJDoNotSave にすると保存せず閉じます)
targetPrj.Save
targetPrj.Close pjSaveChanges

‘ ====================================================================
‘ 4. 【最重要】オブジェクト変数を強制的に完全解放する
‘ ====================================================================
Set targetPrj = Nothing

‘ 5. COMオブジェクトの解放をOSに促すためのわずかなインターバル
DoEvents
Next i

‘ 処理終了後の後始末
Application.ScreenUpdating True
Application.DisplayAlerts = True

MsgBox “すべてのサブプロジェクトの更新が正常に完了しました!処理時間: ” & Format(Timer – startTime, “0.00”) & “秒”, vbInformation
Exit Sub

ErrorHandler:
‘ エラー発生時も画面描画とメモリ解放の安全装置を必ず通す
Application.ScreenUpdating True
Application.DisplayAlerts = True

If Not targetPrj Is Nothing Then
On Error Resume Next
targetPrj.Close pjDoNotSave
Set targetPrj = Nothing
On Error GoTo 0
End If

MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “致命的なエラー”
End Sub

‘ ——————————————————————————
‘ 個別のプロジェクトに対する処理をカプセル化(モジュール分割の思想)
‘ ——————————————————————————
Private Sub UpdateSingleProjectLogic(ByRef prj As Project)
Dim t As Task

‘ 例:すべてのタスクの制約日をチェック、あるいは特定のフラグを立てる等
For Each t In prj.Tasks
If Not t Is Nothing Then
‘ ここにビジネスロジックを書く
‘ 例: t.Text1 = “Updated”
End If
Next t

‘ タスクオブジェクトのループ内変数も念のため解放
Set t = Nothing
End Sub

4. コードの深掘り:なぜこれでメモリがリークしないのか?

初心者の方に向けて、このコードのキモとなる部分を紐解いていきましょう。

① `Set targetPrj = Nothing` の魔力

ループの最後にある `Set targetPrj = Nothing`。これが今回の主役です。
VBAでは、変数がスコープから外れる(ループが一周する)までメモリが保持されることがあります。明示的に `Nothing` を代入することで、「この変数が指していたプロジェクトのメモリ領域を今すぐ手放してください」とVBAのガーベジコレクションに強く要求することができます。

② `DoEvents` によるOSへの息継ぎ

連続して重い処理を行うと、VBAはCPUとメモリを占有し続け、Windowsがメモリの掃除(ガベージコレクション)を行うタイミングを失います。
`DoEvents` を挟むことで、OSに制御を一時的に渡し、メモリの断片化やリソースの解放処理を挟み込む隙を作ることができます。これが長時間のバッチ処理を途中で落とさないための職人技です。

③ エラーハンドラーでの二重チェック

万が一、処理の途中でファイルが破損していたり、誰かがロックしていたりしてエラーが発生した場合でも、`ErrorHandler` 内で `targetPrj.Close` と `Set targetPrj = Nothing` を実行するようにしています。これにより、「エラーで止まったあともメモリにゴミが残り続ける」という最悪の事態を防ぐことができます。

5. まとめ:ここをクリアすれば、Project VBAの基本はバッチリ!

お疲れ様でした!今回は、大規模なサブプロジェクト運用におけるメモリリーク対策という、実務の現場でしか得られない「極限の知見」をお伝えしました。

  • プロジェクトを開いたら、必ず `Set targetPrj = Nothing` で解放する。
  • 重いループの中では `DoEvents` を使ってOSに息継ぎをさせる。
  • エラー時も例外処理で確実にオブジェクトを片付ける。

この3つを意識するだけで、あなたの書くVBAマクロは見違えるほどタフになり、どれほど巨大なプロジェクトファイルを相手にしてもビクともしなくなります。

ここをクリアしたあなたなら、もう「マクロの記録」に頼る必要はありません。自信を持って、自分の手で最高の自動化システムを構築してくださいね。応援しています!

タイトルとURLをコピーしました