【テクニカル・上級編】【COM通信エラーハンドリング】CreateObject失敗時のHRESULTコード判定とレジストリ非依存のエラー代替処理 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

VBScriptを掌握する極限の知見:COM通信エラーハンドリングとレジストリ非依存のフォールバック戦略

レガシーシステムの深部、あるいはインフラの自動化レイヤーにおいて、VBScriptとWSH(Windows Script Host)はいまだに現役のインフラストラクチャとして稼働し続けている。しかし、COM(Component Object Model)の初期化は、環境依存の罠に満ちている。

特に、ターゲットとなるDLL/OCXが未登録である場合や、DLL Hellと呼ばれるバージョン競合に直面したとき、素朴な `CreateObject` は容赦なくスクリプトをクラッシュさせる。エラーコード `429`(コンポーネントはオブジェクトを作成できません)に怯えるだけの開発者は、もはやプロフェッショナルとは言えない。

今回は、`CreateObject` 失敗時のHRESULT(エラーコード)の厳密なキャプチャ、レジストリに依存しない動的インスタンス生成、そして極限まで最適化されたエラーハンドリングの設計思想を解き明かす。

1. COMオブジェクト初期化の裏側と「エラー 429」の正体

VBScriptで `Set obj = CreateObject(“ProgID”)` を実行した瞬間、裏ではOLE/COMのランタイムが動き出す。
OSのレジストリ(`HKEY_CLASSES_ROOT`)からProgIDを検索し、CLSID(Class ID)を特定、さらにInprocServer32やLocalServer32キーから実体となるDLL/EXEのパスを割り出してプロセス空間にロードする。

この一連のライフサイクルのどこかで破綻が生じた場合、VBScriptランタイムは実行時エラー 429を投げる。

ここで重要なのは、VBScript標準の `On Error Resume Next` は、エラーが発生したという事実(Err.Number)は捉えられるが、なぜ失敗したのかという詳細なHRESULT(32ビットのエラーコード)をそのまま透過的には返さない点である。多くの場合、`Err.Number` は `-2147024894`(ファイルが見つかりません: `0x80070002`)や、`0x80040154`(クラスが登録されていません)といったCOM固有のファサードに変換される。

このHRESULTを正確にデコードし、環境差異を吸収する堅牢なラッパー関数を構築することが、プロフェッショナルなVBScriptアーキテクチャの第一歩となる。

2. 実装:堅牢なCOMオブジェクト生成コンストラクター(フォールバック機構付き)

以下のコードは、単なるエラー回避ではない。「指定された本命のCOMコンポーネントの生成に失敗した場合、HRESULTを判定しつつ、別バージョンのコンポーネントや代替COM、さらにはレジストリをバイパスする動的ロード(可能な場合)へシームレスにフォールバックする」極限のコンストラクターパターンである。

Option Explicit

‘ ==============================================================================
‘ 致命的なCOM初期化エラーを完全に制御する堅牢なファクトリー関数
‘ ==============================================================================
Function SafeCreateObjectWithFallback(ByVal primaryProgId, ByVal fallbackProgId)
Dim targetObj
Dim originalErrNumber, originalErrDescription, actualHResult

‘ エラーハンドリングを有効化
On Error Resume Next

‘ 1. プライマリ(本命)コンポーネントの生成を試行
Set targetObj = CreateObject(primaryProgId)

‘ エラーの捕捉とHRESULTの退避
If Err.Number <> 0 Then
originalErrNumber = Err.Number
originalErrDescription = Err.Description
‘ VBScriptの負数は32ビットのHRESULTに直結しているため、16進数化してログや判定に使う
actualHResult = “0x” & Right(CStr(Hex(originalErrNumber)), 8)

‘ ログ出力(実運用ではWScript.StdErrやイベントログに出力すべき)
WScript.Echo “[WARNING] Primary COM Failed: ” & primaryProgId & _
” (HRESULT: ” & actualHResult & ” – ” & originalErrDescription & “)”

Err.Clear

‘ 2. フォールバック(予備)コンポーネントの生成を試行
If Not IsEmpty(fallbackProgId) And fallbackProgId <> “” Then
WScript.Echo “[INFO] Attempting fallback to: ” & fallbackProgId
Set targetObj = CreateObject(fallbackProgId)

If Err.Number <> 0 Then
Dim fbErrNumber: fbErrNumber = Err.Number
Dim fbHResult: fbHResult = “0x” & Right(CStr(Hex(fbErrNumber)), 8)
WScript.Echo “[ERROR] Fallback COM also failed: ” & fallbackProgId & ” (HRESULT: ” & fbHResult & “)”
Set targetObj = Nothing
End If
Else
Set targetObj = Nothing
End If
End If

‘ エラーハンドリングを通常に戻す
On Error GoTo 0

‘ 最終的なオブジェクト(またはNothing)を返却
Set SafeCreateObjectWithFallback = targetObj
End Function

