在前两章讲了枚举进程和窗口的方式实现对进程多开的检测.
详情请见:
如果在这两种方法均无法达到很好检测效果的情况下,那么可以使用全局对象的方式来实现.
在Windows中典型可用于共享进程数据的全局对象有:
Event(事件)
Mutex(互斥体)
Semaphore(信号量)
它们均有一个共同点:随进程的生命结束而减少对象的引用或关闭对象.
基于这种特性,我们刚好可以拿来进行对进程数量的检测.
使用Event检测:
//调用CreateEventW来创建事件
//成功返回TRUE,失败FALSE
BOOL myCreateEvent(LPWSTR pName)
{
BOOL bRet = FALSE;
__try
{
if (!pName || !wcslen(pName))
return bRet;
HANDLE hMutex = CreateEventW(NULL,FALSE, FALSE, pName);
if (!hMutex)
return bRet;
if (GetLastError() == ERROR_ALREADY_EXISTS)
return bRet;
bRet = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER) { ; }
//发生异常错误,返回NULL
return bRet;
}
BOOL CheckCountByEvent(LONG nMaxCount)
{
WCHAR strMoudleName[MAX_PATH] = { 0 };
GetModuleFileNameW(GetModuleHandleW(NULL), strMoudleName, sizeof(strMoudleName));
if (!wcslen(strMoudleName))//没取到模块路径,直接返回TRUE,意味可能系统环境异常
return TRUE;
WCHAR* strName = wcsrchr(strMoudleName, L'\\');
if (!strName)//路径异常
return TRUE;
BOOL isLimit = TRUE;
strName += 1;
for (size_t i = 0; i < nMaxCount; i++)
{
WCHAR strMutant[MAX_PATH] = { 0 };
wsprintfW(strMutant, L"Event_%s_%d", strName, i);
BOOL isCreate = myCreateEvent(strMutant);
if (isCreate)
{
isLimit = FALSE;
break;
}
}
return isLimit;
}
代码解释:
我们直接调用CheckCountByEvent()函数,并且传递最大限制数量即可实现多开限制.
函数内部通过GetModuleFileNameW()获取进程程序的文件名,并以此作为基础Event名字.
在函数内实现一个遍历,实现每启动一次程序便起创建一个名字为"Event_程序名字_序号"的Event.
如果在限制数量内无法创建Event,则代表已经达到了最大限制数量,或者系统环境有异常.
其实在这里,您可以将事件名字修改为一个常量字符串,例如:"MY_GAME_序号",这种方式可以避免用户通过修改文件名实现躲避检测.
但我们此处故意使用文件名作为基础名字,是为了以后章节介绍的关于对程序运行期间作弊行为检测而做铺垫(本章不讲).
您可以理解为它是一种暗桩即可.
在myCreateEvent()函数中的实现非常简单,就是通过传入的名字创建Event,如果GetLastError()的返回值是ERROR_ALREADY_EXISTS则表示Event重复创建,并且返回FALSE
另外关于对Mutex和Semaphore的检测原理与此相同,而且这三种全局对象可以同时存在,即:您可以同时使用这些全局对象的检测,而不会互相干扰.
关于Mutex和Semaphore的检测代码:
//调用CreateMutexW来创建互斥体
//成功返回TRUE,失败FALSE
BOOL myCreateMutex(LPWSTR pName)
{
BOOL bRet = FALSE;
__try
{
if(!pName || !wcslen(pName))
return bRet;
HANDLE hMutex = CreateMutexW(NULL,FALSE, pName);
if (!hMutex)
return bRet;
if(GetLastError() == ERROR_ALREADY_EXISTS)
return bRet;
bRet = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER) { ; }
//发生异常错误,返回NULL
return bRet;
}
BOOL CheckCountByMutex(LONG nMaxCount)
{
WCHAR strMoudleName[MAX_PATH] = { 0 };
GetModuleFileNameW(GetModuleHandleW(NULL), strMoudleName, sizeof(strMoudleName));
if (!wcslen(strMoudleName))//没取到模块路径,直接返回TRUE,意味可能系统环境异常
return TRUE;
WCHAR* strName = wcsrchr(strMoudleName, L'\\');
if(!strName)//路径异常
return TRUE;
BOOL isLimit = TRUE;
strName += 1;
for (size_t i = 0; i < nMaxCount; i++)
{
WCHAR strMutant[MAX_PATH] = { 0 };
wsprintfW(strMutant, L"Mutex_%s_%d", strName, i);
BOOL isCreate = myCreateMutex(strMutant);
if (isCreate)
{
isLimit = FALSE;
break;
}
}
return isLimit;
}
//调用OpenSemaphoreW来创建信号量
//成功返回TRUE,失败FALSE
BOOL myCreateSemaphore(LPWSTR pName,DWORD nMaxCount)
{
BOOL bRet = FALSE;
__try
{
if (!pName || !wcslen(pName))
return bRet;
//判断信号量是否存在
HANDLE hSema = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS, FALSE, pName);
if (hSema)
return FALSE;
//创建信号量
hSema = CreateSemaphoreW(NULL, 0, 1, pName);
if(!hSema)//创建失败,可能是系统环境异常,直接返回FALSE
return FALSE;
bRet = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER) { ; }
//发生异常错误,返回NULL
return bRet;
}
BOOL CheckCountBySemaphore(LONG nMaxCount)
{
WCHAR strMoudleName[MAX_PATH] = { 0 };
GetModuleFileNameW(GetModuleHandleW(NULL), strMoudleName, sizeof(strMoudleName));
if (!wcslen(strMoudleName))//没取到模块路径,直接返回TRUE,意味可能系统环境异常
return TRUE;
WCHAR* strName = wcsrchr(strMoudleName, L'\\');
if (!strName)//路径异常
return TRUE;
BOOL isLimit = TRUE;
strName += 1;
for (size_t i = 0; i < nMaxCount; i++)
{
WCHAR strMutant[MAX_PATH] = { 0 };
wsprintfW(strMutant, L"Semaphore_%s_%d", strName,i);
BOOL isCreate = myCreateSemaphore(strMutant, nMaxCount);
if (isCreate)
{
isLimit = FALSE;
break;
}
}
return isLimit;
}
最后的调用:
//限制多开数量
LONG nMaxCount = 2;
BOOL bEvent, bMutex, bSemaphore;
//判断进程数量是否超出限制数量
bEvent = CheckCountByEvent(nMaxCount);
bMutex = CheckCountByMutex(nMaxCount);
bSemaphore = CheckCountBySemaphore(nMaxCount);
if(bEvent || bMutex|| bSemaphore)
MessageBoxA(0, "客户端开启数量超出限制!", "提示", 0);
运行效果如图: