判断程序的实例是否已经启动,无非是通过设立某个标识,让下次启动程序时知道该实例已经运行。嗯,可是在WIN32中每个进程都有自已独立的空间,那么如何处理呢,下面提供两种方案:

方案一,使用内核对象
因为内核对象是可以跨进程存在的,因此我们可以通过创建一个命名互斥体(Mutex)内核对象来判断,当用同一个名字的来创建Mutex时,CreateMutex会返回一个指向该互斥体的句柄,但是GetLastError会得到ERROR_ALREADY_EXISTS的返值。因此我们就可以判断程序已有一个实例在运行。下面是其中的关键代码:

   
  m_hmutex = ::CreateMutex( NULL,FALSE,appID);
  

if(m_hmutex == NULL) return FALSE; //

if( ::GetLastError() == ERROR_ALREADY_EXISTS ) //
{
return FALSE;
}
else
{
return TRUE;
}

方案二,使用共享数据段
背景知识:EXE和DLL文件映像由许多区组成如代码在.text段中,初始化数据在.data段中,未初始化数据在.bss段中。系统在加载EXE和DLL时,实际上是使用了内存映射,为了减少加载时间,同一EXE文件多个实例实际在系统中只有一份。但一般地如果其中某个实例对某个数据区进行写时,系统会使用Copy-On-Write机制将这个数据区在虚拟内存中复制一份出来,并映射到该实例原先的地址空间,也就实现了进程数据的唯一性,而不会干扰其它进程。但是我们可以通过设置让系统关闭掉这个机制。哪么如何做呢?
在Visual Studio中你可以程序中加上以下几行:

   
  #pragma comment(linker,"/SECTION:Share,RWS") //指示编译器Share是可读写和共享的,当然你也可以通过设置链接器选项直接加上 /SECTION:Share,RWS,不过我更喜欢这个,因此其他朋友就不必自已去设置这个选项了。
  
#pragma data_seg("Share") // 开始自已的数据段
int g_AppInit =0; //必需初始化,否则不会将编译器不会将其放入Share这个区
#pragma data_seg()

还有一种将某个变量置于特别数据段的方式:

   
  __declspec(allocate("Share")) int g_AppInit =0;
 

这种方式的好处是无论你初不初始化这个变量都将置于该Share段内
哪么如何判断呢是否启动了实例呢,很简单,看以下代码:

   
  g_AppInit ++ ;
  
if(g_AppInit >1)
{
AfxMessageBox("A instance are runed!");
return FALSE;
}

相关的问题:如何通知前一个实例
解决了重复启动的问题,为了获得更佳的用户体验,往往我们要使前一个实例激活,如何做呢?使用消息是一个不错的方法。首先你需要在启动程序时登记一个全局消息。

   
  WM_APPACTIVE = ::RegisterWindowMessage(appID);
 

相同的appID字符串会给出相同的消息值,并且总是在0xC000- 0xFFFF区间中,然后当你发现已启动程序实例时通知前一个实例:

   
  DWORD dectype = BSM_APPLICATIONS;  //仅向应用程序发送
  

BroadcastSystemMessage(BSF_POSTMESSAGE,
&dectype,
WM_APPACTIVE,
0,0);

前一个程序实例收到这个消息后,进行处理,可以前置窗口激活等等。

狗悦工口大魔王 answered 10 years, 10 months ago

Your Answer