EternalWindows
ウインドウ管理 / 二重起動

今回は、アプリケーションの二重起動を防止する方法について説明します。 単純に考えると二重起動を防止するとは、 既にアプリケーションが起動されているかどうかを調べることですから、 この「起動されているかどうか」をどうやって特定するのかが重要になってきます。

大抵のアプリケーションは、起動されると共にウインドウを作成します。 この言葉を言い換えると、ウインドウが作成されていたら アプリケーションは起動されているということになるでしょう。 ということは、アプリケーションが作成したウインドウが存在するかどうかを調べ、 その結果がTRUEならば、アプリケーションを起動しなければよいということになります。 ウインドウが存在するかどうかを調べるには、FindWindowを呼び出します。

HWND FindWindow(
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName
);

lpClassNameは、探したいウインドウのクラス名を指定します。 lpWinodwNameは、探したいウインドウのウインドウタイトルを指定します。 戻り値は、指定した文字列と一致するウインドウが存在するならばそのハンドルが、 存在しない場合はNULLが返ることになります。次に、FindWindowの呼び出しの例を示します。

hwnd = FindWindow(szAppName, szAppName);
if (hwnd != NULL)
	return 0;

FindWindowがNULLでないウインドウハンドルを返したということは、 指定したクラス名とウインドウタイトルを持つウインドウを見つかったということですから、 そのときは二重起動が発生したと認識し、適切な処理を行います。 上の例では return 0; としていますから、このコードをWinMainに記述すれば、 WinMainから制御を返すこととなり、アプリケーションは直ちに終了することになります。

上記コードは、二重起動を防ぐ方法として十分成り立っていますが、 もう少しユーザーの操作性を支援してもよいかもしれません。 たとえば、ウインドウが最小化しているのであれば、それを元のサイズに戻したり、 ウインドウが他のウインドウに隠れているならば、それを前面に持ってくるなど。 exeファイルをクリックしても何も起きなかったら、 ユーザーはクリックする位置を間違えたかと思うかもしれませんが、 作成されているウインドウがユーザーの注意を引き付ければ、その心配はありません。 ウインドウが最小化しているかどうかは、IsIconicで確認できます。

BOOL IsIconic(
  HWND hWnd
);

hWndは、最小化しているかを調べるウインドウのハンドルを指定します。 ウインドウが最小化している場合は、0以外の値が返ります。 最小化している場合は、0が返ります。

最小化されているウインドウを元に戻すには、OpenIconを呼び出します。

BOOL OpenIcon(
  HWND hWnd
);

hWndは、元に戻したいウインドウのハンドルを指定します。 元に戻すことができた場合は、0以外の値が返ります。

ウインドウを前面に持ってくるには、SetForegroundWindowを呼び出します。

BOOL SetForegroundWindow(
  HWND hWnd
);

hWndは、フォアグラウンドにしたいウインドウのハンドルを指定します。 フォアグラウンドウインドウとは、アクティブで最前面に表示されており、 ユーザーが作業中のウインドウのことです。

今回のプログラムは上記した関数を呼び出すことにより、 単純に二重起動を防止するだけでなく、ユーザービリティの面も考慮しています。

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample-FindWindow");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;
	
	hwnd = FindWindow(szAppName, szAppName);
	if (hwnd != NULL) {
		if (IsIconic(hwnd))
			OpenIcon(hwnd);
		else
			SetForegroundWindow(hwnd);
		return 0;
	}

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

二重起動を防止できているかを確認するには、アプリケーションを2回起動します。 2回目の起動で新しくウインドウが作成されなかったら成功です。 既存ウインドウのクラス名とウインドウ名が衝突しないよう、 szAppNameの初期値は少し変更しています。

hwnd = FindWindow(szAppName, szAppName);
if (hwnd != NULL) {
	if (IsIconic(hwnd))
		OpenIcon(hwnd);
	else
		SetForegroundWindow(hwnd);
	return 0;
}

IsIconicで最小化を確認し、最小化状態であればOpenIconで元に戻し、 そうでなければSetForegroundWindowでウインドウをフォアグラウンドにします。 ちなみに、OpenIconは内部でWM_QUERYOPENというメッセージをセンドするので、 WM_QUERYOPENの戻り値を利用すればOpenIconの動作を制御できます。

case WM_QUERYOPEN:
	return 0;

WM_QUERYOPENで0を返した場合、ウインドウは最小化の状態から元に戻れません。 そのため、このメッセージでメッセージボックスを表示すれば、 ウインドウを元に戻してよいかの確認が可能となります。 0以外の値を返した場合は、ウインドウは元のサイズに戻ります。 DefWindowProcは、このメッセージでは常にTRUEを返します。

今回はアプリケーションが起動されているかどうかを ウインドウが作成されているかどうかで判断したわけですが、 この方法は場合によっては利用できないこともあります。 ウインドウを作成しないようなアプリケーションは、その代表といえるでしょう。 このようなときは、ミューテックスと呼ばれるカーネルオブジェクトを作成し、 それをプログラムの起動時にオープンできるかどうかで二重起動を防止します。 ただし、この方法は明示的に二重起動を確認するためのオブジェクトを作成しますから、 FindwWindowを使った方法と比べると若干、効率性に劣るといえるかもしれません。 なぜなら、ウインドウを作成することは、暗黙のうちに 二重起動を防止するための手がかりを作成しているのと同じことですから、 その手がかりに注目するFindWindowの方法は、単純ながら非常にスマートであるといえます。


戻る