【テクニカル・上級編】【上級プロフェッショナル向け】大量図面処理における「GDIリソース」監視と、リソース枯渇前のAcadApplication自動再起動 – AutoCAD VBA解析バイブル

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

【極限の自動化】GDIリソース枯渇を回避する、AutoCADバッチ処理の自己修復アーキテクチャ

数百、数千という膨大な図面ファイルを連続して処理する。これは、自動化エンジニアにとって日常的な、しかし極めて「危険な」任務である。

多くの開発者は、単に `Documents.Open` と `SaveAs` をループさせるだけのコードを書く。しかし、そのコードは必ず「ある時」に死ぬ。数時間、あるいは数日走り続けた後に、AutoCADは突然の「Out of Memory」を吐き出すか、あるいはWindowsごとフリーズさせ、システム全体を不安定な泥沼へと引きずり込む。

なぜ、完璧に見えるコードが失敗するのか? その答えは、VBAの変数管理の範疇を超えた、Windows OSレベルのリソース管理、とりわけGDI(Graphics Device Interface)ハンドルの枯渇にある。

本稿では、レガシーな環境から最新のワークフローまでを支える、プロフェッショナルが実装すべき「自己修復型バッチ処理アーキテクチャ」の真髄を解説する。

1. 敗北の真因:なぜ `Set obj = Nothing` では足りないのか

VBAにおけるオブジェクトの解放、すなわち `Set obj = Nothing` は、あくまでCOM(Component Object Model)のリファレンスカウントを減らす行為に過ぎない。

しかし、AutoCADのような巨大なアプリケーションにおいて、図面を開き、図形を生成し、表示を更新するプロセスは、OSのグラフィックスリソースであるGDIハンドルを大量に消費する。

GDIリソースの不可視な蓄積

GDIハンドルは、ウィンドウの描画、フォント、ビットマップ、ペン、ブラシなどの描画オブジェクトを管理するためのOSリソースである。
AutoCADの内部エンジン(C++層)は、VBAからオブジェクトが解放された後も、描画キャッシュや内部的な図形管理のために、これらのハンドルを即座に解放しないことが多々ある。

これが蓄積し、OSが許容するハンドル上限に達した瞬間、AutoCADは描画不能に陥り、強制終了(Crash)する。この現象は、単純なメモリ(RAM)不足とは全く別の、より致命的な「リソース枯渇」である。

2. 監視戦略:Windows APIによるリソースの定量化

エンジニアが直感で「そろそろ重くなってきた」と感じるのは、敗北の兆候でしかない。プロフェッショナルは、Windows APIを用いて、プロセスが消費しているリソースを数値として監視しなければならない。

具体的には、以下の2点を監視対象とする。
1. GDI Handles: グラフィックス描画リソースの枯渇監視。
2. Working Set (Memory): プロセスが占有している物理メモリ量。

以下に、これらを計測するためのWindows APIの宣言と実装例を示す。

' --- API Declarations ---
Option Explicit

If VBA7 Then

' 64bit環境用 Declare PtrSafe Function GetGuiResources Lib "user32" (ByVal hProcess As LongPtr) As Long Declare PtrSafe Function GetCurrentProcess Lib "kernel32" () As LongPtr Declare PtrSafe Function GetProcessMemoryInfo Lib "psapi.dll" ( _ ByVal hProcess As LongPtr, _ ByRef lpProcessMemoryInfo As PROCESS_MEMORY_COUNTERS, _ ByVal cb As Long) As Long

Else

' 32bit環境用 Declare Function GetGuiResources Lib "user32" (ByVal hProcess As Long) As Long Declare Function GetCurrentProcess Lib "kernel32" () As Long Declare Function GetProcessMemoryInfo Lib "psapi.dll" ( _ ByVal hProcess As Long, _ ByRef lpProcessMemoryInfo As PROCESS_MEMORY_COUNTERS, _ ByVal cb As Long) As Long

End If

