Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Preventing a second instance of an exe being started

Status
Not open for further replies.

Bryan - Gendev

Programmer
Jan 9, 2011
408
AU
I am attempting to stop that second 'click' on a shortcut....

I have found this code amongst others as a preferred method.

It works on my dev Pc but not on a clients.

I call this procedure early in main.prg with

IF IsAppRun( APPID )
QUIT
ENDIF

after using

#DEFINE APPID "App0001-99"

at the start of main.prg

<code>
***************************************************************************
* Program....: IsAppRun.prg
* Compiler...: Visual FoxPro 06.00.8492.00 for Windows
* Abstract...: Checks for a window which is created with a Unique ID by and
* ...........: in the application. Combination of Semaphore and API.
* ...........: Based on code originally posted into the Public Domain
* ...........: by Christof Lange
***************************************************************************
*!* FUNCTION IsAppRun( tcUniqueID )
PARAMETERS tcUniqueID
LOCAL llRetVal, lcUniqueID
*** MUST pass an application ID to this function!
IF EMPTY(tcUniqueID) OR VARTYPE( tcUniqueID ) # "C"
MESSAGEBOX( 'An Application Specific Character ID is mandatory' + CHR(13) ;
+ 'when calling the IsAppRun() function', 16, 'Developer Error' )
RETURN .T.
ELSE
*** Strip out any spaces
lcUniqueID = STRTRAN( tcUniqueID, " " )
ENDIF
*** First check for the existence of the Semaphore window
IF WEXIST("_Semaphore_")
RETURN .T.
ENDIF
*** Look for an occurrence of this ID as a Window Name
DECLARE INTEGER FindWindow IN Win32Api AS FindApp String, String
IF FindApp( NULL, lcUniqueID ) > 0
*** We found one! Set Return Value
llRetVal = .T.
ELSE
*** Create a new window with this ID
DEFINE WINDOW _Semaphore_ IN DESKTOP FROM 1,1 TO 2,2 TITLE lcUniqueID
ENDIF
*** Return Status Flag
RETURN llRetVal

</code>

On his PC it throws a program error dialogue with
Not a character expression.

Works when I step in debugger and run the compiled code on my PC but not his.

Anyone have any clues who has used this?

Thanks

GenDev
 
Yes, without the line of error there is no clue what has to be a character expression.

APPID is character, the function IsAppRun() also explicitly checks VARTYPE( tcUniqueID ) # "C".

FindWindow should also be available on any Windows Version, it's a very old Win32API function.

So I don't spot anything problematic in the code you give us, therefore it's probably somewhere else.

Bye, Olaf.
 
Hello.

To prevent the applications to started twice, I’m using “Createmutex” API. In this code, lcAppName is the application. It will look if the application is already running. If it’s the case, it will bring the app window and return false. If no, it will continue and return true.

Code:
  *** Declare API functions
  DECLARE INTEGER CreateMutex IN WIN32API INTEGER lnAttributes, INTEGER lnOwner, STRING @lcAppName
  DECLARE INTEGER GetProp IN WIN32API INTEGER lnhWnd, STRING @lcAppName
  DECLARE INTEGER SetProp IN WIN32API INTEGER lnhWnd, STRING @lcAppName, INTEGER lnValue
  DECLARE INTEGER CloseHandle IN WIN32API INTEGER lnMutexHandle
  DECLARE INTEGER GetLastError IN WIN32API 
  DECLARE INTEGER GetWindow IN USER32 INTEGER lnhWnd, INTEGER lnRelationship
  DECLARE INTEGER GetDesktopWindow IN WIN32API 
  DECLARE BringWindowToTop IN Win32APi INTEGER lnhWnd
  DECLARE ShowWindow IN WIN32API INTEGER lnhWnd, INTEGER lnStyle
  
  *** Try and create a new MUTEX with the name of the passed application
  lnMutexHandle = CreateMutex( 0, 1, @lcAppName )
  
  *** Le code 183 indique un objet déja crée - Tente d'afficher l'application ***
  IF GetLastError() = 183 OR ISNULL(lnMutexHandle) THEN
   
   llContinue = .F.
   .nMutexHandle = lnMutexHandle
   
   *** Get the hWnd of the first top level window on the Windows Desktop.
   lnhWnd = GetWindow( GetDesktopWindow(), 5 )
    
   *** Loop through the windows. 
   DO WHILE lnhWnd > 0
       
    *** Is this the one that we are looking for?
    *** Look for the property we added the first time
    *** we launched the application
    IF GetProp( lnhWnd, @lcAppName ) = 1 THEN 
     *** Activate the app and exit stage left
     BringWindowToTop( lnhWnd )
     ShowWindow( lnhWnd, 3 )
     EXIT
    ENDIF									&& GetProp( lnhWnd, @lcAppName ) = 1
    lnhWnd = GetWindow( lnhWnd, 2  )
   ENDDO									&& WHILE lnhWnd > 0
   
   *** Close the 'failed to open' MUTEX handle
   CloseHandle( lnMutexHandle )

