スケジュールの整合性を守る:MS Project 計算エンジンの「沈黙の不整合」を制御する極限の設計論
プロジェクト管理における最大の敵は、スケジュールの遅延ではない。「計算が完了していない状態で、誤ったスケジュールが保存されること」だ。
MS Projectは、ASAP(最短)やALAP(最長)といった制約条件、リソース平準化、依存関係の連鎖に基づき、極めて複雑な制約充足問題を解いている。しかし、大規模なプロジェクトファイルにおいて、エンジンの再計算は決して瞬時ではない。エンジンの計算が「保留(Pending)」状態、あるいは手動計算モード(Manual Calculation)のまま、ユーザーや外部の自動化プロセスが「保存」を強行したとき、プロジェクトの整合性は崩壊する。
本稿では、シニアエンジニアが直面する「計算待ち状態での不整合保存」という致命的なリスクを回避するため、Applicationレベルでの監視実装と、メモリ管理、そしてWindows APIを駆使した堅牢なガードメカニズムについて解説する。
1. 致命的な陥穽:CalculationMode の罠
多くの開発者は、`Application.CalculationMode` が `xlCalculationManual` であることを確認するだけで満足する。しかし、それは表面的なチェックに過ぎない。
真に警戒すべきは、「計算エンジンが論理的な収束(Convergence)に達していない状態」だ。複雑な制約条件が絡み合う大規模ファイルでは、プロパティを変更した直後、エンジンはバックグラウンドで再計算の準備、あるいは計算プロセスを開始する。この「計算の空白時間」にデータが書き込まれると、計算結果が反映されていない「幽霊のようなスケジュール」がファイルに定着してしまう。
これを防ぐには、単なるプロパティの監視ではなく、アプリケーションの動作状態を決定論的に制御する「ガード(Guard)」の実装が必要となる。
2. 実装アーキテクチャ:Integrity Guard Pattern
我々が構築すべきは、保存(Save)操作をインターセプトし、エンジンの計算状態を検証するラッパー層である。
Windows API によるプロセス状態の捕捉
VBA単体では、MS Projectの内部スレッドの状態を完全に把握することは困難である。そこで、Windows APIを用いて、アプリケーションが「ビジー状態」にないか、あるいはメッセージループが適切に処理されているかを補助的に確認する手法を組み合わせる。
クラスモジュールによるカプセル化
オブジェクトのライフサイクルを厳密に管理するため、監視ロジックは標準モジュールではなく、クラスモジュールに封じ込める。これにより、メモリリークを防ぎ、COM参照のカウントを正確に制御する。
3. 極限の実装例
以下に、計算エンジンの整合性を監視し、不適切な保存を阻止するためのクラスモジュールの設計を示す。
Class Module: `clsProjectGuard`
Option Explicit
' Windows APIの宣言:プロセスの状態やウィンドウの整合性を確認するために使用
Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr
Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hwnd As LongPtr) As Long
Private m_app As MSProject.Application
' プロジェクトエンジンの状態を定義する列挙型
Public Enum EngineStatus
STATUS_OK = 0
STATUS_CALCULATION_PENDING = 1
STATUS_MANUAL_MODE = 2
STATUS_ENGINE_BUSY = 3
End Enum
' コンストラクタ
Private Sub Class_Initialize()
Set m_app = MSProject.Application
End Sub
' デストラクタ:オブジェクトの明示的な解放
Private Sub Class_Terminate()
' COM参照の循環を防ぐため、明示的にNothingを代入
Set m_app = Nothing
End Sub
''' <summary>
''' 計算エンジンの整合性を検証する
''' </summary>
''' <returns>EngineStatus</returns>
Public Function ValidateEngineIntegrity() As EngineStatus
On Error GoTo ErrorHandler
' 1. 計算モードのチェック
' xlCalculationAutomatic (0) でない場合、整合性が保証されないリスクがある
If m_app.CalculationMode <> 0 Then ' 0 = xlCalculationAutomatic
ValidateEngineIntegrity = STATUS_MANUAL_MODE
Exit Function
End If
' 2. 計算の実行状態を擬似的に検証
' MS Project VBAには直接的な "IsCalculating" プロパティが存在しないため、
' 計算をトリガーし、その際の応答性を確認するアプローチをとる
' ※大規模ファイルでは、この操作自体に重みがあるため注意が必要
On Error Resume Next
m_app.Calculate
If Err.Number <> 0 Then
ValidateEngineIntegrity = STATUS_ENGINE_BUSY
Err.Clear
Exit Function
End If
On Error GoTo ErrorHandler
' 3. ウィンドウ状態の確認(API利用)
' ユーザーがダイアログを開いている、あるいは別の操作を行っている場合は避ける
If IsWindowVisible(GetForegroundWindow()) = 0 Then
ValidateEngineIntegrity = STATUS_ENGINE_BUSY
Else
ValidateEngineIntegrity = STATUS_OK
End If
Exit Function
ErrorHandler:
ValidateEngineIntegrity = STATUS_ENGINE_BUSY
End Function
''' <summary>
''' 安全な保存プロセスを実行する
''' </summary>
Public Sub SafeSave()
Dim status As EngineStatus
status = ValidateEngineIntegrity()
Select Case status
Case STATUS_OK
m_app.Save
Debug.Print "Integrity Verified: Save Successful."
Case STATUS_MANUAL_MODE
MsgBox "警告: 計算モードが手動になっています。整合性を確保するため自動計算に切り替えます。", vbExclamation
m_app.CalculationMode = 0 ' xlCalculationAutomatic
m_app.Calculate
m_app.Save
Case STATUS_CALCULATION_PENDING, STATUS_ENGINE_BUSY
MsgBox "エラー: 計算エンジンがビジー状態です。計算が完了するまで保存を中断しました。", vbCritical
End Select
End Sub
Standard Module: `modMain`
Option Explicit
''' <summary>
''' 業務アプリケーションのエントリポイント
''' </summary>
Public Sub ExecuteSecureWorkflow()
Dim guard As clsProjectGuard
' オブジェクトの生成
Set guard = New clsProjectGuard
On Error GoTo CleanUp
' --- 業務ロジックの開始 ---
Debug.Print "Executing complex scheduling logic..."
' ここで大量の制約条件変更やリソース平準化を行う
' 例: ActiveProject.Tasks(1).ConstraintType = pjAsap
' --- 業務ロジックの終了 ---
' 保存の実行(ガードを通す)
guard.SafeSave()
CleanUp:
' メモリ管理の鉄則:明示的な解放
' これを怠ると、Excelや他のCOMクライアントからProjectプロセスが解放されない
If Not guard Is Nothing Then
Set guard = Nothing
End If
If Err.Number <> 0 Then
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End If
End Sub
4. アーキテクトの視点:パフォーマンスとメモリの最適化
この実装において、シニアエンジニアが留意すべきは以下の3点だ。
I. COMオブジェクトのライフサイクル管理
VBAにおける `Set obj = Nothing` は、単なる作法ではない。特にMS Projectを外部(VB.NETやC#)からCOM経由で操作する場合、参照カウントが適切にデクリメントされないと、`msproject.exe` がゾンビプロセスとしてメモリに残り続ける。これはサーバーサイドでの自動化において、リソース枯渇の主因となる。必ず `Class_Terminate` を活用し、依存関係の末端から順に解放せよ。
II. 計算負荷のトレードオフ
`m_app.Calculate` を検証プロセスに組み込むことは、確実性を高める一方で、計算コストを支払うことを意味する。数万タスクを持つプロジェクトでは、この一瞬の待ち時間がクリティカルなパフォーマンス低下を招く。
実務的な解決策としては、「変更フラグ(Dirty Flag)」を自前で管理することだ。オブジェクトのプロパティを変更した時のみフラグを立て、保存直前にそのフラグが立っている場合のみ、厳格な `ValidateEngineIntegrity` を走らせる設計にせよ。
III. レガシー環境への配慮
`PtrSafe` を用いたAPI宣言は、64bit環境への移行を前提としている。レガシーな32bit環境が混在するエンタープライズ環境では、`LongPtr` の適切な使い分けが、システム全体の安定性を左右する。
結論
MS Projectの自動化において、エンジンの挙動を「ブラックボックス」として扱うことは、プロフェッショナルとしての放棄に等しい。計算エンジンの状態を監視し、決定論的な制御下に置くこと。それこそが、大規模なプロジェクト管理システムを支える、真のエンジニアリングである。
