【上級】AutoCAD 202x系での「読み取り専用」図面を強制的に別名保存して編集ロックを解除する極限のVBA/APIワークフロー
AutoCADを用いたエンタープライズ向けの自動化ソリューションにおいて、避けて通れない最大の障壁の一つが「ファイルロックとアクセス権の競合」です。
複数ユーザーが同一サーバー上の図面にアクセスする環境や、PDM(製品データ管理)システムと連携した自動バッチ処理において、対象図面が「読み取り専用(ReadOnly)」で開かれてしまうケースは日常茶飯事です。この状態のまま処理を強行しようとすると、書き込み不可による致命的なエラー(Runtime Error)でシステムが停止するか、最悪の場合、メモリ上のオブジェクト参照が迷子になりAutoCAD自体がクラッシュします。
一般的なVBA解説書に書かれているような「エラーが発生したら処理をスキップする」という逃げの設計は、プロフェッショナルな現場では許されません。
本極限解説では、読み取り専用で開かれた図面を、Windows APIを駆使して安全なテンポラリ領域へ退避(SaveAs)させ、編集可能な(Read-Write)ドキュメントとして再ロードし、元のファイルロックを完全に解除してシームレスに編集を継続するための、極めて堅牢なアーキテクチャを提示します。
—
1. 読み取り専用図面とCOMライフサイクルの真実
なぜ、AutoCAD VBAにおいて「読み取り専用」の解除はこれほどまでに厄介なのでしょうか。その理由は、AutoCADのドキュメントライフサイクル管理とファイルシステムのロックメカニズム(`.dwl`/`.dwl2`)の密接な結合にあります。
1.1 AutoCADのファイルロック(DWL)の挙動
AutoCADは図面を開くと、同一ディレクトリに `図面名.dwl` および `図面名.dwl2` というロックファイルを生成します。これは、他のセッションが同ファイルを書き込みモードで開くのを防ぐためのOSレベルおよびアプリケーションレベルの排他制御です。
図面が「読み取り専用」で開かれている場合、現在のセッションはそのファイルの書き込みロックを所有していません。この状態で `Save` を呼び出すと拒絶されます。
1.2 `SaveAs` の罠とActiveXの参照整合性
「それなら、単に `SaveAs` で別名保存すれば解決するのでは?」と考えるのは素人です。
AutoCAD VBAにおいて、現在アクティブなドキュメント(`ThisDrawing` または特定の `AcadDocument`)に対して `SaveAs` を実行すると、ドキュメントオブジェクトの内部ポインタ(ファイルパス、ハンドル、およびCOM参照)が保存先の新しいファイルへと切り替わります。
しかし、202x系のマルチドキュメント環境(MDI)においては、以下の問題が牙を剥きます。
1. ロックの残存: 元のファイルが読み取り専用のままメモリにゴーストとして残る、またはロックファイル(`.dwl`)が解放されない。
2. 非同期実行の壁: `SaveAs` 実行直後にドキュメントを `Close` しようとすると、AutoCADのコマンド実行キューとVBAの実行スレッドが同期せず、タイミングによって「呼び出しが拒否されました」等のCOMエラー(HRESULT: 0x80010001 / RPC_E_CALL_REJECTED)が発生する。
3. メモリリーク: 参照カウント(Reference Count)が適切にデクリメントされず、AutoCADのプロセス(`acad.exe`)が終了してもメモリを占有し続ける。
これらの課題をクリアするためには、Windows APIによるファイル制御、COMメッセージフィルタの意識、そして厳密なオブジェクトライフサイクル管理を組み合わせた「完全な同期シーケンス」が必要不可欠です。
—
2. 堅牢なアーキテクチャ設計
本ソリューションの処理フローは以下の通りです。
[開始]
│
├─ 1. 対象ドキュメントの「ReadOnly」状態を検出
│
├─ 2. Windows APIによる「安全な一時ファイルパス」の生成
│
├─ 3. 現在の図面を一時パスへ強制的に [SaveAs] (一時的にロック回避)
│
├─ 4. 元の「読み取り専用図面」を保存なしで [Close] (ファイルロックの完全解放)
│
├─ 5. 一時ファイルを「読み書き可能モード(ReadOnly:=False)」として [Open]
│
├─ 6. 新しいドキュメントオブジェクトへの「COM参照の再バインド」
│
└─ [編集可能な状態で処理続行]
このシーケンスを、VBAのシングルスレッドモデルに適合させ、AutoCAD 202x系(64bit)で完璧に動作させるコードを実装します。
—
3. 極限のソリューションコード(VBA)
以下のコードは、64bit環境(VBA7)に完全対応し、エラーハンドリング、Windows APIによるテンポラリファイル生成、COMオブジェクトの厳密な解放を網羅した、実戦仕様のモジュールです。
標準モジュール(例:`ModRobustDocumentController`)に配置して使用してください。
Option Explicit
‘ ==============================================================================
‘ Windows API 宣言 (64bit / 32bit 互換)
‘ ==============================================================================
If VBA7 Then
Private Declare PtrSafe Function GetTempPath Lib “kernel32” Alias “GetTempPathA” ( _
ByVal nBufferLength As Long, _
ByVal lpBuffer As String) As Long
Private Declare PtrSafe Function GetTempFileName Lib “kernel32” Alias “GetTempFileNameA” ( _
ByVal lpszPath As String, _
ByVal lpPrefixString As String, _
ByVal wUnique As Long, _
ByVal lpTempFileName As String) As Long
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Function GetTempPath Lib “kernel32” Alias “GetTempPathA” ( _
ByVal nBufferLength As Long, _
ByVal lpBuffer As String) As Long
Private Declare Function GetTempFileName Lib “kernel32” Alias “GetTempFileNameA” ( _
ByVal lpszPath As String, _
ByVal lpPrefixString As String, _
ByVal wUnique As Long, _
ByVal lpTempFileName As String) As Long
Private Declare Sub Sleep Lib “kernel32″ (ByVal dwMilliseconds As Long)
End If
‘ ==============================================================================
‘ 定数定義
‘ ==============================================================================
Private Const MAX_PATH As Long = 260
Private Const DWG_VERSION_CURRENT As Integer = 64 ‘ AutoCAD 2018/202x 形式 (AcSaveAsType: ac2018_dwg)
”’
”’
”’ 対象となるAcadDocumentオブジェクト
”’
Public Function ElevateReadOnlyDocument(ByRef targetDoc As AcadDocument) As AcadDocument
On Error GoTo ErrorHandler
Dim acadApp As AcadApplication
Set acadApp = targetDoc.Application
‘ 1. 読み取り専用判定
If Not targetDoc.ReadOnly Then
Debug.Print “[INFO] ドキュメントは既に読み書き可能です: ” & targetDoc.FullName
Set ElevateReadOnlyDocument = targetDoc
Exit Function
End If
Debug.Print “[START] 読み取り専用解除シーケンスを開始します。対象: ” & targetDoc.Name
‘ 2. Windows APIを使用した安全な一時ファイルパスの構築
Dim tempDwgPath As String
tempDwgPath = CreateSecureTempDwgPath()
If tempDwgPath = “” Then
Err.Raise vbObjectError + 1001, “ElevateReadOnlyDocument”, “一時ファイルの生成に失敗しました。”
End If
Debug.Print “[INFO] 一時ファイルパスを生成しました: ” & tempDwgPath
‘ 3. 元のファイルパスと名前を退避
Dim originalFullPath As String
originalFullPath = targetDoc.FullName
‘ 4. 一時ファイルとしてSaveAs
‘ ※AcSaveAsTypeは環境に合わせて適切にキャストします
Debug.Print “[EXEC] 一時ファイルへの別名保存を実行中…”
targetDoc.SaveAs tempDwgPath, ac2018_dwg
‘ 同期ズレを防ぐためのウェイトとDoEventsの挿入
DoEvents
Sleep 100 ‘ 100ms待機し、OSのファイルI/O完了を待つ
‘ 5. 元のドキュメント(現在は一時ファイルに切り替わっている)を閉じる
‘ ※SaveAsしたため、このtargetDocの参照先は一時ファイルになっている。これを変更保存なしで閉じる。
Debug.Print “[EXEC] ドキュメントをクローズします…”
targetDoc.Close False, “”
Set targetDoc = Nothing ‘ COMポインタを明示的に解放
DoEvents
Sleep 200 ‘ ファイルロックが完全に解除されるまで十分な時間を確保
‘ 6. 一時ファイルを「ReadOnly:=False」で強制的に開き直す
Debug.Print “[EXEC] 一時ファイルを読み書き可能モードで開き直します…”
Dim elevatedDoc As AcadDocument
Set elevatedDoc = acadApp.Documents.Open(tempDwgPath, False) ‘ 第2引数 ReadOnly = False
DoEvents
‘ 7. 呼び出し元に新しいドキュメント参照を返す
Debug.Print “[SUCCESS] 編集ロックの解除に成功しました。新アクティブドキュメント: ” & elevatedDoc.Name
Set ElevateReadOnlyDocument = elevatedDoc
Exit Function
ErrorHandler:
Dim errDesc As String
errDesc = Err.Description
Debug.Print “[FATAL ERROR] 処理中に致命的なエラーが発生しました: ” & errDesc
‘ 参照のクリーンアップ
Set targetDoc = Nothing
Set ElevateReadOnlyDocument = Nothing
Err.Raise Err.Number, “ElevateReadOnlyDocument -> ” & Err.Source, errDesc
End Function
”’
”’
Private Function CreateSecureTempDwgPath() As String
Dim tempPath As String
Dim tempFileName As String
Dim result As Long
tempPath = String$(MAX_PATH, 0)
result = GetTempPath(MAX_PATH, tempPath)
If result <> 0 Then
tempPath = Left$(tempPath, InStr(tempPath, Chr$(0)) – 1)
tempFileName = String$(MAX_PATH, 0)
‘ プレフィックス “ACD” を持つ一意のテンポラリファイル(.tmp)を生成
result = GetTempFileName(tempPath, “ACD”, 0, tempFileName)
If result <> 0 Then
Dim rawTempPath As String
rawTempPath = Left$(tempFileName, InStr(tempFileName, Chr$(0)) – 1)
‘ Windows APIが作成した空の .tmp ファイルを削除し、拡張子を .dwg に変更
If Dir(rawTempPath) <> “” Then
Kill rawTempPath
End If
CreateSecureTempDwgPath = Left(rawTempPath, Len(rawTempPath) – 4) & “.dwg”
End If
End If
End Function
呼び出し例(クライアントコード)
上記のロジックを実際に稼働させるためのエントリーポイントです。図面を開いた直後に「読み取り専用」だった場合、この関数を介してオブジェクトを安全に置き換えます。
Public Sub RunBatchProcessOnActiveDocument()
Dim acadApp As AcadApplication
Set acadApp = ThisDrawing.Application
Dim currentDoc As AcadDocument
Set currentDoc = acadApp.ActiveDocument
‘ ドキュメントの書き込み権限を昇格
On Error GoTo ProcError
Set currentDoc = ElevateReadOnlyDocument(currentDoc)
If currentDoc Is Nothing Then
MsgBox “ドキュメントの昇格に失敗しました。”, vbCritical
Exit Sub
End If
‘ — ここから編集可能な状態での自動化処理を記述 —
Debug.Print “現在、編集可能です。処理を実行します: ” & currentDoc.FullName
‘ テスト用の適当な図形作成
Dim ptStart(0 To 2) As Double: ptStart(0) = 0#: ptStart(1) = 0#: ptStart(2) = 0#
Dim ptEnd(0 To 2) As Double: ptEnd(0) = 100#: ptEnd(1) = 100#: ptEnd(2) = 0#
currentDoc.ModelSpace.AddLine ptStart, ptEnd
‘ 保存
currentDoc.Save
Debug.Print “一時ファイルへの自動保存が完了しました。”
‘ ————————————————–
Exit Sub
ProcError:
MsgBox “バッチ処理中にエラーが発生しました: ” & Err.Description, vbCritical
End Sub
—
4. チーフアーキテクトが解説する実装の核心
このコードには、一般的な開発者が陥りがちな「罠」を回避するための、高度な意思決定が随所に組み込まれています。
① Windows APIによるテンポラリパス生成の必然性
VBA標準の `Environ(“TEMP”)` などを用いた単純な文字列結合によるファイル名作成は、マルチユーザー環境やマルチプロセス実行において、ファイル名の衝突(コリジョン)を引き起こす温床になります。
`GetTempFileName` APIは、OSレベルで確実に重複しない一意のファイル名を保証します。APIによって一時的に作られたダミーの `.tmp` ファイルを `Kill` で削除した上で、同一パスの拡張子を `.dwg` に変更してAutoCADに渡すアプローチは、最も衝突耐性が高く、セキュリティ上も安全です。
② `DoEvents` と `Sleep` による非同期制御の「同期化」
AutoCADは内部で数多くの非同期処理を行っています。VBAから `SaveAs` や `Close` などの高負荷なI/Oコマンドを発行した直後、AutoCADの内部データベース(AcDbDatabase)の再構築や、OSに対するファイルハンドルのクローズ処理が追いついていない場合があります。
ここに `DoEvents` を挟み、OSのメッセージキューを処理させ、さらに `Sleep` APIによってスレッドを一時停止させることで、「ファイルロックがまだ外れていないのにOpenコマンドが到達して共有違反(Sharing Violation)になる」という、マルチドキュメント環境特有のタイミングエラーを完全に排除しています。
③ COM参照カウンタの厳密な管理
VBAのオブジェクト変数(`targetDoc`)に `Nothing` を代入する処理(`Set targetDoc = Nothing`)は、単なるマナーではありません。
ActiveX/COMの仕様上、オブジェクトへの内部参照が1つでも残っていると、AutoCADはメモリからそのドキュメントの構造体を解放できません。特に、ドキュメントを閉じる操作(`Close`)を行う前に、確実に古い参照を切り離し、再オープンした新しいオブジェクト(`elevatedDoc`)に参照を再バインドすることが、メモリリークを防止するための鉄則です。
—
5. まとめ:レガシーを超越し、202x系を支配する
AutoCAD 202x系は、グラフィックスエンジンやマルチスレッド処理、セキュリティ要件が劇的に進化しています。これに伴い、COMインターフェースを流れるデータの同期タイミングは、旧バージョンよりもはるかにシビアになっています。
今回提示した「Windows APIを介したテンポラリ退避 & 再バインド」のワークフローは、ファイルロックというOSとアプリケーションの隙間に生じる「超難解な不具合」を解決するための、極めて実戦的なアプローチです。
この堅牢な設計思想を自社のコードベースに組み込むことで、システム管理者やシニア開発者は、ユーザーからの「なぜかたまにファイルが開けなくてマクロが止まる」という不条理なクレームから解放され、真にエンタープライズに耐えうるCADオートメーションシステムを構築することができます。