Hope it help.

Nro
 
agree with Nro , mutex is the correct way , see code , I use it as standards always

1) at start of main prg file , add something like this , where the "xyz stock" can be anything at all, this sets the 'semaphore', app will start as normal first time , then if user tries to run a second instance , it is in effect ignored

If Not OneTime("xyz stock")
On Shutdown
Quit
Endif

2) the mutex code is then as follows , with all due reference to one of the vfp greats , mr Kramek

Procedure OneTime
********************************************************************
*** Name.....: FIRSTTIME.PRG
*** Author...: Marcia Akins & Andy Kramek
*** Date.....: 01/11/2004
*** Notice...: Copyright (c) 2004 Tightline Computers, Inc
*** Compiler.: Visual FoxPro 09.00.0000.2124 for Windows
*** Function.: Determine whether an instance of the application is already running
*** .........: This function uses the creation of a named MUTEX to determine whether
*** .........: or not the application is already running. This function should be called
*** .........: near the top of the application's main program to create a named object
*** .........: that can be checked every time the application is started. If the named
*** .........: object exists, the function will try to activate the FoxPro running application.
*** Returns..: Logical
********************************************************************
Lparameters tcAppName
Local lcMsg, lcAppName, lnMutexHandle, lnhWnd, llRetVal
lcMsg = ''
Set Asserts On
If Empty( Nvl( tcAppName, '' ) )
TEXT TO lcMsg NOSHOW
This is another Brain Dead Programmer Error.
You must pass the name of an application to FirstTime().
Have a nice day now...
ENDTEXT
Assert .F. Message lcMsg
Return
Endif
*** Format the passed in program name
lcAppName = Upper( Alltrim( tcAppName ) ) + Chr( 0 )
*** Declare API functions
Declare Integer CreateMutex In WIN32API Integer lnAttributes, Integer lnOwner, String @lcAppName
Declare Integer GetProp In WIN32API Integer lnhWnd, String @lcAppName
Declare Integer SetProp In WIN32API Integer lnhWnd, String @lcAppName, Integer lnValue
Declare Integer CloseHandle In WIN32API Integer lnMutexHandle
Declare Integer GetLastError In WIN32API
Declare Integer GetWindow In USER32 Integer lnhWnd, Integer lnRelationship
Declare Integer GetDesktopWindow In WIN32API
Declare BringWindowToTop In Win32APi Integer lnhWnd
Declare ShowWindow In WIN32API Integer lnhWnd, Integer lnStyle
*** Try and create a new MUTEX with the name of the passed application
lnMutexHandle = CreateMutex( 0, 1, @lcAppName )
*** If the named MUTEX creation fails because it exists already, try to display
*** the existing application
If GetLastError() = 183
*** Get the hWnd of the first top level window on the Windows Desktop.
lnhWnd = GetWindow( GetDesktopWindow(), 5 )
*** Loop through the windows.
Do While lnhWnd > 0
*** Is this the one that we are looking for?
*** Look for the property we added the first time
*** we launched the application
If GetProp( lnhWnd, @lcAppName ) = 1
*** Activate the app and exit stage left
BringWindowToTop( lnhWnd )
ShowWindow( lnhWnd, 3 )
Exit
Endif
lnhWnd = GetWindow( lnhWnd, 2 )
Enddo
*** Close the 'failed to open' MUTEX handle
CloseHandle( lnMutexHandle )
Else
*** Add a property to the FoxPro App so we can identify it again
SetProp( _vfp.HWnd, @lcAppName, 1)
llRetVal = .T.
Endif
Return llRetVal

Endproc

 
I agree that Mutex is a good way to solve the problem. But the method posted by GenDev should also work.

The point is that GenDev already has a satisfactory solution. The error he saw was probably caused by a simple typing error or something similar. If he would give us the information we asked for, we should be able to solve that problem fairly easily.

By all means let him switch to using Mutex if his first approach definitely doesn't work.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips, training, consultancy
 
I too would like to have a line number but the error message
dialogue has no further information.

I'd like to show you visually but with this forum I don't think it's possible.

The small 1" high dialogue has a Title Bar with a windows icon on left and Program Error as a title
It then has a yellow triangle with an exclamation mark then to the right of that a recessed area with the message 'Not a character expression'
Then there are 3 buttons [Cancel] to the left and [Ignore] and [Help] to the right - [Help] goes nowhere


So at run time I'm stuck.


I will try mutex and see if that solves my otherwise insoluble problem.

Thanks

GenDev
 
I have changed to the mutex method.

Works fine on dev PC but same error as before on other PC.

