“简单”研究了一下 Windows 如何禁止应用开启多实例,实际有两个通用方案:使用 CreateFileW 在临时文件夹中创建一个独占的文件实现互斥以及使用 CreateMutexExW 创建一个独占的具名互斥锁实现互斥;需要监听端口的程序使用 bind 时也自带这种效果,这些方法都能实现原子的互斥。
微软实际上推荐使用 CreateFileW 在用户临时文件夹里创建唯一的文件,因为这样可以只禁止单用户多实例不禁止每个用户有自己的实例,但考虑到使用文件的方式会创建不必要的文件,并且获得临时文件夹路径的 API 不保证临时文件夹一定可用(这代表可能因此出现其他的失败情景或者需要进一步处理),因此我选择了一种折衷方案:获得临时文件夹的路径作为具名互斥锁的部分名字。
基础代码非常简单:
C++
// use user's temp folder name as part of mutex name// extra nameautoconstexprextra{L"PlayerWinMutex\0"sv};// name bufferautoname{std::to_array<wchar_t,MAX_PATH+2+extra.size()+1>({})};// get tmp folder, not guaranteed path availablityautolength{GetTempPathW(static_cast<DWORD>(name.size()),&name[0])};// add extra to pathstd::memcpy(&name[length],extra.data(),extra.size()+1);// mutex name can't contain '\'std::replace(&name[0],&name[length],'\\','/');// check mutex is held by another instance, NO NEED TO USE GetLastErrorif(!::CreateMutexExW(nullptr,&name[0],CREATE_MUTEX_INITIAL_OWNER,NULL)){// do something else,// such as print log, notify user or synchroniz with other instance::ExitProcess(1u);}
// enumerate all direct child windows of desktopfor(autopre{::FindWindowExW(nullptr,nullptr,nullptr,nullptr)};// return null if at the endpre!=nullptr;// pass the pre as the 2nd argument to get next handlepre=::FindWindowExW(nullptr,pre,nullptr,nullptr)){// check if window has the specified property// set in MainWindow constructorif(::GetPropW(pre,L"PlayerWinRT")){// show and restore window::ShowWindow(pre,SW_RESTORE);// activate, set foreground and get forcus::SetForegroundWindow(pre);// exit app, DO NOT USE this->EXIT BECAUSE NOT CONSTRUCTED::ExitProcess(1u);}}
在创建窗口时添加如下代码:
C++
HWNDhandle;// current window's handle::SetPropW(handle,L"PlayerWinRT",handle);