【テクニカル・上級編】【上級プロフェッショナル向け】図面保存イベント(BeginSave)をフックした「社外秘情報の自動スクリーニング」 – AutoCAD VBA解析バイブル

スポンサーリンク

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

第4回:図面保存イベント(BeginSave)をフックした「社外秘情報の自動スクリーニング」

AutoCAD VBAにおいて、図面のライフサイクル管理は単なるファイルの入出力ではない。メモリ空間上のCOMオブジェクトの生死、トランザクションの整合性、そしてヒューマンエラーによる情報漏洩の防止という、極めて高度なガバナンス領域である。

本稿では、`DocumentCollection` イベントである `BeginSave` を完全に掌握し、社外へ図面が流出する刹那に「機密情報の自動スクリーニングと保存の強制中断」を実現するアーキテクチャを提示する。

—

1. AutoCADイベントハンドリングの深層と落とし穴

VBAにおけるイベント処理は、一見すると単純な `WithEvents` 宣言によるルーチンのアタッチに過ぎない。しかし、AutoCADというマルチドキュメント(SDI/MDI)かつ非同期処理が混在する巨大なCOMサーバーの内部挙動を理解していなければ、メモリリーク、クラッシュ、あるいは「イベントが捕捉されない」という致命的な不具合に直面する。

特に `BeginSave` イベントは、「ディスクに書き込みが物理的に開始される直前」に発火する。このタイミングで例外をスローするか、ドキュメントのステータスを操作することで、不正なデータの外部流出を根絶することが可能となる。

—

2. アーキテクチャ設計:安全なフックとガバナンスのメカニズム

今回の実装では、以下の要件を満たすコードベースを構築する。

1. クラスモジュールによるイベントの永続化: グローバル領域で `AcadApplication` および `Document` のイベントを監視。
2. 高速パターンマッチング: レイヤー名、特定のブロック属性、およびモデル空間内の特定の文字列(正規表現を利用)を走査。
3. トランザクションの厳密な管理とメモリ最適化: COMオブジェクトの参照を確実に解放し、VBAランタイムの肥大化を防ぐ。

実装コード:`ClsSecurityMonitor.cls` (クラスモジュール)

VERSION 1.0 CLASS
BEGIN
MultiUse = -1 ‘True
END
Attribute VB_Name = “ClsSecurityMonitor”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
‘ ==============================================================================
‘ 伝説のチーフアーキテクトによる実装:BeginSave セキュリティスクリーニング
‘ ==============================================================================
Option Explicit

Private WithEvents AppEvents As AcadApplication
Private isInitialized As Boolean

‘ クラス初期化時にAutoCADアプリケーションイベントを捉える
Public Sub Initialize()
If isInitialized Then Exit Sub
Set AppEvents = ThisDrawing.Application
isInitialized = True
Debug.Print “[SecurityMonitor] Initialized successfully.”
End Sub

‘ クラス終了時のクリーンアップ
Private Sub Class_Terminate()
Set AppEvents = Nothing
isInitialized = False
Debug.Print “[SecurityMonitor] Terminated.”
End Sub

‘ BEGIN SAVE イベントのフック
Private Sub AppEvents_BeginSave(ByVal DocumentLogName As String)
On Error GoTo ErrorHandler

Dim targetDoc As AcadDocument
Set targetDoc = ThisDrawing.Application.ActiveDocument

‘ 1. 非表示レイヤーの機密チェック
If CheckConfidentialLayers(targetDoc) Then
GoTo BlockSave
End If

‘ 2. 特定文字列(社外秘キーワード)の走査
If CheckConfidentialText(targetDoc) Then
GoTo BlockSave
End If

Exit Sub

BlockSave:
‘ 保存の中断処理(AutoCADの内部エラーを意図的に発生させてセーブをキャンセル)
MsgBox “【セキュリティ警告】この図面には社外秘情報または未承認のレイヤーが含まれています。” & vbCrLf & _
“保存処理は強制的に中断されました。”, vbCritical + vbOKOnly, “AutoCAD Security Governance”

‘ エラーを発生させてネイティブの保存処理をアボートさせる
Err.Raise Number:=vbObjectError + 1000, _
Source:=”SecurityMonitor”, _
Description:=”Confidential information detected. Save aborted by system policy.”

Exit Sub

ErrorHandler:
If Err.Number <> (vbObjectError + 1000) Then
MsgBox “セキュリティチェック中に予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End If
‘ エラー情報をクリアしてAutoCADの強制終了を防ぐ
Err.Clear
End Sub

‘ ——————————————————————————
‘ 内部関数:機密レイヤーの存在チェック
‘ ——————————————————————————
Private Function CheckConfidentialLayers(ByVal doc As AcadDocument) As Boolean
CheckConfidentialLayers = False
Dim lay As AcadLayer

