暂时有两种方法实现,直接上代码:
⑴将这段代码放到app的InitInstance中:
// 脱壳部分,正式发布需要打开
HANDLE mutex = NULL;
mutex=CreateMutex(0,false,"RegServer");//创建互斥体,创建进程希望立即拥有互斥体,则设为TRUE,一个互斥体同时只能由一个线程拥有
if(GetLastError() == ERROR_ALREADY_EXISTS)
mutex=OpenMutex(MUTEX_ALL_ACCESS,false,"RegServer");//为现有的一个已命名互斥体对象创建一个新句柄
LPWSTR *szArglist = NULL;
int nArgs;
szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);//szArglist就是保存参数的数组
//nArgs是数组中参数的个数
//数组的第一个元素表示进程的path,也就是szArglist[0],其他的元素依次是输入参数。
if( NULL == szArglist )
{
CloseHandle(mutex);
return FALSE;
}
else
{
if(nArgs > 1)
LocalFree(szArglist);//释放局部内存对象并使句柄失效
else
{
char exeFullPath[MAX_PATH];
char exePath[MAX_PATH];
char exeTmpFullPath[MAX_PATH];
char exeTmpName[MAX_PATH];
memset(exeFullPath,0,sizeof(exeFullPath));
memset(exePath,0,sizeof(exePath));
memset(exeTmpFullPath,0,sizeof(exeTmpFullPath));
strcpy(exeTmpName,"RegServer_bak.exe");
GetModuleFileNameA(NULL,exeFullPath,MAX_PATH);//得到进程的完全路径
strcpy(exePath,exeFullPath);
size_t i;
for(i=strlen(exePath)-1; i>0 && exePath[i]!='\\'; i--);
exePath[i]='\0';//将文件名去掉
sprintf(exeTmpFullPath,"%s\\RegServer_bak.exe",exePath);//定位RegServer_bak.exe副本程序的绝对位置
/*WaitForSingleObject函数用来检测hHandle事件的信号状态,
在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,
线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,
但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:
0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,
直到hHandle所指向的对象变为有信号状态时为止。*/
WaitForSingleObject(mutex,INFINITE);
int count=0;
CString strProcessName = exeTmpName;
//将字符串转换为小写
strProcessName.MakeLower();
while(1)
{
//KILL Process begin
//创建进程快照(TH32CS_SNAPPROCESS表示创建所有进程的快照)
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);//获取当前运行的所有进程信息
//PROCESSENTRY32进程快照的结构体
PROCESSENTRY32 pe;
//实例化后使用Process32First获取第一个快照的进程前必做的初始化操作
pe.dwSize = sizeof(PROCESSENTRY32);
Process32First(hSnapShot,&pe);//得到运行的第一个进程
//如果句柄有效 则一直获取下一个句柄循环下去
while (Process32Next(hSnapShot,&pe))
{
//pe.szExeFile获取当前进程的可执行文件名称
CString scTmp = pe.szExeFile;
//将可执行文件名称所有英文字母修改为小写
scTmp.MakeLower();
//比较当前进程的可执行文件名称和传递进来的文件名称是否相同
//相同的话Compare返回0
if(!scTmp.Compare(strProcessName))
{
//从快照进程中获取该进程的PID(即任务管理器中的PID)
DWORD dwProcessID = pe.th32ProcessID;
HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE,FALSE,dwProcessID);//OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。
::TerminateProcess(hProcess,0);//TerminateProcess函数终止指定进程及其所有线程。
CloseHandle(hProcess);
break;
}
scTmp.ReleaseBuffer();
}
strProcessName.ReleaseBuffer();
//KILL Process end
if(!strcmp(exeFullPath,exeTmpFullPath))
break;
if(count > 300)
break;
CopyFileA(exeFullPath,exeTmpFullPath,FALSE);//复制原文件,制作拷贝
i=GetLastError();
if(i == 0)
{
break;
}
count++;
::Sleep(1000);
}
sprintf(exeTmpFullPath,"\"%s\\RegServer_bak.exe\" -2",exePath);
WinExec(exeTmpFullPath, SW_HIDE);//打开副本
LocalFree(szArglist);
ReleaseMutex(mutex);//释放互斥体同时主线程被杀死,新的线程RegServer_bak.exe拥有互斥对象
CloseHandle(mutex);
return FALSE;
}
}
CloseHandle(mutex);
//end 生成可执行程序的副本 end//
⑵ Windows是多进程操作系统,框架生成的应用程序可以多次运行,形成多个运行实例。
但在有些情况下为保证应用程序的安全运行,要求程序只能运行一个实例,比如程
序要使用只能被一个进程单独使用的特殊硬件(例如调制解调器)时,必须限制程
序只运行一个实例。
这里涉及两个基本的问题,一是在程序的第二个实例启动时,如何发现该程序已有
一个实例在运行,而是如何将第一个实例激活,而第二个实例退出。
对于第一个问题,可以通过给应用程序设置信号量,实例启动时首先检测该信号量,
如已存在,则说明程序已运行一个实例。
第二个问题的难点是获取第一个实例的主窗对象指针或句柄,然后便可用
SetForegroundWindow来激活。虽然FindWindow函数能寻找正运行着的窗口,但该函
数要求指明所寻找窗口的标题或窗口类名,不是实现通用方法的途径。
我们可以用Win32 SDK函数SetProp来给应用程序主窗设置一个特有的标记。
用GetDesktopWindow 可以获取Windows系统主控窗口对象指针或句柄,所有应用程
序主窗都可看成该窗口的子窗口,即可用GetWindow函数来获得它们的对象指针或句
柄。用Win32 SDK函数GetProp查找每一应用程序主窗是否包含有我们设置的特定标
记便可确定它是否我们要寻找的第一个实例主窗。使第二个实例退出很简单,只要
让其应用程序对象的InitInstance函数返回FALSE即可。此外,当主窗口退出时,应
用RemoveProp函数删除我们为其设置的标记。
下面的InitInstance、OnCreate和OnDestroy函数代码将实现上述的操作:
BOOL CEllipseWndApp::InitInstance()
{
// 用应用程序名创建信号量
HANDLE hSem = CreateSemaphore(NULL, 1, 1, m_pszAppName);
// 信号量已存在?
// 信号量存在,则程序已有一个实例运行
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
// 关闭信号量句柄
CloseHandle(hSem);
// 寻找先前实例的主窗口
HWND hWndPrevious = ::GetWindow(::GetDesktopWindow(),GW_CHILD);
while (::IsWindow(hWndPrevious))
{
// 检查窗口是否有预设的标记?
// 有,则是我们寻找的主窗
if (::GetProp(hWndPrevious, m_pszAppName))
{
// 主窗口已最小化,则恢复其大小
if (::IsIconic(hWndPrevious))
::ShowWindow(hWndPrevious,SW_RESTORE);
// 将主窗激活
::SetForegroundWindow(hWndPrevious);
// 将主窗的对话框激活
::SetForegroundWindow(
::GetLastActivePopup(hWndPrevious));
// 退出本实例
return FALSE;
}
// 继续寻找下一个窗口
hWndPrevious = ::GetWindow(hWndPrevious,GW_HWNDNEXT);
}
// 前一实例已存在,但找不到其主窗
// 可能出错了
// 退出本实例
return FALSE;
}
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();// Call this when linking to MFC statically
#endif
CEllipseWndDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
int CEllipseWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
// 设置寻找标记
::SetProp(m_hWnd, AfxGetApp()->m_pszAppName, (HANDLE)1);
return 0;
}
void CEllipseWndDlg::OnDestroy()
{
CDialog::OnDestroy();
// 删除寻找标记
::RemoveProp(m_hWnd, AfxGetApp()->m_pszAppName);
}