原始方法:
在应用程序入口(_tmain或_tWinMain)函数中创建(Create*)一个命名对象(具体创建什么类型无关紧要),Create*返回后,再调用一下GetLassError。如果返回ERROR_ALREADY_EXISTS,表明应用程序的另一个实例正在运行,新的实例就可以退出了。
例码:
int main()
{
HANDLE h = CreateMutex(NULL,FALSE,TEXT("showerrorMutex"));
if (ERROR_ALREADY_EXISTS == GetLastError())
{
cout<<"A old app exit!"<<endl;
CloseHandle(h);
return 0;
}
/* ..........其他代码..........*/
CloseHandle(h); /*关闭句柄*/
return 0;
}
安全风险:
任何进程--即使是最低权限的进程--都能用任何指定的名称来创建一个对象,所以攻击者很容易写一个应用程序来创建一个同名的内核对象。如果它先于单实例应用程序启动,上面“单实例”的应用程序就变成一个“无实例”的应用程序--始终都是已启动就退出。这种攻击就是拒绝服务(DoS)的一种。
扩展方法:
通过专有命名空间,我自己也不是很理解。只贴上代码,详细见<Windows 核心编程>:
#include "Resource.h"
#include "../CommonFiles/CmnHdr.h"
#include <WindowsX.h>
#include <Sddl.h>
#include <tchar.h>
#include <strsafe.h>
//主对话框句柄
HWND g_hDlg;
HANDLE g_hBoundary = NULL;
HANDLE g_hNameSpace = NULL;
HANDLE g_hSingleTon =NULL;
BOOL g_bNamespaceOpened = FALSE;
PCTSTR g_szBoundary = TEXT("3_Boundary");
PCTSTR g_szNameSpace = TEXT("3-NameSpace");
#define DETAILS_CTRL GetDlgItem(g_hDlg,IDC_EDIT_DETAILS)
void addText(PCTSTR pszFormat,...)
{
va_list argList;
va_start(argList,pszFormat);
TCHAR sz[20*1024];
Edit_GetText(DETAILS_CTRL,sz,_countof(sz));
_vstprintf_s(_tcschr(sz, TEXT('\0')), _countof(sz) - _tcslen(sz), pszFormat, argList);
Edit_SetText(DETAILS_CTRL,sz);
va_end(argList);
return ;
}
VOID checkInstances()
{
g_hBoundary = CreateBoundaryDescriptor(g_szBoundary,0);
BYTE localAdminSID[SECURITY_MAX_SID_SIZE];
PSID pLocalAdminSID = &localAdminSID;
DWORD cbSID = sizeof(localAdminSID);
if(!CreateWellKnownSid(WinBuiltinAdministratorsSid,NULL,pLocalAdminSID,&cbSID))
{
addText(TEXT("AddSIDToBoundary failed : %u\r\n"),GetLastError());
return;
}
if (!AddSIDToBoundaryDescriptor(&g_hBoundary,pLocalAdminSID))
{
addText(TEXT("AddSIDto boundary descriptor failed : %u\r\n",GetLastError()));
return;
}
SECURITY_ATTRIBUTES sa;
memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
sa.bInheritHandle = FALSE;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(TEXT("D:"), //去掉D:后面的括号
SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL))
{
addText(TEXT("Security Descriptor create failed : %u\r\n", GetLastError()));
return;
}
g_hNameSpace = CreatePrivateNamespace(&sa,g_hBoundary,g_szNameSpace);
LocalFree(sa.lpSecurityDescriptor);
DWORD dwLastError = GetLastError();
if (NULL == g_hNameSpace)
{
if (dwLastError == ERROR_ACCESS_DENIED)
{
addText(TEXT("Access denied when creating namespace : %u\r\n",GetLastError()));
addText(TEXT("You must be running as Administrator .\r\n"));
return ;
}
else
{
if(dwLastError == ERROR_ALREADY_EXISTS)
{
addText(TEXT("Create private namespace failed : %u\r\n"),GetLastError());
g_hNameSpace = OpenPrivateNamespace(g_hBoundary,g_szBoundary);
if (NULL == g_hNameSpace)
{
addText(TEXT("and open private namespace failed : %u\r\n"),GetLastError());
return ;
}
else
{
addText(TEXT(" but open private namespace succeeded\r\n"));
return;
}
}
else
{
addText(TEXT("Unexpected error occured: %u\r\n"),GetLastError());
return ;
}
}
}
TCHAR szMutexName[64];
StringCchPrintf(szMutexName,_countof(szMutexName),TEXT("%s\\%s"),g_szNameSpace,TEXT("Singleton"));
g_hSingleTon = CreateMutex(NULL,FALSE,szMutexName);
if (ERROR_ALREADY_EXISTS == GetLastError())
{
addText(TEXT("Another instance is running.\r\n"));
addText(TEXT("-----Impossible to access application features.\r\n"));
}
else
{
addText(TEXT("First instance of This app! \r\n"));
addText(TEXT("---->access application features now.\r\n"));
}
}
VOID Dlg_OnCommand(HWND hwnd,int id,HWND hwndCtrl,UINT codeNotify)
{
switch(id)
{
case IDOK:
case IDCANCEL:
EndDialog(hwnd,id);
break;
}
return ;
}
BOOL Dlg_InitDlg(HWND hwnd,HWND hwndFocus,LPARAM lParam)
{
chSETDLGICONS(hwnd,IDI_SINGLETON); //设置对话框图标
g_hDlg = hwnd;
(VOID)checkInstances(); //检查是否已经存在实例
return TRUE;
}
/************************************************************************/
/*
主对话框处理函数
*/
/************************************************************************/
INT_PTR WINAPI dlg_Proc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg)
{
chHANDLE_DLGMSG(hwnd,WM_COMMAND,Dlg_OnCommand);
chHANDLE_DLGMSG(hwnd,WM_INITDIALOG,Dlg_InitDlg);
}
return FALSE;
}
int APIENTRY _tWinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPTSTR lpCmdLine, __in int nShowCmd )
{
/* 忽略无关参数 */
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
//show main window
DialogBox(hInstance,MAKEINTRESOURCE(IDD_SINGLETON),NULL,dlg_Proc);
if (NULL != g_hSingleTon )
{
CloseHandle(g_hSingleTon);
}
if (NULL != g_hNameSpace )
{
if (g_bNamespaceOpened)
{
ClosePrivateNamespace(g_hNameSpace, 0);
}
else
{
ClosePrivateNamespace(g_hNameSpace, PRIVATE_NAMESPACE_FLAG_DESTROY);
}
}
if (NULL != g_hBoundary)
{
DeleteBoundaryDescriptor(g_hBoundary);
}
return 0;
}
注意:此代码是我抄出来的,系统配置:Win7 64位系统VS2013 的win32,在调用ConvertStringSecurityDescriptorToSecurityDescriptor时,本来一直失败(security descriptor create failed),后来在网上搜把其第一个参数(TEXT("D:()"))中的括号去掉了,函数就返回成功了,我也不知是什么原因,在此仅作提示。