Seite 1 von 1

[Windows] Mehrere Instanzen eines Programmes verhindern

Verfasst: 09.04.2012 23:52
von Danilo
Hier ein Code von Dr. Joseph M. Newcomer um mehrere Instanzen eines Programmes
mit einem Mutex zu verhindern, portiert zu PureBasic.

Dabei kann man über Flags festlegen, ob man eine Instanz auf dem ganzen System oder für
jeden Systembenutzer oder auf jedem Desktop im System zulassen möchte.

Der ausführliche Artikel von Dr. Joseph M. Newcomer dazu:
- Avoiding Multiple Instances of an Application (auf seiner Homepage)
- Avoiding Multiple Instances of an Application (bei codeproject)

Code: Alles auswählen

;
; Avoiding Multiple Instances of an Application
;
; by Dr. Joseph M. Newcomer
;
; - http://www.flounder.com/nomultiples.htm
; - http://www.codeproject.com/Articles/538/Avoiding-Multiple-Instances-of-an-Application
;
; PB port by Danilo, April 2012
;
;
; SI_SESSION_UNIQUE                        - Allow one instance per login session
; SI_DESKTOP_UNIQUE                        - Allow one instance per desktop
; SI_TRUSTEE_UNIQUE                        - Allow one instance per user account
; SI_SESSION_UNIQUE | SI_DESKTOP_UNIQUE    - Allow one instance per login session,
;                                            instances in different login sessions
;                                            must also reside on a different desktop
; SI_TRUSTEE_UNIQUE | SI_DESKTOP_UNIQUE    - Allow one instance per user account,
;                                            instances in login sessions running a
;                                            different user account must also reside    
;                                            on different desktops.
; SI_SYSTEM_UNIQUE                         - Allow only one instance on the whole system
;
;
#SI_SESSION_UNIQUE = $0001 ; Allow only one instance per login session
#SI_DESKTOP_UNIQUE = $0002 ; Allow only one instance on current desktop
#SI_TRUSTEE_UNIQUE = $0004 ; Allow only one instance for current user
#SI_SYSTEM_UNIQUE  = $0000 ; Allow only one instance at all (on the whole system)

