FREE ACCESS TEMPLATE VOL.3
Access VBA GetOpenFileName実装でAccessがクラッシュした話【Win32 API構造体サイズエラー】
こんにちは。ニンジンです🥕
「対応履歴管理データベース」シリーズも今回で最終回です。前回、Excel出力機能を紹介した際に少し触れましたが、このテンプレートのファイル選択ダイアログには、実はWizHook.GetFileNameという非公式・非サポートのAPIを使っています。
「非公式なAPIをテンプレートに残すのはさすがにどうなんだ」と思い、Windows標準のGetOpenFileNameW API(Win32 API)を直接呼び出す、より「モダン」な実装に置き換えようとしました。結論から言うと、この挑戦は失敗し、最終的にAccessごとクラッシュする事態にまでなりました。今回は、その一部始終と、そこから得た教訓を紹介します。
この記事で分かること
- なぜAccess VBAには「標準のファイル選択ダイアログ」がないのか
- Win32 APIをVBAから呼び出す際の構造体サイズエラーの正体
- VBAのUDT(ユーザー定義型)でハマりやすい落とし穴
- 生成AIが提案する「モダンな書き方」を鵜呑みにしてはいけない理由
なぜ非公式APIを使う必要があったのか
Word・Excel・PowerPointのApplicationオブジェクトにはFileDialogプロパティがあり、これを使えば数行でファイル選択ダイアログを表示できます。ところが、AccessのApplicationオブジェクトにはこのプロパティが存在しません。
これは実際に今回、最初につまずいたポイントでもあります。「Officeの標準機能だから使えるはず」と思ってApplication.FileDialog(msoFileDialogFilePicker)を試したところ、そもそもAccessにはこのプロパティ自体がなく、コンパイルエラーになりました。
Accessでファイル選択ダイアログを実装する方法は、実質的に次の2択しかありません。
- 非公式の
WizHookオブジェクトを使う(今回のテンプレートの元の実装) comdlg32.dllのWin32 API(GetOpenFileNameW)を直接呼び出す
「非公式のAPIより、Windows標準のAPIを直接呼ぶ方が正当だろう」と考え、後者に挑戦することにしました。
Win32 API(GetOpenFileNameW)への挑戦
Win32 APIを呼び出すには、OPENFILENAMEという構造体をVBAのユーザー定義型(UDT)として定義し、APIにポインタとして渡す必要があります。
Private Type OPENFILENAME
lStructSize As Long
hwndOwner As Long
hInstance As Long
lpstrFilter As String
lpstrCustomFilter As String
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As String
nMaxFile As Long
lpstrFileTitle As String
nMaxFileTitle As Long
lpstrInitialDir As String
lpstrTitle As String
flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As String
lCustData As Long
lpfnHook As Long
lpTemplateName As String
End Type
Private Declare PtrSafe Function GetOpenFileNameW Lib "comdlg32.dll" (pOpenfilename As OPENFILENAME) As Long呼び出し側はこのようになります。
Dim ofn As OPENFILENAME
Dim strFile As String
strFile = String(260, vbNullChar)
With ofn
.lStructSize = Len(ofn)
.hwndOwner = Application.hWndAccessApp
.lpstrFile = strFile
.nMaxFile = Len(strFile)
.flags = OFN_FILEMUSTEXIST Or OFN_PATHMUSTEXIST
End With
If GetOpenFileNameW(ofn) <> 0 Then
PickFile = Left(ofn.lpstrFile, InStr(ofn.lpstrFile, vbNullChar) - 1)
End If一見、教科書的には正しく見えるコードです。コンパイルも通りました。ところが、実際にボタンを押してファイルを選んでも、常に「キャンセルされました」という結果になってしまいました。
構造体サイズエラーとの格闘
GetOpenFileNameWが失敗した理由を調べるため、CommDlgExtendedErrorというAPIでエラーコードを取得したところ、CDERR_STRUCTSIZE(構造体サイズ不正)でした。
Private Declare PtrSafe Function CommDlgExtendedError Lib "comdlg32.dll" () As Long原因を調べていくと、Windows 2000以降のOPENFILENAME構造体には、実はpvReserved・dwReserved・FlagsExという3つの追加メンバーがあることが分かりました。これを構造体定義に加え、.lStructSize = Len(ofn)のまま再挑戦しましたが、それでも同じエラーです。
そこで、正しいはずのバイトサイズ(88バイト)を計算し、直接指定する方法に切り替えました。
Private Const OFN_STRUCT_SIZE As Long = 88
'...
.lStructSize = OFN_STRUCT_SIZEこれも失敗。ここで、VBAのLen(ofn)とLenB(ofn)がそれぞれ何バイトを返しているのか確認してみることにしました。
MsgBox "Len(ofn) = " & Len(ofn) & vbCrLf & "LenB(ofn) = " & LenB(ofn)結果はLen(ofn) = 120、LenB(ofn) = 136。理論値の88とも一致せず、LenとLenBの間でも16バイトの差がありました。ここで「もしかしたらLenBの値が正しいのでは」と考え、.lStructSize = LenB(ofn)を試したところ、Accessごとクラッシュしました。
VBAのUDT内で可変長String型メンバーを扱う場合、実際のメモリ上のレイアウトが、Len・LenBが計算する値のどちらとも、Win32 APIが期待するC言語の構造体レイアウトとも一致しない場合がある、ということを身をもって確認する結果になりました。
安定性を取って元の方式に戻した理由
ここまでで、Win32 API直接呼び出し方式は次の3つの結果を出しています。
- 構造体サイズエラーで動作しない
- 固定値を指定しても動作しない
- 別の値を試したらAccessごとクラッシュ
これ以上原因を追求することも不可能ではありませんが、「非公式だが20年以上の実績があるWizHookを使い続ける」のと「正しく動くかどうか分からないWin32 API実装を追求し続ける」のとでは、配布用テンプレートとしての安定性は前者に軍配が上がると判断しました。最終的に、ファイル選択ダイアログの実装は元のWizHook.GetFileNameに戻しています。
Public Function PickFile(Optional ByVal InitialDir As String = "", Optional ByVal DlgTitle As String = "ファイルの指定") As String
Const ENABLE_WIZHOOK As Long = 51488399
Const DISABLE_WIZHOOK As Long = 0
Dim strFile As String
WizHook.Key = ENABLE_WIZHOOK
Dim lngResult As Long
lngResult = WizHook.GetFileName( _
Application.hWndAccessApp, _
Application.Name, _
DlgTitle, _
DlgTitle, _
strFile, _
InitialDir, _
"すべてのファイル (*.*)|*.*", _
1, 0, 0, -1)
WizHook.Key = DISABLE_WIZHOOK
PickFile = strFile
End Function「非公式だから」という理由だけで安易に採用・却下を決めず、実際に動かして比較したうえで判断する。当たり前のようですが、今回はまさにそれが必要になった場面でした。
よくある質問
QなぜAccess VBAには標準のファイル選択ダイアログがないのですか?
AWord・Excel・PowerPointのApplicationオブジェクトにはFileDialogプロパティがありますが、AccessのApplicationオブジェクトにはこのプロパティ自体が存在しません。Microsoftの設計上の制約で、Accessだけこの機能が用意されていないのが実情です。
QCDERR_STRUCTSIZEエラーはどうすれば解決できますか?
A今回のケースでは、UDT内の可変長String型メンバーの扱いが原因で、Len・LenBのどちらも正しい構造体サイズを返しませんでした。固定長文字列に変更するなど別のアプローチも考えられますが、そこまでするとAPIが期待する「文字列へのポインタ」という前提と食い違う可能性があり、今回はリスクを避けて非公式API(WizHook)に戻す判断をしました。
QWin32 APIをVBAから呼び出すこと自体が危険なのですか?
A正しく実装すれば問題なく動作します。ただし構造体のメモリレイアウトを誤ると、今回のようにアプリケーションごとクラッシュする可能性があります。特にUDT内に文字列型のメンバーが含まれる構造体は要注意です。
QWizHookは今後も使い続けて大丈夫ですか?
A非公式・非サポートのAPIなので、将来のOfficeアップデートで動作しなくなるリスクはゼロではありません。ただし20年以上にわたって多くのAccess開発者に使われてきた実績があり、今回のように正式な代替手段の実装が難航した以上、実用上は許容範囲と判断しました。
Q生成AIにこの実装を任せるとどうなりますか?
AAIも「モダンな書き方」としてWin32 API直接呼び出しを提案してくることがあります。ですが今回のように、コンパイルは通っても実行時に構造体サイズエラーになる、というパターンは実際に動かしてみないと分かりません。AIの提案はあくまで叩き台として受け取り、必ず動作検証する姿勢が欠かせません。
まとめ
「対応履歴管理データベース」シリーズは今回で完結です。
- 第1回:テンプレートの配布と使い方
- 第2回:検索フィルタ・排他制御・Excel出力の設計パターン
- 第3回(今回):ファイル選択ダイアログの実装で味わった苦労話
生成AIによってVBAのコードそのものは誰でも書ける時代になりましたが、「そのコードが実際に動くかどうか」は、結局のところ自分の手で検証するしかありません。今回のシリーズが、AIと一緒にAccess VBAを書いていく際の参考になれば嬉しいです。
Series Navigation
このシリーズの記事一覧
Follow me on X
本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕
@ninjin_py_vba をフォローシリーズ記事一覧
電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。
-
FILE.01 — IoT
Raspberry Pi Pico W × GASラズパイPico W×GASで自作!「LINEで動く温湿度&スマートリモコン」完全ロードマップ
「外出中のペットの室温が心配…」そんな思いからスタートしたIoT自作プロジェクトの総集編。温湿度監視からエアコン遠隔操作まで、仕組みをゼロから作りたい方のための全5回の開発記録です。
シリーズを読む -
FILE.02 — CNC
Arduino × レーザー刻印CNCCNC自作シリーズ
高精度な加工を目指し、本格的な自作CNC製作に挑戦中。現在は基幹パーツであるオリエンタルモーターの納品を待つ「設計・準備編」を公開。ハードとソフトの両面から、理想のマシンを形にする過程をリアルタイムにお届けします。
シリーズを読む -
FILE.03 — XY軸制御
Raspberry Pi × ステッピングモーターXYテーブルシリーズ
Raspberry Piとステッパモーターを使い、ゼロから2軸制御に挑む記録。OS設定から回路設計、多軸制御のPythonコードまで、躓きやすいポイントを徹底図解。電子工作初心者が「動く感動」を味わうための実戦ガイドです。
シリーズを読む -
FILE.04 — 業務自動化
Python × Excel × CustomTkinter【Python開発記】NINJIN Mail制作記
PythonとExcelを連携させ、実務で即戦力となるメール送信ツールを開発。CustomTkinterによるUI構築や、ミスを防ぐテンプレート活用術など、現場の「痒い所に手が届く」自動化ノウハウを細部まで丁寧に解説します。
シリーズを読む -
FILE.05 — 業務自動化
Python × Excel × GUIアプリ化Invoice Maker(請求書・見積書自動作成)
手作業の請求書作成から卒業!PythonでExcelデータを読み込み一括PDF化する「Invoice_Maker」の作り方を全3回で解説。基本ロジックからGUIアプリ化まで、実務で役立つ自動化ノウハウが満載です。
シリーズを読む



コメント