Announcement

Collapse
No announcement yet.

Mutex Question - Bringing Existing App to ForeFront

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Mutex Question - Bringing Existing App to ForeFront

    In this short code, I use a mutex to keep a second instance of an app from being run.

    What I want to do, when a previous instance is found, is to bring the previous app into view, with focus.

    My forum searches kept turning up FindWindow, which won't work because my app title changes each time it is run. My MSDN searches were tantalizing, but still not successful.

    It there a straightforward way to do what I want? I suspect it's easy, just not something I've run into before.

    Code:
    'Compilable Example:
    #Compiler PBWin 10
    #Compile Exe
    #Dim All
    %Unicode = 1
    #Include "Win32API.inc"
    
    Function PBMain() As Long
       Local UniqueName As WStringZ * %Max_Path, hDlg As Dword
       UniqueName = "gbInstanceTest"                     '
       If CreateMutex(ByVal %Null, 0, UniqueName) = 0 Or GetLastError = %ERROR_ALREADY_EXISTS Then
          ? "sorry, I must leave"
          Exit Function
       End If
       Dialog New Pixels, 0, Time$,300,300,200,100, %WS_OverlappedWindow To hDlg
       Dialog Show Modal hDlg
    End Function

  • #2
    I did this a long time ago. What I did was create a memory mapped object containing either the process ID or the thread Id or the main window handle. If the program was already running, that object existed (OpenFileMapping() suceeds) and contains the value for SetActiveWindow() or SetForegroundWindow() or one of those functions which forces a task switch.

    NOTE: SetForegroundWindow and SetActiveWindow USED to be a lot more usable from other processes than they are today. (That was on Win 9x machines).

    What the MMF object becomes is a mutex with extra info which cannot be owned or waited on which I guess means a memory-mapped-file is NOT a mutex.


    MCM

    Comment


    • #3
      Hi MCM,
      Thanks for the info. Did you post any code for that in the forums? I'll go looking.

      I'm a bit surprised that the need I expressed wasn't easily found in the forums or the web. It would seem a normal option a user might want - going with the already running copy of an application.

      Comment


      • #4
        > wasn't easily found in the forums

        So here's your chance to write the demo. You can be famous.

        Comment


        • #5
          Hmmm... If I let 5 versions of the app run, close down 4, then decide on a whim not to let a 6th run ... your memory mapped object approach becomes more complicated, doesn't it? There would need to be, I assume, an array of such objects in case I close the earliest ones down.

          For that matter, if the 5th version tries to run, and I say no, which previous version is brought to the front?

          Comment


          • #6
            If I let 5 versions of the app run, close down 4, then decide on a whim not to let a 6th run ... your memory mapped object approach becomes more complicated, doesn't it?
            Semaphore Object!

            I've been threatening to create a demo of that for about five years. Write that, too, and be [i]doubly[i] famous!

            Comment


            • #7
              That's the crappy thing about programming. Every time I want to do something different, there's something new I have to learn! It's hard to rest on your laurels.

              Comment


              • #8
                Time to "Coin" a new phrase.....
                "If you rest on your laurels,......You are doing it WRONG!!!" :wavey:

                Like what both I am sure Gary and MCM well know is that there are always some FINE!!! examples of a concept in the forums....but there is also always "That missing twist" that makes what you are trying to do, just different enough to be hard to achieve if no example to follow? :coffee4::coffee4::coffee4:
                Engineer's Motto: If it aint broke take it apart and fix it

                "If at 1st you don't succeed... call it version 1.0"

                "Half of Programming is coding"....."The other 90% is DEBUGGING"

                "Document my code????" .... "WHYYY??? do you think they call it CODE? "

                Comment


                • #9
                  Gary -

                  This is roughly what I use.
                  Code:
                  global mtx as long
                  global mtxname as string
                  global found as long
                  global look4 as string
                  
                  function en( byval hWnd as long, byval lParam as long ) as long
                     local title as asciiz * %Max_Path
                  
                     found = 0
                  
                     GetWindowtext hWnd, title, %Max_Path
                     if len( title ) and ( instr( title, look4 ) ) then
                        found = hWnd
                        function = 0
                     else
                        function = 1
                     end if
                  
                  end function
                  
                  function Running() as long
                  
                       function = %false ' Assume this is the 1st instance of the program
                  
                       mtxname = "something unique"
                  
                       mtx = CreateMutex( byval %Null, 0, mtxname )
                       
                           if Mtx = 0 then
                              ' mtx error handling action
                              funxtion = %True
                       elseif GetLastError = %Error_Already_Exists then
                              function = %True    ' Already running
                       end if
                  end function
                  
                  function pbmain() as long
                  
                      if Running() then
                         EnumWindows codeptr( en ), 0
                         if IsTrue( Found ) then
                            if IsTrue( IsIconic( found ) ) then
                               ShowWindow found, %SW_Restore
                               SetForegroundWindow found
                               function = 0
                            else
                               SetForegroundWindow found
                            end if
                         end if
                         exit function
                       end if
                  
                  ' the rest of the pbmain logix
                  
                  
                  
                  
                  end function
                  For the 'something unique' string, I use a string element that is not all likely
                  to be used by any other program on the machine followed by the name of the app and followed by the directory path the app was launched form. This
                  easily creates a unique mutex, but by using the app directory allows the program to run from another app for a 2nd, 3rd, 4th instance if needed just
                  by running from a different directory. Whil this might not be practible for
                  all apps it is quick and easy for some. Tailor this to your needs.

                  As for the instr( title, look4 ), the title is the full title text and the look4 is
                  what you want to use to match. Since you said your title is adjusted as it
                  is running, make sure the look4 string is only as many beginning characters as needed to match. Again this is up to you.

                  I think these routines are flexible enough for you to modify to suit your needs.
                  They have never failed me yet.

                  Enjoy?

                  Skip
                  Last edited by Michael Mattias; 18 May 2012, 09:25 AM. Reason: Add code tags

                  Comment


                  • #10
                    I use:

                    Code:
                        'Check to see if program already running
                        LOCAL smutex AS STRING
                        smutex = "whatever"                                     'Mutex string
                        x = CreateMutex(BYVAL %Null, 0, BYVAL STRPTR(smutex))   'check if running
                        IF x <> %Null THEN                                      'Program probably already running ?
                            IF GetLastError = %ERROR_ALREADY_EXISTS THEN        ' yes
                                hdlg = GetTopWindow(%HWND_DESKTOP)
                                t = PARSE$(SetText(%IDX_TITLE), 1) + $NUL       ' create ASCIIZ version of title
                                x1 = LEN(t): smutex = t                         ' make a destination string for later
                                DO WHILE hdlg
                                    GetWindowText(hdlg, BYVAL STRPTR(smutex), x1)
                                    IF smutex = t THEN                          'is the basic title the same?
                                        ShowWindow(hdlg, %SW_RESTORE)
                                        SetForeGroundWindow(hdlg)
                                        EXIT LOOP
                                    END IF
                                    hdlg = GetWindow(hdlg, %GW_HWNDNEXT)
                                LOOP
                                CloseHandle x   'tidy up after, just to be polite
                                EXIT FUNCTION
                            END IF
                        END IF

                    Comment


                    • #11
                      Good examples BUT....they are based on the Window title......
                      My forum searches kept turning up FindWindow, which won't work because my app title changes each time it is run

                      Comment


                      • #12
                        One easy way is to set %GWL_USERDATA to a unique value...

                        Pierre

                        Code:
                        #COMPILE EXE '#Win 9.07#
                        #REGISTER NONE
                        #DIM ALL
                        #INCLUDE "Win32Api.Inc"
                        
                        $AppName = "Mutex demo"
                        
                        %Label01  = 101
                        
                        '______________________________________________________________________________
                        
                        CALLBACK FUNCTION DlgProc
                        
                        END FUNCTION
                        '______________________________________________________________________________
                        
                        FUNCTION PBMAIN()
                         LOCAL zMutex  AS ASCIIZ * %MAX_PATH
                         LOCAL hMutex  AS DWORD
                         LOCAL dwMutex AS DWORD
                         LOCAL hDlg    AS DWORD
                         LOCAL hTry    AS DWORD
                         LOCAL hIcon   AS DWORD
                        
                         dwMutex = 2019013022 'I used YYYYMMDDHH for a unique number (DWORD is 0 to 4,294,967,295)
                         zMutex  = "MyMutex" 'Unique name, max string lenght is %MAX_PATH, no "\", case sensitive
                         hMutex  = CreateMutex(BYVAL %NULL, 0, zMutex) 'System wide mutex
                         IF GetLastError = %ERROR_ALREADY_EXISTS THEN 'App already started
                           hTry = GetForegroundWindow()
                           DO WHILE hTry
                             IF GetWindowLong(hTry, %GWL_USERDATA) = dwMutex THEN 'Found self via dwMutex
                               EXIT DO
                             END IF
                             hTry = GetWindow(hTry, %GW_HWNDNEXT)
                           LOOP
                           MessageBox(hDlg, $AppName & " is already running!", $AppName, %MB_TOPMOST OR %MB_OK)
                           IF IsIconic(hTry) THEN ShowWindow(hTry, %SW_RESTORE) 'Unminimize exe
                           ShowWindow(hTry, %SW_SHOW)    'Show started exe
                           SetForegroundWindow(hTry)     'Set it foreground
                           EXIT FUNCTION                 'Exit and let the already started exe run
                         END IF
                        
                         DIALOG NEW %HWND_DESKTOP, $AppNAme, , , 200, 100, %WS_CAPTION OR %WS_MINIMIZEBOX  OR %WS_SYSMENU, 0 TO hDlg
                        
                         SetWindowLong(hDlg, %GWL_USERDATA, dwMutex)
                        
                         CONTROL ADD LABEL, hDlg, %Label01, "Mutex demo, try to run a second instance...", 5, 45, 190, 10, %SS_CENTER, %WS_EX_LEFT
                        
                         hIcon = ExtractIcon(GetModuleHandle(""), "Shell32.dll", 294) 'o
                         SetClassLong(hDlg, %GCL_HICON, hIcon)
                         DIALOG SHOW MODAL hDlg CALL DlgProc
                        
                         CloseHandle(hMutex)
                         DestroyIcon(hIcon)
                        
                        END FUNCTION
                        '______________________________________________________________________________
                        '
                        Last edited by Pierre Bellisle; 30 Jan 2019, 10:54 PM.

                        Comment


                        • #13
                          Also if you want to play wih registered Window messages,
                          here is a demo that show how to permit only 3 instances of a program.

                          Pierre

                          Comment


                          • #14
                            Hi Pierre!
                            I think the unique number idea can work for my needs.

                            In the case where more than two copies of the app are started, I could create an array of unique numbers. Then in the FindWindow loop I could search until any of the array members are found. I could even use the upper bound of the array as the limit to the number of instances allowed.

                            And as for unique numbers, with 4G of numbers, I'd think almost any numbers I pick would be fine. There's no need for measures such as checking all open windows to see if they also have the same number I want to use.

                            I'll play with that to see how it works out ...

                            Thanks everyone for their comments.

                            Comment


                            • #15
                              Hi Pierre,
                              A couple of observations ...

                              I can't use the date (as in your example) as the userdata because instances might be created over a period of days.

                              Instead, I'll just arbitrarily assign a permanent unique number. For example, I could pick "1818181818" as the unique number and always use that. What are the odds that another app would pick the same value as its userdata? I don't know of any way to guarantee it. It somewhat like the problem of a GUID - pick a random number and accept that there's a finite, but small, chance someone else will use it.

                              In the case of allowing more than 2 instances, I want, when an instance closes, for the most recent instance to gain focus. It would be nice to have the date somehow embedded within the userdata, but to detect date-time differences as short as how fast a user can start a new instance (double-click time is ms, I'd guess), requires more digits than a LONG supports - provided I also want digits in userdata that are recognizable as a unique number.

                              So, I'd guess there is an API that will tell me when a window is created? I looked briefly but didn't see it yet. Bound to be one. That would let me use the common unique number for user data (no date in it), and then use the find-the-creation-date API with the hTry in your code to get the creation date. It complicate the DO loop a bit, but should work.

                              I'm off to a tennis match but will post some code on this later today.

                              Comment


                              • #16
                                I'll just arbitrarily assign a permanent unique number.....It somewhat like the problem of a GUID - pick a random number and accept that there's a finite, but small, chance someone else will use it.
                                So why not use a GUID as your "Unique Number?" I'll insure that against duplicates. Send me a check for your premium.

                                Oh, doesn't fit neatly in four bytes? So allocate the eight bytes and store a handle or pointer.

                                MCM

                                Comment


                                • #17
                                  Originally posted by Gary Beene View Post
                                  In this short code, I use a mutex to keep a second instance of an app from being run.

                                  What I want to do, when a previous instance is found, is to bring the previous app into view, with focus.

                                  My forum searches kept turning up FindWindow, which won't work because my app title changes each time it is run. My MSDN searches were tantalizing, but still not successful.

                                  It there a straightforward way to do what I want? I suspect it's easy, just not something I've run into before.

                                  Code:
                                  'Compilable Example:
                                  #Compiler PBWin 10
                                  #Compile Exe
                                  #Dim All
                                  %Unicode = 1
                                  #Include "Win32API.inc"
                                  
                                  Function PBMain() As Long
                                     Local UniqueName As WStringZ * %Max_Path, hDlg As Dword
                                     UniqueName = "gbInstanceTest"                     '
                                     If CreateMutex(ByVal %Null, 0, UniqueName) = 0 Or GetLastError = %ERROR_ALREADY_EXISTS Then
                                        ? "sorry, I must leave"
                                        Exit Function
                                     End If
                                     Dialog New Pixels, 0, Time$,300,300,200,100, %WS_OverlappedWindow To hDlg
                                     Dialog Show Modal hDlg
                                  End Function
                                  You haven't specified if you want to control instances just within a namespace or across all namespaces. On older versions of MS server (from memory before 2003) your code would have been effective across all namespaces but now would only effect the current namespace. Not sure about desktop versions with fast switching between users.

                                  Comment


                                  • #18
                                    Gary,
                                    You might consider using the windows global atom table. See the GlobalAddAtom and GlobalFindAtom functions.

                                    Comment


                                    • #19
                                      You might consider using the windows global atom table. See the GlobalAddAtom and GlobalFindAtom functions.
                                      Good thought... maybe combine.....
                                      Code:
                                         Four_byte_User_Value =  GlobalAtom (GUIDTXT$(GUID$())
                                      ....???

                                      Comment


                                      • #20
                                        This is not as simple issue as it may seem.The problem with most semaphore objects is that if any program that has referenced that object closes (crashes?) without first reliqueshing its reference to that object then as the object still has a count it will continue to exist after all programs that referenced it closed just like a memory leak. Using a file (memory mapped or not) with count or other details has the smae problem as if the program which placed its details in the file doesn't remove them then those erronius details will exist after the program has closed. I think the same applies to a Global Atom if it isn't removed.
                                        The Microsoft answer for this problem if only one instance of a programme is allowed to be running no matter how many namespaces exist is for the first occurance of the program to open a Locked file and the starting of an instance of the programme should fail if it can't open and lock that file.
                                        It appears that no matter how the instance of the programme which created the lock ended in an unspecified time the lock will be released. I have been using the method for years and it works. I have a programme that must have only one instance running. If I login and start the programme remotely (from a different country) if the inhouse people login even with the same user name they wont be able to see its GUI but still can't run another instance. On the rare occassions it has terminated unexpectadly they can restart it.
                                        This created a whole another problem, How to close the program gracefully if it is in another namespace? I got around this by building a TCP/IP server into the programme that allows the programme to be controlled by an external programme including gracefull close.
                                        A quick Google check found that fast user switching on desktops uses similar namespaces but not what the default scope of a semaphore object is but assume that safest is to add a scope prefix like Global or Local

                                        Comment

                                        Working...
                                        X