Project VBAを掌握せよ:プロジェクト保存における「原子性」の強制とトランザクション管理の極意
VBAの世界において「保存」という行為は、極めて脆弱な境界線である。`Project.Save`を呼べばそれで安泰だと考えているなら、貴殿はまだ「泥沼のデータ破損」を経験していない未熟なエンジニアだ。
特にMS Projectのオブジェクトモデルにおいて、保存処理は非同期的なリスクを多分に孕んでいる。ネットワーク断絶、ディスクフル、あるいは共有違反。これらが発生した際、Projectは中途半端な状態でファイルポインタを閉じ、プロジェクトを「ゴミ」へと変える。
本稿では、COMアドイン(VB.NET)を介し、VBA単体では到達不可能なレベルの「原子性(Atomicity)」を担保するトランザクション制御の極限を説く。
—
1. なぜVBA単体での「保存」は無力なのか
VBAの`Save`メソッドは、OSのファイルI/O層に対する抽象化が高すぎる。I/Oエラーが発生した瞬間、VBAの実行スタックは例外を投げ、プロジェクトの状態は「保存中」という不安定なフェーズで凍結される。
我々が求めるのは、「保存成功の確約があるまで、元のファイルを汚染させない」というデータベースに近いトランザクション管理である。これを実現するには、VBAのコンテキストを離れ、Windows APIとCOMのライフサイクル管理にまで踏み込む必要がある。
—
2. 実装戦略:シャドウコピーによる二段構えの防衛
直接本番ファイルに書き込むのではなく、以下のプロセスを強制する。
1. セッション開始: トランザクション・フラグを立てる。
2. ステージング: プロジェクトを一時フォルダへ保存。
3. 検証: ファイルアクセス権と整合性をWindows API (`CreateFile`等) で確認。
4. アトミック置換: `ReplaceFile` APIを用いて、一時ファイルを本番へ昇格させる。
実装コード(VB.NET COMアドイン側)
このロジックはVBAから呼び出されるCOMアドインの核心部である。
.net
Imports System.IO
Imports System.Runtime.InteropServices
Public Class ProjectTransactionManager
‘ Windows API: ファイルのアトミック置換を実行
Private Shared Function ReplaceFile(lpReplacedFileName As String, lpReplacementFileName As String, _
lpBackupFileName As String, dwReplaceFlags As Integer, _
lpExclude As IntPtr, lpReserved As IntPtr) As Boolean
End Function
Public Sub SafeSave(project As Object, targetPath As String)
Dim tempPath As String = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() & “.mpp”)
Try
‘ 1. 一時領域へ保存
project.SaveAs(tempPath)
‘ 2. 本番ファイルとのアトミック置換
‘ REPLACEFILE_IGNORE_MERGE_ERRORS を指定して整合性を強制
If Not ReplaceFile(targetPath, tempPath, Nothing, 1, IntPtr.Zero, IntPtr.Zero) Then
Throw New Exception(“ファイル置換に失敗。エラーコード: ” & Marshal.GetLastWin32Error())
End If
Catch ex As Exception
‘ 失敗時は一時ファイルを確実に破棄し、ロールバック状態を維持
If File.Exists(tempPath) Then File.Delete(tempPath)
Throw New ApplicationException(“トランザクション異常終了。状態を維持しました。”, ex)
End Try
End Sub
End Class
—
3. シニアエンジニアが意識すべきメモリ最適化
COMアドイン経由でProjectオブジェクトを操作する場合、「参照カウント(Reference Count)」の管理を怠るな。
VBAから`Application`オブジェクトを渡す際、内部的にマーシャリングが発生する。処理終了後に明示的に`Marshal.ReleaseComObject`を叩かないエンジニアは、メモリリークという時限爆弾を抱えることになる。
.net
‘ 重要: COMオブジェクトの解放
If project IsNot Nothing Then
System.Runtime.InteropServices.Marshal.ReleaseComObject(project)
project = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
End If
—
4. レガシー環境における「防波堤」としての運用
既存のVBAコードを全面的に書き換える必要はない。以下のように、既存の保存ボタンのイベントをフックし、トランザクション・ラッパーを噛ませるだけで良い。
‘ VBA側での呼び出し例
Sub ExecuteRobustSave()
On Error GoTo ErrHandler
Dim txManager As Object
Set txManager = CreateObject(“MyCompany.ProjectTransaction.Manager”)
‘ プロジェクトの状態を保持したまま安全に保存
txManager.SafeSave Application.ActiveProject, “C:\Data\Project_Master.mpp”
Exit Sub
ErrHandler:
MsgBox “致命的なエラー: ” & Err.Description, vbCritical
End Sub
—
結論:システムは「壊れること」を前提に設計せよ
「保存が成功するはずだ」という楽観的な前提は、エンタープライズ開発における最大の罪である。
今回紹介したアトミック置換手法は、小手先のテクニックではない。OSレベルのファイルシステム特性を理解し、COMのライフサイクルを制御下に置くという、アーキテクトとしての矜持を示す実装だ。
貴殿が管理するプロジェクトファイルが、ある日突然の停電やネットワーク断絶によって失われることは、もはや許されない。この「堅牢なる保存」を武器に、システム全体の信頼性を一段上の次元へと引き上げろ。
それが、真のエンジニアの仕事だ。
