【実務・中級編】【上級プロフェッショナル向け】大量図面のオープン・クローズに伴うGDIリソース消費の監視と強制ガベージコレクション – AutoCAD VBA解析バイブル

スポンサーリンク

AutoCAD VBAを掌握する極限の知見

【上級】数千枚の図面バッチ処理で破綻しない!GDIリソース監視とAutoCAD強制的再起動ライフサイクル管理

業務自動化エンジニアの皆さん、日々の図面処理お疲れ様です。
夜間に数千枚単位のDWGファイルをVBAで一斉処理し、「さあ寝よう」とPCを離れ、朝に出社してみたら――。

「メモリー不足 (Out of Memory)」のエラーメッセージを残して、処理が全体の3割でフリーズしていた。

この悪夢のような光景に直面したことはないでしょうか?

私は数多くの大規模プラントやゼネコンの図面自動化プロジェクトを統括してきましたが、その中で最も厄介な敵は、VBAのコードミスではありません。「AutoCADという巨大なCOMサーバーのゾンビ化と、GDI/ユーザーリソースのリーク」です。

今回は、数千枚規模の図面オープン・クローズを完遂させるための、プロフェッショナルだけが知る「リソース監視とプロセスの強制ライフサイクル管理」の極意を伝授します。

—

なぜ「単なるループ処理」では数千枚の壁を越えられないのか?

多くの開発者は、次のようなコードを書きます。

‘ 【アンチパターン】絶対にやってはいけない実装
Dim i As Long
For i = 1 To 5000
Dim acadDoc As AcadDocument
Set acadDoc = Documents.Open(“C:\Drawings\Drw_” & i & “.dwg”)

‘ 何らかの処理
Call ModifyDrawing(acadDoc)

acadDoc.Close False
Set acadDoc = Nothing
Next i

一見、何の問題もないように見えます。ドキュメントを開き、閉じ、変数を解放しているからです。
しかし、AutoCADのCOMオブジェクトモデルの背後(C++のネイティブ層)では、ドキュメントの開閉に伴うGDIハンドルやデバイスコンテキスト(DC)、そして内部キャッシュが完全に解放しきれず、周回数を重ねるごとに確実にメモリが肥大化していきます。

COMの参照カウンタが0になっても、AutoCAD自体のヒープ領域やWindowsのGDIハンドル枯渇はVBAのガベージコレクション(GC)の制御外です。結果として、2,000枚を超えたあたりでAutoCADの挙動が怪しくなり、最悪の場合はCOM例外を吐いて沈没します。

—

解決策:VBAからの「プロセス単位の自律的リフレッシュ」

この問題を根本から解決するアプローチは一つしかありません。
「一定数の図面を処理したら、AutoCADプロセスを一度完全に殺し、メモリをクリーンな状態にしてからプロセスを再起動する」ことです。

しかし、VBAから直接外部プロセスをコントロールするのは一筋縄ではいきません。さらに、現在のGDIリソース消費量を正確にモニタリングし、安全なタイミングで世代交代を行う必要があります。

ここでは、Windows APIを活用してリソース消費量を監視し、安全にAutoCADを再起動させながら数万枚の図面をも踏破する、プロダクション品質のコードアーキテクチャを公開します。

—

プロダクションコード:自己防衛型・バッチ処理コントローラー

以下のコードは、単なるサンプルではありません。実際の現場で稼働し、数千枚の図面処理を完遂させてきた堅牢な設計を持つモジュールです。

Option Explicit

