二重起動せずアクティブ化するEXEにする [C++ Builder]

常駐アプリを作っていると、二重起動しないようにしたくなります。更にEXEを実行したなら常駐状態からアクティブ状態になると便利です。これを実現するには、Mutex で二重起動を防ぎ、起動済みプロセス一覧からEXE名+ウィンドウ名で一致するウィンドウを探し、アクティブ化するようにします。

EXE名+ウィンドウ名で探さずとも FindWindow() を使ってウィンドウ名だけでもある程度は実現できるのですが、他に同じウィンドウ名のアプリが居ると正しいウィンドウハンドルが取得できない可能性があります。EXE名で絞ってからウィンドウ名の一致するウィンドウハンドルを探した方が正確に特定できるので、EXE名+ウィンドウ名という方法を取った方が良いです。

EXE名+ウィンドウ名で探す方法と、FindWindowを使ったウィンドウ名で探す方法の両方とも記載しておきます。ウィンドウ名が他と重複する可能性がないなら FindWindow でも十分です。

二重起動を防ぎ、EXE名+ウィンドウ名でウィンドウを探してアクティブ化する

起動済みプロセス一覧から同じEXE名とウィンドウ名のウィンドウを探す方法です。

背景色が少し濃くなっている箇所が追加部分です。
コメントに★がある行は、自身のアプリに合わせて変更が必要な箇所です。

  • 29行目の mutexName は、システムで唯一の名前になるように変更する必要があります。

起動するときに二重起動の判定とアクティブ化をするので {プロジェクト名}.cpp の _tWinMain() に処理を追加していきます。

#include <vcl.h>
#pragma hdrstop
#include <tchar.h>
#include <psapi.h> // EnumProcesses()
//---------------------------------------------------------------------------
USEFORM("Unit1.cpp", Form1);
//---------------------------------------------------------------------------
// 二重起動防止のための Mutex
HANDLE mutexSingleInstanceApp;

// 同じEXE名を持つウィンドウハンドルを探すための構造体と関数群
struct _EnumWindowsArg {
	UnicodeString exePath;  // 検索対象の実行可能ファイルフルパス(自身のフルパス)
	std::vector<HWND> *foundWindowHandles;

	_EnumWindowsArg(UnicodeString exePath, std::vector<HWND> *foundWindowHandles) {
		this->exePath = exePath;
		this->foundWindowHandles = foundWindowHandles;
    }
};
HWND _FindRunningHwnd(UnicodeString windowCaption);
void _ListHwndWithMatchingExePath(UnicodeString exePath, std::vector<HWND> *hWnds);
BOOL __stdcall _EnumWindowsProc(HWND i_hwnd, LPARAM lp);
UnicodeString _GetExePathFromPid(DWORD pid);
UnicodeString _GetCaption(HWND hWnd);

int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
	const UnicodeString mutexName = L"mutexSingleInstanceApp"; // ★システムで唯一の名前にする
	mutexSingleInstanceApp = CreateMutex(NULL, true, mutexName.c_str());
	DWORD le = GetLastError();
	if (le == 0) {
		// 初回起動なら CreateMutex は成功する。何もせず起動へ。
	}
	else if (le == ERROR_ALREADY_EXISTS) {
		// 起動プロセス一覧から、同じEXE名と "Form1" をウィンドウ名(キャプション)に
        // 持つウィンドウハンドルを探す。
		HWND hWnd = _FindRunningHwnd(/* caption = */ L"Form1");
		if (hWnd == NULL)
			return -1;

		// アイコン状態なら復元する
		if (IsIconic(hWnd))
			ShowWindowAsync(hWnd, SW_RESTORE);

		// フォアグラウンドにする (アクティブ化する)
		SetForegroundWindow(hWnd);

		return 1;
	}
	else {
		return 255;
	}

	try {
		Application->Initialize();
		Application->MainFormOnTaskBar = true;
		Application->CreateForm(__classid(TForm1), &Form1);
		Application->Run();
	}
	catch (Exception &exception) {
		Application->ShowException(&exception);
	}
	catch (...) {
		try {
			throw Exception("");
		}
		catch (Exception &exception) {
			Application->ShowException(&exception);
		}
	}
	ReleaseMutex(mutexSingleInstanceApp);
	return 0;
}
// ---------------------------------------------------------------------------

/*
 * 指定したキャプション(tagetCaption)を持つウィンドウハンドルを探す。
 */
HWND _FindRunningHwnd(UnicodeString targetCaption)
{
	HWND result = NULL;

	// 自身と同じ実行可能ファイル名を持つ全ウィンドウハンドルを取得してリストにする。
	std::vector<HWND> *hWnds = new std::vector<HWND>();
	_ListHwndWithMatchingExePath(Application->ExeName, hWnds);

	// 取得したリストから targetCaption をキャプションに持つウィンドウを探す。
	std::vector<HWND>::iterator it;
	for (it = hWnds->begin(); it != hWnds->end(); it++) {
		HWND hWnd = *it;

		if (IsWindow(hWnd) == FALSE) continue;  // ウィンドウではない
		if (_GetCaption(hWnd) != targetCaption) continue; // targetCaption が不一致

		// targetCaption を持つウィンドウが見つかった。
		result = hWnd;
		break;
	}
	return result;
}

