【テクニカル・上級編】【実務中級者向け】「読み取り専用」図面を編集してしまった際の救済策:一時フォルダへの自動退避と通知機能 – AutoCAD VBA解析バイブル

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

破綻するオートメーションへの防波堤:読み取り専用図面の「編集エラー」を捕捉する極限の救済アーキテクチャ

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)にアクセスして「救う」。この思想こそが、単なるスクリプターと、伝説的なアーキテクトを分かつ境界線である。

貴殿のコードが、予期せぬ事態においても設計者の汗と努力を、一滴たりとも漏らさず守り抜くことを願う。

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