For Each lay In doc.Layers
‘ 例: “CONFIDENTIAL” や “SECRET” で始まるレイヤーが存在するか
If UCase(Left(lay.Name, 5)) = “SEC_” Or UCase(Left(lay.Name, 6)) = “CONF_” Then
‘ さらに、そのレイヤーがFrozenまたはOffであっても、図面内に存在すること自体を検知する場合
CheckConfidentialLayers = True
Exit Function
End If
Next lay

‘ メモリ解放
Set lay = Nothing
End Function

‘ ——————————————————————————
‘ 内部関数:テキストエンティティの走査
‘ ——————————————————————————
Private Function CheckConfidentialText(ByVal doc As AcadDocument) As Boolean
CheckConfidentialText = False

Dim ent As AcadEntity
Dim mTextObj As AcadMText
Dim textObj As AcadText
Dim blockRef As AcadBlockReference

‘ モデル空間を走査
Dim i As Long
For i = 0 To doc.ModelSpace.Count – 1
Set ent = doc.ModelSpace.Item(i)

If TypeOf ent is AcadText Then
Set textObj = ent
If InStr(1, textObj.TextString, “社外秘”, vbTextCompare) > 0 Then
CheckConfidentialText = True
Exit For
End If
ElseIf TypeOf ent is AcadMText Then
Set mTextObj = ent
If InStr(1, mTextObj.TextString, “社外秘”, vbTextCompare) > 0 Then
CheckConfidentialText = True
Exit For
End If
End If
Next i

‘ オブジェクト参照の明示的破棄(メモリ最適化の極意)
Set ent = Nothing
Set mTextObj = Nothing
Set textObj = Nothing
End Function

—

3. グローバルモジュールからのライフサイクル制御

クラスモジュール単体ではインスタンスが消滅するため、図面オープン時に自動でインスタンスを生成し、永続化させる必要がある。

実装コード:`ModAutoLoad.bas` (標準モジュール)

Option Explicit

‘ グローバル変数としてセキュリティモニターを保持
Public G_SecurityMonitor As ClsSecurityMonitor

‘ 図面オープン時(あるいはアドインロード時)のエントリポイント
Public Sub InitializeSecurityMonitor()
If G_SecurityMonitor Is Nothing Then
Set G_SecurityMonitor = New ClsSecurityMonitor
G_SecurityMonitor.Initialize
MsgBox “社外秘情報自動スクリーニング・システムが有効化されました。”, vbInformation, “System Ready”
End If
End Sub

‘ 終了時のクリーンアップ
Public Sub TerminateSecurityMonitor()
Set G_SecurityMonitor = Nothing
End Sub

—

4. チーフアーキテクトが指摘する「実運用上の罠と最適化」

1. COMオブジェクトの参照ループとメモリリーク:
AutoCAD VBAにおける最大の悪夢は、`For Each` ループやエンティティの取得時に発生するCOM参照の解放漏れである。特に `ModelSpace` や `Layers` コレクションを走査する際は、ループ内で取得したオブジェクト変数(`Set ent = …`)を、ループの抜け際や処理の完了時に必ず `Set xxx = Nothing` で明示的に解放しなければならない。これを怠ると、AutoCADのプロセス内にゴミメモリが蓄積し、長時間の稼働で致命的なパフォーマンス低下を招く。

2. 例外トラップの設計思想:
`BeginSave` 内で `Err.Raise` を発生させて保存を中断させるアプローチは、強力であると同時に諸刃の剣である。キャッチし損ねたエラーがAutoCAD本体のセッションをクラッシュさせ、未保存のデータを吹き飛ばすリスクがある。必ず `On Error GoTo ErrorHandler` を厳格に記述し、異常系においてもCADが安全に継続稼働できるフェイルセーフを担保すること。

3. 大規模図面におけるパフォーマンスチューニング:
数万〜数百万のエンティティを持つ実務図面において、全てのエンティティをVBAの `For` ループで線形探索するのは、処理時間が数秒〜数十秒に達し実用的ではない。
本番環境でこれを極限まで高速化する場合、VBA単体での力技を避け、ObjectARX (.NET API) による非同期インデックススキャンをCOM経由で呼び出す、あるいは必要なレイヤー・画層情報のみをピンポイントでクエリする設計(SelectionSetの活用など)へ昇華させるべきである。

—

結語

VBAはレガシーな言語として語られがちだが、AutoCADの深部(イベントモデルとCOMインターフェース)を正しく理解したエンジニアが操るとき、それは強固なエンタープライズ・ガバナンスツールへと変貌する。
本稿で示したコードをベースに、各社のセキュリティポリシーに合わせた正規表現エンジンの統合や、外部API(LMSやPDMサーバー)との連携を実装せよ。妥協なきアーキテクチャこそが、プロフェッショナルエンジニアの証である。

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