_Based on an example from Microsofts whitepaper 'Optimizing Applications for Windows NT Server Terminal Server Edition, version 4.0'_
Suppose you want to prevent your Progress application to be launched more than once on each computer. The startup procedure would contain something like this:
IF IsAppAlreadyRunning(NO, "MyProgressApplication") THEN DO: MESSAGE "'MyProgressApplication' is already running on this machine," SKIP "only one instance is allowed." VIEW-AS ALERT-BOX. QUIT. END. ... RUN LetAnotherInstanceRun("MyProgressApplication").
There are several ways to implement this functionality. This topic will use a mutex.
A mutex is an object that can only be owned by one thread at a time. The general purpose of a mutex is to synchronize threads, ie to have other threads wait until the mutex is released. So if your application creates and owns a named mutex, other applications can not get ownership of the same mutex. Function IsAppAlreadyRunning creates a named mutex, procedure LetAnotherInstanceRun closes the mutex.
{windows.i} DEFINE VARIABLE hAppRunningMutex AS INTEGER NO-UNDO INITIAL 0. FUNCTION IsAppAlreadyRunning RETURN LOGICAL (p-OnePerSystem AS LOGICAL, p-AppName AS CHARACTER): DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO. DEFINE VARIABLE MutexName AS CHARACTER NO-UNDO. MutexName = ''. IF p-OnePerSystem AND ValidateProductSuite("Terminal Server") THEN MutexName = MutexName + "Global\". MutexName = MutexName + p-AppName + ' is running'. RUN CreateMutexA IN hpApi(0,0,MutexName, OUTPUT hAppRunningMutex). IF hAppRunningMutex NE 0 THEN DO: /* we should check GetLastError = ERROR_ALREADY_EXISTS, but unfortunately GetLastError doesn't work with Progress until 9.0B */ /* Instead we will try to get ownership of the Mutex. This will be easy if we created the mutex, but will be impossible if another instance created the mutex (and still holds ownership) */ RUN WaitForSingleObject IN hpApi (hAppRunningMutex,100, OUTPUT ReturnValue). IF NOT (ReturnValue={&WAIT_ABANDONED} OR ReturnValue={&WAIT_OBJECT_0}) THEN DO: RUN CloseHandle IN hpApi(hAppRunningMutex, OUTPUT ReturnValue). hAppRunningMutex = 0. END. END. RETURN (hAppRunningMutex=0). END.
The first parameter, p-OnePerSystem specifies if the application is allowed to run more than once per system. This is useful when the application is installed on Microsoft Windows Terminal Server hosting multiple users. If p-OnePerSystem=No, the application can be launched once by each user. If p-OnePerSystem=Yes the application can run only once on the entire Terminal Server system, in other words: by only one user at a time. This might be useful for batch processes perhaps?
Procedure LetAntotherInstanceRun closes the mutex, making it available to other threads. This decreases the usage-count of the mutex. If the usage-count decreases to zero (like now) the mutex will be deleted. It is not very important to run this procedure because the mutex will be closed automatically by Windows when the Progress session quits.
PROCEDURE LetAnotherInstanceRun : DEFINE INPUT PARAMETER p-AppName AS CHARACTER NO-UNDO. DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO. IF hAppRunningMutex NE 0 THEN DO: RUN CloseHandle IN hpApi(hAppRunningMutex, OUTPUT ReturnValue). hAppRunningMutex = 0. END. END PROCEDURE.
Function ValidateProductSuite checks if the application is installed on and running on a Windows Terminal Server machine :
{windows.i} FUNCTION ValidateProductSuite RETURN LOGICAL (SuitName AS CHARACTER): DEFINE VARIABLE key-hdl AS INTEGER NO-UNDO. DEFINE VARIABLE lpBuffer AS MEMPTR NO-UNDO. DEFINE VARIABLE lth AS INTEGER NO-UNDO. DEFINE VARIABLE datatype AS INTEGER NO-UNDO. DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO. DEFINE VARIABLE retval AS LOGICAL NO-UNDO INITIAL FALSE. RUN RegOpenKeyA IN hpApi ( {&HKEY_LOCAL_MACHINE}, "System\CurrentControlSet\Control\ProductOptions", OUTPUT key-hdl, OUTPUT ReturnValue). IF ReturnValue NE {&ERROR_SUCCESS} THEN RETURN FALSE. /* make buffer large enough The maximum size is supposed to be MAX_PATH + 1 */ ASSIGN lth = {&MAX_PATH} + 1 SET-SIZE(lpBuffer) = lth. RUN RegQueryValueExA IN hpApi ( key-hdl, "ProductSuite", 0, /* reserved, must be 0 */ OUTPUT datatype, GET-POINTER-VALUE(lpBuffer), INPUT-OUTPUT lth, OUTPUT ReturnValue). IF ReturnValue = {&ERROR_SUCCESS} THEN retval = (GET-STRING(lpBuffer,1)=SuitName). SET-SIZE(lpBuffer)=0. IF key-hdl NE 0 THEN RUN RegCloseKey IN hpApi (key-hdl,OUTPUT ReturnValue). RETURN retval. END FUNCTION.