To limit the number of running application, this code can be used. In this example the limit will by set by a value set in the database, the limiting has to be done after open files.
Please not that some templates and derived WindowManager classes don’t like being terminated by RETURN LEVEL:Notify from the Init() method.
Semaphore names must be unique, they share a system wide namespace.
Inside application Main (Frame) procedure:
#AT Local Data \ Generated Declarations
AppLock &ISemaphore
LOC:ApplicationLimit LONG
#AT Local Objects \ Abc Objects \ Window Manager \ Init () DATA
LockErr LONG
#AT Local Objects \ Abc Objects \ Window Manager \ Init () CODE, priority 7800
LOC:ApplicationLimit = 3 ! Or fetch from database
AppLock &= NewNamedSemaphore('MyApp_MaximumApplications', LOC:ApplicationLimit, LOC:ApplicationLimit, LockErr)
IF AppLock &= NULL THEN
MESSAGE('Error NULL object preparing application limit')
END
IF LockErr = ERROR_ALREADY_EXISTS THEN ! 183=ERROR_ALREADY_EXISTS
! Use existing semaphore
ELSIF LockErr > WAIT:OK THEN
MESSAGE('Error ' & LockErr & ' preparing application limit')
AppLock.Kill()
AppLock &= NULL
ELSE
AppLock.Release(LOC:ApplicationLimit) ! Make 3 available
END
IF AppLock.TryWait(100) = 1 THEN ! Reserve one
AppLock.Kill()
AppLock &= NULL
MESSAGE('Maximum number of applications has been reached.|Close another application first.')
RETURN LEVEL:Notify
END
#AT Local Objects \ Abc Objects \ Window Manager \ Kill () CODE, priority 5050
IF NOT AppLock &= NULL THEN ! Release one
AppLock.Release()
AppLock.Kill()
AppLock &= NULL
END
1 Like
I had posted below comments in Skype chat about naming your Semaphore.
- Named Kernel Objects are in in scope across all Processes so your name must be unique, don’t use “MySemaphore”.
- Include a GUID would be good to make it very unique. The editor has a feature to insert one under Right-click + Insert + GUID
- All Object types (Mutex, Semaphone, Event…) share the same namespace so maybe append “_Semaphore”. If you reuse the same Name for a different type Object you’ll get Error 6 ERROR_INVALID_HANDLE which is not much help.
- Using PATH() can make object names unique per install BUT Object Names cannot contain \ backslash so change to _ Underscore
- Maximum length is same as a file name 260 bytes
- There are special names like "Global" that can be used across all sessions on WTS.
- You should read MSDN in CreateSemaphore and OpenSemaphore.
- Read “Windows via C/C++, fifth edition by Jeffrey Richter” which covers all Kernel Objects very well. You can find a free PDF download.
Your name 'MyApp_MaximumApplications'
should include a GUID so it is very unique. You could also include the PATH() to allow your EXE running in different folders to be started. As I noted in #4 the Backslash must be changed to another character.
For the Semaphore name you could use COMMAND(‘0’) that returns the Full Path and EXE Name. That way you do not need to decide a name. I would still add a GUID. That would have a weakness that the user could Copy the EXE and run that for more instances.
I made some changes and pasted below. This is not tested.
I would have the First program that created the Semaphore not release all 3 i.e. keep 1 for itself. Also if the TryWait() is anything other than Wait:TimeOut (e.g. WAIT:NoHandle WAIT:Failed WAIT:Abandoned) that would indicate bugs so I show the WaitResult in the message. There were a few places a RETURN was needed.
LOC:ApplicationLimit = 3 ! Or fetch from database
LOC:NameSemphore='MyApp_MaximumApplications_0567D94A-9BDE-4110-A73D-F722E2E9EB4A'
!TODO Clean Name to remove any \ Backslash
AppLock &= NewNamedSemaphore(LOC:NameSemphore, |
LOC:ApplicationLimit, | !Initial Locked Count, take all
LOC:ApplicationLimit, | !Maximum Lock Count
LockErr)
IF AppLock &= NULL THEN
MESSAGE('NULL Semaphore preparing application limit ' & |
'|Error '& LockErr &'|Name ' &LOC:NameSemphore ,Command('0')) !CB Show LockErr and Name
AppLock.Kill()
RETURN LEVEL:Notify !CB Return or Crash?
END
CASE LockErr
OF 0 !This EXE Created the Semaphore it must be First
!Do any ONE TIME initialization needed before opening the door to others
!e.g. file conversion. Maybe delay the Release until First opens the Frame.
AppLock.Release(LOC:ApplicationLimit - 1) ! Make 2 available, keep 1
OF ERROR_ALREADY_EXISTS ! 183=ERROR_ALREADY_EXISTS
! Use existing semaphore. Lock Initial count and Max were ignored.
WaitResult = AppLock.TryWait(100) ! Reserve one, wait up to 0.1 seconds
IF WaitResult <> WAIT:OK THEN ! Normally fail with WAIT:TIMEOUT
AppLock.Kill()
AppLock &= NULL
MESSAGE('Maximum number of applications has been reached.' & |
'|Close another application first.' & |
CHOOSE(WaitResult=WAIT:TIMEOUT,'','||TryWait unexpected result: ' & WaitResult &'|Name ' &LOC:NameSemphore ) ,| !Cb show error
'Name of Program')
RETURN LEVEL:Notify
END
ELSE !LockErr <> 0 and <>183
!?? Doubt you'll ever get here because a failure will probably leave AppLock = Null ??
MESSAGE('Unknown NewNamedSemaphore Error ' & LockErr & ' preparing application limit' & |
'||Name ' &LOC:NameSemphore,Command('0'))
AppLock.Kill()
AppLock &= NULL
RETURN LEVEL:Notify
END
I would show the LockErr number in messages as well as the Name for unexpected fails.