Project VBAの深淵:FSOを用いたプロジェクト一括操作の極致
プロジェクト管理において、Project VBAは強力だが、そのメモリ管理とオブジェクトのライフサイクルは、まさに「地雷原」である。数千のMPPファイルを処理する際、安易なループ処理はメモリリークを招き、最悪の場合、Projectアプリケーションのハングアップを引き起こす。
本稿では、FileSystemObject (FSO) を駆使し、再帰処理によるファイル検索、そしてメモリ消費を最小限に抑えた「堅牢な一括処理アーキテクチャ」の設計思想を解き明かす。
—
1. なぜ「FSO」なのか:Win32 APIとの距離感
VBA標準の `Dir` 関数は、再帰処理においてスタックを汚しやすく、マルチスレッドに近い複雑なタスクには向かない。また、Windows API (`FindFirstFile` / `FindNextFile`) を直接叩くアプローチは確かに高速だが、保守性を犠牲にする。
我々アーキテクトがFSOを選ぶ理由は、その「オブジェクト指向的な階層構造」と、COMインターフェースによる安定性にある。メモリリークを恐れるのであれば、重要なのは「いかに早く書くか」ではなく、「いかに確実かつ局所的にメモリを解放するか」だ。
—
2. 堅牢なファイル検索・オープンエンジンの実装
以下のコードは、再帰的探索とProjectのアプリケーションインスタンスを制御するテンプレートである。ポイントは、`DoEvents`によるメッセージポンプの解放と、`Set = Nothing`によるオブジェクトの生存期間(ライフサイクル)の厳格な管理だ。
Option Explicit
‘ 参照設定: Microsoft Scripting Runtime (scrrun.dll)
‘ 参照設定: Microsoft Project 16.0 Object Library
Public Sub BatchProcessProjectFiles(ByVal folderPath As String)
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
Dim rootFolder As Object
Set rootFolder = fso.GetFolder(folderPath)
‘ 再帰処理開始
RecursiveFileProcess rootFolder, fso
‘ 明示的解放
Set rootFolder = Nothing
Set fso = Nothing
End Sub
Private Sub RecursiveFileProcess(ByVal folder As Object, ByVal fso As Object)
Dim file As Object
Dim subFolder As Object
Dim projApp As Object
‘ サブフォルダを再帰的に探索
For Each subFolder In folder.SubFolders
RecursiveFileProcess subFolder, fso
Next
‘ MPPファイルのみを処理
For Each file In folder.Files
If LCase(fso.GetExtensionName(file.Path)) = “mpp” Then
Set projApp = GetObject(, “MSProject.Application”)
‘ インスタンスがなければ新規作成(環境に応じて分岐)
If projApp Is Nothing Then Set projApp = CreateObject(“MSProject.Application”)
ProcessSingleProject file.Path, projApp
‘ 処理後のメモリ最適化
DoEvents
End If
Next
End Sub
Private Sub ProcessSingleProject(ByVal filePath As String, ByRef app As Object)
Dim prj As Project
On Error Resume Next
‘ ReadOnlyで開き、メモリへの負荷を最小限に抑える
Set prj = app.Projects.Open(filePath, ReadOnly:=True)
If Not prj Is Nothing Then
‘ ここにビジネスロジックを記述(ベースライン設定やデータ抽出など)
Debug.Print “Processing: ” & prj.Name
‘ 処理終了後、即座にクローズ
prj.Close pjDoNotSave
Set prj = Nothing
End If
On Error GoTo 0
End Sub
—
3. シニアエンジニアが意識すべき「隠れたコスト」
メモリの断片化と再起動の戦略
Project VBAのCOMオブジェクトは、連続してファイルを開閉し続けると、ヒープ領域にゴミが蓄積される。100ファイルを超えるバッチ処理を行う場合、`ProcessSingleProject` の中だけで完結させず、一定回数(例えば50ファイル)ごとにアプリケーションを `Quit` させ、プロセスを再起動する「リフレッシュ戦略」を組み込むべきだ。
ベースライン設定の罠
ベースラインをプログラムから設定する際、`BaselineSave` メソッドを多用すると、undoスタックが肥大化する。大量の変更を行う場合は、`Application.ScreenUpdating = False` を徹底し、最後に `CalculateAll` を呼び出すことで、描画コストと再計算コストを劇的に削減できる。
レガシー環境でのAPI活用
もし、ネットワークドライブ上の不安定なファイルにアクセスする場合、`WNetGetConnection` を使用してパスをUNC形式に正規化することを推奨する。ドライブレターに依存する処理は、環境移行時に必ず破綻する。
—
結論:コードは「状態」を管理する装置である
我々が書くコードは、単なる命令列ではない。Projectという巨大なCOMサーバーの「状態」を制御する管理術である。FSOでファイルを列挙し、Projectのオブジェクトモデルを流麗に使いこなす。この時、最も重要なのは「自分が作り出したオブジェクトを、責任を持って殺すこと」だ。
メモリリークを恐れるな。オブジェクトの生存期間を視覚化し、明示的に制御する。それこそが、伝説的なシステムを維持するための唯一の道である。
貴殿のシステムにおいて、このアーキテクチャが安定した自動化の一助とならんことを願う。次回の深掘りでは、プロジェクト間依存関係の動的解決と、XML形式を用いた高速エクスポート手法について触れる予定だ。
