オブジェクトは使用後に解放しないとメモリ上に残る可能性があると分かってはいたのですが、実害を受けたことがなかったので、いい加減にしていました。
ところが今回、Wordアドインでやっかいな問題を引き起こすことになってしまいました。
今後は「オブジェクトの解放は確実に行う」をスタイルに加え、再発を防止したいと思います。
発生した問題:Wordの不時起動
数週間前から、特定の環境でWordが以下のように不時起動するようになりました。
- Wordを正常に終了させたはずなのに、タスクマネージャーを見るとバックグラウンドプロセスに「Microsoft Word」が残っている。
- その数分後、何も操作していないにもかかわらず、Wordが突然起動してしまう。
- この現象は、「VA公用文」のようなクラスモジュールを利用したVBAアドインを
Startup
フォルダに入れて使用している場合に発生する。 - しかし、症状が発生したりしなかったりと再現性が不安定で、別のPCでは全く問題が起きないこともある。
「VA公用文」は、これまで何年もの間、多くの環境で正常に動作してきました。このため、当初はPC環境や他のソフトウェアとの競合を疑いました。
原因の特定:オブジェクト管理の甘さ
試行錯誤した結果、「Wordのアップデートにより、アプリケーション終了時のメモリやプロセスの管理がより厳格になり、これまで潜在化していた問題が表面化したのではないか」という仮説を立てました。
この仮説に基づき、Geminiを使ってオブジェクト変数の管理方法を見直した結果、2つの問題があることを突き止めました。
主因:解放されないグローバルオブジェクト
最大の問題点は、クラスモジュールのインスタンスを、標準モジュールの先頭でグローバル変数として生成・保持しているコードパターンでした。
' --- 問題のあったコード ---
' 標準モジュールの先頭
Option Explicit
' モジュールが読み込まれた時点で、自動的にオブジェクトが生成され、メモリに常駐する
Dim Variables As New VariablesManager
これは、いわば「会社(Word)の業務終了時刻が来ても、オフィス(メモリ)に居残り続ける社員(オブジェクト)」のようなものです。Wordが終了しようとしても、この居残り社員がいるためにVBAプロジェクト全体が「まだ仕事中です」と判断し、オフィスを完全に施錠(メモリを解放)できない状態になっていたようです。その結果、Word本体のプロセスも終了しきれず、バックグラウンドに残留していたと推定されます。
副因:解放が不確実なローカルオブジェクト
さらに、プロシージャ内で使用されるParagraph
やFileSystemObject
といったローカルなオブジェクト変数も、明示的に解放していませんでした。VBAの原則ではこれらは自動的に解放されますが、「VA公用文」のような複雑なアプリケーションでは解放が不完全になることがあり、アプリケーションの不安定化を助長していた可能性があります。
解決策:徹底したオブジェクト管理
この問題を解決するため、VBAプログラミングの基本に立ち返り、「オブジェクトは、必要な時に生成し、使い終わったら速やかに解放する」という原則を徹底しました。
グローバルオブジェクトの管理
最大の問題であったグローバルオブジェクトは、以下のように修正しました。
' --- 修正後のコード ---
' 標準モジュールの先頭
Option Explicit
' ここでは宣言だけ行い、オブジェクトは生成しない
Dim Variables As VariablesManager
Sub MainProcess()
' ★処理の開始時に、オブジェクトを生成する
Set Variables = New VariablesManager
' --- ここでVariablesオブジェクトを利用した処理を実行 ---
' ...
' ---------------------------------------------------
' ★処理の終了時に、オブジェクトを確実に解放する
Set Variables = Nothing
End Sub
これにより、オブジェクトの寿命はプロシージャが実行されている間だけに限定され、Wordの正常なシャットダウンが保証されます。
ローカルオブジェクトの明示的な解放
さらに、プロシージャ内で使用するローカルなオブジェクト変数も、すべて明示的に解放するようにしました。
Sub SomeOtherProcess()
Dim pars As Paragraphs
Dim fso As Object
On Error GoTo Cleanup ' エラー時も解放処理へ飛ぶ
Set pars = ActiveDocument.Paragraphs
Set fso = CreateObject("Scripting.FileSystemObject")
' ... 処理 ...
Cleanup: ' 正常終了時もエラー時もここを通る
' ★使い終わったローカルオブジェクトをすべて解放する
Set pars = Nothing
Set fso = Nothing
End Sub
追記
Office サポート チームから、次のような情報が公開されました。
やはり、Wordのバージョンアップがトリガーでした。
最新バージョン(バージョン 2509 ビルド 16.0.19231.20138)では、旧バージョンの「VA公用文」でも問題が生じないことを確認しました。
コメント
Officeサポートチームからの情報に関する事項を追記しました。