【テクニカル・上級編】【初心者】フォームのOpenArgsをJSON形式で拡張する:画面間データ受け渡しのスマートな設計 – Access VBA解析バイブル

スポンサーリンク

【Access VBA】OpenArgsをJSONで超越する:64bit対応・極限のマルチパラメータ伝送アーキテクチャ

1. 序論:グローバル変数の終焉と、画面間結合度の極小化

Microsoft Accessを用いた業務アプリケーション開発において、避けては通れないのが「フォーム間のデータ受け渡し」である。

多くの現場では、遷移先フォームに値を渡すために、以下のような「アンチパターン」が平然と踏襲されている。

  • グローバル(Public)変数の乱用: どこで書き換えられたか追跡不能になり、メモリリークや意図しないバグの温床となる。
  • TempVarsコレクションへの依存: 型安全性が失われ、文字列や数値の暗黙的型変換によるバグを誘発する。
  • 遷移先フォームのコントロールを直接書き換える (`Forms!遷移先!コントロール = 値`): フォーム間の結合度(Coupling)が極大化し、単体テストやデザイン変更が不可能なレガシーコードと化す。

Accessには、これらの結合度を排除するために `DoCmd.OpenForm` の引数として `OpenArgs` が用意されている。しかし、`OpenArgs` は単一の文字列(`String`)しか受け取れないという致命的な制約を持つ。

この制約を突破するために、かつてはカンマやパイプ(`|`)で区切った文字列を `Split` 関数で分解する手法が使われてきた。しかし、この方法は「引数の順序への依存」「値自体に区切り文字が含まれる場合のエスケープ処理」「ネストした複雑な構造の表現不可」といった限界を露呈する。

本稿では、この `OpenArgs` を JSON(JavaScript Object Notation)形式 で拡張し、64bit/32bit両環境において完全かつ高速に動作する、堅牢なマルチパラメータ伝送アーキテクチャを提示する。

2. 64bit化の罠:MSScriptControlの死と、MSHTMLによるブレイクスルー

VBAでJSONを扱う際、かつては `MSScriptControl.ScriptControl` を用いてJavaScriptの `JSON.parse` を呼び出す手法が一般的であった。しかし、このActiveXコントロールは 32bit専用 であり、現在の主流である 64bit版Office環境では完全に動作しない

また、ピュアVBAで記述された巨大なJSONパーサライブラリをインポートする手法は、コードベースを肥大化させ、保守コストを増大させる。

そこで我々が採用すべき極限の解決策は、Windows OSに標準搭載されている `htmlfile`(MSHTML)COMオブジェクト を利用し、内部のJScriptエンジンをバックエンドとして動作させるアプローチである。

この手法であれば、
1. 外部ライブラリ(DLLや参照設定)が一切不要
2. 32bit/64bit環境に完全対応
3. OSのレジストリやセキュリティポリシーに依存せず安定動作する

という、エンタープライズ保守において最も重要な「ポータビリティ」と「堅牢性」を同時に獲得できる。

3. 極限の実装:JSONシリアライザ / デシリアライザ

まずは、VBAの `Scripting.Dictionary` や各種プリミティブ型をJSON文字列に変換(シリアライズ)し、逆にJSON文字列からVBAで扱えるオブジェクトへと復元(デシリアライズ)する共通モジュールを構築する。

新規に標準モジュール `mod_JSON` を作成し、以下のコードを実装してほしい。

Option Compare Database
Option Explicit

‘========================================================================================
‘ モジュール名: mod_JSON
‘ 概要: 64bit/32bit両対応 MSHTML(JScript)を利用した超軽量・高速JSON相互変換エンジン
‘========================================================================================

Private Const HTML_ENGINE_CLASS As String = “htmlfile”

”’

”’ DictionaryオブジェクトまたはコレクションをJSON文字列に変換(シリアライズ)します。
”’

Public Function Stringify(ByRef obj As Object) As String
On Error GoTo ErrorHandler

Dim html As Object
Set html = CreateObject(HTML_ENGINE_CLASS)

‘ 最新のレンダリングモード(IE11互換)を強制し、JSONオブジェクトを有効化
html.write “

‘ JScript側にオブジェクトをバインドするための架け橋を構築
Set html.parentWindow.vbaTarget = obj

‘ JScriptのJSON.stringifyを実行
Stringify = html.parentWindow.eval(“JSON.stringify(vbaTarget)”)

CleanUp:
Set html = Nothing
Exit Function