‘ Windows API Declarations for Process Monitoring & Garbage Collection
If VBA7 Then
Private Declare PtrSafe Function GetCurrentProcessId Lib “kernel32” () As Long
Private Declare PtrSafe Function GetGuiResources Lib “user32” (ByVal hProcess As LongPtr, ByVal uiFlags As Long) As Long
Private Declare PtrSafe Function OpenProcess Lib “kernel32” (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As LongPtr
Private Declare PtrSafe Function CloseHandle Lib “kernel32” (ByVal hObject As LongPtr) As Long
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
‘ 32bit環境互換(必要に応じて)
Private Declare Function GetCurrentProcessId Lib “kernel32” () As Long
Private Declare Function GetGuiResources Lib “user32” (ByVal hProcess As Long, ByVal uiFlags As Long) As Long
Private Declare Function OpenProcess Lib “kernel32” (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib “kernel32” (ByVal hObject As Long) As Long
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If

Const GR_GDIOBJECTS As Long = 0
Const GR_USEROBJECTS As Long = 1
Const PROCESS_QUERY_INFORMATION As Long = &H400

‘ 設定定数
Const BATCH_LIMIT As Long = 100 ‘ 何枚処理ごとにAutoCADを再起動するか
Const GDI_THRESHOLD As Long = 8500 ‘ GDIオブジェクトの安全限界値(OSの制限に依存)

Public Sub ExecuteMassProcessing()
Dim targetFiles As Collection
Set targetFiles = GetTargetDrawingFiles(“C:\MyDrawings\”)

If targetFiles.Count = 0 Then
MsgBox “処理対象の図面がありません。”, vbExclamation
Exit Sub
End If

Dim processedCount As Long
processedCount = 0

Dim fileIndex As Long
For fileIndex = 1 to targetFiles.Count

‘ — 1. ライフサイクル監視とプロセスのリフレッシュ判定 —
If processedCount >= BATCH_LIMIT Or CheckGdiLeak() Then
Debug.Print “— AutoCADリフレッシュを実行します (処理数: ” & processedCount & “) —”

‘ Excel/Access等から外部起動している場合の再接続ロジックをここに挟む
‘ AutoCAD自身から実行している場合は、シェルスクリプト等との連携設計が必要になります
Call RefreshAutoCADSession()

processedCount = 0
End If

‘ — 2. 図面のオープンと堅牢なエラーハンドリング —
Dim currentFile As String
currentFile = targetFiles(fileIndex)

If ProcessSingleDrawing(currentFile) Then
processedCount = processedCount + 1
End If

‘ 念のためのVBAメモリ解放
DoEvents
Next fileIndex

MsgBox “すべての図面処理が正常に完了しました。”, vbInformation
End Sub

Private Function ProcessSingleDrawing(filePath As String) As Boolean
On Error GoTo ErrorHandler

Dim acadDoc As AcadDocument
‘ ReadOnlyモードで開くことで、意図しないロックや不要なトランザクションログの肥大化を防ぐ
Set acadDoc = ThisDrawing.Application.Documents.Open(filePath, True)

‘ 【実務的注意点】 図面が開くまで少しウェイトを入れるか、レイアウトのロードを待つ
‘ 大規模図面ではOpenメソッドの直後にオブジェクトにアクセスするとヌルポインタになることがある

‘ — ここに実際の業務ロジックを記述 —
‘ Call ExecuteBusinessLogic(acadDoc)

acadDoc.Close False ‘ 変更を保存せずに閉じる(読み取り専用の場合)
Set acadDoc = Nothing

ProcessSingleDrawing = True
Exit Function

ErrorHandler:
Debug.Print “Error processing file: ” & filePath & ” | Description: ” & Err.Description
‘ 異常終了時もオブジェクトの参照を確実に破棄
On Error Resume Next
If Not acadDoc Is Nothing Then
acadDoc.Close False
Set acadDoc = Nothing
End If
ProcessSingleDrawing = False
End Function

Private Function CheckGdiLeak() As Boolean
Dim hProc As LongPtr
hProc = OpenProcess(PROCESS_QUERY_INFORMATION, 0, GetCurrentProcessId())

If hProc <> 0 Then
Dim gdiCount As Long
gdiCount = GetGuiResources(hProc, GR_GDIOBJECTS)
CloseHandle hProc

Debug.Print “Current GDI Objects: ” & gdiCount

If gdiCount > GDI_THRESHOLD Then
CheckGdiLeak = True
Exit Function
End If
End If

CheckGdiLeak = False
End Function

Private Sub RefreshAutoCADSession()
‘ 【アーキテクチャ上の設計指針】
‘ AutoCAD内部のVBAから実行している場合、自身をKillすることはできません。
‘ そのため、厳密な数千枚のバッチ処理を行う場合は、Excel等の外部VBAホスト、
‘ または .NET (C#) の外部コンソールアプリケーションからAutoCADをCOM経由で
‘ 起動・終了(Quitメソッド)を繰り返すアーキテクチャを採用するのが鉄則です。

‘ 例: COM経由の場合
‘ acadApp.Quit
‘ Set acadApp = Nothing
‘ Sleep 3000 ‘ プロセス完全消滅まで待機
‘ Set acadApp = New AutoCAD.Application

DoEvents
Sleep 2000 ‘ 簡易的な冷却時間
End Sub

Private Function GetTargetDrawingFiles(folderPath As String) As Collection
Dim col As New Collection
Dim fileName As String

fileName = Dir(folderPath & “.dwg”)
Do While fileName <> “”
col.Add folderPath & fileName
fileName = Dir()
Loop

Set GetTargetDrawingFiles = col
End Function

—

チーフアーキテクトからの実務アドバイス

1. 「読み取り専用(ReadOnly)」の徹底
大量の図面を走査・解析・情報抽出するだけであるならば、`Documents.Open` の第二引数には必ず `True`(読み取り専用)を指定してください。書き込み権限を持ったまま開くと、AutoCAD内部で不必要なUNDO履歴やロックファイルが生成され、メモリプレッシャーが何倍にも跳ね上がります。

2. エラートラップ時のオブジェクトリーク防衛
`On Error GoTo ErrorHandler` を実装する際、エラー発生時に `acadDoc.Close` を呼び出すのは定石ですが、「そのクローズ自体が失敗した場合」の二重トラップ対策(`On Error Resume Next` の局所的利用)を忘れないでください。ここが甘いと、1回目のエラーでルーチン全体のCOM接続が腐敗します。

3. 外部ホスト(Excel / C#)からの制御への移行
もし処理する図面が5,000枚を超えるような極限の現場であれば、AutoCAD内部のVBAIDEからマクロを実行するスタイルは捨て去るべきです。ExcelのVBA、あるいはC#のConsoleアプリを司令塔(マスタープロセス)とし、COM経由でAutoCADを起動・破棄(`Quit`)をループさせるアーキテクチャこそが、トラブルフリーな自動化を実現する唯一の王道です。

プロフェッショナルたるもの、「動けばいい」ではなく、「何千回回してもリソースが1バイトもリークしない」極限の堅牢性をデザインしようではありませんか。あなたの開発する自動化システムが、夜間の残業をゼロにすることを確信しています。

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