日記 Visual Studio 2019 C++ Windowsアプリの二重起動を防止する方法を調べた

## 概要

多重起動を防止する方法には、Atomを使用する、Mutexを使用する、MFCを使用する、といった方法があるらしいのですが、よく分からなかったので、exe名からプロセスの存在チェックをする方法を取りました。

## 処理概要

* 自分のプロセスIDから自分のexe名を取得
* 同じexe名のプロセスIDを全て取得
* 自分以外のプロセスIDが既にあった場合
  * タスクトレイの中に入っている場合、元のサイズに戻す
  * フォアグラウンドにする

## プログラム

### 起動時の初期処理に追加

```Sample.cpp
// グローバル変数
// プロセスIDからウインドウハンドルを取得用
HWND hWnd_existed;

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // ------------------------------------------------------------------------
    // 二重起動防止
    // ------------------------------------------------------------------------

    // 自分のプロセスIDから自分のexe名を取得
    DWORD dwCurrentProcessId = GetCurrentProcessId();
    HANDLE hProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
        FALSE,
        dwCurrentProcessId
    );
    TCHAR szModuleName[MAX_PATH];
    GetModuleBaseName(hProcess, NULL, szModuleName, MAX_PATH);

    // 自分のexe名と同じプロセスのプロセスIDを取得(自分のプロセスIDは除く)
    DWORD dwExeProcessIds[1024] = { 0 };
    GetExeOtherProcessIds(szModuleName, dwExeProcessIds, dwCurrentProcessId);

    // 既に起動済みだった場合
    if (0 < dwExeProcessIds[0])
    {
        // プロセスIDからウインドウハンドルを取得
        EnumWindows(EnumWindowsProcMy, dwExeProcessIds[0]);
        if (hWnd_existed)
        {
            // タスクトレイの中に入っている場合、元のサイズに戻す
            if (IsIconic(hWnd_existed))
            {
                ShowWindow(hWnd_existed, SW_RESTORE);
            }
            else
            {
                // 見つかったウィンドウをフォアグラウンドにする
                SetForegroundWindow(GetLastActivePopup(hWnd_existed));
            }
        }
        return FALSE;
    }
```

### EXE名から同じEXE名のプロセスIDを取得する関数

```Sample.cpp
// sTargetExeNameのプロセスIDをdwExeProcessIdsに設定
// dwIgnoreProcessIdのプロセスIDは除外
VOID GetExeOtherProcessIds(CString sTargetExeName, DWORD* dwExeProcessIds, DWORD dwIgnoreProcessId)
{
    DWORD dwAllProcessIds[1024] = { 0 };
    DWORD cbNeeded = 0;
    if (!EnumProcesses(dwAllProcessIds, sizeof(dwAllProcessIds), &cbNeeded))
    {
        return;
    }

    int j = 0;
    int nProc = cbNeeded / sizeof(DWORD);
    for (int i = 0; i < nProc; i++)
    {
        if (dwAllProcessIds[i] == dwIgnoreProcessId)
        {
            continue;
        }

        TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");

        // Get a handle to the process.
        HANDLE hProcess = OpenProcess(
            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
            FALSE,
            dwAllProcessIds[i]
        );

        // Get the process name.
        if (hProcess)
        {
            HMODULE hMod;
            DWORD cbNeeded;

            if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
                &cbNeeded))
            {
                GetModuleBaseName(hProcess, hMod, szProcessName,
                    sizeof(szProcessName) / sizeof(TCHAR));

                CString sProcName = CString(szProcessName).MakeUpper();
                if (sProcName == sTargetExeName.MakeUpper())
                {
                    dwExeProcessIds[j] = dwAllProcessIds[i];
                    ++j;
                }
            }

            // Release the handle to the process.
            CloseHandle(hProcess);
        }
    }
}
```

### プロセスIDからウインドウハンドルを取得する関数

```Sample.cpp
// プロセスIDからウインドウハンドルを取得
// https://stackoverflow.com/questions/11711417/get-hwnd-by-process-id-c
BOOL CALLBACK EnumWindowsProcMy(HWND hwnd, LPARAM lParam)
{
    DWORD lpdwProcessId;
    GetWindowThreadProcessId(hwnd, &lpdwProcessId);
    if (lpdwProcessId == lParam)
    {
        hWnd_existed = hwnd;
        return FALSE;
    }
    return TRUE;
}
```