ErrorHandler:
Err.Raise Err.Number, “mod_JSON.Stringify”, “JSONシリアライズに失敗しました: ” & Err.Description
Resume CleanUp
End Function

”’

”’ JSON文字列をJScriptのObject(Dictionaryライクにアクセス可能)に変換(デシリアライズ)します。
”’

Public Function Parse(ByVal jsonString As String) As Object
On Error GoTo ErrorHandler

‘ 空文字およびNullチェック
If Len(Trim$(jsonString)) = 0 Then
Set Parse = Nothing
Exit Function
End If

Dim html As Object
Set html = CreateObject(HTML_ENGINE_CLASS)
html.write “

‘ JSON文字列をJScriptの評価空間に安全に渡す
html.parentWindow.execScript “function getParsed(json) { return JSON.parse(json); }”, “JScript”

‘ パースの実行
Set Parse = html.parentWindow.getParsed(jsonString)

CleanUp:
Set html = Nothing
Exit Function

ErrorHandler:
Err.Raise Err.Number, “mod_JSON.Parse”, “JSONパースに失敗しました。データ構造またはシンタックスが不正です: ” & Err.Description
Resume CleanUp
End Function

技術的特異点:MSHTMLを近代化する `` タグの挿入

`htmlfile` を単純に生成しただけでは、互換モードが古いJScriptエンジン(ES3相当)で起動するため、`JSON` オブジェクトが未定義となりエラーになる。
`html.write ““` を書き込むことで、内部エンジンを強制的に近代化(Edgeモード/ES5以上)し、ネイティブの `JSON.parse` および `JSON.stringify` を呼び出すことに成功している。

4. 実践:画面間データ伝送の実装パターン

それでは、実際にこのJSONエンジンを用いて、送信側(呼び出し元フォーム)から受信側(遷移先フォーム)へ、型情報を維持したまま複数のパラメータを送受信する実装例を示す。

シナリオ

  • 送信側: 顧客管理画面。選択された「顧客ID(長整数型)」、「処理モード(文字列型)」、「更新日時(日付型)」を、ひとつのJSONにまとめて遷移先(詳細編集画面)に送る。
  • 受信側: 詳細編集画面。起動時に `OpenArgs` からJSONをデコードし、それぞれの型で安全に値を取り出して初期処理を行う。

4.1. 送信側(呼び出し元フォーム)の実装

呼び出し元のボタンクリックイベント等に実装する。ここでは `Scripting.Dictionary` を用いて、厳密なキー・バリューのペアを定義する。

> 注意: `Scripting.Dictionary` を使用するため、VBAの参照設定で 「Microsoft Scripting Runtime」 を有効にしておくことを推奨する(レイトバインドでも動作するが、開発時のインテリセンスの恩恵を受けるため)。

Private Sub btnOpenDetail_Click()
On Error GoTo ErrorHandler

‘ 1. 送信データのコンテナを構築
Dim payload As Scripting.Dictionary
Set payload = New Scripting.Dictionary

‘ 2. 異なるデータ型を混在させて格納
payload.Add “CustomerID”, 10548& ‘ Long型 (明示的型リテラル)
payload.Add “ProcessMode”, “EDIT_STRICT” ‘ String型
payload.Add “Timestamp”, Format$(Now, “yyyy-mm-dd hh:nn:ss”) ‘ Date形式のString
payload.Add “IsAdmin”, True ‘ Boolean型

‘ 3. JSON文字列へシリアライズ
Dim jsonArgs As String
jsonArgs = mod_JSON.Stringify(payload)

‘ 4. 遷移先フォームをOpenArgsを伴って起動
DoCmd.OpenForm FormName:=”frm_CustomerDetail”, _
View:=acNormal, _
WindowMode:=acDialog, _
OpenArgs:=jsonArgs

CleanUp:
‘ 参照カウンタを減算し、メモリを明示的に解放
Set payload = Nothing
Exit Sub

ErrorHandler:
MsgBox “画面遷移エラー: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub

4.2. 受信側(呼び出し先フォーム:`frm_CustomerDetail`)の実装

受信側のフォームでは、`Form_Open` イベント(または `Form_Load` イベント)で `OpenArgs` をインターセプトし、型変換を行いながら内部変数へ安全に展開する。

Option Compare Database
Option Explicit

‘ フォーム内部のプライベートメンバ変数(カプセル化の徹底)
Private m_CustomerID As Long
Private m_ProcessMode As String
Private m_Timestamp As Date
Private m_IsAdmin As Boolean

