【テクニカル・上級編】【初心者向け】図面を開かずにファイル名とパスを取得:Dir関数とThisDrawing.Pathの使い分け – AutoCAD VBA解析バイブル

スポンサーリンク

1. はじめに:なぜ「図面を開かずに取得する」ことが絶対防衛線なのか

AutoCADの自動化において、初学者が最初に突き当たる巨大な壁がある。それが「図面処理の処理速度の指数関数的劣化」と「メモリリークによるセッションクラッシュ」だ。

数枚の図面を処理する程度であれば、愚直に `Documents.Open` を叩き、`ThisDrawing.Path` や `ThisDrawing.Name` を取得して閉じるコードでも破綻しない。しかし、対象が数百〜数万枚のエンタープライズな図面アーカイブ、あるいはファイルサーバ越し(UNCパス)のバッチ処理となった瞬間、そのアーキテクチャは破滅的な崩壊を迎える。

AutoCADがDWGファイルを開く際、内部では何が起きているか。

  • データベース(AcDbDatabase)のヒープ領域への展開
  • ディスプレイスクリプトおよびグラフィックスパイプラインの初期化
  • 外部参照(XREF)の解決と再帰的なディスクI/O
  • フォント(SHX/TrueType)のマッピング解決
  • 登録されたObjectARX/DBXカスタムオブジェクトおよびリアクタの初期化

パスとファイル名、あるいはファイルのメタデータ(DWGバージョンや更新日時)を知りたいだけのために、これらギガバイト単位のメモリ帯域とCPUサイクルを消費する行為は、システム設計として明白な誤りである。

本稿では、`ThisDrawing.Path` の真の挙動と限界を解剖した上で、VBAネイティブの `Dir` 関数が抱えるレガシーな落とし穴、さらにはそれらを凌駕するWindows APIを用いたノンブロッキングな超高速ファイル走査、そしてDWGを一切開かずにバイナリヘッダから直接バージョンを抜き出す極限の技術までを網羅して解説する。

—

2. `ThisDrawing.Path` の境界線とCOMコンテキストの制約

2.1 `ThisDrawing` の正体

AutoCAD VBA環境において、`ThisDrawing` は暗黙的に `ThisDrawing.Application.ActiveDocument` へのショートカットとしてバインドされている。

Dim currentPath As String
currentPath = ThisDrawing.Path

極めてシンプルに見えるが、システム管理者やシニアエンジニアは以下のリスクを常に考慮しなければならない。

