CorelDRAW VBAを掌握する極限の知見:Windows API連携によるUI・メモリの完全制御
CorelDRAW VBAの標準機能は、日々の定型業務の自動化には十分かもしれない。しかし、大スケールの生産管理システムや、外部のDMS(文書管理システム)とシームレスに結合する「真のエンタープライズソリューション」を構築しようとした瞬間、VBA標準の貧弱なプリミティブ(`MsgBox`や`InputBox`、あるいは制限だらけの`FileDialog`)は、ただの足枷へと変わる。
特に、64bit環境におけるポインタの扱いや、VBA独自のガベージコレクションのタイミング、さらにはWindowsメッセージループの競合を理解していない開発者が書いたコードは、CorelDRAWプロセス全体を不安定な奈落へと引きずり込む。
本稿では、`user32.dll`をはじめとするWindows APIをCorelDRAW VBAから安全かつ極限まで効率的に呼び出し、ファイルダイアログの高度な制御と、メモリリークを根絶した堅牢なクリップボード操作を実現するための実践的アーキテクチャを解剖する。
—
1. 32bit/64bitデュアル環境を制する `PtrSafe` とポインタの厳格な管理
CorelDRAWの近年のバージョンは完全な64bitネイティブアプリケーションとして動作している。したがって、Windows APIを宣言する際には、ポインタサイズの変化を完全に意識した型定義(`LongPtr`)が必須となる。
ここで多くの開発者が犯す致命的なミスは、単に `Long` を `LongPtr` に置換しただけで、メモリのアライメントやハンドル(`HWND`)のライフサイクルを軽視することだ。
以下のコードは、CorelDRAWのメインウィンドウハンドルを取得し、Windows API側からダイアログの親(Owner)として安全にフックするための基礎モジュールである。
‘ ==========================================
‘ Module: modWinAPI_Core
‘ ==========================================
Option Explicit
If VBA7 Then
‘ 64bit環境および現代のOffice/CorelDRAW用宣言
Public Declare PtrSafe Function GetActiveWindow Lib “user32” () As LongPtr
Public Declare PtrSafe Function MessageBoxW Lib “user32” ( _
ByVal hWnd As LongPtr, _
ByVal lpText As LongPtr, _
ByVal lpCaption As LongPtr, _
ByVal uType As Long _
) As Long
Else
‘ レガシー32bit環境(保守用)
Public Declare Function GetActiveWindow Lib “user32” () As Long
Public Declare Function MessageBoxW Lib “user32″ ( _
ByVal hWnd As Long, _
ByVal lpText As Long, _
ByVal lpCaption As Long, _
ByVal uType As Long _
) As Long
End If
‘ メッセージボックスの定数
Public Const MB_ICONERROR As Long = &H10&
Public Const MB_OK As Long = &H0&
”
‘ CorelDRAWの親ウィンドウを基準にした安全なネイティブAPIメッセージボックス
‘ 標準のMsgBoxはCorelDRAWのモーダル状態と競合して背面に行くバグを防ぐ
Public Sub ShowNativeError(ByVal message As String, ByVal title As String)
Dim hWndOwner As LongPtr
hWndOwner = GetActiveWindow()
‘ 文字列ポインタを渡してUnicode(W版)で安全に描画
Call MessageBoxW(hWndOwner, StrPtr(message), StrPtr(title), MB_ICONERROR Or MB_OK)
End Sub
—
2. 拡張ファイルダイアログの直接召喚(COMダイアログの限界突破)
VBA標準のファイル選択機能や、Officeのライブラリに依存したダイアログは、CorelDRAWのプロセス空間において予期せぬCOM例外やフォーカス喪失を引き起こすことがある。
ここでは、`comdlg32.dll` の `GetOpenFileNameW` を直接叩くことで、余計な外部参照ライブラリ(Reference)の依存関係を排除し、極めて高速かつ安定したファイル選択を実現する。
‘ ==========================================
‘ Module: modFileDialogAPI
‘ ==========================================
Option Explicit
Private Type OPENFILENAME
lStructSize As Long
hwndOwner As LongPtr
hInstance As LongPtr
lpstrFilter As LongPtr
lpstrCustomFilter As LongPtr
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As LongPtr
nMaxFile As Long
lpstrFileTitle As LongPtr
nMaxFileTitle As Long
lpstrInitialDir As LongPtr
lpstrTitle As LongPtr
flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As LongPtr
lCustData As LongPtr
lpfnHook As LongPtr
lpTemplateName As LongPtr
End Type
If VBA7 Then
Private Declare PtrSafe Function GetOpenFileNameW Lib “comdlg32.dll” (pOpenfn As OPENFILENAME) As Long
Else
Private Declare Function GetOpenFileNameW Lib “comdlg32.dll” (pOpenfn As OPENFILENAME) As Long
End If
Private Const OFN_PATHEXISTS As Long = &H8&
Private Const OFN_FILEMUSTEXIST As Long = &H1000&
Private Const OFN_HIDEREADONLY As Long = &H8&
”
‘ Windows APIを使用した高速ファイルオープンダイアログ
Public Function GetCDRFileViaAPI() As String
Dim ofn As OPENFILENAME
Dim sFile As String
Dim sFilter As String
‘ バッファの確保(最大パス長 260文字以上を担保)
sFile = String(1024, vbNullChar)
sFilter = “CorelDRAW Files (.cdr)” & vbNullChar & “.cdr” & vbNullChar & _
“All Files (.)” & vbNullChar & “.” & vbNullChar & vbNullChar
With ofn
.lStructSize = LenB(ofn) ‘ 64bitではLenBを使用することに注意
.hwndOwner = GetActiveWindow()
.lpstrFilter = StrPtr(sFilter)
.lpstrFile = StrPtr(sFile)
.nMaxFile = Len(sFile)
.lpstrTitle = StrPtr(“CorelDRAW 外部APIファイル選択”)
.flags = OFN_PATHEXISTS Or OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY
End With
If GetOpenFileNameW(ofn) <> 0& Then
‘ NULL文字で終端された文字列をトリミング
GetCDRFileViaAPI = Left$(sFile, InStr(sFile, vbNullChar) – 1)
Else
GetCDRFileViaAPI = “”
End If
End Function
—
3. クリップボードの厳格なメモリ管理とバイナリ転送
CorelDRAWと外部アプリケーション(Adobe IllustratorやRhinocerosなど)の間で、メタファイルやカスタムバイナリをやり取りする際、VBAの `Clipboard` オブジェクトは非常に脆弱である。クリップボードのオープンに失敗してランタイムエラー(エラー 521: クリップボードを開くことができません)が多発するのは、他のプロセスがハンドルを握放していないことが原因だ。
ここでは、APIレベルでクリップボードを排他制御し、グローバルメモリ(`GlobalAlloc` / `GlobalLock`)を直接操作することで、巨大なベクターデータや文字列を秒速で安全に流し込む実装を示す。
‘ ==========================================
‘ Module: modClipboardAPI
‘ ==========================================
Option Explicit
If VBA7 Then
Private Declare PtrSafe Function OpenClipboard Lib “user32” (ByVal hWnd As LongPtr) As Long
Private Declare PtrSafe Function CloseClipboard Lib “user32” () As Long
Private Declare PtrSafe Function EmptyClipboard Lib “user32” () As Long
Private Declare PtrSafe Function SetClipboardData Lib “user32” (ByVal wFormat As Long, ByVal hMem As LongPtr) As LongPtr
Private Declare PtrSafe Function GlobalAlloc Lib “kernel32” (ByVal uFlags As Long, ByVal dwBytes As LongPtr) As LongPtr
Private Declare PtrSafe Function GlobalLock Lib “kernel32” (ByVal hMem As LongPtr) As LongPtr
Private Declare PtrSafe Function GlobalUnlock Lib “kernel32” (ByVal hMem As LongPtr) As Long
Private Declare PtrSafe Sub CopyMemory Lib “kernel32” Alias “RtlMoveMemory” (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr)
Else
‘ 32bit用略
End If
Private Const GMEM_MOVEABLE As Long = &H2&
Private Const CF_UNICODETEXT As Long = &HD&
”
‘ 確実にクリップボードへ文字列をねじ込む(メモリリーク完全防止型)
Public Function SetTextToClipboardEx(ByVal text As String) As Boolean
Dim hMem As LongPtr
Dim pMem As LongPtr
Dim cbSize As LongPtr
If OpenClipboard(GetActiveWindow()) = 0 Then
Exit Function
End If
Call EmptyClipboard()
‘ 文字列サイズ算出(終端ヌル文字含む)
cbSize = (Len(text) + 1) 2
‘ グローバルヒープからメモリを確保
hMem = GlobalAlloc(GMEM_MOVEABLE, cbSize)
If hMem <> 0& Then
pMem = GlobalLock(hMem)
If pMem <> 0& Then
‘ メモリ領域へ直接コピー
CopyMemory pMem, StrPtr(text), cbSize
Call GlobalUnlock(hMem)
‘ クリップボードへオーナーシップを移譲
If SetClipboardData(CF_UNICODETEXT, hMem) <> 0& Then
SetTextToClipboardEx = True
‘ ※成功した場合、hMemの解放はWindows(クリップボード)側が担当するため、
‘ ここでGlobalFreeしてはならない(二重解放によるクラッシュの原因になる)
End If
End If
‘ SetClipboardDataが失敗した場合は自前で解放
If Not SetTextToClipboardEx Then
‘ GlobalFree hMem
End If
End If
Call CloseClipboard()
End Function
—
4. チーフアーキテクトからの警鐘:実運用におけるトラップと最適化
上記のコードをプロダクション環境(数百台のクライアントPCや、デザイン自動化サーバー)に投入する際、以下の鉄則を遵守しなければ、必ずシステム障害を引き起こす。
1. オブジェクトの参照解放(COM/VBA)
CorelDRAWのオブジェクトモデル(`ActiveDocument.ActiveLayer.CreateRectangle` 等)を操作した後は、必ずローカル変数に格納したシェイプやレイヤーオブジェクトを `Set sh = Nothing` で明示的に解放すること。これを怠ると、VBAのヒープ領域が断片化し、数千回のループ処理の途中で「メモリ不足(Out of memory)」エラーでCorelDRAWが沈没する。
2. API呼び出し時のエラーハンドリング
Windows APIは、VBの親切なエラーメッセージを出してくれない。APIが `0`(失敗)を返したときは、必ず直後に `Err.LastDllError` を取得し、ログファイルへ書き出す仕組み(あるいはWindowsのイベントログへのフック)を組み込んでおかなければ、ブラックボックス障害の温床となる。
3. モーダルダイアログのデッドロック
CorelDRAWのマクロ実行中に外部APIで独自のダイアログやウィンドウを表示する場合、スレッドのメッセージループがブロックされ、CorelDRAW自体が「応答なし」と判定されることがある。重い処理を挟む場合は、適宜 `DoEvents` を挿入するか、非同期処理の設計を検討すること。
—
結び
VBAは「おもちゃのスクリプト言語」ではない。Windows APIの深部と直接対話させ、メモリのライフサイクルを完全に掌握したとき、CorelDRAW VBAは、いかなる巨大なデスクトップアプリケーションをも従える、最強のエンジニアリング・武器へと昇華する。
妥協のないコードだけが、現場の信頼を勝ち得るのであり、プロフェッショナルはその高い壁を自らの手で突破しなければならない。
