はじめに
VBAの Shell
関数で外部のアプリケーションを起動する際、何も考えずに実行すると、ボタンを押すたびに新しいウィンドウがどんどん開いてしまいます。これを防ぎ、「もし既に起動していたら、新しくは起動しない」という制御を加えたい場面は非常に多くあります。
このような重複起動の防止は、Windows APIの FindWindow
関数を利用することで、スマートに実装できます。FindWindow
は、指定した条件に一致するウィンドウが現在開かれているかを探し出し、そのハンドル(識別子)を返す関数です。
この記事では、FindWindow
APIを使って、特定のアプリケーションが既に実行中かどうかを確認し、重複起動を回避する方法を解説します。
重複起動を防止するVBAサンプルコード
このマクロは、Windowsの「電卓」が既に起動しているかを確認し、起動していない場合のみ、新しく電卓を起動します。
Declare
ステートメントは、モジュールの最上部(Sub
などのプロシージャよりも前)に記述する必要があります。
完成コード
'--- モジュールの最上部にAPI関数を宣言 ---
' 64bit/32bit両対応
#If VBA7 Then
' ウィンドウを探すための FindWindowA API関数を宣言
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String _
) As LongPtr
#Else
Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String _
) As Long
#End If
' 電卓の重複起動をチェックして、起動していなければ実行する
Sub LaunchCalculatorIfNotRunning()
Const APP_CLASS_NAME As String = "CalcFrame" ' 電卓のウィンドウクラス名
Dim windowHandle As LongPtr
'--- 1. FindWindowで、電卓のウィンドウが存在するか探す ---
' 第1引数にクラス名を、第2引数(ウィンドウタイトル)は指定しないのでvbNullString
windowHandle = FindWindow(APP_CLASS_NAME, vbNullString)
'--- 2. 結果を判定 ---
' ハンドルが見つかった(0でない)場合は、既に起動している
If windowHandle <> 0 Then
MsgBox "電卓は既に起動しています。", vbInformation
Else
' ハンドルが見つからなかった(0)場合は、起動していないのでShellで実行
MsgBox "電卓を起動します。", vbInformation
Shell "calc.exe", vbNormalFocus
End If
End Sub
コードの解説
Declare PtrSafe Function FindWindow ...
Windows APIである FindWindow
関数をVBAで利用可能にするための宣言です。
Lib "user32"
: この関数が、Windowsのユーザーインターフェース関連の機能が集められたuser32.dll
というライブラリに含まれていることを示します。Alias "FindWindowA"
:FindWindow
関数のANSI版(A
が付くバージョン)を呼び出すことを明示しています。- 引数:
lpClassName As String
: 探したいウィンドウの「クラス名」を文字列で指定します。lpWindowName As String
: 探したいウィンドウの「タイトルバーのテキスト」を文字列で指定します。
ウィンドウクラス名とは?
ウィンドウクラス名は、アプリケーションのウィンドウがどのような種類のものかを示す、内部的な名前です。例えば、電卓は "CalcFrame"
、メモ帳は "Notepad"
というクラス名を持っています。調べたいアプリケーションのクラス名は、Spy++
のような専用ツールで確認できます。
windowHandle = FindWindow(APP_CLASS_NAME, vbNullString)
FindWindow
関数を呼び出している部分です。
- 第1引数に、定数として定義した電卓のクラス名
"CalcFrame"
を渡します。 - 第2引数のウィンドウタイトルは、今回は使わないため
vbNullString
(空の文字列)を渡します。 - 戻り値:
- 条件に一致するウィンドウが見つかった場合、そのウィンドウのハンドル(ウィンドウを識別するためのユニークな数値)が返されます。
- 見つからなかった場合は、
0
が返されます。
If windowHandle <> 0 Then ...
FindWindow
の戻り値が 0
かどうかを判定することで、アプリケーションが既に起動しているかを判断できます。0
でなければ(ハンドルが取得できれば)起動済み、0
ならば未起動、というロジックです。
まとめ
今回は、FindWindow
APIを使って、アプリケーションの重複起動を防止する方法を解説しました。
FindWindow
API関数で、特定のウィンドウが存在するかをチェックできる。- ウィンドウの識別に、タイトルバーの文字列よりも変化しにくい「クラス名」を使うのが確実。
- 戻り値が
0
かどうかで、ウィンドウの有無を判定する。
このテクニックを使えば、「二重起動すると問題が起きるツール」や、「一度起動したら、ボタンの役目はそのウィンドウを最前面に表示するだけに変えたい」といった、より高度で安定したアプリケーション連携マクロを作成することができます。