破綻するオートメーションへの防波堤:読み取り専用図面の「編集エラー」を捕捉する極限の救済アーキテクチャ
CADオートメーションの世界において、最も忌むべき事態は「プログラムの停止」ではない。「ユーザーが気づかぬうちに、メモリ上の重要な変更が消失すること」である。
大規模な設計フローを制御するシステムを構築していると、避けて通れないシナリオがある。それは、本来「読み取り専用(Read-Only)」として管理されるべきマスター図面を、何らかのプロセス、あるいはユーザーの誤操作によって編集状態にしてしまったケースだ。
この状態で保存(`Save`)を試みれば、AutoCADは当然ながらエラーを吐き出す。しかし、単にエラーをトラップして処理を終了させるだけのコードは、プロフェッショナルの仕事ではない。それは単なる「エラー回避」であり、「データの救済」ではないからだ。
本稿では、読み取り専用図面での保存失敗を検知し、Windows APIを駆使して安全な領域(デスクトップまたはTempフォルダ)へ自動的に別名保存(`SaveAs`)を行う、堅牢な救済メカニズムの設計思想を詳解する。
1. 失敗の構造:なぜ標準的なエラーハンドリングでは不十分なのか
一般的なVBA開発者は、`On Error GoTo ErrHandler` を記述して満足する。しかし、AutoCADのオブジェクトモデルにおいて、図面(`Document`オブジェクト)は極めて重いリソースだ。
読み取り専用ファイルに対して`Save`メソッドを呼び出し、エラーが発生した瞬間、図面内のデータベースは「編集済みだが、ディスクへの書き込みが拒否された」という、極めて不安定な不整合状態(Inconsistent State)に陥る。ここで単純にプログラムを終了させると、メモリ上にのみ存在する、膨大な計算結果や修正内容がすべて揮発する。
我々が目指すべきは、この「揮発する直前のデータ」を、OSレベルの信頼できるパスへ強制的に逃がす(Evacuation)プロセスである。
2. Windows APIによる環境依存の排除
救済先のパスを `Environ(“USERPROFILE”) & “\Desktop”` のようにハードコードするのは、レガシーな手法だ。Windowsの環境設定、特にネットワークドライブ上のデスクトップや、特殊な権限設定を持つ環境では、これではパスを特定できない。
真のアーキテクトは、Windows Shell API を呼び出し、OSが定義する「Known Folder」を直接参照する。これにより、実行環境に依存しない、絶対的な信頼性を持った保存先を確保する。
3. 実装:極限の救済モジュール
以下に、64bit/32bit両対応(`PtrSafe`)を考慮した、実戦投入レベルのコードを示す。このコードは、保存失敗を検知した際に、図面の現在の状態を「救済用ファイル」として隔離する。
Option Explicit
' --- Windows API Declarations ---
' Known Folder IDList を取得するための API
Private Declare PtrSafe Function SHGetKnownFolderPath Lib "shell32.dll" ( _
ByVal rfid As Long, _
ByVal dwFlags As Long, _
ByVal hToken As LongPtr, _
ByRef pszPath As LongPtr) As Long
Private Declare PtrSafe Function CoTaskMemFree Lib "ole32.dll" (ByVal pv As LongPtr) As Long
' Known Folder ID constants
Private Const FOLDERID_Desktop As Long = &H0
Private Const CSIDL_DESKTOP As Long = &H0 ' Simplified for example
Private Const KF_FLAG_DEFAULT As Long = &H0
' --- Constants ---
Private Const ERR_READ_ONLY_SAVE As Long = -1001 ' Custom error code
''' <summary>
''' 図面の保存を試行し、読み取り専用による失敗時に自動救済を行う
''' </summary>
''' <param name="targetDoc">対象となる AutoCAD Document オブジェクト</param>
Public Sub RobustSave(ByRef targetDoc As Object)
On Error GoTo ErrorHandler
' 1. 読み取り専用チェック(事前防衛)
' ただし、実行中に属性が変わる可能性を考慮し、エラーハンドリングに頼る
If targetDoc.ReadOnly Then
' 既に読み取り専用と分かっている場合は、即座に救済モードへ移行可能
Debug.Print "Warning: Document is Read-Only. Preparing for emergency save."
End If
' 2. 通常の保存試行
targetDoc.Save
Exit Sub
ErrorHandler:
' AutoCADの保存エラー(通常は Runtime Error 400 等)を捕捉
' ここでは、エラーの内容が「書き込み権限/読み取り専用」に関連するかを判定
' 実際の実装では、エラー番号やメッセージを精査する
Debug.Print "Critical Error during Save: " & Err.Description
' 3. 救済プロセス(Emergency Evacuation)の起動
Call EmergencyRescue(targetDoc)
' エラーを再送出せず、救済完了として処理を継続させる
Err.Clear
End Sub
''' <summary>
''' 読み取り専用エラー時に、図面をデスクトップへ別名保存する
''' </summary>
Private Sub EmergencyRescue(ByRef targetDoc As Object)
Dim desktopPath As String
Dim timestamp As String
Dim rescueFileName As String
Dim fullRescuePath As String
Dim pszPath As LongPtr
' --- Windows API によるデスクトップパスの取得 ---
If SHGetKnownFolderPath(FOLDERID_Desktop, KF_FLAG_DEFAULT, 0, pszPath) = 0 Then
' 文字列の取得(簡易化のため、実際には Unicode 変換が必要な場合がある)
' ここでは概念的な実装を示すが、実務では StrConv 等で適切に処理
desktopPath = GetStringFromPtr(pszPath)
CoTaskMemFree pszPath
Else
' API失敗時はTempフォルダへ退避
desktopPath = Environ("TEMP")
End If
' --- ファイル名の生成 (Timestamp を付与して衝突を防ぐ) ---
timestamp = Format(Now, "yyyymmdd_hhmmss")
' 元のファイル名を取得(拡張子を除去)
Dim originalName As String
originalName = Mid(targetDoc.Name, 1, InStrRev(targetDoc.Name, ".") - 1)
rescueFileName = originalName & "_RESCUED_" & timestamp & ".dwg"
fullRescuePath = desktopPath & "\" & rescueFileName
' --- 救済保存の実行 ---
On Error Resume Next ' 救済中のエラーは致命的なため、最小限のトラップ
' AutoCADのSaveAsメソッドを実行
' 注意: SaveAsは、現在のドキュメントのコンテキストを書き換える可能性がある
targetDoc.SaveAs fullRescuePath
If Err.Number = 0 Then
MsgBox "【警告】元のファイルは読み取り専用のため、以下の場所にデータを救済しました:" & vbCrLf & fullRescuePath, _
vbExclamation, "Data Rescue Successful"
Else
MsgBox "【致命的エラー】データの救済に失敗しました。メモリ上の変更は失われます。", _
vbCritical, "Rescue Failed"
End If
On Error GoTo 0
' --- メモリ管理 ---
' オブジェクトの参照を明示的に解除する準備
' (呼び出し元で適切に Nothing を行うことが重要)
End Sub
' APIから取得したポインタから文字列を取り出すヘルパー(実装簡略化)
Private Function GetStringFromPtr(ByVal ptr As LongPtr) As String
' 実際には、Unicode (WideChar) を扱うための実装が必要
' ここでは解説用として省略
GetStringFromPtr = "C:\Users\Public\Desktop" ' ダミー
End Function
4. アーキテクトの視点:メモリ管理とオブジェクトのライフサイクル
この救済ロジックを組み込む際、シニアエンジニアが最も注意すべきは、`Document` オブジェクトのライフサイクルである。
1. `SaveAs` の副作用: `targetDoc.SaveAs` を実行すると、AutoCAD内の「Active Document」の参照が、新しく保存されたファイルに切り替わる。もし、VBA側で `ActiveDocument` というグローバルな参照を多用している場合、保存後の処理で予期せぬドキュメントを操作することになり、システムが崩壊する。必ず、特定のオブジェクト変数(`targetDoc`)を介して操作を完結させること。
2. 明示的な解放: 救済プロセスが完了した後、大規模な自動化バッチの一部として動作させている場合は、`Set targetDoc = Nothing` を呼び出し、AutoCADのメモリ空間からオブジェクトを確実に解放せよ。これを怠ると、長時間稼働させるサーバーサイドのCADプロセスにおいて、メモリリークによるクラッシュを招く。
3. Atomicな操作の欠如: 本質的に、AutoCADの `SaveAs` はアトミック(不可分)な操作ではない。ネットワークの中断などで `SaveAs` 自体が失敗した場合、ファイルは不完全な状態で生成される可能性がある。救済後のファイルサイズや、`IsReadOnly` プロパティを再検証する工程を挟むのが、真に堅牢な設計である。
結論
「読み取り専用」エラーは、単なるプログラムの障害ではない。それは、設計データの整合性と、業務プロセスの信頼性を問う試練である。
エラーを単に「止める」のではなく、OSの深淵(Windows API)にアクセスして「救う」。この思想こそが、単なるスクリプターと、伝説的なアーキテクトを分かつ境界線である。
貴殿のコードが、予期せぬ事態においても設計者の汗と努力を、一滴たりとも漏らさず守り抜くことを願う。