1. 未保存ドキュメントの空文字トラップ
新規作成直後(`Drawing1.dwg` など)で、一度もストレージにコミットされていない図面の場合、`ThisDrawing.Path` は実行時エラーを起こさず、静かに `””`(長さゼロの文字列)を返す。これに後続コードで `\` を連結してファイル操作を試みれば、カレントディレクトリを汚染する致命的なバグへと直結する。

2. マルチドキュメント(MDI)環境のフォーカス遷移
ユーザーのGUI操作や非同期イベントによって `ActiveDocument` のフォーカスが切り替わると、`ThisDrawing` が指し示すインスタンスが動的に変化し、意図しない図面のパスを掴む競合状態(Race Condition)が発生する。

3. メモリオーバーヘッド
パスを取得するためだけに `Documents.Open` を呼べば、AutoCADのプロセスワーキングセットは急激に肥大化する。VBA内部のガベージコレクション(参照カウント)はCOMの参照解放を即座にOSへ返還しないため、大規模バッチでは `Out of Memory` を引き起こす主因となる。

—

3. `Dir` 関数の実力とレガシー特有の致命的欠陥

AutoCADのプロセスを汚染せず、ファイルシステムから直接エントリを読み取る最も原始的な方法がVBA標準の `Dir` 関数である。

3.1 ステートフルな設計による「再帰走査の破綻」

`Dir` 関数は、引数を省略して呼び出すことで「次のファイル」を取得するステートフル(内部にイテレータの静的状態を保持する)なAPIだ。

‘ ※このコードは一見動作するが、内部で別のDirを呼ぶと状態が完全に破壊される
Dim fileName As String
fileName = Dir(“C:\Drawings\.dwg”)

Do While Len(fileName) > 0
‘ ここで再帰的に別フォルダを走査しようとして Dir() を叩くと、
‘ 親ループのイテレータが上書きされ、無限ループまたは早期脱走を引き起こす。
fileName = Dir()
Loop

社内ネットワークに存在する無数の階層化されたプロジェクトフォルダを走査する際、`Dir` のこの仕様は極めて危険である。また、UNCパス(`\\Server\Share\`)におけるタイムアウトや、Shift-JISで表現できないUnicode固有文字(サロゲートペアや特殊記号)を含むファイル名を正しく扱えず、ファイルが存在するにもかかわらず空振りを起こす。

—

4. 極限の最適化:Windows APIによるダイレクト・ファイルシステムアクセス

数千枚規模の図面管理ツールや、社内一括マイグレーションシステムを構築する場合、AutoCADのAPIもVBAネイティブのファイル関数も捨て、OSのカーネルへ直接問い合わせるのがアーキテクチャとしての最適解となる。

`kernel32.dll` の `FindFirstFileW` および `FindNextFileW` を呼び出すことで、以下の圧倒的なアドバンテージを得られる。

  • COMオーバーヘッドゼロ: AutoCADのグラフィックスやデータベースを1ミリ秒も消費しない。
  • 完全なUnicode対応: 文字化けによる取得漏れを完全に排除。
  • メモリの極小化: OSのファイルアロケーションテーブルの走査結果を構造体に直接マッピングするため、数万ファイルであってもAutoCADプロセスのワーキングセットは平坦なまま維持される。

4.1 64bit AutoCAD環境完全対応のファイル列挙基盤

以下は、64bit(AutoCAD 2014〜2025以降)で完全動作する、Windows API直結の超高速走査モジュールである。

Option Explicit

If VBA7 Then
Private Declare PtrSafe Function FindFirstFileW Lib “kernel32” ( _
ByVal lpFileName As LongPtr, _
ByVal lpFindFileData As LongPtr) As LongPtr

Private Declare PtrSafe Function FindNextFileW Lib “kernel32” ( _
ByVal hFindFile As LongPtr, _
ByVal lpFindFileData As LongPtr) As Long

Private Declare PtrSafe Function FindClose Lib “kernel32” ( _
ByVal hFindFile As LongPtr) As Long
Else
Private Declare Function FindFirstFileW Lib “kernel32” ( _
ByVal lpFileName As Long, _
ByVal lpFindFileData As Long) As Long

Private Declare Function FindNextFileW Lib “kernel32” ( _
ByVal hFindFile As Long, _
ByVal lpFindFileData As Long) As Long

Private Declare Function FindClose Lib “kernel32” ( _
ByVal hFindFile As Long) As Long
End If

Private Const INVALID_HANDLE_VALUE As Long = -1
Private Const MAX_PATH As Long = 260

‘ FILETIME 構造体 (UTC)
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type

‘ WIN32_FIND_DATAW 構造体
Private Type WIN32_FIND_DATAW
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName(0 To (MAX_PATH 2) – 1) As Byte ‘ WCHAR 260文字分
cAlternate(0 To 27) As Byte ‘ 8.3形式用
End Type

—

5. 究極のハック:DWGファイルを開かずに内部バージョンを直接特定する

ファイル名とパスが取得できたとしても、「開いてAutoCADのバージョン互換性を判定する」のでは片手落ちだ。シニアエンジニアであれば、バイナリのファイルシグネチャを直接読み取り、図面を開くことすらなくフォーマットを同定する手法を選択する。

DWGファイルは先頭6バイトにマジックナンバー(例: `AC1032` = AutoCAD 2018〜2024形式)を常に保持している。これをバイナリストリームで直接読み取れば、COMのインスタンス化すら行わず、0.1ミリ秒単位でバージョン判定が完了する。

5.1 実装:ファイル名走査と超高速DWGヘッダパース

以下のコードは、フォルダ内の全DWGファイルをWindows APIで高速走査し、一切ファイルを開かずにファイル名、フルパス、そしてDWGバージョンをコンソールにダンプする完全な実装例である。

Public Sub FastScanDrawingsWithoutOpen()
Dim targetFolder As String
targetFolder = “C:\Engineering\Drawings” ‘ 走査対象ディレクトリ

‘ 末尾のパスデリミタ補正
If Right$(targetFolder, 1) <> “\” Then targetFolder = targetFolder & “\”

Dim searchPattern As String
searchPattern = targetFolder & “.dwg”

Dim findData As WIN32_FIND_DATAW
#If VBA7 Then
Dim hFind As LongPtr
#Else
Dim hFind As Long
#End If

‘ Unicodeポインタを渡してファイル検索を開始
hFind = FindFirstFileW(StrPtr(searchPattern), VarPtr(findData))

If hFind = INVALID_HANDLE_VALUE Then
Debug.Print “指定されたパスに図面が存在しないか、アクセス権がありません: ” & targetFolder
Exit Sub
End If

Dim fileName As String
Dim fullPath As String
Dim dwgVersion As String
Dim processedCount As Long

On Error GoTo Cleanup

Do
‘ バイト配列からUnicode文字列を復元(Null終端の切り落とし)
fileName = StripNulls(findData.cFileName)

‘ カレントおよび親ディレクトリ参照(. / ..)の除外
If fileName <> “.” And fileName <> “..” Then
fullPath = targetFolder & fileName

‘ 図面を開かずにバイナリヘッダからバージョンを直接抽出
dwgVersion = ReadDwgHeaderVersion(fullPath)

‘ 処理結果の出力(実務ではここでデータベース登録やログ書き込みを行う)
Debug.Print “File: ” & fileName & ” | Version: ” & dwgVersion & ” | Path: ” & fullPath
processedCount = processedCount + 1
End If

‘ 次のファイルを検索
Loop While FindNextFileW(hFind, VarPtr(findData)) <> 0

Cleanup:
‘ OSリソース(検索ハンドル)を確実に解放
If hFind <> INVALID_HANDLE_VALUE Then
FindClose hFind
End If

Debug.Print “走査完了. 処理ファイル数: ” & processedCount
End Sub

‘ WCHAR配列から終端Nullを取り除きVBA文字列に変換するヘルパー
Private Function StripNulls(rawBytes() As Byte) As String
Dim result As String
result = rawBytes ‘ バイト配列を直接文字列にキャスト

Dim nullPos As Long
nullPos = InStr(result, vbNullChar)
If nullPos > 0 Then
StripNulls = Left$(result, nullPos – 1)
Else
StripNulls = Trim$(result)
End If
End Function

‘ DWGファイルのバイナリ先頭6バイトからAutoCADのバージョンシグネチャを取得
Private Function ReadDwgHeaderVersion(ByVal filePath As String) As String
Dim fileNum As Integer
Dim headerBytes(0 To 5) As Byte
Dim sig As String

On Error GoTo FileError

fileNum = FreeFile
‘ バイナリモード、排他制御共有読み込み(他プロセスが開いていても読み取り可能)
Open filePath For Binary Access Read Shared As #fileNum
Get #fileNum, 1, headerBytes
Close #fileNum

sig = StrConv(headerBytes, vbUnicode)

Select Case sig
Case “AC1032”: ReadDwgHeaderVersion = “AutoCAD 2018/2021/2024 (AC1032)”
Case “AC1027”: ReadDwgHeaderVersion = “AutoCAD 2013/2016 (AC1027)”
Case “AC1024”: ReadDwgHeaderVersion = “AutoCAD 2010 (AC1024)”
Case “AC1021”: ReadDwgHeaderVersion = “AutoCAD 2007 (AC1021)”
Case “AC1018”: ReadDwgHeaderVersion = “AutoCAD 2004 (AC1018)”
Case “AC1015”: ReadDwgHeaderVersion = “AutoCAD 2000 (AC1015)”
Case “MC0.0” : ReadDwgHeaderVersion = “AutoCAD Release 1.x”
Case Else: ReadDwgHeaderVersion = “未知のフォーマット (” & sig & “)”
End Select
Exit Function

FileError:
If fileNum <> 0 Then Close #fileNum
ReadDwgHeaderVersion = “読み込みエラー(アクセス拒否/破損)”
End Function

—

6. 設計判断の指針:適材適所のアーキテクチャ選定

システムのライフサイクルや要件定義に応じて、どの技術を選択すべきかの指針を以下にまとめる。

| 手法 | メリット | デメリット / リスク | 最適なユースケース |
| :— | :— | :— | :— |
| `ThisDrawing.Path` | ・最もシンプル
・現在のアクティブな文脈と連動 | ・開いていない図面は取得不可
・未保存図面で空文字トラップ
・MDI環境での誤爆リスク | 今ユーザーが作業しているアクティブ図面に対して単発実行するマクロ |
| `Dir` 関数 | ・AutoCADに図面をロードしない
・コード量が少なく導入が容易 | ・再帰処理で内部ステートが破壊される
・UNCパスでの遅延やUnicodeの文字化け | 単一ディレクトリ内の単純なバッチで、ファイル名がShift-JIS内に収まる場合 |
| Windows API走査
+ バイナリ直読
| ・極限の処理速度
・AutoCADのCOMリソース消費ゼロ
・Unicode完全対応
・ファイルを開かずにバージョン同定 | ・API宣言とメモリ構造体の理解が必要
・Win32 APIの知識が求められる | 数百〜数万枚規模のエンタープライズ一括処理、図面管理システムへの登録バッチ、資産調査 |

アーキテクトの視点:なぜこの技術がレガシーを救うのか

AutoCADのプロセスは、図面を開閉するたびにヒープの断片化(Heap Fragmentation)を少しずつ蓄積させていく。VBAというCOMラッパーを介した制御では、この断片化を完全に防ぐことはできない。

だからこそ、「ファイル情報が欲しいだけなら、AutoCADのプロセスに図面を一切触らせない」というアプローチが極めて重要になる。ファイルシステムとOSのバイナリインターフェースを直接叩くことで、AutoCAD本体のライフサイクルを完全に防護し、数万回のループであっても1メガバイトのメモリリークすら許さない堅牢なバッチ基盤が実現するのだ。

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