Les applications consistent souvent en un exécutable et plusieurs DLL. Pour déployer ce type d’application il faut copier tous les fichiers. Il serait plus pratique de ne déployer qu’un seul fichier : l’exécutable. On pourrait utiliser ILMerge qui fonctionne plutôt bien, mais on peut également le faire simplement depuis Visual Studio.

L’idée est de copier les dépendances dans les ressources de l’application. Pour cela il faut copier les DLL dans le projet (il est possible de les ajouter en tant que lien) et définir l’action de compilation “ Embedded Resource ”.

Embedded Resource

Lors de l’exécution il faut indiquer que l’assembly se trouve dans les ressources de l’exécutable. Pour cela nous allons utiliser l’évènement AppDomain.AssemblyResolve. Cet évènement nous permet de renvoyer l’assembly demandé lorsqu’il n’est pas trouvé automatiquement. Il est également intéressant de charger le fichier de symbôles associé (fichier PDB).

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
   string baseResourceName = Assembly.GetExecutingAssembly().GetName().Name + "." + new AssemblyName(args.Name).Name;
   byte[] assemblyData = null;
   byte[] symbolsData = null;
   using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(baseResourceName + ".dll"))
   {
   if (stream == null)
   return null;
   assemblyData = new Byte[stream.Length];
   stream.Read(assemblyData, 0, assemblyData.Length);
   }
   using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(baseResourceName + ".pdb"))
   {
   if (stream != null)
   {
   symbolsData = new Byte[stream.Length];
   stream.Read(symbolsData, 0, symbolsData.Length);
   }
   }
   return Assembly.Load(assemblyData, symbolsData);
}

La fonction n’a rien de vraiment intéressant, on récupère le nom de la dll demandée, et on la charge depuis les ressources de l’exécutable.

A l’utilisation il y a une subtilité : il ne faut pas utiliser une DLL directement depuis la méthode Main. En effet si tel est le cas, l’assembly devra être résolu avant que l’on ait put s’abonner à l’évènement AssemblyResolve => plantage. Le Main doit donc contenir l’abonnement à l’évènement et l’appel à la méthode principale (que l’on peut décorer avec l’attribut MethodImpl (MethodImplOptions. NoInlining)).

static void Main(string[] args)
{
   AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
   if (Debugger.IsAttached)
   {
   SafeMain();
   }
   else
   {
   try
   {
   SafeMain();
   }
   catch (Exception ex)
   {
   Console.WriteLine(ex.ToString());
   }
   }
}
 
[MethodImpl(MethodImplOptions.NoInlining)]
static void SafeMain()
{
   Console.WriteLine(Lib1.Test.Add(1, 2));
}

Et voilà,

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