Project VBAを掌握する極限の知見:計算エンジンの挙動を制御する隠し設定と`Application.Options`の全貌
Microsoft ProjectのVBA開発において、多くのエンジニアが直面する最大の壁は「コードの実行速度」ではない。「意図した通りに動かないスケジュール計算エンジン」そのものである。
特に、ASAP(可能な限り早く)やALAP(可能な限り遅く)、あるいは複雑なリソース制約やカレンダーの例外が絡み合う大規模プロジェクトにおいて、VBAからタスクやアサインメントを操作した瞬間に走る「暗黙の再計算(Re-calculation)」は、システム全体のパフォーマンスを致命的に破壊する。
本稿では、一般のドキュメントやリファレンスには決して載ることのない、`Application.Options`プロパティ群を用いたスケジュール計算エンジンの深部制御と、メモリ最適化、そしてレガシー環境における極限のチューニング手法を、チーフアーキテクトの視点から解き明かす。
—
1. スケジュール計算エンジンのライフサイクルと「暗黙のコスト」
MS Projectのバックエンドには、非常に堅牢ではあるが極めて重いCPM(クリティカルパス法)ベースの計算エンジンが常駐している。
VBAから `Task.Start` や `Task.Duration` を書き換えるたびに、エンジンはプロジェクト全体のネットワークダイアグラムを走査し、先行・後続タスクの制約を評価し直す。
数千行規模のWBSを操作する場合、この「1変更ごとの自動再計算」が発生すると、処理時間が数分から数十分へと膨れ上がる。
これを断ち切る唯一の手段が、計算エンジンの明示的な停止と、バッチ処理化である。
—
2. `Application.Options` による計算エンジンの完全掌握
プロジェクトの振る舞いをグローバル、あるいはセッション単位で制御するには、`Application` オブジェクトのオプション群をコードから直接叩く必要がある。
以下のコードは、高負荷なバッチ処理を行う直前に計算エンジンを沈黙させ、メモリ消費とCPUサイクルを極限まで最適化するためのアーキテクチャである。
Option Explicit
‘ ==============================================================================
‘ モジュール名: MdlEngineController
‘ 概要: MS Projectの計算エンジンを制御し、大量データ処理の高速化を実現する
‘ ==============================================================================
Private m_OriginalCalculationMode As Boolean
Public Sub InitializeEngineForBatch()
On Error GoTo ErrorHandler
‘ 1. 自動計算の無効化(これが最大のパフォーマンスブースター)
‘ ※注意: この状態ではタスク間の依存関係や日付は再計算されません。
m_OriginalCalculationMode = Application.Calculation
Application.Calculation = False
‘ 2. バックグラウンド再計算の停止
‘ アイドル時に勝手にエンジンが走り出すのを防ぐ
AppSetting_Set “BackgroundRecalculation”, False
‘ 3. 競合解決ダイアログや警告の抑制(UIスレッドのブロック回避)
Application.DisplayAlerts = False
Exit Sub
ErrorHandler:
MsgBox “エンジン初期化エラー: ” & Err.Description, vbCritical
End Sub
Public Sub RestoreEngineState()
On Error GoTo ErrorHandler
‘ 状態の復元と、溜まりに溜まった変更の一括再計算(Dirty Flagのフラッシュ)
Application.Calculation = m_OriginalCalculationMode
AppSetting_Set “BackgroundRecalculation”, True
Application.DisplayAlerts = True
‘ 強制的にプロジェクト全体のスケジュールを再評価
If Not m_OriginalCalculationMode Then
Application.CalculateAll
End Sub
Exit Sub
ErrorHandler:
MsgBox “エンジン復元エラー: ” & Err.Description, vbCritical
End Sub
‘ レジストリや内部オプションへ安全にアクセスするヘルパー
Private Sub AppSetting_Set(ByVal settingName As String, ByVal val As Variant)
On Error Resume Next
‘ ProjectのOptionsプロパティ動的設定
CallByName Application.Options, settingName, VbSet, val
On Error GoTo 0
End Sub
—
3. 制約条件(ASAP/ALAP等)の動的制御とメモリ管理
ASAP(As Soon As Possible)やALAP(As Late As Possible)などの制約条件(ConstraintType)をVBAから操作する際、COMオブジェクトの参照リークに起因するメモリ肥大化がよく問題になる。
VBAのガーベージコレクションは非常に緩慢であり、特に `Project.Tasks` コレクションの走査においては、明示的にオブジェクト変数を解放(`Set obj = Nothing`)しなければ、COMラッパーがメモリ上に残留し続け、最終的にExcel/Projectプロセスがクラッシュする。
効率的なタスク走査と制約設定のコードパターン
Public Sub ApplyConstraintsBatch(ByVal targetConstraint As PjConstraint, ByVal targetDate As Date)
Dim prj As Project
Set prj = ActiveProject
Dim tsk As Task
Dim tskCollection As Tasks
Set tskCollection = prj.Tasks
Dim i As Long, lngCount As Long
lngCount = tskCollection.Count
‘ 計算エンジン停止状態でバッチ処理
InitializeEngineForBatch
‘ For Each ではなくインデックスアクセスを使用(COMのオーバーヘッド削減)
For i = 1 To lngCount
‘ Nothing判定(途中で削除されたインデックスのスキップ)
On Error Resume Next
Set tsk = tskCollection(i)
On Error GoTo 0
If Not tsk Is Nothing Then
‘ サマリタスク(プロジェクトサマリ含む)の除外
‘ サマリタスクに直接制約をかけると計算エンジンが例外を吐くか、整合性が壊れる
If Not tsk.Summary And tsk.ExternalTask = False Then
‘ 制約タイプの変更
tsk.ConstraintType = targetConstraint
‘ ASAP/ALAP以外で日付が必要な制約の場合のみ日付を設定
Select Case targetConstraint
Case pjConstraintMustStartOn, pjConstraintMustFinishOn, _
pjConstraintStartNoEarlierThan, pjConstraintStartNoLaterThan, _
pjConstraintFinishNoEarlierThan, pjConstraintFinishNoLaterThan
tsk.ConstraintDate = targetDate
Case Else
‘ ASAP, ALAP等の場合はConstraintDateの変更は不要(無視されるかエラーの元)
End Select
End If
End If
‘ ループごとのメモリ解放は速度低下を招くため、一定数ごとにオブジェクトを破棄
Set tsk = Nothing
‘ 1000件ごとにDoEventsを挟み、OSへのコンテキストスイッチを許容(システム安定化)
If i Mod 1000 = 0 Then DoEvents
Next i
‘ 終了処理と一括再計算
RestoreEngineState
‘ 最終的なルートオブジェクトの解放
Set tskCollection = Nothing
Set prj = Nothing
End Sub
—
4. レガシー環境とシステム間連携におけるリスクヘッジ
エンタープライズ環境では、MS Projectは孤立して存在しない。ERP、SAP、あるいは独自基幹システムとの間でXMLやMDB(Access)、あるいはDatabase(SQL Server)を介したデータ同期が常に行われている。
ここで問題になるのが、「システム連携時のタイムゾーンとカレンダーの乖離」である。
Windows APIを活用した高精度処理とパフォーマンス計測
外部システムから取得した非構造化データをインポートする際、処理のボトルネックを特定するためには、VBAの `Timer` 関数(精度が粗い)ではなく、Windows APIの `QueryPerformanceCounter` を用いたミリ秒単位の計測が不可欠となる。
‘ 32bit/64bit両対応の高精度タイマー宣言
If VBA7 Then
Private Declare PtrSafe Function QueryPerformanceFrequency Lib “kernel32” (ByRef lpFrequency As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceCounter Lib “kernel32” (ByRef lpPerformanceCount As Currency) As Long
Else
Private Declare Function QueryPerformanceFrequency Lib “kernel32” (ByRef lpFrequency As Currency) As Long
Private Declare Function QueryPerformanceCounter Lib “kernel32” (ByRef lpPerformanceCount As Currency) As Long
End If
Public Sub MeasureEnginePerformance()
Dim freq As Currency, startCount As Currency, endCount As Currency
Dim elapsedTime As Double
QueryPerformanceFrequency freq
QueryPerformanceCounter startCount
‘ — 負荷テスト対象の処理 —
ApplyConstraintsBatch pjConstraintAsSoonAsPossible, 0
‘ —————————-
QueryPerformanceCounter endCount
elapsedTime = CDbl(endCount – startCount) / CDbl(freq)
Debug.Print “スケジュール計算エンジンのバッチ処理時間: ” & Format(elapsedTime, “0.0000”) & ” 秒”
End Sub
レガシー保守上の鉄則:エージェントの暴走を防ぐ
古いアドインやマクロが組み込まれた環境では、`Application.Calculation = True` に戻した瞬間に、古いイベントプロシージャ(`Project_Calculate` など)が連鎖的に発火し、無限ループやメモリリークを引き起こすケースがある。
これを防ぐためには、グローバルなフラグ変数(セマフォ)を用意し、イベントハンドラー内での多重実行をハードコードレベルでブロックしなければならない。
‘ ThisProjectモジュール等のイベント内
Private m_IsProcessing As Boolean
Private Sub Project_Calculate(ByVal pj As Project)
If m_IsProcessing Then Exit Sub
m_IsProcessing = True
‘ 独自の整合性チェックやログ出力等をここに記述
m_IsProcessing = False
End Sub
—
5. チーフアーキテクトからの提言
MS ProjectのVBA開発において、コードの美しさは「どれだけ簡潔に書くか」ではなく、「背後で稼働する巨大なスケジュールエンジンの機嫌を損ねないか」で決まる。
`Application.Options` を手懐け、計算エンジンのライフサイクルを完全に制御下に置いた者だけが、数万行規模の複雑なプロジェクトスケジュールを瞬時に、かつエラーなくドライビングする特権を得る。
妥協のない設計と、メモリの隅々まで目を光らせる厳格なコーディングによって、あなたのシステムを真のエンタープライズグレードへと昇華させてほしい。