I will take it to my MCPC later.

GenDev
 
gendev,

what you describe is the system error message. Read about error handling in the help, you can do a better message than that, you can include infos from ASTACKINFO(), SYS(16), PROGRAM(), LINENO(), LINENO(1), MESSAGE() and MESSAGE(1). Indeed ASTACKINFO() is really sufficient to not only tell where exactly the error happened, but also what else is on the current call stack, so where the current program/method was called from, and where that was called from etc.


ASTACKINFO() even gives you source code of the lines on the call stack, like MESSAGE(1) does for the last (erronneous) line executed. You just need to activate debig info, or not deactivate it. You can see if that is checked in project info.

Put an errhandler() function in your start program at the end of it and as a first net code line put ON ERROR DO errhandler WITH whatever parameters you need or want. ASTACKINFO() makes allmost all parameters you could pass unneccessary. And the rest info also can be retrieved by AERROR() within the error handler.

Bye, Olaf.

Bye, Olaf.
 
hmm , looks like prob is tight at top ( not in the win/mutex code ) , why use #define , see vfp help
**Compile time constants are not recognized when placed within quotation marks**

why not just
APPID="App0001-99" && at top of main prog , then stays in scope always

IF IsAppRun("App0001-99")
QUIT
ENDIF

etc

#define is prob doing
IF IsAppRun(App0001-99)
QUIT
ENDIF


 
sry , prev code should be

APPID="App0001-99" && at top of main prog , then stays in scope always
IF IsAppRun(APPID)
QUIT
ENDIF

or even , just plain old
IF IsAppRun("App0001-99")
QUIT
ENDIF


 
I open a file in the current folder when an app starts up and hold it open. Then if a second copy tries to open the same file, it can't - so it exits gracefully.

Very simple, very effective.

Regards

Griff
Keep [Smile]ing

There are 10 kinds of people in the world, those who understand binary and those who don't.
 
cliper,

with a
#DEFINE APPID "App0001-99"

the precompiler does replace
IsAppStart(APPID)
to be compiled as
IsAppStart("App0001-99")

the precompiler is not striping off the quotes. Otherwise you could only define sttrings, but you can also define for example:

#DEFINE ONE 1

How would that work out in your way of thinking about precompiling code with constants?

It's true constants are not replaced within strings, eg IsAppStart("APPID") would remain as is, but that's not the case here.

Even in case the precompiler would turn the call to IsAppRun(App0001-99), as you fear is done, the error would be "variable App0001 is not found", not "not a character expression".

Bye, Olaf.
 
but if the apps are run multi-user , and launched from same network shared folder , then a second SEPERATE user is barred also ?
 
Well, yes, but that would be a very network intensive way of loading an app - I usually give my users local copies of the .exe, rather than all sharing one on a network.

B-)

Regards

Griff
Keep [Smile]ing

There are 10 kinds of people in the world, those who understand binary and those who don't.
 
but if the apps are run multi-user , and launched from same network shared folder , then a second SEPERATE user is barred also ?

Why not place the semaphore file in a folder on the user's local drive, such as My Documents?

If I've understood it right, Griff's solution seems like it should work, and is much simpler. The only possible problem would be if the first instance doesn't get time to open the file before the second instance is launched, which could happen if the user double-clicks twice on a shortcut in rapid succession.

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips, training, consultancy
 
I know this doesn't address the question, but maybe we should ask if we really want to prevent multiple instances.

I take the opposite view and explicitly allow multiple instances in all my applications. It's often convenient for the user to be able to do a quick query or update while a slow process is going on elsewhere in the application.

It's true this can give rise to locking issues - if the user tries to update the same record in different instances, for example - but that's handled by my normal multi-user code.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips, training, consultancy
 
Actually as of gendevs post from 20 Oct 11 1:05
gendev said:
I have changed to the mutex method.

Works fine on dev PC but same error as before on other PC.

It's very unlikely the same error "not a character expression" is within the IsAppStart() function and the mutex function.

All the discussion about other mechanism is welcome, but the FAQ section already has afew solutions.

Before gendev doesn't implement an error handler reporting the location of his remaining error, it's quite fruitless to suggest further different methods of preventing double starts.

It's very very likely the error "not a character expression" comes from somewhere else. Because it's very very unlikely that the same error persists, once you completely exchange a code section you suspected being the source of the error, don't you agree?

Bye, Olaf.
 
agreed olaf , maybe some simple errorhandler like

1) at top of main prog
On Error Do prgerr With Sys(16),Lineno()

2) at footer of main prog
******************************************************
Procedure prgerr
******************************************************
Parameters Prog,Line

** build <<message text>> from Prog,Line , error() , message()
( error() and message() return details of what fired the error)

=Messagebox(<<message text>>,48)
** cleanup and quit
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top