‘ — 実行テストの例 —
Sub Main()
‘ 例:存在しないXMLパーサーを指定し、古いバージョンへフォールバックさせる
Dim xmlDom
Set xmlDom = SafeCreateObjectWithFallback(“MSXML2.DOMDocument.6.0”, “MSXML2.DOMDocument.3.0”)

If Not xmlDom Is Nothing Then
WScript.Echo “[SUCCESS] COM Object acquired successfully.”
‘ オブジェクトの明示的解放(メモリ管理の鉄則)
Set xmlDom = Nothing
Else
WScript.Echo “[FATAL] All COM instantiation attempts failed.”
End If
End Sub

Main()

3. レジストリ非依存へのアプローチ:アクティベーションコンテキストとDLLの直接ロード

前述の方法は「異なるProgIDへのフォールバック」だが、さらに踏み込んで「そもそもレジストリにCOMコンポーネントが登録されていない(レジストリフリーCOM / SxS: Side-by-Side Execution)」環境や、管理者権限なしで特定のローカルDLLをロードしなければならない極限の要件はどう扱うべきか。

VBScript単体では、標準の `CreateObject` はレジストリの `HKEY_CLASSES_ROOT` を参照するため、レジストリ非依存の完全な動的ロードを直接記述することはできない。しかし、アーキテクトとしての知見があれば、以下の設計アプローチでこれを突破できる。

1. WMI(Windows Management Instrumentation)やShell.Application等のOS標準COMの活用
カスタムDLLのレジストリ登録ができない場合、システムにあらかじめ存在するプリインストールされたCOMオブジェクト(例: `WbemScripting.SWbemLocator` など)を介してシステム操作を代行させる。
2. 補助的なCOMラッパー(ネイティブDLL)の動的配置とRegsvr32のサイレント実行
スクリプトの実行開始時に、必要なDLLがカレントディレクトリや特定のリソースフォルダに存在する場合、WScript.Shellを用いて管理者権限昇格(UACバイパスまたは適切なコンテキスト)を伴わずにユーザーハイブ(`HKCU`)へのCOM登録(Per-User COMRegistration)を行う。

Per-User COM(ユーザー単位のレジストリ登録)の活用

Windows 7以降(およびWindows 10/11)、COMコンポーネントは `HKEY_CURRENT_USER\Software\Classes` に登録することで、管理者権限なしでそのユーザー空間だけにCOMを有効化できる。

‘ レジストリを持たない、あるいは管理者権限なしでDLLをユーザー領域に紐付ける例
Sub RegisterPerUserCOM(ByVal dllPath)
Dim wshShell
Set wshShell = CreateObject(“WScript.Shell”)

‘ regsvr32は通常HKCR(HKEY_CLASSES_ROOT = HKLM\Software\Classes)をいじるため管理者が必要だが、
‘ マニフェストファイルを使ったSide-by-Sideアセンブリ、またはユーザーハイブへの事前マッピングを行う。
‘ ※実務では、インストーラーフェーズでHKCUへCLSIDを手動書き込みする手法が採られる。

Set wshShell = Nothing
End Sub

4. プロフェッショナルのメモリ最適化とガベージコレクションの制御

VBScriptのオブジェクト管理において最も見落とされがちなのが、COM参照のデタッチ(解放)のタイミングである。

VBScriptエンジンは参照カウント方式(Reference Counting)を採用しているが、スクリプトが終了するまでメモリが解放されないケースや、循環参照が発生してメモリリークを引き起こすケースがある。
特に、長時間稼働するWSH常駐プロセスや、大量のファイルを処理するバッチスクリプトでは、以下の鉄則を遵守しなければならない。

  • 使い終わったオブジェクトは即座に `Set obj = Nothing` を明示する。
  • エラーハンドリングブロック内であっても、生成に失敗した変数は確実にクリーンアップする。
  • グローバル変数へのCOMオブジェクトの保持は最小限に抑え、ローカルスコープ(Sub/Function内)で完結させてスコープアウトによる自動解放を促す。

Sub ProcessHeavyTask()
Dim conn, rs
On Error Resume Next

Set conn = CreateObject(“ADODB.Connection”)
conn.Open “Provider=…”

If Err.Number <> 0 Then
‘ 失敗時も確実にクリーンアップ
Set conn = Nothing
Exit Sub
End If

Set rs = conn.Execute(“SELECT FROM LargeTable”)
‘ 処理…

‘ 逆順での明示的解放(構造化されたリソース管理)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If

If Not conn Is Nothing Then
conn.Close
Set conn = Nothing
End If

On Error GoTo 0
End Sub

総括

VBScriptにおけるCOM通信エラーハンドリングは、単なる「エラーを無視する技術」ではない。
どのレイヤーで何が原因で失敗したのか(HRESULTの解析)、環境依存性をどう排除するか(フォールバックとPer-User COM)、そしてリソースをどう正確に管理するかという、システムアーキテクチャの縮図である。

レガシーな技術であっても、その背後にあるOSのメカニズムを深く理解し、極限まで最適化されたコードを書くこと。それこそが、真のエンジニアリングの姿である。

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