/*
 * 実行ファイル名の一致する全てのウィンドウハンドルを取得する
 */
void _ListHwndWithMatchingExePath(UnicodeString exePath, std::vector<HWND> *hWnds)
{
	_EnumWindowsArg *args = new _EnumWindowsArg(exePath, hWnds);
	EnumWindows((WNDENUMPROC)_EnumWindowsProc, (LPARAM)args);
    delete args;
}

/*
 * EnumWindows() のコールバック関数。
 * _ListHwndWithMatchingExePath()で使用する。
 */
BOOL __stdcall _EnumWindowsProc(HWND hWnd, LPARAM lp)
{
	_EnumWindowsArg *args = (_EnumWindowsArg*)lp;

	// hWnd の 実行可能ファイルパスを取得し、
	// 指定した exePath と一致するか判定する。
	DWORD pid;
	if (GetWindowThreadProcessId(hWnd, &pid) == 0)
		return TRUE;

	UnicodeString exePath = _GetExePathFromPid(pid);
	if (exePath != args->exePath)
		return TRUE;

    // exepath と一致したのでリストに追加する。
	args->foundWindowHandles->push_back(hWnd);

    // _EnumWindowsProc を継続する。
	return TRUE;
}

/*
 * pid からEXEパスを取得する
 */
UnicodeString _GetExePathFromPid(DWORD pid)
{
    UnicodeString result;

    wchar_t exepath[1024];
    const int exepathLen = sizeof(exepath) * sizeof(wchar_t);

	HANDLE hProcess = OpenProcess(
		PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, -1, pid);
	if (hProcess == NULL) {
		return "";
	}

	unsigned long len = exepathLen;
	BOOL succeed = QueryFullProcessImageName(hProcess, 0, exepath, &len);
	if (succeed)
		result = exepath;
	else
		result = "";

	// プロセスオブジェクトのハンドルをクローズ
    CloseHandle(hProcess);

	return result;
}

/*
 * ウィンドウハンドルからキャプションを取得する
 */
UnicodeString _GetCaption(HWND hWnd)
{
	UnicodeString result;

    // caption を取得する.
	DWORD sendMessageResult;
	wchar_t caption[256];
	memset(caption, 0, sizeof(caption));
	SendMessageTimeout(hWnd, WM_GETTEXT, (sizeof(caption) - sizeof(wchar_t)),
			(LPARAM)caption, SMTO_ABORTIFHUNG | SMTO_BLOCK,
			/* timeout = */100, (PDWORD_PTR)&sendMessageResult);

	result = caption;
    return result;
}

二重起動を防ぎ、FindWindow でウィンドウを探してアクティブ化する

同じEXE名を探すより簡単な方法です。ウィンドウ名(キャプション)が被る可能性がないなら FindWindow() だけでも十分です。

背景色が少し濃くなっている箇所が追加部分です。
コメントに★がある行は、自身のアプリに合わせて変更が必要な箇所です。

  • 14行目の mutexName は、システムで唯一の名前になるように変更する必要があります。
  • 22行目の FindWindow() の第二引数も、自身のメインフォームのキャプション(例であれば Form1->Caption)に設定した文字列に変更にする必要があります。
    例では第二引数のキャプションしか指定していませんが、キャプションだけでは他と重複する可能性がある場合は、第一引数のクラス名も指定すると良いです。

起動するときに二重起動の判定とアクティブ化をするので {プロジェクト名}.cpp の _tWinMain() に処理を追加していきます。

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop
#include <tchar.h>
//---------------------------------------------------------------------------
USEFORM("Unit1.cpp", Form1);
//---------------------------------------------------------------------------
// 二重起動防止のための Mutex
HANDLE mutexSingleInstanceApp;

int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
	const UnicodeString mutexName = L"mutexSingleInstanceAppLight"; // ★システムで唯一の名前にする
	mutexSingleInstanceApp = CreateMutex(NULL, true, mutexName.c_str());
	DWORD le = GetLastError();
	if (le == 0) {
		// 初回起動なら CreateMutex は成功する。何もせず起動へ。
	}
	else if (le == ERROR_ALREADY_EXISTS) {
		// 既に起動している "単一起動にする" をキャプションに持つウィンドウハンドルを探す。
		HWND hWnd = FindWindow(NULL, L"単一起動にする"); // ★メインフォームのキャプションにする

		// アイコン状態なら復元する
		if (IsIconic(hWnd))
			ShowWindowAsync(hWnd, SW_RESTORE);

		// フォアグラウンドにする (アクティブ化する)
		SetForegroundWindow(hWnd);

		return 1;
	}
	else {
		return 255;
	}

	try {
		Application->Initialize();
		Application->MainFormOnTaskBar = true;
		Application->CreateForm(__classid(TForm1), &Form1);
		Application->Run();
	}
	catch (Exception &exception) {
		Application->ShowException(&exception);
	}
	catch (...) {
		try {
			throw Exception("");
		}
		catch (Exception &exception) {
			Application->ShowException(&exception);
		}
	}
	ReleaseMutex(mutexSingleInstanceApp);
	return 0;
}
// ---------------------------------------------------------------------------

コメント