FAQ12-2

Q. 32ビットアプリで二重起動をチェックするには

 16ビットアプリケーションでは,WinMain()にhPrevInstanceという引数があり,この値をチェックすることで,EXEファイルを二重起動したときのチェックができました.
 しかし,32ビットアプリケーションでは,hPrevInstanceの値は常にNULLで,二重起動のチェックをすることができません.
 いろいろ他の人に聞いてみたところ,二重起動のチェックをするには,どうやらFindWindow()でウィンドウの存在をチェックするしかないようなのですが,本当にそれしかないのでしょうか.
 特に問題がなければそれでもよかったのですが,スタートアップに同じEXEを二つ登録すると,ウィンドウ作成が完了しないうちに二つのEXEが起動してしまい,FindWindow()ではチェックできないことがわかりました.
 どうか解決方法を教えてください.


A. ツールヘルプ関数を使う

 Windows3.1時代には,toolhelp.dllを利用してタスク管理をいろいろ行うことができましたが,Windows95にも同様にツールヘルプ関数があります(WindowsNTにはありません).
 ツールヘルプ関数を使えば,プロセスの管理などができるので,これを使って同じプロセスが既に起動されているかを調べれば,FindWindow()の問題を回避することができます.
 Windows95のツールヘルプ関数は,従来のツールヘルプとは異なり,kernelk32.dllの中にあります.
 しかし,どういうわけかインポートライブラリkernel32.libは,ツールヘルプ関数が使えるようになっておらず,普通のAPIのようにして使うことはできません.
 リスト12-5のように,GetProcAddress()を使って,直接kernel32.dllから関数のアドレスを取得します.
 リスト12-5のFindAnotherProcess()は,自分と同じEXEファイルのプロセスが存在すればtrue,存在しなければfalseを返す関数です.
 関数の前に#include <tlhelp32.h>をしていますが,これはツールヘルプ関数を使うときに必要なファイルです.ここでは構造体の宣言だけを利用しています.
 関数の前半は,各種ツールヘルプ関数のアドレスをkernel32.dllから取得する部分です.ここで厳重に注意が必要なのはGetProcAddres()の失敗チェックを必ずやらなくてはいけないということです.チェックしないと,WindowsNTで実行したときにエラーになってしまいます.
 実際の処理は,まずGetModuleFileName()で自分自身プロセスのファイル名を獲得します.ファイル名とは,EXEやDLLのファイル名のことです.
 次にProcess32First()とProcess32Next()を使って現在存在する全てのプロセスをチェックしていきます.チェックは,自分のファイル名と,検索対象のファイル名が一致するかどうかで判断し,一致する数が二つ以上になれば,自分の他にも同じプロセスが存在するということになります.一つは必ず自分のプロセスが一致するので,必ず「二つ以上」です.

[リスト12-5] :

#include <tlhelp32.h>

BOOL FindAnotherProcess( void ) {

	HANDLE hKernel;
	HANDLE (WINAPI *pfnCreateToolhelp32Snapshot)( DWORD, DWORD );
	BOOL (WINAPI *pfnProcess32First)( HANDLE, LPPROCESSENTRY32 );
	BOOL (WINAPI *pfnProcess32Next)( HANDLE, LPPROCESSENTRY32 );
	hKernel = GetModuleHandle( "KERNEL32.DLL" );
	if( !hKernel ) return FALSE;
	(FARPROC&)pfnCreateToolhelp32Snapshot = GetProcAddress( hKernel, "CreateToolhelp32Snapshot" );
	(FARPROC&)pfnProcess32First = GetProcAddress( hKernel, "Process32First" );
	(FARPROC&)pfnProcess32Next = GetProcAddress( hKernel, "Process32Next" );
	if( !pfnCreateToolhelp32Snapshot ) return FALSE;
	
	int cnt = 0;
	BOOL fFound = FALSE;
	HANDLE hSnap = pfnCreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
	if( hSnap != (HANDLE)-1 ) {
		PROCESSENTRY32 pe;
		pe.dwSize = sizeof(PROCESSENTRY32);
		char	szMyExe[MAX_PATH];
		GetModuleFileName(hInstance, szMyExe, sizeof(szMyExe) );

		if( pfnProcess32First( hSnap, &pe ) ) {
			do {
				if( lstrcmpi( pe.szExeFile, szMyExe ) == 0  ) {
					cnt ++;
					if( cnt >= 2 ) {
						fFound = TRUE;
						break;
					}
				}
			} while( pfnProcess32Next( hSnap, &pe ) );
		}
		CloseHandle( hSnap );
	}
	return fFound;	//TRUE:同じプロセスが存在する FALSE:存在しない
}
 もしかしたら,FindWindow()するよりもかなり遅いかもしれないので,FindWindow()してウィンドウが見つからなかった場合にのみ,念のためやっておく程度にすることをお勧めします(リスト12-6).

[リスト12-6] :

HINSTANCE	hInstance;

int WinMain( HANDLE hInstance, HANDLE, LPSTR lpCmdLine, int nCmdShow) {

	::hInstance = hInstance;
	HWND	hwndFound = FindWindow( szClassName, NULL );

	if( hwndFound == NULL ) {
		if( FindAnotherProcess() ) {
			return 0;
		}
	} else {
		SetForegroundWindow( hwndFound );
		return 0;
	}
		:
		:
}

Back to FAQ main page