用Semaphore检测运行实例的个数

发表于:2007-07-14来源:作者:点击数: 标签:
在 VB 中人们一般用App.PrevInstance检查是否有某个程序的实例已经在运行,如果返回值是True,就表明前面至少已经有一个实例在运行。但是这种方法无法确切知道究竟有多少个实例在运行,因此,只能用它来限制某一时刻只能有一个实例在运行的情况。 分析问题
VB中人们一般用App.PrevInstance检查是否有某个程序的实例已经在运行,如果返回值是True,就表明前面至少已经有一个实例在运行。但是这种方法无法确切知道究竟有多少个实例在运行,因此,只能用它来限制某一时刻只能有一个实例在运行的情况。
分析问题
对于限制运行指定个数实例的处理方法,传统上一般通过使用FindWindow()方法来查找相同标题的窗口或窗口类来实现。但是这种方法存在一定缺陷,一方面有些程序在运行过程中有时会改变标题;另一方面其他程序也可以轻易地用SetWindowText()来改变某个窗口的标题。所以这种方法准确率不高。
本文介绍一种方法可以克服这种缺点,基本思路是让程序的第一个实例启动时在一个公共的地方建立一个公共计数器,并且赋起始值,以后每次程序启动时,读该计数器取得已经运行的程序实例个数,并且比较是否已经达到需要限制的最大值,如果不是则继续加载该程序并使计数器加1,否则终止加载并退出,同时,正常加载的程序在退出时还要使计数器减1以释放资源。
由于Win32使用新的地址映射机制,使得它的的进程地址空间不像Win16那样是连续的,而且进程通常也不可以访问其他进程的地址空间(除非通过特殊方法)。为此,就需要用到Win32的IPC机制,该机制是Win32专门用于进程间通信的方法,它特别定义了几个内部对象。Semaphore对象就是其中的一种资源对象,它在进程间是可见的,因此,用它可以实现限制同时对一个资源的有限个访问。
相关函数
1.CreateSemaphore()
CreateSemaphore(lpSemaphoreAttributes As SECURITY_ATTRIBUTES, ByVal lInitialCount As Long, ByVal lMaximumCount As Long, ByVal lpName As String);
该函数是Windows提供用来创建一个Semaphore信号的函数,其参数含义如下:
lpSemaphoreAttributes:安全属性参数,是为Windows NT设置的,在Windows 95下可以忽略。但是在VB中若如上述声明,则不能忽略,忽略后该函数有时不能正确执行,并返回0。此时,可以设置其为默认值,或者改为Byval lpSemaphoreAttributes as long,然后再传入0。
lInitialCount:Semaphore的初始值,一般设为0或lMaxmumCount。
lMaximunCount:Semaphore信号的最大值。
lpName:该信号名,以便其他进程对其进行调用,若是相同进程可以设为Null。
函数成功时返回创建的Semaphore信号的句柄。该函数有一个特点,就是在要创建的信号已经创建了的情况下,它等同于函数OpenSemaphore(),仅仅是打开该Semaphore信号,并返回信号句柄。
2.ReleaseSemaphore()
ReleaseSemaphore(ByVal hSemaphore As Long, ByVal lReleaseCount As Long,lpPreviousCount As Long);
hSemaphore:函数CreateSemaphore()返回的Semaphore信号句柄;
lReleaseCount: 当前信号值的改变量;
lpPreviousCount:返回的Semaphore信号被加之前的值,可用于跟踪测试
如果Semaphore信号当前值加上lReleaseCount后不超过CreateSemaphore()中设定的最大值lMaximunCount,函数返回1(True),否则返回0(False),可用GetLastError()得到其失败的详细原因。
3.WaitForSingleObject()
WaitForSingleObject(ByVal hHandle As Long,  
ByVal dwMilliseconds As Long);
hHandle:等待对象的句柄;
dwMilliseconds:等待时间。
该函数可以实现对一个可等待对象的等待操作,获取操作执行权。当等待的对象被释放时函数成功返回,同时使等待对象变为有信号状态,或者超时返回。该函数用于等待Semaphore信号时,若Semaphore信号不为0,则函数成功返回,同时使Semaphore信号记数减1。
我们可以利用它建立一个记数信号,每次当相同的程序加载时使记数器减1,退出的时候使计数器加1,这样就可以限制相同程序的多份拷贝同时运行。
编程实现
下面的这段代码用于限制一个程序最多只能同时运行4个实例:
Private Declare Function ReleaseSemaphore Lib “kernel32”(ByVal hSemaphore As Long, ByVal lReleaseCount As Long, lpPreviousCount As Long) As Long

Private Declare Function CreateSemaphore Lib “kernel32” Alias “CreateSemaphoreA”  
(lpSemaphoreAttributes As SECURITY_ATTRIBUTES, ByVal lInitialCount As Long, ByVal lMaximumCount As Long, ByVal lpName As String) As Long 

Private Declare Function WaitForSingleObject Lib “kernel32” (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long

Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Dim Semaphore As String, Sema As long, Security As SECURITY_ATTRIBUTES
Dim PrevSemaphore As Long, Turn As Long

Private Sub Form_Load()
Security.bInheritHandle = True
'默认的安全值
Security.lpSecurityDescriptor = 0  
Security.nLength = Len(Security)
Semaphore = “Instance”
’创建或打开一个Semaphore记数信号,设资源空闲使用量为4
Sema = CreateSemaphore(Security, 4, 4, Semaphore)  
'申请一个权限,并立即返回  
Turn = WaitForSingleObject (Sema, 0)  
'如果不是正常返回,则表示没有申请到资源的使用权限
If Turn <> 0 Then  
MsgBox “Full!”
End
End If
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
’在当前值上加1,表示有一个程序退出,释放了一个权限,PrevSemaphore参数接收释放前的计数器的值
ReleaseSemaphore Sema, 1, PrevSemaphore
End Sub
小 结
编译后生成可执行文件Semaphore.exe。运行第一个实例后,申请的等待立刻正常返回,此时计数器的值为3,运行第4个实例后计数器的值为0,当试图运行第5个实例时,由于此时已无可用的计数器资源,所以立刻返回一个失败的等待,通知程序不能再运行更多的实例,并立刻退出。这种信号机制是进程间通信的一种重要机制,课本中常常用超市收款机的排队机制来做比喻,其实现原理就是把WaitForSingleObject的等待时间设置为无穷大,直到有一个实例(顾客)退出(服务完),后面等待的实例就可以运行了。但是在实际编程中,一个无限等待(被冻结)的进程没有什么实际用处,没有必要继续等待,所以等待时间通常都设为0,如果等不到资源就立刻退出。
本程序在VB 6.0企业版、Windows 98下调试通过。

原文转自:http://www.ltesting.net