Private Sub Form_Open(Cancel As Integer)
On Error GoTo ErrorHandler

‘ OpenArgsの存在チェック
If IsNull(Me.OpenArgs) Then
MsgBox “パラメータが渡されていません。この画面を直接開くことはできません。”, vbExclamation, “起動エラー”
Cancel = True
Exit Sub
End If

‘ 1. JSON文字列のデシリアライズ
Dim param As Object
Set param = mod_JSON.Parse(Me.OpenArgs)

If param Is Nothing Then
MsgBox “パラメータのパースに失敗しました。”, vbCritical, “起動エラー”
Cancel = True
Exit Sub
End If

‘ 2. 型安全な値の取り出し(JScriptのObjectプロパティへアクセス)
‘ ※JScriptオブジェクトのプロパティは、CallByNameまたはドット演算子で動的に評価される
m_CustomerID = CLng(param.CustomerID)
m_ProcessMode = CStr(param.ProcessMode)
m_Timestamp = CDate(param.Timestamp)
m_IsAdmin = CBool(param.IsAdmin)

‘ 3. 読み込んだパラメータに基づく画面初期化処理
Call InitializeForm

CleanUp:
‘ JScript COMオブジェクトの明示的解放
Set param = Nothing
Exit Sub

ErrorHandler:
MsgBox “初期化エラー: ” & Err.Description, vbCritical, “システムエラー”
Cancel = True
Resume CleanUp
End Sub

Private Sub InitializeForm()
‘ 実際の初期化ロジック
Me.txtCustomerID.Value = m_CustomerID
Me.lblMode.Caption = “現在のモード: ” & m_ProcessMode

If m_IsAdmin Then
Me.btnDelete.Visible = True
Else
Me.btnDelete.Visible = False
End If
End Sub

5. メモリ管理とパフォーマンスの深淵

VBA開発において、シニアエンジニアが最も警戒すべきは 「サイレントなメモリリーク(リソース枯渇)」 である。特にAccessは、長時間起動し続ける運用が多いため、COMオブジェクトのライフサイクル管理には極限の注意を払わなければならない。

5.1. COM参照カウンタと明示的解放(`Nothing`)の絶対ルール

VBAのガベージコレクションは参照カウンタ方式である。`htmlfile` のような外部COMコンポーネントを生成した場合、プロシージャを抜ければ自動的に解放されると過信してはならない。特に、JScriptエンジン内にVBAのオブジェクト(`Dictionary`)をバインドする処理(`html.parentWindow.vbaTarget = obj`)は、境界を越えた循環参照を生み出すリスクを孕んでいる。

これを防ぐため、`mod_JSON` 内および呼出側において、処理終了時に必ず `Set html = Nothing` および `Set param = Nothing` を実行し、参照カウンタをゼロへと回帰させる設計を徹底している。

5.2. BSTR(VBA String)のメモリ負荷軽減

VBAの `String` 型は、Windowsの内部表現である BSTR(Unicode双方向文字列) である。BSTRの生成と結合は、メモリの再確保とコピーを頻繁に行うため、極めてコストが高い。

本手法で採用した `JSON.stringify` は、C++で実装されたMSHTMLのネイティブ領域でシリアライズ処理が行われるため、VBA上で文字列結合(`&`)を繰り返してJSONを手動構築するよりも 遥かに高速かつ低メモリ消費 で動作する。

6. まとめ:レガシーシステムをモダンへと昇華させる

`DoCmd.OpenForm` の `OpenArgs` をJSONでラップするという設計は、単なる「テクニック」ではない。これは、「画面間の密結合を排除し、各フォームを独立したコンポーネントとして再定義する」 という、オブジェクト指向設計の思想そのものである。

本稿で提示した `MSHTML` を用いたポータブルなJSONエンジンは、以下の価値をシステムにもたらす。

1. 32bit/64bitの完全な互換性(Officeのアップグレードに耐えうる保守性)。
2. グローバル変数の完全排除による、不具合解析コストの劇的な削減。
3. データ構造の柔軟性(配列、ネストされたオブジェクトの伝送も可能)。

レガシーとされるAccess VBAであっても、アーキテクチャの設計次第で、モダンなWebアプリケーションに匹敵する疎結合で堅牢なシステムへと昇華させることができるのだ。この極限の知見を、ぜひ貴方のシステムの保守・新規開発に役立てていただきたい。

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