MFCアプリで二重起動防止をするときは、InitInstanceで名前付きミューテックスをCreateMutexで作り、直後のGetLastError()が183(ERROR_ALREADY_EXISTS)かどうかで、同じアプリがすでに動いているかを判断します。同じ名前のミューテックスがすでにあってもCreateMutexは失敗せず、既存のミューテックスのハンドルを返すので、戻り値がNULLかどうかでは1つめと2つめの起動を区別できません。
CreateMutexの戻り値とGetLastErrorの組み合わせ
CreateMutexWの説明では、名前付きミューテックスがこの呼び出しの前からあった場合、戻り値は既存のミューテックスのハンドルになり、GetLastErrorはERROR_ALREADY_EXISTSを返すとされています。
| 戻り値 | GetLastError() | 意味 | アプリがすること |
|---|---|---|---|
| NULL以外 | 0(ERROR_SUCCESS) | このプロセスがミューテックスを新しく作った。最初の起動 | そのまま画面を開く。ハンドルはアプリが終わるまで閉じない |
| NULL以外 | 183(ERROR_ALREADY_EXISTS) | 同じ名前のミューテックスがすでにあり、そのハンドルが返った。2つめの起動 | 1つめのウィンドウを前に出し、InitInstanceでFALSEを返して終わる |
| NULL | 6(ERROR_INVALID_HANDLE) | 同じ名前のイベント・セマフォ・ファイルマッピングなど、ミューテックス以外のオブジェクトがある | ミューテックスの名前を、ほかと重ならないものに変える |
| NULL | 上記以外 | ミューテックスを作ることも開くこともできなかった | 番号をログに残して起動をやめる |
183をFormatMessageで文章にすると「既に存在するファイルを作成することはできません。」になります。ファイルの話に読めますが、ミューテックスでも同じ番号が使われます。
同じexeを2つ起動したときのCreateMutexとGetLastError 183
Visual Studio 2026、Debug/x64のMFCダイアログで試しました。InitInstanceの最初でCreateMutex(NULL, FALSE, _T("Local\\MFCGuide.251.SingleInstance"))を呼び、1つめの起動が画面を開いている間に、同じexeをもう1つ起動しています。2つめの起動は、自分が見た値を1つめのウィンドウへメッセージで送ってから終わるので、両方の値が1つめの画面に並んでいます。

