Comment empecher le lancement d'une application si celle-ci tourne deja ?
Nous exposerons dans cet article deux méthodes permettant d'empêcher plusieurs instances de votre application d'être exécutées en même temps : l'une employant App.PrevInstance, l'autre employant des mutex. Nous exposerons ensuite les dangers de telles solutions. Les deux méthodes exposées sont classiques, mais de nombreuses autres peuvent être implémentées. Celles-ci incluent, par exemple, la recherche de fenêtres sur base de leur titre, l'ouverture d'un port TCP sur la machine locale, la création de clés de registre ou de fichiers, mais l'utilisation de variables partagées pour un serveur d'ActiveX. Il est important, lors de l'utilisation de tels procédés, d'en connaître les limites d'utilisation. Le choix de l'un ou l'autre de ces moyens dépendra principalement du degré de sécurité que vous souhaitez : empêcher deux instances d'un lecteur multimédia pour le confort de l'utilisateur est fort différent d'empêcher les écritures simultanées de données, par des processus concurrents, menant a des corruptions ou a la perte de ces mêmes données. App.PrevInstanceVous pouvez employer la propriete PrevInstance de l'objet App qui permet de savoir s'il existe deja une instance de l'application. Cette méthode présente l'avantage d'être particulièrement simple a mettre en oeuvre. Néanmoins, un utilisateur astucieux pourra exécuter plusieurs instances, par exemple en copiant l'exécutable d'un dossier vers un autre. Sub Main()
If App.PrevInstance = True Then MsgBox "Ce programme tourne déjà.", vbExclamation, "Erreur" Else frmMain.Show End If
End Sub MutexUn mutex est un objet de synchronisation offert par le système d'exploitation. Lors de l'utilisation de tels objets, il est important de s'assurer que leur utilisation est correcte, faute de quoi la synchronisation pourrait ne pas être réalisée correctement. Pour commencer, nous allons créer un objet qui nous servira a manipuler le mutex plus facilement. Dans un module de classe, ajoutez le code suivant. Option Explicit
Private Const SYNCHRONIZE = &H100000 Private Const STANDARD_RIGHTS_REQUIRED = &HF0000
Private Const MUTANT_QUERY_STATE = &H1 Private Const MUTANT_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or SYNCHRONIZE Or MUTANT_QUERY_STATE)
Private Const MUTEX_MODIFY_STATE = MUTANT_QUERY_STATE Private Const MUTEX_ALL_ACCESS = MUTANT_ALL_ACCESS
Private Const STATUS_WAIT_0 As Long = &H0& Private Const STATUS_ABANDONED_WAIT_0 As Long = &H80& Private Const STATUS_TIMEOUT As Long = &H102&
Private Const WAIT_FAILED = &HFFFFFFFF Private Const WAIT_OBJECT_0 As Long = ((STATUS_WAIT_0) + 0) Private Const WAIT_ABANDONED As Long = ((STATUS_ABANDONED_WAIT_0) + 0) Private Const WAIT_ABANDONED_0 As Long = ((STATUS_ABANDONED_WAIT_0) + 0)
Private Const INFINITE As Long = &HFFFF&
Private Const ERROR_ALREADY_EXISTS As Long = &HB7&
Private Declare Function apiCreateMutex _ Lib "kernel32" _ Alias "CreateMutexA" _ ( _ lpMutexAttributes As Any, _ ByVal bInitialOwner As Long, _ ByVal lpName As String _ ) _ As Long Private Declare Function apiReleaseMutex _ Lib "kernel32" _ Alias "ReleaseMutex" _ ( _ ByVal hMutex As Long _ ) _ As Long Private Declare Function apiOpenMutex _ Lib "kernel32" _ Alias "OpenMutexA" _ ( _ ByVal dwDesiredAccess As Long, _ ByVal bInheritHandle As Long, _ ByVal lpName As String _ ) _ As Long Private Declare Function CloseHandle _ Lib "kernel32" _ ( _ ByVal hObject As Long _ ) _ As Long Private Declare Function WaitForSingleObject _ Lib "kernel32" _ ( _ ByVal hHandle As Long, _ ByVal dwMilliseconds As Long _ ) _ As Long
Private mMutexHandle As Long Private mMutexOwner As Boolean
Public Function OpenMutex(ByVal Name As String) As Boolean CloseMutex mMutexHandle = apiOpenMutex(MUTEX_ALL_ACCESS Or SYNCHRONIZE, 0, Name) OpenMutex = (mMutexHandle = 0) End Function
Public Function CreateMutex(Name As String, ByRef OwnOnCreation As Boolean) As Boolean CloseMutex
If OwnOnCreation Then mMutexHandle = apiCreateMutex(ByVal 0&, 1, Name) If mMutexHandle <> 0 Then If (Err.LastDllError = ERROR_ALREADY_EXISTS) Then OwnOnCreation = False Else mMutexOwner = True End If End If Else mMutexHandle = apiCreateMutex(ByVal 0&, 0, Name) End If CreateMutex = (mMutexHandle <> 0) End Function
Public Function Acquire(Optional Timeout As Long = INFINITE) As Boolean If IsAttached Then If IsOwner Then Acquire = True Else Select Case WaitForSingleObject(mMutexHandle, Timeout) Case WAIT_ABANDONED, WAIT_OBJECT_0 mMutexOwner = True Case Else mMutexOwner = False End Select Acquire = mMutexOwner End If End If End Function
Public Function IsAttached() As Boolean IsAttached = (mMutexHandle <> 0) End Function
Public Function IsOwner() As Boolean IsOwner = mMutexOwner End Function
Public Sub CloseMutex() If IsAttached Then If IsOwner Then ReleaseMutex End If CloseHandle mMutexHandle mMutexHandle = 0 End If End Sub
Public Sub ReleaseMutex() If IsAttached And mMutexOwner Then apiReleaseMutex mMutexHandle mMutexOwner = False End If End Sub
Private Sub Class_Terminate() CloseMutex End Sub
Maintenant que cette classe est créée, nous allons l'utiliser dans la form principale de notre projet. Option Explicit
Private mMutex As CMutex
Private Sub Form_Load() Const APP_NAME As String = "Mon Application" Set mMutex = New CMutex
If Not mMutex.CreateMutex("Global\" & APP_NAME & " / multi-instances control mutex", True) Then MsgBox "Une erreur s'est produite. Impossible de determiner si l'application s'execute deja ! (" & Err.LastDllError & ")" Unload Me ElseIf Not mMutex.IsOwner Then MsgBox "Une autre instance de cette application est en cours d'execution !" Unload Me End If End Sub
Private Sub Form_Unload(Cancel As Integer) mMutex.CloseMutex End Sub Le préfixe Global\ au nom n'est pas anodin. Celui-ci assure, sur un système sur lequel plusieurs sessions utilisateurs peuvent s'executer parallelement, que le mutex sera considère pour l'entièreté du système et pas uniquement pour la session courante. La constante APP_NAME rend le nom du mutex unique. Ceci garanti que quel que soit le nom de l'exécutable, ou son chemin d'exécution, l'application n'aura au plus qu'une instance démarrée. On peut aussi envisager d'utiliser App.ExeName qui, bien qu'elle ne garantisse pas l'unicité, permet à l'utilisateur de contourner la protection si la première instance venait à ne plus s'exécuter correctement. InconvénientsLe principal inconvénient des applications dont plusieurs processus ne peuvent exister simultanement est qu'elles sont vulnérables aux dénis de service. N'importe quel logiciel malicieux peut prendre la place d'une telle application de sorte a en empêcher le démarrage. Il faut dans un premier temps s'assurer que le design de l'application est correct et requiert effectivement que seule une instance soit présente a la fois. Si c'est le cas, l'objet de synchronisation devrait, au minimum, disposer de droits de sécurité permettant raisonnablement d'éviter ce problème. Par exemple, si le test est réalise sur base de l'existence d'un fichier, seul l'administrateur de la machine, et non les utilisateurs, devrait y avoir accès. Ceci impliquerait aussi que seul l'administrateur puisse utiliser le logiciel. Il est a remarquer que notre implémentation de CMutex utilise le contexte de sécurité de l'utilisateur courant. Il est possible de spécifier plus finement les restrictions a l'aide de l'argument lpMutexAttributes de l'API CreateMutex. De nombreuses méthodes reposent sur le contenu ou d'un fichier ou d'une clés de registre. Outre le fait que ces méthodes ne puissent pas, de manière inhérente, assurer que deux instances simultanées ne soient pas en cours d'exécution, dans le cas d'un crash, rien ne garanti de pouvoir exécuter a nouveau l'application sans éditer le fichier ou la clé en question. Il faut donc faire attention a l'usage de telles méthodes. Aller plus loin
Voir aussi : |