Dans cet article nous allons voir comment empêcher un utilisateur d’ouvrir plusieurs instances d’une application et comment notifier la première instance lorsque l’utilisateur lance la seconde.

Ceux qui utilisent VB.NET se demandent surement le pourquoi de cet article. En effet il suffit de cocher une case :

VB.NET

En C# cette options n’est malheureusement pas disponible. On peut cependant retrouver le même comportement en ajoutant une référence à “Microsoft. VisualBasic” :

class Program
{
   class App : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
   {
   public App()
   {
   IsSingleInstance = true;
   }
   protected override void OnCreateMainForm()
   {
   MainForm = new Form1();
   }
   }
   static void Main(string[] args)
   {
   var app = new App();
   app.Run(args);
   }
}

C’est plutôt simple, mais ça ne fonctionne que pour les applications Windows Forms. Pour les autres types d’application il faut donc mettre la main à la pâte.

Plusieurs mauvaises idées existent. La première consiste à lister les processus avec “Process. GetProcesses” afin de tester si un processus avec le même nom existe. Plusieurs applications peuvent avoir le même nom de processus donc cette méthode n’est pas fiable. La deuxième consiste à créer un fichier lorsque l’on lance l’application. Si le fichier existe déjà au lancement de l’application alors une instance existe déjà. Le problème est de veiller à bien supprimer le fichier à la fermeture de l’application (même lorsque celle-ci plante lamentablement).

Il faut donc trouver une bonne méthode et comme souvent il suffit de se tourner vers Windows. Lorsque deux threads ou plus doivent accéder en même temps à une ressource partagée, le système a besoin d’un mécanisme de synchronisation pour garantir qu’un seul thread à la fois utilise la ressource. Mutex est une primitive de synchronisation qui accorde à un seul thread un accès exclusif à la ressource partagée. Si un thread acquiert un mutex, l’autre thread qui veut acquérir ce mutex est interrompu jusqu’à ce que le premier thread libère le mutex. Les mutex peuvent être nommés afin d’être partagés entre les processus.

L’idée est donc de créer un Mutex au lancement de l’application et d’en acquérir la propriété. Si une deuxième application se lance elle ne pourra pas prendre la propriété de ce Mutex.

const string AppId = "Local\\1DDFB948-19F1-417C-903D-BE05335DB8A4"; // Unique par application 
static void Main(string[] args) 
{ 
   using (Mutex mutex = new Mutex(false, AppId)) 
   { 
   if (!mutex.WaitOne(0)) 
   { 
   Console.WriteLine("2nd instance"); 
   return; 
   } 
   Console.WriteLine("Started"); 
   Console.ReadKey(); 
   } 
}

La deuxième étape est de notifier la première instance que l’utilisateur à essayer d’en lancer une autre. Pour cela on peut utiliser les IPC (InterProcess Communication). La première application va déclarer le canal IPC. Les autres instances vont s’y connecter et envoyer un message à la première instance.

static void Main(string[] args) 
{ 
   using (Mutex mutex = new Mutex(false, AppId)) 
   { 
   if (!mutex.WaitOne(0)) { ... } 
   IpcChannel channel = new IpcChannel(AppId); 
   ChannelServices.RegisterChannel(channel, false); 
   RemotingConfiguration.RegisterWellKnownServiceType(typeof(SingleInstance), "RemotingServer", WellKnownObjectMode.Singleton); 
   Console.WriteLine("Started"); 
   Console.ReadKey(); 
   } 
} 
  
// Objet utilisé par le client et le serveur 
private class SingleInstance : MarshalByRefObject 
{ 
   public void Execute(string[] args) 
   { 
   Console.WriteLine("Second instance: " + string.Join(" ", args)); 
   } 
}

Maintenant les autres instances vont envoyer un message au serveur :

if (!mutex.WaitOne(0)) 
{ 
   IpcChannel channel = new IpcChannel(); 
   ChannelServices.RegisterChannel(channel, false); 
   SingleInstance app = (SingleInstance)Activator.GetObject(typeof(SingleInstance), string.Format("ipc://{0}/RemotingServer", AppId)); 
   app.Execute(args); 
   return; 
}

Il y a d’autres mécanismes pour communiquer entre applications tels ques les Message Windows (RegisterWindowMessagePostMessage) ou les RPC. Une liste assez complète est disponible sur MSDN (http://msdn.microsoft.com/en-us/library/windows/desktop/aa365574(v=vs.85).aspx).

Et voilà.

Le code complet : https://gist.github.com/meziantou/84b46cee16e9b565675e

Ne ratez plus aucunes actualités avec la newsletter mensuelle de SoftFluent