[Windows] Mehrere Instanzen eines Programmes verhindern

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
Danilo
-= Anfänger =-
Beiträge: 2284
Registriert: 29.08.2004 03:07

[Windows] Mehrere Instanzen eines Programmes verhindern

Beitrag 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.
Zuletzt geändert von Danilo am 10.04.2012 14:42, insgesamt 3-mal geändert.
cya,
...Danilo
"Ein Genie besteht zu 10% aus Inspiration und zu 90% aus Transpiration" - Max Planck
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Beitrag 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
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Beitrag 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
Zuletzt geändert von ts-soft am 11.04.2012 01:25, insgesamt 1-mal geändert.
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
Danilo
-= Anfänger =-
Beiträge: 2284
Registriert: 29.08.2004 03:07

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Beitrag 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
cya,
...Danilo
"Ein Genie besteht zu 10% aus Inspiration und zu 90% aus Transpiration" - Max Planck
Nino
Beiträge: 1300
Registriert: 13.05.2010 09:26
Wohnort: Berlin

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Beitrag von Nino »

Sehr nützlich, vielen Dank!
Benutzeravatar
TheCube
Beiträge: 169
Registriert: 20.07.2010 23:59
Computerausstattung: Risen 3400G 16MB Win10-64Bit
Wohnort: NRW

Re: [Windows] Mehrere Instanzen eines Programmes verhindern

Beitrag 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}
Antworten