【テクニカル・上級編】プロ向け:計算エンジンの「再計算保留」状態を監視し、ユーザーに警告を出す仕組み – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

スケジュールの整合性を守る: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の自動化において、エンジンの挙動を「ブラックボックス」として扱うことは、プロフェッショナルとしての放棄に等しい。計算エンジンの状態を監視し、決定論的な制御下に置くこと。それこそが、大規模なプロジェクト管理システムを支える、真のエンジニアリングである。

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