ミューテックスによる2重起動防止の罠

Windowsでプロセスの二重起動の防止にはミューテックスが使われます。
サンプルなどをみると簡単なのですが、意外とハマる罠があるので注意しましょう。
ちなみに以下のサンプルコードはVC2008で試しています。

罠1:OpenMutexしてはいけない

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <locale.h> //_tsetlocale用
#include <conio.h>	//getch用
int _tmain(int argc, _TCHAR* argv[])
{
	_tsetlocale( LC_ALL, _T("Japanese") );	//_tprintfで日本語を表示させるために必要
	HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, _T("2重起動防止ミューテックス"));
	if(hMutex != NULL)
	{
		_tprintf(_T("既に起動しています!"));
	}
	else
	{
		hMutex = CreateMutex(NULL, FALSE, _T("2重起動防止ミューテックス"));
		_tprintf(_T("1つめの起動です!"));
	}
	getch();	//キーを入力するまで待つ
	if(hMutex != NULL)
	{
		::CloseHandle(hMutex);
	}
	return 0;
}

試してみると一見うまく動くように見えます。
ところが高速に連続して起動を行うと二重起動してしまいます。
理由は1つ目のプロセスがOpenMutexを実行後、CreateMutexを実行する前に2つ目のプロセスがOpenMutexを実行するとまだミューテックスが作成されていないためOpenMutexでNULLが返ってくるためです。

罠2:CreateMutexの戻りをチェックを省略してはいけない

int _tmain(int argc, _TCHAR* argv[])
{
	_tsetlocale( LC_ALL, _T("Japanese") );	//_tprintfで日本語を表示させるために必要
	HANDLE hMutex = ::CreateMutex(NULL, FALSE, _T("2重起動防止ミューテックス"));
	if(::GetLastError() == ERROR_ALREADY_EXISTS)
	{
		_tprintf(_T("既に起動しています!"));
	}
	else
	{
		_tprintf(_T("1つめの起動です!"));
	}
	getch();	//キーを入力するまで待つ
	if(hMutex != NULL)
	{
		::CloseHandle(hMutex);
	}
	return 0;
}

罠1の問題は解消されました。
しかし、CreateMutexの戻り値を確認していません。
これがNULLを返すパターンがあります。
Windows XPの場合は実行ファイルのアイコンを右クリック→「別のユーザーとして実行」から別ユーザー権限で実行します。
すでに違うユーザーが同名のミューテックスを作成済みの場合、作成に失敗します。

罠3:第1引数のLPSECURITY_ATTRIBUTESにNULLを指定してはいけない

「別のユーザーとして実行」を実行した場合にミューテックスの作成に失敗します。
ACLの設定を行うことで回避します。
一応↓がACLの設定部分のエラーチェックは入っていないもののだいたいの完成系になります。

int _tmain(int argc, _TCHAR* argv[])
{
	_tsetlocale( LC_ALL, _T("Japanese") );	//_tprintfで日本語を表示させるために必要
	//フルアクセス
	SECURITY_DESCRIPTOR sd;
	InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION);
	SetSecurityDescriptorDacl(&sd, TRUE, 0, FALSE);	    
	SECURITY_ATTRIBUTES secAttribute;
	secAttribute.nLength = sizeof (secAttribute);
	secAttribute.lpSecurityDescriptor = &sd;
	secAttribute.bInheritHandle = TRUE; 

	HANDLE hMutex = ::CreateMutex(&secAttribute, FALSE, _T("2重起動防止ミューテックス"));
	if(hMutex == NULL)
	{
		_tprintf(_T("ミューテックスの取得に失敗!"));
	}
	else if(::GetLastError() == ERROR_ALREADY_EXISTS)
	{
		_tprintf(_T("既に起動しています!"));
	}
	else
	{
		_tprintf(_T("1つめの起動です!"));
	}
	getch();	//キーを入力するまで待つ
	if(hMutex != NULL)
	{
		::CloseHandle(hMutex);
	}
	return 0;
}

応用

逆に考えると一般的な二重起動を許可しないアプリケーションでも「別のユーザーとして実行」を行うことで二重起動できる可能性があります。
いくつかのオンラインゲームを試したところ、この方法で二重起動するものもありました。(CrackProof系)
ネットゲームなどで二重起動したいときに一度試してみる価値はあると思います。