Aの最初の起動では、戻り値がNULL以外でGetLastError()は0でした。Bの2つめの起動でも戻り値はNULL以外で、GetLastError()だけが183に変わりました。戻り値がNULLかどうかで判定するコードだと、2つめも最初の起動と同じ扱いになり、画面が2つ開きます。
Bの2つめは183を見て1つめのウィンドウを探し、SetForegroundWindowを呼びました(戻り値は0以外で、ウィンドウは前面に出ました)。そのあとInitInstanceでFALSEを返して、画面を開かずに終わりました。
CとDは、1つめの画面の中でCMutexを作って試した結果です。Cは同じ名前でCMutexを作った直後の値、Dは関数のローカル変数のCMutexが閉じたあとに同じ名前で作り直したときの値です。
InitInstanceに書く二重起動防止のコード
ミューテックスのハンドルは、アプリのクラスのメンバー変数に持たせます。ミューテックスは最後のハンドルが閉じられたときに消えるので、1つめの起動がハンドルを閉じてしまうと、その後に起動した2つめは183を受け取れません。
// MyApp.h
class CMyApp : public CWinApp
{
// Construction
public:
CMyApp();
// Overrides
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
// Implementation
protected:
HANDLE m_hSingleInstance; // アプリが終わるまで閉じない
DECLARE_MESSAGE_MAP()
};
2つめの起動は、1つめのメインウィンドウを探して前に出してから終わります。ウィンドウの題名で探すと、題名にファイル名が入るアプリでは見つからないことがあります。この例では、1つめのメインウィンドウにSetPropで目印を付けておき、2つめはEnumWindowsで全部のトップレベルウィンドウを順に調べて、目印の付いたウィンドウを探しています。ミューテックスの名前と目印の名前は、アプリのクラスとメインウィンドウのクラスの両方で使います。
// SingleInstance.h(MyApp.cpp と MyDlg.cpp からインクルードする)
#pragma once
// 同じログオンセッションの中で1つだけ起動させる
static const TCHAR szSingleInstanceMutexName[] = _T("Local\\MyCompany.MyApp.SingleInstance");
// 1つめの起動のメインウィンドウに付ける目印
static const TCHAR szMainWindowProp[] = _T("MyCompany.MyApp.MainWindow");
// MyApp.cpp
#include "SingleInstance.h"
static BOOL CALLBACK FindMainWindow(HWND hWnd, LPARAM lParam)
{
if (::GetProp(hWnd, szMainWindowProp) == NULL)
return TRUE; // 次のウィンドウを調べる
*reinterpret_cast<HWND*>(lParam) = hWnd;
return FALSE; // 見つかったので列挙をやめる
}
static void ActivateRunningInstance()
{
HWND hWnd = NULL;
::EnumWindows(FindMainWindow, reinterpret_cast<LPARAM>(&hWnd));
if (hWnd == NULL)
return;
if (::IsIconic(hWnd))
::ShowWindow(hWnd, SW_RESTORE); // 最小化されていたら元に戻す
::SetForegroundWindow(hWnd);
}
CMyApp::CMyApp()
: m_hSingleInstance(NULL)
{
}
BOOL CMyApp::InitInstance()
{
m_hSingleInstance = ::CreateMutex(NULL, FALSE, szSingleInstanceMutexName);
const DWORD dwError = ::GetLastError(); // ほかのAPIを呼ぶ前に保存する
if (m_hSingleInstance == NULL)
{
TRACE(_T("CreateMutex failed: %lu\n"), dwError);
return FALSE;
}
if (dwError == ERROR_ALREADY_EXISTS)
{
ActivateRunningInstance();
return FALSE; // 画面を開かずに終わる
}
CWinApp::InitInstance();
// ここから下は、ウィザードが作ったコードのまま
// ...
}
int CMyApp::ExitInstance()
{
if (m_hSingleInstance != NULL)
{
::CloseHandle(m_hSingleInstance);
m_hSingleInstance = NULL;
}
return CWinApp::ExitInstance();
}
GetLastError()は、CreateMutexの次の行で変数に保存しています。間にほかのAPIを呼ぶと、そのAPIが値を書き換えることがあるためです(GetLastErrorは失敗直後に読む)。CreateMutexは成功したときにも183を残すので、成功か失敗かにかかわらず、次の行で保存しています。
MFCのAfxWinMainは、InitInstanceがFALSEを返したときもExitInstanceを呼んでから終わります。2つめの起動が開いたハンドルも、ここで閉じられます。ExitInstanceで閉じなくても、プロセスが終わればハンドルはシステムが閉じます。
目印は、ダイアログベースのアプリならOnInitDialogでSetPropを呼んで付け、OnDestroyでRemovePropを呼んで外します。SDI / MDIのアプリなら、CMainFrameのOnCreateとOnDestroyに同じ処理を書きます。
// MyDlg.cpp
#include "SingleInstance.h"
BOOL CMyDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
::SetProp(GetSafeHwnd(), szMainWindowProp, reinterpret_cast<HANDLE>(1));
// ...
return TRUE;
}
void CMyDlg::OnDestroy()
{
::RemoveProp(GetSafeHwnd(), szMainWindowProp);
CDialogEx::OnDestroy();
}
SetForegroundWindowは、呼んだプロセスが前面のプロセスから起動された、最後の入力を受け取った、などの条件を満たすときだけウィンドウを前面に出せます。条件を満たさないときはウィンドウが前に出ず、タスクバーのボタンが点滅します。
CMutexで書く場合:構築の直後にGetLastErrorを読み、ローカル変数にしない
MFCのCMutexを使っても、判定の仕方は同じです。Visual Studio 2026のMFCのソース(mtex.cpp)では、CMutexのコンストラクタは::CreateMutexを呼んだあと、戻り値がNULLなら例外を投げるだけで、ほかのAPIは呼んでいません。そのため、構築の次の行でGetLastError()を読めば183が取れます。CMutexの説明にも、作ったあとにGetLastErrorで、ミューテックスが前からあったかを確かめるよう書かれています。
// MyApp.h(CMutexで書く場合)
#include <afxmt.h>
class CMyApp : public CWinApp
{
// ...
// Implementation
protected:
CMutex* m_pSingleInstance; // アプリが終わるまで持つ
};
// MyApp.cpp
CMyApp::CMyApp()
: m_pSingleInstance(NULL)
{
}
BOOL CMyApp::InitInstance()
{
m_pSingleInstance = new CMutex(FALSE, szSingleInstanceMutexName);
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
ActivateRunningInstance();
return FALSE;
}
// ...
}
int CMyApp::ExitInstance()
{
delete m_pSingleInstance;
m_pSingleInstance = NULL;
return CWinApp::ExitInstance();
}
CMutexをInitInstanceのローカル変数にすると、関数を抜けたときにデストラクタがハンドルを閉じます。ダイアログベースのアプリは、InitInstanceの中でDoModalを呼んで画面が閉じるまで待つので、ローカル変数でも画面を開いている間はハンドルが残ります。SDI / MDIのアプリは、InitInstanceの中でメインウィンドウを表示してからTRUEを返し、そのあとRunでメッセージループを回します。ローカル変数のCMutexはInitInstanceを抜けるときに閉じるので、画面を使っている間はもうハンドルがありません。
ほかにハンドルを持つプロセスがなければミューテックスも消え、次に起動したプロセスのGetLastError()は0になります。

