—
title: SolidWorks VBAを掌握する極限の知見:PDMアドインに頼らない堅牢なセッション管理と排他制御
date: 2023-10-27
categories: [SolidWorks VBA, システム連携]
tags: [SolidWorks, VBA, PDM, Windows API, 排他制御, メタデータ, レガシーシステム]
—
永きにわたり、CADと生産システムの自動化の最前線に立ち続けてきた者として、諸君が日々直面するであろう「理想と現実」の狭間での葛藤を、私は肌で知っている。特にSolidWorks VBAを活用したシステム連携において、既成のPDMアドインに依存せず、より低レベルな制御を求める声は、現場の真の課題を映し出している。
本稿では、その極限の領域、すなわちSolidWorksアセンブリファイルのバイナリ・メタデータ解析と、VBAから直接外部PDMシステムへのチェックイン・チェックアウト制御に焦点を当てる。専用アドインの呪縛から解放され、標準APIとファイルシステム制御を駆使することで、ローカルキャッシュとサーバーリポジトリ間の同期を安全に行う、堅牢なセッション管理と排他制御の実装パターンを、私の魂を込めて解説しよう。
これは、単なるリファレンスの引き写しではない。オブジェクトのライフサイクル、パフォーマンスの重み、そしてシステム間連携の深淵を知り尽くした者にしか語り得ない「真実」である。シニアエンジニア、そして社内システム管理者諸君よ、覚悟して読み進めてほしい。
PDM連携の深層:なぜ「ファイルをコピーするだけ」では足りないのか
SolidWorksファイル、特にアセンブリファイルは、そのバイナリ構造の奥底に、参照部品へのパス、構成情報、そして数多のカスタムプロパティといった、CADシステムとしての生命線を宿している。PDMシステムが提供する価値は、単なるファイルの保管庫ではない。バージョン管理、排他制御、そしてこれら複雑な参照関係の整合性を保つことにある。
PDMアドインは、SolidWorksの内部APIを介してこれらの複雑な処理を抽象化し、ユーザーに透過的な操作を提供する。しかし、そのアドインが特定のPDMベンダーに強く依存している場合、コスト、互換性、あるいは既存のレガシーシステムとの連携において、極めて大きな制約となることがある。
我々が目指すのは、この「アドインの抽象化レイヤー」を自らの手で再構築し、ファイルシステムレベルでの制御とSolidWorks APIを組み合わせることで、PDMシステムの機能をVBAから直接、かつ安全にエミュレートすることだ。これは、究極の柔軟性と、時に既存のPDMでは対応しきれないニッチな業務フローへの対応を可能にする。
しかし、この道のりは険しい。SolidWorksファイル内部の参照パスは、ローカルキャッシュとサーバーリポジトリで異なる場合があり、単純なコピーでは「参照が見つからない」という致命的なエラーを引き起こす。また、複数のユーザーが同時にファイルを編集しないよう、厳格な排他制御が必須となる。
堅牢なセッション管理と排他制御:Windows APIの活用
PDMシステムが提供する排他制御は、多くの場合、ファイルシステムレベルでのロックや、データベース上のフラグ管理によって実現されている。VBAからこれを模倣するには、Windows APIを直接呼び出し、OSが提供する低レベルな機能を利用することが不可欠だ。
1. ファイルシステムレベルでの排他制御:LockFileExの真髄
`LockFileEx`関数は、ファイルの一部または全体を排他的にロックするための強力なメカニズムを提供する。これは、複数のプロセスが同時にファイルに書き込むことを防ぎ、データの整合性を保証するために不可欠である。
Windows APIの宣言
.net
‘ — Windows API 宣言 —
Private Declare PtrSafe Function CreateFile Lib “kernel32” Alias “CreateFileW” ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long) As LongPtr
Private Declare PtrSafe Function LockFileEx Lib “kernel32” ( _
ByVal hFile As LongPtr, _
ByVal dwFlags As Long, _
ByVal dwReserved As Long, _
ByVal nNumberOfBytesToLockLow As Long, _
ByVal nNumberOfBytesToLockHigh As Long, _
ByRef lpOverlapped As OVERLAPPED) As Long
Private Declare PtrSafe Function UnlockFileEx Lib “kernel32” ( _
ByVal hFile As LongPtr, _
ByVal dwReserved As Long, _
ByVal nNumberOfBytesToUnlockLow As Long, _
ByVal nNumberOfBytesToUnlockHigh As Long, _
ByRef lpOverlapped As OVERLAPPED) As Long
Private Declare PtrSafe Function CloseHandle Lib “kernel32” ( _
ByVal hObject As LongPtr) As Long
‘ — 定数と構造体 —
Private Const GENERIC_READ As Long = &H80000000
Private Const GENERIC_WRITE As Long = &H40000000
Private Const FILE_SHARE_READ As Long = &H1
Private Const FILE_SHARE_WRITE As Long = &H2
Private Const FILE_SHARE_DELETE As Long = &H4
Private Const CREATE_NEW As Long = 1
Private Const CREATE_ALWAYS As Long = 2
Private Const OPEN_EXISTING As Long = 3
Private Const OPEN_ALWAYS As Long = 4
Private Const TRUNCATE_EXISTING As Long = 5
Private Const FILE_ATTRIBUTE_NORMAL As Long = &H80
Private Const FILE_FLAG_OVERLAPPED As Long = &H40000000
Private Const LOCKFILE_FAIL_IMMEDIATELY As Long = &H1
Private Const LOCKFILE_EXCLUSIVE_LOCK As Long = &H2
Private Type OVERLAPPED
Internal As LongPtr
InternalHigh As LongPtr
Offset As Long
OffsetHigh As Long
hEvent As LongPtr
End Type
ファイルロック処理の実装パターン
.net
‘ PDMチェックアウト時にファイル全体を排他的にロックする関数
Function PDM_CheckOut_LockFile(ByVal filePath As String) As LongPtr
Dim hFile As LongPtr
Dim ov As OVERLAPPED
Dim bResult As Long
‘ ファイルを開く(排他ロックのため、他のプロセスからの読み書きを許さない)
‘ dwShareMode = 0 は共有モードなしを意味する
hFile = CreateFile(filePath, _
GENERIC_READ Or GENERIC_WRITE, _
0, _
0, _
OPEN_EXISTING, _
FILE_ATTRIBUTE_NORMAL, _
0)
If hFile = 0 Then
Debug.Print “Error: CreateFile failed for ” & filePath & “. Error Code: ” & Err.LastDllError
PDM_CheckOut_LockFile = 0
Exit Function
End If
‘ ファイル全体を排他的にロック
‘ LOCKFILE_FAIL_IMMEDIATELY: ロックできなかった場合、即座に失敗を返す
‘ LOCKFILE_EXCLUSIVE_LOCK: 排他的ロック
bResult = LockFileEx(hFile, _
LOCKFILE_EXCLUSIVE_LOCK Or LOCKFILE_FAIL_IMMEDIATELY, _
0, _
0, _
0, _
ov) ‘ ファイル全体をロックするため、Offset/Lengthは0
If bResult = 0 Then
Debug.Print “Error: LockFileEx failed for ” & filePath & “. Error Code: ” & Err.LastDllError
CloseHandle hFile
PDM_CheckOut_LockFile = 0
Exit Function
End If
Debug.Print “File ‘” & filePath & “‘ successfully locked.”
PDM_CheckOut_LockFile = hFile ‘ ロックハンドルを返す
End Function
‘ PDMチェックイン時にロックを解除する関数
Sub PDM_CheckIn_UnlockFile(ByVal hFile As LongPtr)
Dim ov As OVERLAPPED
Dim bResult As Long
If hFile = 0 Then Exit Sub
bResult = UnlockFileEx(hFile, 0, 0, 0, ov) ‘ ファイル全体のロックを解除
If bResult = 0 Then
Debug.Print “Error: UnlockFileEx failed. Error Code: ” & Err.LastDllError
Else
Debug.Print “File handle ” & hFile & ” successfully unlocked.”
End If
CloseHandle hFile ‘ ファイルハンドルを閉じる
End Sub
この`PDM_CheckOut_LockFile`関数は、指定されたファイルを排他的にロックし、他のプロセスからの書き込みを防ぐ。`dwShareMode = 0`とすることで、ファイルを開く時点で他の共有アクセスを拒否し、さらに`LockFileEx`で排他ロックをかけることで、二重の安全性を確保する。ロックに失敗した場合、即座に`LOCKFILE_FAIL_IMMEDIATELY`フラグによりエラーが返されるため、デッドロックを回避できる。
2. メタデータの永続化:Alternate Data Streams (ADS) の活用
PDMシステムは、ファイル本体とは別にバージョン情報、チェックイン者、ステータスなどのメタデータを管理する。専用のアドインがない場合、これらの情報をどこに格納するかが課題となる。ファイル名に情報を埋め込むのは醜悪であり、別途データベースを用意するのは複雑すぎる。
ここで活用できるのが、NTFSファイルシステムのAlternate Data Streams (ADS) だ。これは、ファイル本体に影響を与えずに、追加のデータをファイルに紐付けて保存できる機能である。ユーザーは通常のエクスプローラからはこのストリームを見ることはできないが、プログラムからはアクセス可能だ。
ADSへの書き込み処理の実装パターン
.net
‘ ADSにデータを書き込む関数
Function WriteToADS(ByVal filePath As String, ByVal streamName As String, ByVal data As String) As Boolean
Dim hFile As LongPtr
Dim bytesWritten As Long
Dim bResult As Long
Dim adsPath As String
adsPath = filePath & “:” & streamName & “:$DATA” ‘ ADSのパス形式
‘ ADSを開く(存在しない場合は作成)
hFile = CreateFile(adsPath, _
GENERIC_WRITE, _
0, _
0, _
CREATE_ALWAYS, _
FILE_ATTRIBUTE_NORMAL, _
0)
If hFile = 0 Then
Debug.Print “Error: CreateFile failed for ADS ‘” & adsPath & “‘. Error Code: ” & Err.LastDllError
WriteToADS = False
Exit Function
End If
‘ データをADSに書き込む
bResult = WriteFile(hFile, _
StrPtr(data), _
Len(data) 2, _
bytesWritten, _
0) ‘ OVERLAPPED構造体は使わない(同期書き込み)
If bResult = 0 Then
Debug.Print “Error: WriteFile failed for ADS ‘” & adsPath & “‘. Error Code: ” & Err.LastDllError
WriteToADS = False
Else
Debug.Print “Data written to ADS ‘” & adsPath & “‘: ” & data
WriteToADS = True
End If
CloseHandle hFile
End Function
‘ ADSからデータを読み込む関数
Function ReadFromADS(ByVal filePath As String, ByVal streamName As String) As String
Dim hFile As LongPtr
Dim bytesRead As Long
Dim bResult As Long
Dim adsPath As String
Dim buffer() As Byte
Dim fileSize As LongPtr
adsPath = filePath & “:” & streamName & “:$DATA”
‘ ADSを開く
hFile = CreateFile(adsPath, _
GENERIC_READ, _
FILE_SHARE_READ, _
0, _
OPEN_EXISTING, _
FILE_ATTRIBUTE_NORMAL, _
0)
If hFile = 0 Then
‘ ストリームが存在しない場合もエラーではないと判断することも可能
‘ Debug.Print “Error: CreateFile failed for ADS ‘” & adsPath & “‘. Error Code: ” & Err.LastDllError
ReadFromADS = “”
Exit Function
End If
‘ ADSのサイズを取得
‘ GetFileSizeEx (64bit対応) を使うのがより堅牢だが、VBAからはDeclareが複雑になるため、ここでは簡略化
fileSize = GetFileSize(hFile, 0) ‘ GetFileSizeはVBA組み込み関数ではないため、これもAPI宣言が必要
‘ 簡略化のため、ここでは適当なバッファサイズで読み込むが、実運用ではファイルサイズ取得が必須
ReDim buffer(0 To 1023) ‘ 仮のバッファサイズ
bResult = ReadFile(hFile, _
buffer(0), _
UBound(buffer) + 1, _
bytesRead, _
0)
If bResult = 0 Then
Debug.Print “Error: ReadFile failed for ADS ‘” & adsPath & “‘. Error Code: ” & Err.LastDllError
ReadFromADS = “”
Else
‘ 読み込んだバイト数に応じて文字列に変換
ReadFromADS = LeftB(buffer, bytesRead) ‘ LeftBはバイナリデータを扱う
End If
CloseHandle hFile
End Function
‘ — GetFileSize API宣言 (必要に応じて追加) —
Private Declare PtrSafe Function GetFileSize Lib “kernel32” ( _
ByVal hFile As LongPtr, _
ByRef lpHighWord As Long) As Long
‘ GetFileSizeEx がより望ましいが、OVERLAPPEDを扱うことになるのでここでは割愛
ADSには、チェックイン時のバージョン番号、最終チェックインユーザー、チェックアウトステータス、あるいはPDMシステムのIDなどを埋め込むことができる。これにより、ファイル自体が自身のPDMメタデータを保持し、サーバー側のデータベースと連携することなく、ローカルでの状態管理を強化できる。ただし、ADSはNTFS固有の機能であり、FAT32などのファイルシステムでは失われることに注意が必要だ。
SolidWorks APIによるファイル参照パスの操作とローカルキャッシュ管理
PDMシステムがチェックアウトを行う際、サーバー上のファイルパスをローカルのキャッシュパスに適切に書き換える処理が内部的に行われる。VBAでこれを模倣する場合、`IModelDoc2.ReplaceReferences`メソッドが極めて重要となる。
.net
‘ SolidWorksアプリケーションオブジェクト
Dim swApp As SldWorks.SldWorks
Set swApp = Application.SldWorks
‘ アセンブリドキュメント
Dim swAssy As SldWorks.AssemblyDoc
Set swAssy = swApp.ActiveDoc
If swAssy Is Nothing Then
MsgBox “アクティブなアセンブリドキュメントがありません。”, vbCritical
Exit Sub
End If
‘ チェックアウトしたファイルが配置されるローカルキャッシュディレクトリ
Const LOCAL_CACHE_DIR As String = “C:\PDM_LocalCache\”
‘ PDMサーバー上の基準パス (例: \\PDMServer\Vault\)
Const PDM_SERVER_PATH As String = “\\PDMServer\Vault\”
‘ — PDMチェックアウト後の参照パス更新 —
‘ 例として、アセンブリ内のすべての参照部品のパスをローカルキャッシュに置き換える
Dim vReferences As Variant
Dim i As Long
Dim sOldPath As String
Dim sNewPath As String
Dim bRet As Boolean
‘ 現在のアセンブリが参照している全てのドキュメントのパスを取得
vReferences = swAssy.GetReferences
If Not IsEmpty(vReferences) Then
For i = 0 To UBound(vReferences) Step 2 ‘ GetReferencesはパスとドキュメント名がペアで返される
sOldPath = vReferences(i) ‘ 参照されている古いパス
‘ PDMサーバーパスからローカルキャッシュパスへの変換ロジック
If InStr(1, sOldPath, PDM_SERVER_PATH, vbTextCompare) > 0 Then
sNewPath = Replace(sOldPath, PDM_SERVER_PATH, LOCAL_CACHE_DIR, 1, 1, vbTextCompare)
Else
‘ PDM管理外のパスの場合、あるいは既にローカルキャッシュパスの場合の処理
sNewPath = sOldPath
End If
If sOldPath <> sNewPath Then
Debug.Print “Replacing reference: From ‘” & sOldPath & “‘ to ‘” & sNewPath & “‘”
‘ ReplaceReferences メソッドで参照パスを更新
‘ 第1引数: 古い参照パス
‘ 第2引数: 新しい参照パス
‘ 第3引数: 参照が新しいパスにリダイレクトされるかどうか (Trueでリダイレクト)
bRet = swAssy.ReplaceReferences(sOldPath, sNewPath, True)
If Not bRet Then
Debug.Print “Warning: Failed to replace reference for ” & sOldPath
End If
End If
Next i
End If
‘ 変更を保存
bRet = swAssy.Save
If Not bRet Then
Debug.Print “Error: Failed to save assembly after reference update.”
End If
‘ オブジェクトの明示的な解放
Set swAssy = Nothing
Set swApp = Nothing
このコードは、開いているアセンブリの全参照を走査し、PDMサーバー上のパスをローカルキャッシュのパスに置き換える。これはチェックアウト操作の核となる部分であり、PDMシステムが内部的に行っている参照解決プロセスをVBAで模倣する。
また、`ISldWorks.OpenDoc6`メソッドの`swOpenDocOptions_e`オプションは、キャッシュの扱いにおいて非常に重要だ。
`swOpenDocOptions_OverrideCache`は、SolidWorksの内部キャッシュを無視し、常にディスクからファイルを読み込むよう強制する。これは、PDMシステムから最新のファイルを取得した直後に開く際に、古いキャッシュが使用されるのを防ぐために活用できる。
`swOpenDocOptions_LoadMissing`は、参照が見つからない場合に、その部品をスキップしてアセンブリを開くことを許可する。これは、参照パスの解決に失敗した場合でも、ドキュメント自体は開きたい場合に有用だが、デバッグ時には注意が必要だ。
堅牢なセッション管理とエラーハンドリング
PDM連携は、ネットワーク、ファイルシステム、そしてSolidWorks自体の安定性に依存する、非常にデリケートなプロセスである。トランザクションの概念を導入し、失敗時のロールバック戦略を確立することが不可欠だ。
トランザクション処理の考え方
1. ロック取得: PDM_CheckOut_LockFileでファイルを排他的にロック。
2. ファイルコピー: サーバーからローカルキャッシュへファイルをコピー。
3. メタデータ更新: ADSにチェックアウト情報(ユーザー、日時、バージョンなど)を書き込む。
4. SolidWorksで開く: `swApp.OpenDoc6`でファイルを開き、必要に応じて`ReplaceReferences`でパスを更新。
5. 編集中…: ユーザーがSolidWorksで作業。
6. 保存: `swAssy.Save`で変更を保存。
7. チェックイン準備: ADSにチェックイン準備情報を書き込む。
8. ファイルコピー: ローカルキャッシュからサーバーへファイルをコピー(上書き)。
9. メタデータ更新: サーバー側のメタデータ(またはADS)を更新。
10. ロック解放: PDM_CheckIn_UnlockFileでファイルをアンロック。
この一連の操作のどこかで失敗した場合、例えばファイルコピー中にネットワークが切断された場合、既に取得したロックを解放し、ローカルに作成した中間ファイルを削除するなど、元の状態に戻すためのロールバックが必要となる。これは、`On Error GoTo`ステートメントと、`Finally`ブロックに相当する処理(クリーンアップ)を組み合わせることで実現できる。
ログ記録の徹底
全てのPDM操作(チェックアウト、チェックイン、ロック、アンロック、参照更新など)において、詳細なログを記録することは、問題発生時の原因究明に不可欠である。ログには、日時、ユーザー名、対象ファイルパス、操作内容、成功/失敗ステータス、エラーコード、エラーメッセージを含めるべきだ。
パフォーマンスとメモリ管理:SolidWorksオブジェクトの真のライフサイクル
VBAにおけるSolidWorks APIの操作は、COMオブジェクトのライフサイクル管理と密接に関連している。不適切なオブジェクト管理は、メモリリークやSolidWorksプロセスのゾンビ化を引き起こし、システム全体のパフォーマンスを著しく低下させる。
オブジェクトの明示的解放
`Set obj = Nothing`は基本中の基本だが、これだけでは不十分な場合がある。特に、`GetObject`や`CreateObject`で取得したCOMオブジェクトは、VBAの参照カウンタが正確に機能しないことが報告されている。
.net
‘ SolidWorksオブジェクトを確実に解放するためのユーティリティ関数
‘ この関数は、VBAから直接利用できるAPIではなく、概念的なものとして提示
‘ 実際には、Marshal.ReleaseComObject (VB.NET/C#) に相当する機能が必要になる
‘ VBAでは、Set obj = Nothing の徹底と、SolidWorksを終了する際に全てのドキュメントを閉じることで対応する
Sub ReleaseSolidWorksObject(ByRef obj As Object)
If Not obj Is Nothing Then
‘ ここで COM オブジェクトの参照カウンタを明示的に減らす処理を記述するが、
‘ VBAでは Marshal.ReleaseComObject のような直接的なAPIは提供されていない。
‘ そのため、ここでは Set obj = Nothing の徹底と、
‘ SolidWorksアプリケーションの適切な終了が重要であることを強調する。
Set obj = Nothing
End If
End Sub
‘ 使用例
Dim swApp As SldWorks.SldWorks
Set swApp = Application.SldWorks ‘ または CreateObject(“SldWorks.Application”)
‘ … 処理 …
‘ 処理終了後
If Not swApp Is Nothing Then
‘ SolidWorksアプリケーション自体を終了する必要がある場合
‘ swApp.ExitApp ‘ これを実行するとSolidWorksが終了する
ReleaseSolidWorksObject swApp ‘ オブジェクト解放
End If
`Set obj = Nothing`の徹底に加え、SolidWorksアプリケーションオブジェクト自体を取得した場合、そのプロセスを適切に終了させるか、使用後は全てのドキュメントを閉じ、オブジェクトを解放することが重要だ。長時間実行されるバッチ処理などでは、定期的にSolidWorksアプリケーションを再起動することも、メモリリーク対策として有効な手段となる。
レガシー環境とシステム間連携の極限
このアプローチは、旧バージョンのSolidWorksや、ファイルサーバーベースのPDMシステム、あるいは専用PDMが存在しない環境において、極めて強力な武器となる。しかし、そこには常に「互換性」と「セキュリティ」の壁が立ちはだかる。
- OSバージョン: Windows XP時代のNTFSとWindows 10/11のNTFSでは、ADSの挙動やセキュリティモデルに微妙な違いがある可能性がある。Windows APIの動作は、OSのパッチレベルによっても変化しうる。
- SolidWorksバージョン: APIの変更は頻繁ではないものの、稀に重要なメソッドの引数や挙動が変わることがある。常にターゲットとするSolidWorksバージョンのAPIヘルプを参照し、テストを徹底すること。
- セキュリティポリシー: ADSの利用は、セキュリティソフトウェアによってはマルウェアの隠蔽に利用される可能性があると判断され、監視や削除の対象となることがある。社内のITセキュリティポリシーを事前に確認することが不可欠だ。
VBAでWindows APIを直接操作するこの手法は、確かに強力だが、その分、開発者にはOSレベルの深い理解と、極めて慎重なデバッグが求められる。
結論:技術の真髄を掌握する者だけが立つ領域
我々が探求してきたのは、単なる自動化のテクニックではない。それは、システム連携の奥底に潜む本質的な課題を、自らの手で解き明かす「技術至上主義」の精神そのものである。専用アドインに頼らず、VBAとWindows APIを駆使してPDMライクな制御を実現することは、途方もない労力と深い知識を要求する。
しかし、この知見を掌握した者だけが、既成のシステムでは解決し得ない、現場の真のニーズに応えることができる。コストに囚われず、特定のベンダーに縛られず、真に自律したシステムを構築する力。これこそが、伝説的なチーフアーキテクトが追い求める「極限の知見」である。
この技術は、決して誰もが簡単に手を出せる領域ではない。だが、もし諸君が、限界を超える挑戦を求めるシニアエンジニアであるならば、この深淵に足を踏み入れる価値は十二分にある。VBAはレガシーではない。使い手次第で、システムの核心を穿つことができる、鋭利な刃なのである。
—