Procedure.s CreateUniqueName( name.s, mode.l = #SI_DESKTOP_UNIQUE )
    Protected output.s = "Global\"+name
    
    If mode & #SI_DESKTOP_UNIQUE
        Protected hDesk.i, cchDesk.l, userinfo.s
        output   + "-"
        hDesk    = GetThreadDesktop_( GetCurrentThreadId_() )
        cchDesk  = #MAX_PATH - Len(name) - 1
        userinfo = Space(#MAX_PATH)
        
        If GetUserObjectInformation_( hDesk, #UOI_NAME, @name, cchDesk, @cchDesk )
            output + Trim(name)
        Else
            output + "Win9x"
        EndIf
    EndIf
    If mode & #SI_SESSION_UNIQUE
        Protected hToken.i, cbBytes.l, *pTS
        If OpenProcessToken_( GetCurrentProcess_(), #TOKEN_QUERY, @hToken )
            If GetTokenInformation_( hToken, #TokenStatistics, #Null, cbBytes, @cbBytes )=0 And GetLastError_() = #ERROR_INSUFFICIENT_BUFFER
                *pTS = AllocateMemory( cbBytes )
                If *pTS
                    If GetTokenInformation_( hToken, #TokenStatistics, *pTS, cbBytes, @cbBytes )
                        output + "-" + RSet(Hex(PeekQ(*pts+8),#PB_Quad),16,"0")
                    EndIf
                    FreeMemory(*pTS)
                EndIf
            EndIf
        EndIf
    EndIf
    If mode & #SI_TRUSTEE_UNIQUE
        Protected user.s  , cchUser.l = 64
        Protected domain.s, cchDomain = 64
        user   = Space(cchUser)
        domain = Space(cchDomain)
        If GetUserName_( @user, @cchUser )
            output + "-" + Trim(user)
        EndIf
        cchDomain = GetEnvironmentVariable_( "USERDOMAIN", @domain, cchDomain )
        
        output + "-" + Trim(domain)
    EndIf
    
    ProcedureReturn Left(output,#MAX_PATH)
EndProcedure

Procedure IsInstancePresent( name.s , mode.l = #SI_DESKTOP_UNIQUE )
    Protected err, uniqueName.s
    Static hMutex = #Null
    If hMutex = #Null
        uniqueName = CreateUniqueName( name, mode )
        hMutex = CreateMutex_( #Null, #False, @uniqueName )
        err = GetLastError_()
        If err = #ERROR_ALREADY_EXISTS : ProcedureReturn #True : EndIf
        If err = #ERROR_ACCESS_DENIED  : ProcedureReturn #True : EndIf
    EndIf
    ProcedureReturn #False
EndProcedure

;------------------------------------------------------------------------------------------------

#APPGUID = "{B883B6AB-AE6C-4ADC-AA13-5B3D0195AD5F}"

; Debug CreateUniqueName(#APPGUID, #SI_SESSION_UNIQUE )
; Debug CreateUniqueName(#APPGUID, #SI_DESKTOP_UNIQUE )
; Debug CreateUniqueName(#APPGUID, #SI_TRUSTEE_UNIQUE )
; Debug CreateUniqueName(#APPGUID, #SI_SYSTEM_UNIQUE  )
; 
; Debug CreateUniqueName(#APPGUID, #SI_SESSION_UNIQUE | #SI_DESKTOP_UNIQUE )
; Debug CreateUniqueName(#APPGUID, #SI_TRUSTEE_UNIQUE | #SI_DESKTOP_UNIQUE )


If IsInstancePresent(#APPGUID, #SI_SYSTEM_UNIQUE )
    End ; already running
EndIf

;
; start of program
;
MessageRequester("Info","Program running...")
Wie ts-soft im nächsten Beitrag sagte, verwendet man es am besten mit einer
eigenen, universellen GUID, so dass keine doppelten Namen auftreten können.

Eine eigene GUID kann man mit folgendem Code erstellen:

Code: Alles auswählen

;
; by ts-soft and Flype
;
; http://www.purebasic.fr/english/viewtopic.php?f=5&t=23233&start=11
;
Procedure.s MakeGUID()
  Protected guid.GUID, lpsz.s{78}
  If CoCreateGuid_(@guid) = #S_OK
    ProcedureReturn PeekS(@lpsz, StringFromGUID2_(guid, @lpsz, 78), #PB_Unicode)
  EndIf
EndProcedure

Debug MakeGUID()
Für jedes neue Programm muss man eine neue GUID erstellen, um doppelte Namen zu verhindern.

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Verfasst: 10.04.2012 00:22
von ts-soft
:allright:
Sehr nützlich :D

Es sollte vielleicht noch erwähnt werden, das für den Namen eine GUID empfehlenswert ist, ansonsten
könnte es doch doppelte Namen geben. MakeGUID sollte man über die Boardsuche finden können, bzw.
im CodeArchiv gibt es das glaub ich auch.

Gruß
Thomas

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Verfasst: 10.04.2012 05:01
von ts-soft
Hier ein Beispiel, das auf Danilo Code beruht und eine Möglichkeit zeigt, wie man:
  • 1. eine zweite Instance verhindert.
    2. die Parameter der zweiten Instance an die erste übergibt.
    3. die erste Instance in den Vordergrund bringt.
Nützlich für alle arten von Editoren usw.

Code: Alles auswählen

EnableExplicit

#SI_SESSION_UNIQUE = $0001 ; Allow only one instance per login session
#SI_DESKTOP_UNIQUE = $0002 ; Allow only one instance on current desktop
#SI_TRUSTEE_UNIQUE = $0004 ; Allow only one instance for current user
#SI_SYSTEM_UNIQUE  = $0000 ; Allow only one instance at all (on the whole system)

Procedure.s CreateUniqueName(name.s, mode.l = #SI_DESKTOP_UNIQUE )
  Protected output.s = "Global\" + name
  
  If mode & #SI_DESKTOP_UNIQUE
    Protected hDesk.i, cchDesk.l, userinfo.s
    output   + "-"
    hDesk    = GetThreadDesktop_(GetCurrentThreadId_())
    cchDesk  = #MAX_PATH - Len(name) - 1
    userinfo = Space(#MAX_PATH)
    
    If GetUserObjectInformation_(hDesk, #UOI_NAME, @name, cchDesk, @cchDesk)
      output + Trim(name)
    Else
      output + "Win9x"
    EndIf
  EndIf
  If mode & #SI_SESSION_UNIQUE
    Protected hToken.i, cbBytes.l, *pTS
    If OpenProcessToken_(GetCurrentProcess_(), #TOKEN_QUERY, @hToken)
      If GetTokenInformation_(hToken, #TokenStatistics, #Null, cbBytes, @cbBytes) = 0 And GetLastError_() = #ERROR_INSUFFICIENT_BUFFER
        *pTS = AllocateMemory(cbBytes)
        If *pTS
          If GetTokenInformation_(hToken, #TokenStatistics, *pTS, cbBytes, @cbBytes)
            output + "-" + RSet(Hex(PeekQ(*pts + 8), #PB_Quad), 16, "0")
          EndIf
          FreeMemory(*pTS)
        EndIf
      EndIf
    EndIf
  EndIf
  If mode & #SI_TRUSTEE_UNIQUE
    Protected user.s  , cchUser.l = 64
    Protected domain.s, cchDomain = 64
    user   = Space(cchUser)
    domain = Space(cchDomain)
    If GetUserName_(@user, @cchUser)
      output + "-" + Trim(user)
    EndIf
    cchDomain = GetEnvironmentVariable_("USERDOMAIN", @domain, cchDomain)
    
    output + "-" + Trim(domain)
  EndIf
  
  ProcedureReturn Left(output, #MAX_PATH)
EndProcedure

Procedure IsInstancePresent(name.s, mode.l = #SI_DESKTOP_UNIQUE)
  Protected err, uniqueName.s
  Static hMutex = #Null
  If hMutex = #Null
    uniqueName = CreateUniqueName(name, mode)
    hMutex = CreateMutex_(#Null, #False, @uniqueName)
    err = GetLastError_()
    If err = #ERROR_ALREADY_EXISTS : ProcedureReturn #True : EndIf
    If err = #ERROR_ACCESS_DENIED  : ProcedureReturn #True : EndIf
  EndIf
  ProcedureReturn #False
EndProcedure

; Beispiel:
#myGUID$ = "{62880033-2E0A-47BD-A920-73B9C9E8DFE9}" ; einmalige ID

Global myAPPMsg = RegisterWindowMessage_(CreateUniqueName(#myGUID$))
Global parastring.s

Procedure myWinCB(hWnd, uMsg, wParam, lParam)
  Protected result = #PB_ProcessPureBasicEvents
  Protected.COPYDATASTRUCT CD, *pCD
  Protected para.s, count, i
 
  Select uMsg
    Case myAPPMsg
      If wParam = 0 ; wir übermitteln unser hWnd
        SendMessage_(lParam, myAPPMsg, WindowID(0), 0)
      ElseIf lParam = 0 ; unser hWnd ist angekommen, wir übermitteln die Parameter
        CD\cbData = StringByteLength(parastring) + SizeOf(Character)
        CD\lpData = @parastring
        SendMessage_(wParam, #WM_COPYDATA, hWnd, cd)
        SetForegroundWindow_(wParam) ; und bringen die erste Instance in den Vordergrund
      EndIf
     
    Case #WM_COPYDATA
      If IsGadget(0) ; nur die erste Instance hat gadgets! Bitte entsprechend anpassen.
        *pCD = lParam
        para = PeekS(*pCD\lpData)
        count = CountString(para, ";")
        ; hier reagieren wir auf die an die zweite Instance übergebenen Parameter
        ; als wären sie für diese Instance bestimmt ;-)
        For i = 1 To count
          AddGadgetItem(0, -1, StringField(para, i, ";"))
        Next
      EndIf
  EndSelect
 
  ProcedureReturn result
EndProcedure

Define i, count = CountProgramParameters() - 1

If count > -1
  For i = 0 To count
    parastring + ProgramParameter(i) + ";"
  Next
EndIf

If IsInstancePresent(#myGUID$)
  OpenWindow(0, 0, 0, 0, 0, "", #PB_Window_Invisible)
  SetWindowCallback(@myWinCB())
  SendMessage_(#HWND_BROADCAST, myAPPMsg, 0, WindowID(0))
  While WindowEvent() : Wend
  End
EndIf

OpenWindow(0, #PB_Ignore, #PB_Ignore, 640, 480, "MainApp")
ListViewGadget(0, 5, 5, 100, 470)
SetWindowCallback(@myWinCB())

Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Verfasst: 10.04.2012 14:26
von Danilo
Kleine Änderung im ersten Code:

Code: Alles auswählen

RSet(Hex(PeekQ(*pts+8)),8,"0")
geändert zu

Code: Alles auswählen

RSet(Hex(PeekQ(*pts+8),#PB_Quad),16,"0")
Check für AllocateMemory() hinzugefügt:

Code: Alles auswählen

*pTS = AllocateMemory( cbBytes )
If *pTS

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Verfasst: 11.04.2012 14:40
von Nino
Sehr nützlich, vielen Dank!

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Verfasst: 15.04.2012 02:26
von TheCube
Ich frage mich gerade, warum nun alle Uniquenames mit führendem "Global\" (also erstmal systemweit-fähig) erstellt werden ? Wirkt für mich etwas unnötig,
weil "#SI_SESSION_UNIQUE" und "#SI_TRUSTEE_UNIQUE" für sich selbst schon für jeden User (bzw. dessen Anmeldung) einmalig sind. Ja gut ... stört natürlich auch nicht.
Hier der Einfachheit halber der Output von Danilos Code:

Code: Alles auswählen

 CreateUniqueName(#APPGUID, #SI_SESSION_UNIQUE ) ; Global\{B883B6AB-AE6C-4ADC-AA13-5B3D0195AD5F}-000000000002C2FB <-Nummer bei jeder Useranmeldung neu
 CreateUniqueName(#APPGUID, #SI_DESKTOP_UNIQUE ) ; Global\{B883B6AB-AE6C-4ADC-AA13-5B3D0195AD5F}-Default <-Ich habe nur einen Desktop (wie die meisten?)
 CreateUniqueName(#APPGUID, #SI_TRUSTEE_UNIQUE ) ; Global\{B883B6AB-AE6C-4ADC-AA13-5B3D0195AD5F}-UserName-PCname
 CreateUniqueName(#APPGUID, #SI_SYSTEM_UNIQUE  ) ; Global\{B883B6AB-AE6C-4ADC-AA13-5B3D0195AD5F}