1 year ago

#66757

test-img

Andrew Truckle

Using a mutex to limit instance of application is not respected if one instance if started from Visual Studio

In InitInstance of my dialog application I use this code to detect other running versions:

strOwner.LoadString(IDS_APP_MUTEX);

m_hMutex = ::CreateMutex(nullptr, FALSE, strOwner);

HWND hOtherInstance = nullptr;
if (DetectRunningInstance(hOtherInstance))
{
    DetectFileToOpenFromFileExplorer();
    TryToOpenFileInOtherInstance(hOtherInstance);

    return FALSE; // Terminates the creation
}

The other functions referred to are:

bool CMeetingScheduleAssistantApp::DetectRunningInstance(HWND& rhOtherWnd) noexcept
{
    HWND hOther = nullptr;

    if (m_hMutex != nullptr) // indicates running instance
    {   
        if (::GetLastError() == ERROR_ALREADY_EXISTS)
        {
            EnumWindows(searcher, reinterpret_cast<LPARAM>(&hOther));

            if (hOther != nullptr)
            {
                ::SetForegroundWindow(::GetLastActivePopup(hOther));

                if (IsIconic(hOther))
                {
                    ::ShowWindow(hOther, SW_RESTORE);
                }

                rhOtherWnd = hOther;
                return true; // terminates the creation
            }

        }
    }

    return false;
}

And:

void CMeetingScheduleAssistantApp::DetectFileToOpenFromFileExplorer()
{
    CCommandLineInfo cmdInfo;

    m_bOpenFileFromFileExplorer = false;
    m_strFileToOpenFromFileExplorerPath;

    ParseCommandLine(cmdInfo);
    if (PathFileExists(cmdInfo.m_strFileName))
    {
        m_bOpenFileFromFileExplorer = true;
        m_strFileToOpenFromFileExplorerPath = cmdInfo.m_strFileName;
    }
}

And:

void CMeetingScheduleAssistantApp::TryToOpenFileInOtherInstance(HWND hOtherInstance)
{
    CString strFile = GetFileToOpenFromFileExplorerPath();

    S_COPY_PACKET sCopyDataPacket{};
    _tcscpy_s(sCopyDataPacket.szFile, strFile);
    sCopyDataPacket.guidSignature = CopyData_Signature;
    COPYDATASTRUCT cds;
    cds.dwData = COPYDATA_TYPE_MSA;
    cds.cbData = sizeof(sCopyDataPacket);
    cds.lpData = &sCopyDataPacket;

    DWORD_PTR dwResult{};
    if (SendMessageTimeout(hOtherInstance, WM_COPYDATA,
                    NULL, reinterpret_cast<LPARAM>(&cds), SMTO_BLOCK, 2000, &dwResult) != 0)
    {
        // The message was sent and processed
        if (dwResult == FALSE)
        {
            // The other instance returned FALSE. This is probably because it
            // has a pop-up window open so can't open the file
            ::OutputDebugString(_T("InitInstance::SendMessageTimeout [dwResult was FALSE].\n"));
        }
    }
    else
    {
        const DWORD dwError = ::GetLastError();
        if (dwError == ERROR_TIMEOUT)
        {
            // The message timed out for some reason
            ::OutputDebugString(_T("InitInstance::SendMessageTimeout [ERROR_TIMEOUT].\n"));
        }
        else
        {
            // Another unknown error
        }
        CString strError;
        strError.Format(_T("InitInstance::SendMessageTimeout [%d: %s]\n"), dwError,
                                (LPCTSTR)GetLastErrorAsStringEx(dwError));

        ::OutputDebugString(strError);
    }
}

And the seracher:

BOOL CALLBACK CMeetingScheduleAssistantApp::searcher(HWND hWnd, LPARAM lParam) noexcept
{
    DWORD_PTR   result{};

    const LRESULT ok = ::SendMessageTimeout(hWnd,
        theApp.UWM_ARE_YOU_ME_MSG,
        0, 0,
        SMTO_BLOCK |
        SMTO_ABORTIFHUNG,
        200,
        &result);
    if (ok == 0)
        return TRUE; // ignore this and continue

    if (result == theApp.UWM_ARE_YOU_ME_MSG)
    {
        // found it
        HWND *target = reinterpret_cast<HWND *>(lParam);
        *target = hWnd;

        return FALSE; // stop search
    }

    return TRUE; // continue search
}

Under normal circumstances this is fully functional. But I have a specific scenario where my mutex is not honored. I think this started with Visual Studio 2022 but I no longer have 2019 edition to confirm.


The scenario

My PC has an instance of the software I am developing "installed" and it has a shortcut on the taskbar. This links to the executable in the installed folder. If I simply use this shortcut then no issues, no multiple instances.

But, if I start my application from inside VS2022 instead, and then accidently click my other shortcut on the taskbar, I end up with two instances running. Why does this happen? I appreciate the otherinstance is running inside VS2022 but it is still the same software.

Is this by design or a coding oversight?


Update

  • If I manually double-click the release build EXE in file explorer, and then try to invoke the application in my VS2022 environment, it will jump to the existing instance. But ...

  • If I invoke the application in my VS2022 environment first, and then double-click the same release build EXE in file explorer, I end up with a multiple instance.

visual-c++

mfc

mutex

visual-studio-2022

windows-11

0 Answers

Your Answer

Accepted video resources