データ消失の淵から救う:AutoCAD VBAにおける「読み取り専用」回避と強制リダイレクト・アーキテクチャ
実務の最前線において、エンジニアが最も忌むべき事態は何か。それはバグではない。「数時間に及ぶ設計作業の消失」だ。
AutoCADを用いた大規模な自動化システムを運用していると、避けて通れない現象がある。ネットワーク上の共有サーバ上の図面を、複数のユーザーが同時に、あるいは別のプロセスがロックした状態で開いてしまうケースだ。この時、AutoCADは図面を「読み取り専用(ReadOnly)」として開く。
初級者はここで単純な `On Error Resume Next` でエラーを握りつぶし、あるいは単にエラーダイアログを出して終了させる。だが、我々プロフェッショナルが構築すべきは、「ユーザーの作業内容をいかなる状況下でも物理的に保護し、安全な領域へ退避させるリカバリ・ロジック」である。
本稿では、ReadOnly状態を検知し、上書き保存の失敗を予見した上で、Windowsのテンポラリ領域へ自動的に「別名保存(SaveAs)」を強制する、堅牢なリダイレクト・アーキテクチャを解説する。
1. 失敗のメカニズム:なぜ標準の `Save` では足りないのか
AutoCADの `Document.Save` メソッドは、ファイルがReadOnlyである場合、あるいはファイルシステムレベルで書き込み権限が拒否された場合、実行時エラーをスローする。
問題は、このエラーが発生した時点では、「メモリ上の変更内容は、図面を閉じれば消滅する」という点にある。標準的なエラーハンドリングでは、エラーをキャッチした瞬間に「保存に失敗しました」と告げることしかできず、ユーザーの努力を救うことはできない。
我々が実装すべきは、「保存の試行」ではなく「保存の条件判定」に基づく、プロアクティブ(先行的)な制御である。
2. 実装戦略:防御的保存(Defensive Saving)の設計
以下の3つのレイヤーでロジックを構成する。
1. 状態監視レイヤー: `Document.ReadOnly` プロパティを事前にスキャン。
2. リダイレクト・レイヤー: ReadOnlyまたは書き込み不可の場合、`Environ(“TEMP”)` を利用して安全なパスを生成。
3. 確実な永続化レイヤー: `FileSystemObject` を併用し、物理的なファイル生存を確認した後にオブジェクトを解放。
実装コード例:RobustSaveEngine
以下に、シニアエンジニアが実務に投入すべきレベルの、堅牢な保存ロジックを示す。
Option Explicit
' ==============================================================================
' Module: RecoveryModule
' Description: AutoCAD図面の読み取り専用状態を検知し、作業内容を保護する
' 強制リダイレクト保存エンジン。
' ==============================================================================
' Windows APIの宣言(ファイル属性の取得等、高度な判定が必要な場合に使用)
' 今回はVBA標準のFileSystemObjectとAutoCAD APIを組み合わせた、
' COMの安定性を重視した設計とする。
Sub ExecuteSecureSave(ByRef targetDoc As AcadDocument)
Dim fso As Object
Dim targetPath As String
Dim backupPath As String
Dim timestamp As String
Dim isReadOnly As Boolean
On Error GoTo ErrorHandler
' 1. オブジェクトの有効性確認
If targetDoc Is Nothing Then
Err.Raise vbObjectError + 513, "ExecuteSecureSave", "対象ドキュメントがNothingです。"
End If
Set fso = CreateObject("Scripting.FileSystemObject")
targetPath = targetDoc.FullName
isReadOnly = targetDoc.ReadOnly
' 2. 保存戦略の決定
If isReadOnly Then
' --- 【重要】読み取り専用モードの場合のリダイレクト処理 ---
Debug.Print "ReadOnly状態を検知。リカバリモードへ移行します。"
' タイムスタンプの生成 (YYYYMMDD_HHMMSS形式)
timestamp = Format(Now, "yyyymmdd_hhmmss")
' Windowsのテンポラリディレクトリを取得し、安全な保存先を構築
' 例: C:\Users\Name\AppData\Local\Temp\RECOVERY_DrawingName_20231027_120000.dwg
backupPath = Environ("TEMP") & "\RECOVERY_" & _
fso.GetBaseName(targetPath) & "_" & timestamp & ".dwg"
Debug.Print "退避先パス: " & backupPath
' 3. 強制別名保存の実行
' SaveAsを使用することで、ReadOnly属性を破棄した新しいファイルとして書き出す
targetDoc.SaveAs backupPath
MsgBox "【警告】元のファイルは読み取り専用です。" & vbCrLf & _
"作業内容は以下のテンポラリ領域に強制退避されました:" & vbCrLf & _
backupPath, vbExclamation, "データ保護リカバリ完了"
Else
' 通常保存
targetDoc.Save
Debug.Print "通常保存が完了しました。"
End If
CleanUp:
' 4. メモリ管理:オブジェクトの明示的解放
' 大規模な自動化処理では、これを行わないとAutoCADのメモリリークを招く
Set fso = Nothing
Exit Sub
ErrorHandler:
' 5. エラーハンドリング
' ファイルロック等の致命的なエラーが発生した場合の処理
MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
"Error No: " & Err.Number & vbCrLf & _
"Description: " & Err.Description, vbCritical, "システムエラー"
' エラー発生時もリソース解放へ向かう
Resume CleanUp
End Sub
3. アーキテクトの視点:パフォーマンスとメモリの最適化
このコードを単なる「動くコード」で終わらせてはならない。システム全体の安定性を担保するための、極限の知見を補足する。
オブジェクトのライフサイクル管理
VBAにおいて、`AcadDocument` や `FileSystemObject` といったCOMオブジェクトは、スコープを抜けただけでは即座にメモリから解放されない場合がある。特に、ループ内で大量の図面を処理する自動化ツールでは、`Set obj = Nothing` を明示的に記述することが、メモリ断片化を防ぐ唯一の手段である。
Windows API と FileSystemObject の使い分け
ファイルが「なぜ」ReadOnlyなのかをより深く探るなら、Windows APIの `GetFileAttributes` を呼び出し、`FILE_ATTRIBUTE_READONLY` をチェックする手法がある。しかし、AutoCADのCOMインターフェースにおいては、`targetDoc.ReadOnly` プロパティが既にAutoCAD内部のロック状態を反映している。APIを過剰に呼び出すことは、コンテキストスイッチのオーバーヘッドを生むため、「AutoCADのプロパティを信じ、ファイル操作はFSOに任せる」のが、最もパフォーマンスと安定性のバランスが取れた設計である。
異常系への備え:Tempディレクトリの信頼性
`Environ(“TEMP”)` は、ユーザーのローカルドライブを指すため、ネットワークの切断や共有サーバのダウンに対して極めて強い。設計者は、「ネットワークが死んでも、ローカルに証拠を残す」という思想を持つべきだ。これが、レガシーな環境から最新のクラウドストレージ同期環境までを貫く、普遍的なアーキテクチャの原則である。
結論
「読み取り専用」はエラーではない。それは、システムがユーザーのデータを守るために発しているシグナルである。
優れたエンジニアは、エラーを止めることだけを目的としない。エラーが発生した「その後」の、データの連続性をいかに確保するかを設計する。今回提示したリダイレクト・ロジックを、君たちの堅牢なフレームワークの一部として組み込んでほしい。
「コードは書くものではない。生存戦略を構築するものだ。」
