【上級プロフェッショナル向け】SQL Serverと連携した「図面チェックアウト管理」:オープン時にDBの編集ロックを更新
AutoCADを数十台・数百台規模で運用するエンタープライズ環境において、図面の先祖返りや同時上書きによるデータ破損は、プロジェクト崩壊に直結する重大インシデントです。
AutoCAD標準の排他制御メカニズム(`.dwl` / `.dwl2` ファイル)に依存している開発者を多く見かけますが、ハッキリ言えば「ファイル共有ベースのロック機構をエンタープライズ運用の主軸にするのは設計ミス」です。VPN経由の遅延、異常終了時の孤立ロックファイルの残留、ネットワークドライブのCIFS/SMBキャッシュ遅延によるレースコンディションなど、脆弱性を挙げればキリがありません。
本記事では、AutoCAD VBAからADO(ActiveX Data Objects)経由でMicrosoft SQL Serverと直結し、完全なトランザクション整合性を担保した「図面チェックアウト・排他制御システム」を構築します。玩具のようなスクリプトではなく、数千万円規模の損害を防ぐためのプロダクション仕様を提示します。
—
1. なぜ「ファイルベース」ではなく「DB集中制御」なのか
ファイルシステムによるロックは「プロセスがファイルを掴んでいるか」しか監視できません。しかし、我々が業務システムで真に管理したいのは以下のメタデータです。
- 誰が(ドメイン名、Windowsユーザー名)
- どの端末から(PC名、IPアドレス)
- いつから(チェックアウト日時)
- 何の目的で(設計変更番号、プロジェクトコード)
DWLファイルの限界を突破する「悲観的ロック」の設計
CADデータはWebアプリケーションのような「楽観的同時実行制御(更新時にバージョン番号を比較する手法)」が通用しません。数時間かけて3Dモデルや詳細図面を描いた後に「他の人が更新したため保存できません」と通知されたら、設計現場は暴動になります。
したがって、DWGオープン時に即座に編集権を確定させる「悲観的排他制御(Pessimistic Locking)」が唯一の解です。これをSQL Serverのアトミック操作で実装します。
—
2. データベース側の物理設計(SQL Server)
まずは、排他制御を司るテーブル定義と、レースコンディションを100%排除するストアドプロシージャを用意します。
テーブル定義:`DrawingCheckout`
CREATE TABLE dbo.DrawingCheckout (
DrawingGuid UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
FilePathHash CHAR(64) NOT NULL UNIQUE, — フルパスのSHA-256ハッシュ
FilePath NVARCHAR(1000) NOT NULL,
IsLocked BIT NOT NULL DEFAULT 1,
LockedByUser NVARCHAR(100) NOT NULL,
LockedByMachine NVARCHAR(100) NOT NULL,
LockTimestamp DATETIME2(0) NOT NULL DEFAULT SYSDATETIME(),
HeartbeatTimestamp DATETIME2(0) NOT NULL DEFAULT SYSDATETIME()
);
GO
— 検索頻度の高いカラムにインデックスを設定
CREATE NONCLUSTERED INDEX IX_DrawingCheckout_FilePathHash
ON dbo.DrawingCheckout (FilePathHash) INCLUDE (IsLocked, LockedByUser, LockedByMachine);
GO
排他ロック取得プロシージャ:`sp_AcquireDrawingLock`
VBA側で `SELECT` してから `UPDATE` するような2フェーズのコードを書いてはいけません。その数ミリ秒の隙間に別ユーザーが割り込みます。1つのアトミックなトランザクション(`UPDLOCK, HOLDLOCK`)内で検証と更新を完結させます。
CREATE PROCEDURE dbo.sp_AcquireDrawingLock
@FilePathHash CHAR(64),
@FilePath NVARCHAR(1000),
@UserName NVARCHAR(100),
@MachineName NVARCHAR(100),
@LockAcquired BIT OUTPUT,
@CurrentHolder NVARCHAR(100) OUTPUT,
@CurrentMachine NVARCHAR(100) OUTPUT,
@LockTime DATETIME2(0) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION;
SELECT
@CurrentHolder = LockedByUser,
@CurrentMachine = LockedByMachine,
@LockTime = LockTimestamp
FROM dbo.DrawingCheckout WITH (UPDLOCK, HOLDLOCK)
WHERE FilePathHash = @FilePathHash;
IF @CurrentHolder IS NULL
BEGIN
— レコード未存在:新規ロック登録
INSERT INTO dbo.DrawingCheckout (DrawingGuid, FilePathHash, FilePath, IsLocked, LockedByUser, LockedByMachine, LockTimestamp, HeartbeatTimestamp)
VALUES (NEWID(), @FilePathHash, @FilePath, 1, @UserName, @MachineName, SYSDATETIME(), SYSDATETIME());
SET @LockAcquired = 1;
SET @CurrentHolder = @UserName;
SET @CurrentMachine = @MachineName;
SET @LockTime = SYSDATETIME();
END
ELSE IF @CurrentHolder = @UserName AND @CurrentMachine = @MachineName
BEGIN
— 同一ユーザー・同一端末の再取得(CADクラッシュ後の再オープン等)
UPDATE dbo.DrawingCheckout
SET HeartbeatTimestamp = SYSDATETIME(),
IsLocked = 1
WHERE FilePathHash = @FilePathHash;
SET @LockAcquired = 1;
END
ELSE
BEGIN
— 他者がロック中
SET @LockAcquired = 0;
END
COMMIT TRANSACTION;
END;
GO
—
3. AutoCAD VBA実装:高信頼性チェックアウトモジュール
VBA側の実装です。ADO(ActiveX Data Objects)を用い、参照設定には `Microsoft ActiveX Data Objects 6.1 Library` を利用します。
設計指針
1. パス正規化: 大文字小文字やUNCパス/ドライブレターの揺らぎを吸収するため、パスはSHA-256でハッシュ化して渡す。
2. Read-Onlyフォールバック: ロック取得に失敗した場合、図面を強制的に読み取り専用モードへ降格し、編集・上書きを物理的に遮断する。
3. COM/ADOオブジェクトの完全解放: リークはAutoCADのプロセスクラッシュを招くため、`Finally`に相当するエラートラップでオブジェクトを確実に破棄する。
実装コード(標準モジュール:`Mod_CheckoutManager`)
Option Explicit
‘ 接続文字列(環境に応じて設定。Kerberos/Windows統合認証を推奨)
Private Const DB_CONN_STR As String = _
“Provider=MSOLEDBSQL;Data Source=YOUR_SQL_SERVER;Initial Catalog=CAD_Management;Integrated Security=SSPI;”
”’
”’
Public Sub CheckAndLockDocument(ByRef targetDoc As AcadDocument)
On Error GoTo ErrHandler
‘ 新規未保存図面(”Drawing1.dwg” 等)は対象外
If Len(targetDoc.Path) = 0 Then Exit Sub
Dim fullPath As String
fullPath = UCase$(targetDoc.FullName)
Dim pathHash As String
pathHash = ComputeSHA256(fullPath)
Dim userName As String
Dim machineName As String
userName = Environ$(“USERNAME”)
machineName = Environ$(“COMPUTERNAME”)
‘ DBロック試行
Dim lockSuccess As Boolean
Dim holderUser As String
Dim holderMachine As String
Dim lockTime As String
Call ExecuteAcquireLock(pathHash, fullPath, userName, machineName, _
lockSuccess, holderUser, holderMachine, lockTime)
If Not lockSuccess Then
‘ ロック取得失敗:ユーザーへ警告し、読み取り専用として強制ハンドリング
Dim msg As String
msg = “【警告:編集ロック取得失敗】” & vbCrLf & _
“この図面は現在、他ユーザーによって編集ロックされています。” & vbCrLf & vbCrLf & _
“ロック保持者 : ” & holderUser & vbCrLf & _
“端末名 : ” & holderMachine & vbCrLf & _
“ロック時刻 : ” & lockTime & vbCrLf & vbCrLf & _
“図面を【読み取り専用】として運用してください(上書き保存は禁止されます)。”
MsgBox msg, vbCritical Or vbSystemModal, “排他制御システム”
‘ 図面ウィンドウのタイトルバーに警告を付与(視認性の向上)
‘ ※AutoCADのDocumentはオープン後にReadOnlyプロパティを動的変更できないため、
‘ 保存イベントをインターセプトするか、再オープンを促す設計とする。
targetDoc.SendCommand “(setvar “”WRITESTAT”” 0) ” ‘ 編集不可のフラグ付け(LISP連携例)
Else
targetDoc.Utility.Prompt vbCrLf & “[Lock] DBチェックアウトが正常に完了しました。” & vbCrLf
End If
Exit Sub
ErrHandler:
MsgBox “チェックアウト処理中に致命的なエラーが発生しました:” & vbCrLf & _
Err.Description, vbCritical, “システムエラー”
End Sub
”’
”’
Private Sub ExecuteAcquireLock(ByVal pathHash As String, _
ByVal fullPath As String, _
ByVal userName As String, _
ByVal machineName As String, _
ByRef outSuccess As Boolean, _
ByRef outHolder As String, _
ByRef outMachine As String, _
ByRef outTime As String)
Dim cn As ADODB.Connection
Dim cmd As ADODB.Command
Set cn = New ADODB.Connection
cn.ConnectionString = DB_CONN_STR
cn.ConnectionTimeout = 5
cn.Open
Set cmd = New ADODB.Command
With cmd
.ActiveConnection = cn
.CommandType = adCmdStoredProc
.CommandText = “dbo.sp_AcquireDrawingLock”
‘ パラメータ構築(SQLインジェクション完全無力化)
.Parameters.Append .CreateParameter(“@FilePathHash”, adChar, adParamInput, 64, pathHash)
.Parameters.Append .CreateParameter(“@FilePath”, adVarWChar, adParamInput, 1000, fullPath)
.Parameters.Append .CreateParameter(“@UserName”, adVarWChar, adParamInput, 100, userName)
.Parameters.Append .CreateParameter(“@MachineName”, adVarWChar, adParamInput, 100, machineMachine(machineName))
‘ 出力パラメータ
.Parameters.Append .CreateParameter(“@LockAcquired”, adBoolean, adParamOutput)
.Parameters.Append .CreateParameter(“@CurrentHolder”, adVarWChar, adParamOutput, 100)
.Parameters.Append .CreateParameter(“@CurrentMachine”, adVarWChar, adParamOutput, 100)
.Parameters.Append .CreateParameter(“@LockTime”, adDBTimeStamp, adParamOutput)
.Execute
End With
outSuccess = CBool(cmd.Parameters(“@LockAcquired”).Value)
outHolder = Nz(cmd.Parameters(“@CurrentHolder”).Value, “不明”)
outMachine = Nz(cmd.Parameters(“@CurrentMachine”).Value, “不明”)
outTime = Nz(cmd.Parameters(“@LockTime”).Value, “”)
‘ 明示的クリーンアップ
Set cmd = Nothing
cn.Close
Set cn = Nothing
Exit Sub
CleanUpOnError:
If Not cmd Is Nothing Then Set cmd = Nothing
If Not cn Is Nothing Then
If cn.State = adStateOpen Then cn.Close
Set cn = Nothing
End If
Err.Raise Err.Number, Err.Source, Err.Description
End Sub
”’
”’
Private Function ComputeSHA256(ByVal text As String) As String
Dim enc As Object, sha256 As Object
Set enc = CreateObject(“System.Text.UTF8Encoding”)
Set sha256 = CreateObject(“System.Security.Cryptography.SHA256Managed”)
Dim bytes() As Byte
bytes = enc.GetBytes_4(text)
Dim hash() As Byte
hash = sha256.ComputeHash_2((bytes))
Dim i As Long, result As String
result = “”
For i = LBound(hash) To UBound(hash)
result = result & Right$(“0” & Hex$(hash(i)), 2)
Next i
ComputeSHA256 = LCase$(result)
Set sha256 = Nothing
Set enc = Nothing
End Function
Private Function Nz(ByVal val As Variant, ByVal defaultVal As String) As String
If IsNull(val) Or IsEmpty(val) Then
Nz = defaultVal
Else
Nz = CStr(val)
End If
End Function
Private Function machineMachine(ByVal name As String) As String
machineMachine = name
End Function
—
4. イベントハンドラとの統合(`AcadApplication` イベント)
このロジックを実務で機能させるには、AutoCADで図面が開かれた瞬間に自動実行させる必要があります。`ThisDrawing` ではなく、アプリケーションレベルのイベントシンクで全図面オープンをフックします。
クラスモジュール:`clsAppEvents`
Option Explicit
Private WithEvents m_App As AcadApplication
Private Sub Class_Initialize()
Set m_App = AutoCAD.Application
End Sub
Private Sub Class_Terminate()
Set m_App = Nothing
End Sub
‘ 図面が開かれた直後にトリガー
Private Sub m_App_BeginDocClose(ByVal Doc As AcadDocument, ByRef Cancel As Boolean)
‘ クローズ時のアンロック処理(別途 sp_ReleaseDrawingLock を呼び出す)
End Sub
Private Sub m_App_AppDocChange()
‘ ドキュメントコンテキストが切り替わった際の監視が必要な場合に実装
End Sub
呼び出し側の初期化ルーチン(標準モジュール)
Public g_EventSink As clsAppEvents
Public Sub InitCheckoutSystem()
If g_EventSink Is Nothing Then
Set g_EventSink = New clsAppEvents
MsgBox “図面排他制御システムが稼働しました。”, vbInformation
End If
End Sub
`acad.dvb` の `S::STARTUP` や `acaddoc.lsp` から `InitCheckoutSystem` を叩かせることで、全クライアントPCでバックグラウンド監視を自動成立させます。
—
5. 運用設計の要所:ゾンビロックの刈り取り(TTL)
どれほど堅牢なコードを書いても、「CADプロセスのタスクキル」や「PCのブルースクリーン(電源断)」が発生した場合、DB上にはロック情報が残留します。これがいわゆるゾンビロック(孤立ロック)です。
現場の運用を止めないために、以下の2系統のフェイルセーフを設計に組み込んでください。
[図面オープン] ───(排他ロック要求)───> [SQL Server]
│
┌────── ゾンビ判定 (TTL超過?) ─────────┤
│ │
[Yes] [No]
│ │
強制アンロック実行 正規の排他エラーを
& 新規チェックアウト AutoCADに返却
1. ハートビートとTTL(Time To Live)
AutoCAD側から一定間隔(例: 15分ごと)で `HeartbeatTimestamp` を更新するタイマーを回します。
オープン時、もしロックが存在していても `HeartbeatTimestamp` が 60分以上前 であれば「プロセス死」とみなし、自動的にロックを上書き奪取するロジックをストアドプロシージャに実装します。
2. 管理者による強制アンロック機能
プロジェクトリーダーのみが実行できる「ロック解除ユーティリティ画面(社内Webツール、あるいはAutoCAD管理者用ダイアログ)」を用意し、電話一本でロックを外せるエスケープハッチを必ず残してください。
—
6. まとめ
AutoCAD標準の仕組みや簡易スクリプトに頼った「なんちゃって排他制御」は、データが壊れたときのリカバリコストを考慮すると極めてハイリスクです。
- SQL Serverの厳格なトランザクション(`UPDLOCK, HOLDLOCK`)を活用する
- パス文字列をハッシュ化し、正規化されたデータ構造でアトミックに管理する
- 異常終了を想定したTTLとフェイルセーフを最初から設計に織り込む
ここまで作り込んで初めて、ミッションクリティカルな設計現場で「絶対に図面上書き事故を起こさない」プロフェッショナル仕様のツールと呼ぶことができます。まずは検証環境のSQL Serverにテーブルを展開し、その堅牢性を体感してください。