上の画像は、最初に載せた実行結果と同じものです。Cは、1つめのハンドルが開いている間に同じ名前でnew CMutexを呼んだ直後の値で、183になりました。Dは、関数のローカル変数のCMutexが閉じたあとに同じ名前でCreateMutexを呼んだ結果で、183ではなく0が返りました。
ミューテックスの名前の付け方(Local\とGlobal\)
Kernel object namespacesによると、ミューテックスなどの名前は、ログオンのセッションごとに分かれた場所に作られます。名前の先頭に何も付けない場合とLocal\を付けた場合は、そのセッションの中だけで同じ名前として扱われます。リモートデスクトップやユーザーの切り替えで同じPCに別のセッションがあっても、それぞれのセッションで1つずつ起動できます。PC全体で1つにしたいときは、名前の先頭にGlobal\を付けます。
名前には、ほかのアプリと重ならないように会社名やアプリ名を入れます。CreateMutexWの説明には、悪意のあるユーザーが同じ名前のミューテックスを先に作ると、アプリを起動できなくされるおそれがあることと、その対策(名前をランダムにして権限のあるユーザーだけが読める場所に保存する、ユーザーのプロファイルのフォルダーにロックしたファイルを作る)も書かれています。
まとめ
- 2つめの起動でもCreateMutexの戻り値はNULL以外になります。二重起動は、直後に保存したGetLastError()が183(ERROR_ALREADY_EXISTS)かどうかで判定します。
- ミューテックスのハンドルは、アプリのメンバー変数に持たせて、アプリが終わるまで閉じません。CMutexをInitInstanceのローカル変数にすると、SDI / MDIではInitInstanceを抜けるときに閉じてしまい、その後の2つめの起動を見分けられません。
- 2つめの起動は、1つめのメインウィンドウを探して前に出し、InitInstanceでFALSEを返して終わります。
