多用户、多会话 Server版操作系统下 程序唯一实例运行

来源:互联网 发布:洁面仪有用吗 知乎 编辑:程序博客网 时间:2024/06/05 15:35
很多时候我们需要做这样的事:只允许当前程序只有唯一一个实例运行。

这样的情况包括串口程序、网络socket等。

这时我们有多种解决方法。

常见的像用互斥、信号量等比运行实例生存期更长的内核对象,或者使用内存映射(点击打开链接)等。

如使用互斥内核对象:

                 HANDLE hmute=CreateMutex(NULL,FALSE,"BCB");                 if(GetLastError()==ERROR_ALREADY_EXISTS)                 {                     Application->MessageBoxA("程序已运行","提示",MB_OK);//此处你可以做其他处理 如激活已运行的实例                     return 1;                 }//记得CloseHandle:)
当时如果在多用户操作系统,如Window Server 2003之类的操作系统,上述做法就不奏效了,这时我们看看MSDN:

点击打开链接

注意这段:

HANDLE WINAPI CreateMutex(  __in_opt  LPSECURITY_ATTRIBUTES lpMutexAttributes,  __in      BOOL bInitialOwner,  __in_opt  LPCTSTR lpName);
lpName:

The name can have a "Global\" or "Local\" prefix to explicitly create the object in the global or session name space. The remainder of the name can contain any character except the backslash character (\). For more information, see Kernel Object Namespaces. Fast user switching is implemented using Terminal Services sessions. Kernel object names must follow the guidelines outlined for Terminal Services so that applications can support multiple users.

Windows 2000:  If Terminal Services is not running, the "Global\" and "Local\" prefixes are ignored. The remainder of the name can contain any character except the backslash character.

The object can be created in a private namespace. For more information, see Object Namespaces.

该名称可以由一个“Global\”或“Local\”前缀在全局或会话名称空间的对象显式地创建。其余的名称可以包含反斜杠字符(\)以外的任何字符。欲了解更多信息,请参阅内核对象的命名空间。快速用户切换使用终端服务会话。内核对象名称必须遵循的准则,概述终端服务,使应用程序可以支持多个用户。


由MSDN解释,我们知道了如何让互斥内核对象在多会话之间可见,即将lpName参数设置为如下

const char *lpName="Global\\XXXXXXX";

既然知道是这样了,于是我们开始编码:

//BCB记得 #define STRICT#include <tlhelp32.h>HANDLE hMutex=NULL;HWND g_Hwnd=NULL;DWORD g_dwProcessId=0;String g_strExeName;const char *lpName="Global\\lhy-1989-03-23";//void  GetProcessID(){  HANDLE hSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);  PROCESSENTRY32 pt32={0};  pt32.dwSize=sizeof pt32;  if(Process32First(hSnap,&pt32))  {    do    {      String strExe=pt32.szExeFile;      if(strExe==g_strExeName)      {        g_dwProcessId=pt32.th32ProcessID;        break;      }     }while(Process32Next(hSnap,&pt32));  }  CloseHandle(hSnap);}BOOL CALLBACK EnumWindowByProcessId(HWND hwnd,LPARAM lParam){    DWORD dwProcessID=0;    GetWindowThreadProcessId(hwnd,&dwProcessID);    if(dwProcessID==g_dwProcessId)    {       HWND pHwnd=GetParent(hwnd);       while(GetParent(pHwnd))       pHwnd=GetParent(pHwnd);       if(pHwnd)       g_Hwnd=pHwnd;    }    return 1;}int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){     g_strExeName=ExtractFileName(Application->ExeName);          hMutex=CreateMutex(NULL,FALSE,lpName);     if(GetLastError()==ERROR_ALREADY_EXISTS)     {                    CloseHandle(hMutex);                    GetProcessID();                    if(g_dwProcessId)                    {                        EnumWindows(EnumWindowByProcessId,0);                        if(g_Hwnd)                        {                            ShowWindow(g_Hwnd,SW_RESTORE);//如果已经运行了实例,Show已运行实例,然后退出。               return 1;                                                  }         }    //....  CloseHandle(hMutex);}
如果大家仔细瞧过这段代码的思路,再放到2003下多会话运行,我们可以通过任务管理员查看到相应进程,会发现根本没有达到我们的目的! 即实例唯一允许。


既然上面的代码达不到效果,我们来找找原因,

看看我们的思路:

先通过检测发现如果已经有同名互斥对象存在,那么就对比实例名称获取进程ID,再使用EnumWindows枚举主窗口句柄,显示已运行窗口然后退出。

仔细瞧上述思路,发现我们依赖HWND来提醒用户程序已运行,那么是不是HWND的作用域不足以跨会话呢?:(没找到相关确切资料。单步下,发现在另外一个会话中无法获取目标HWND!

好吧,那我们就试试使用进程句柄来作为提示。

                  

                     hMutex=CreateMutex(NULL,FALSE,lpName);                     if(GetLastError()==ERROR_ALREADY_EXISTS)                     {                        GetProcessID();                        if(g_dwProcessId)                        {                                if(Application->MessageBox("另一个客户会话正在运行该程序,是否结束已运行的程序重新从本会话启动?",                                "提示",MB_ICONQUESTION|MB_YESNO)==IDYES)                                {                                  HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,False,g_dwProcessId);                                  HANDLE hFront=OpenMutex(MUTEX_ALL_ACCESS,False,lpName);                                  if(hFront)                                  CloseHandle(hFront);                                                                   TerminateProcess(hProcess,1);//无奈。。。                                  WaitForSingleObject(hProcess,2000);                                }                                else                                {                                 CloseHandle(hMutex);                                 return 1;                                }                        }                     }

哈,成功了!由此我们可以得出HWND的作用域只限于当前会话。