' プロセスメモリ情報の構造体 Type PROCESS_MEMORY_COUNTERS cb As Long PageFaultCount As Long PeakWorkingSetSize As LongPtr WorkingSetSize As LongPtr QuotaPeakPagedPoolUsage As LongPtr QuotaPagedPoolUsage As LongPtr QuotaPeakNonPagedPoolUsage As LongPtr QuotaNonPagedPoolUsage As LongPtr PagefileUsage As LongPtr PeakPagefileUsage As LongPtr End Type ''' <summary> ''' 現在のプロセスのGDIハンドル数を取得する ''' </summary> Function GetCurrentGDIHandleCount() As Long Dim hProcess As LongPtr hProcess = GetCurrentProcess() GetCurrentGDIHandleCount = GetGuiResources(hProcess) End Function ''' <summary> ''' 現在のプロセスのWorking Set (物理メモリ使用量) をMB単位で取得する ''' </summary> Function GetCurrentMemoryUsageMB() As Double Dim hProcess As LongPtr Dim pmc As PROCESS_MEMORY_COUNTERS Dim ret As Long hProcess = GetCurrentProcess() pmc.cb = LenB(pmc) ret = GetProcessMemoryInfo(hProcess, pmc, pmc.cb) If ret <> 0 Then ' WorkingSetSize (バイト) を MB に変換 GetCurrentMemoryUsageMB = Round(pmc.WorkingSetSize / 1024 / 1024, 2) Else GetCurrentMemoryUsageMB = 0 End If End Function

3. アーキテクチャ設計:外部コントローラー・パターン

ここで、極めて重要な設計上の決定事項がある。

「AutoCAD自身に、自分自身を再起動させるロジックを書いてはならない」

AutoCAD内部(VBAプロジェクト内)で、リソース枯渇を検知してプロセスを終了させようとしても、プロセスが不安定な状態では、終了処理すら正常に完結しないリスクがある。また、再起動後の「次にどのファイルから再開するか」という状態管理(State Management)も、自身のプロセス内では困難である。

正解:外部コントローラーによるオーケストレーション

真のプロフェッショナルは、AutoCADを「Worker(作業員)」、別の軽量なアプリケーション(Excel VBAやスタンドアロンのVBScript/VB.NET)を「Manager(管理者)」として分離する。

# システム構成図

1. Manager (Excel/VBScript):

  • 処理対象のファイルリストを管理。
  • AutoCADのプロセスを起動。
  • 定期的にAutoCADのPID(プロセスID)を監視し、GDI/メモリ使用量をチェック。
  • 閾値を超えた場合、AutoCADを強制終了(TaskKill)し、新しいAutoCADプロセスを立ち上げて、次のファイルから再開。

2. Worker (AutoCAD VBA):

  • Managerから渡されたファイルを開き、処理して保存する。
  • 終了時には必ず `Set obj = Nothing` を行い、リソースの解放を試みる。

# Manager側の実装ロジック(疑似コード)

' --- Manager側 (Excel VBA等の外部プロセス) のメインループ ---
Sub ExecuteMassiveBatch()
    Dim fileList As Collection
    Set fileList = GetFileList("C:\Projects\DWG_Batch\")
    
    Dim currentIndex As Integer: currentIndex = 1
    Dim isRunning As Boolean: isRunning = True
    
    Do While currentIndex <= fileList.Count
        ' 1. AutoCADを起動し、Workerに渡す
        ' (ここでAutoCADを起動し、特定のコマンドを実行させる)
        LaunchAutoCADWorker fileList(currentIndex)
        
        ' 2. 実行中、リソースを監視するループ
        Do
            DoEvents
            Sleep 5000 ' 5秒おきに監視
            
            Dim gdiCount As Long: gdiCount = GetRemoteGDIHandleCount(acadPID)
            Dim memUsage As Double: memUsage = GetRemoteMemoryUsage(acadPID)
            
            ' 3. 閾値判定 (例: GDIハンドルが10,000を超えたら危険)
            If gdiCount > 10000 Or memUsage > 4096 Then
                Debug.Print "Threshold exceeded. Restarting AutoCAD..."
                KillAutoCADProcess acadPID
                Exit Do ' 監視ループを抜けて、次のAutoCAD起動へ
            End If
            
            ' Workerが正常終了したか確認
            If Not IsProcessRunning(acadPID) Then
                ' 処理完了またはエラー終了
                Exit Do
            End If
        Loop
        
        ' 次のファイルへ
        currentIndex = currentIndex + 1
    Loop
    
    MsgBox "All tasks completed successfully."
End Sub

4. 結論:堅牢な自動化への規律

大量の図面処理における自動化は、単なる「コードの記述」ではなく、「実行環境の制御」である。

1. リソースの限界を認める: AutoCADは無限にメモリを消費できる魔法の道具ではない。
2. 観測可能性(Observability)を確保する: APIを用いて、OSレベルの数値を常に監視せよ。
3. 関心の分離(Separation of Concerns)を徹底する: 実行(Worker)と管理(Manager)を分離し、致命的な事態に備えた「リセットボタン」を設計に組み込め。

このアーキテクチャを採用することで、あなたのシステムは、深夜のサーバー室で音もなく力尽きることはなくなる。それは、設計者がシステムの「死」を予見し、あらかじめ「再生」の仕組みを組み込んだ証である。

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