Comment exécuter du code dans un Assembly sans le charger dans le AppDomain principal


Description du problème

Supposons que votre programme utilise un ou des Dlls définis par vos utilisateurs. Ceux-vi vont s’attendre à être en mesure de recompiler leur(s) Dll(s) plusieurs fois pendant l’exécution de votre programme. Il y a de fortes chances que vous ne puissiez pas leur dire de redémarrer votre programme dès qu’ils modifient l’un de leurs Dlls.

Le problème, c’est qu’avec le .Net Framework, dès qu’un Assembly est chargé dans un AppDomain, il ne peut plus jamais être déchargé pour la vie du AppDomain et cela empêche de modifier le Dll sur le disque.

Un programme .Net a toujours un AppDomain principal et tous les Assemblies que vous chargez dans celui-ci ne seront pas déchargés tant et aussi longtemps que votre programme s’exécutera.

Description de la solution

Il faut donc créer un nouvel AppDomain temporaire, charger les Dlls de vos utilisateurs, les utiliser, puis le détruire.

Ce premier article va aborder un cas simple mais courant, où les utilisateurs doivent définir des classes implantant une interface. L’interface sera définie dans un Dll qui sera chargé à la fois par le client (votre programme) et les composantes de vos utilisateurs.

L’exemple que je vais utiliser a 4 composants:

1) L’exécutable principal (HostApplication.exe)

image

2) Le Dll contenant l’interface (Interfaces.dll)

3) Deux Dlls d’exemple (PluginA.dll, PluginB)

On peut voir un diagramme ici qui illustre l’EXE, les AppDomains, les Dlls et les classes.

image

On voit que Interfaces.dll sera chargé dans les deux AppDomains mais que PluginA.dll et PluginB.dll resteront dans le AppDomain temporaire. Cela aura pour effet de permettre la modification de ceux-ci sur le disque pendant que le programme s’exécute.

Détails sur ce qui se passe

Au démarrage de mon programme, on voit qu’il n’y a qu’un seul AppDomain appelé comme l’exécutable:

image

Il suffit de choisir le Dll et la classe à appeler puis cliquer sur Execute:

image

Le résultat sera affiché et un message apparaîtra afin d’empêcher l’AppDomain temporaire d’être détruit:

image

image

Si on rafraîchit Application Domain Viewer avant de fermer le message, on voit l’AppDomain temporaire qui a chargé PluginA.dll et Interfaces.dll:

image

Une fois qu’on ferme le message, l’AppDomain est détruit et on voit que Interfaces.dll est toujours chargé dans le AppDomain principal:

image

Aperçu du code source

En gros, j’ai défini une classe AppDomainProxy qui supporte IDisposable afin de pouvoir être utilisée dans une clause using.

Cette classe définit une méthode CreateInstance qui accepte un AssemblyName et un ClassName et qui retourne une référence de type ISampleInterface.

using (AppDomainProxy pAppDomainProxy = new AppDomainProxy())
{
  ISampleInterface pI = pAppDomainProxy.CreateInstance(cboAssemblyName.Text, cboClassName.Text);

  if (pI != null)
  {
    txtOutput.Text = pI.GetValue(txtParameter.Text);
    MessageBox.Show(« Now is the time to look both AppDomain before it gets deleted! »);
  }
  else
  {
    throw new ApplicationException(« Could not load assembly  » + cboAssemblyName.Text);
  }
}

Dans cette exemple, l’interface est toute simple:

public interface ISampleInterface
{
  string GetValue(string sParam);
}

Les classes des utilisateurs doivent implanter l’interface ISampleInterface et hériter de MarshallByRef:

public class ClassA : MarshalByRefObject, ISampleInterface
{
  public string GetValue(string sParam)
  {
    return String.Format(« {0} from ClassA », sParam);
  }
}

La création d’un AppDomain se fait ainsi (à l’interne de la classe AppDomainProxy):

mpAppDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(« B »));

Et sa destruction ainsi (dans Dispose):

AppDomain.Unload(mpAppDomain);

J’ai mis l’exemple au complet dans GitHub ici (VS.Net 2008).

AppDomain 102

Ma prochaine entrée de blogue vous montrera comment faire de la .Net Reflection sur des classes chargés dans un AppDomain temporaire, sans les charger dans le AppDomain principal.

C’est pratique s’il vous faut explorer dynamiquement des Assembly sans empêcher les utilisateurs de les modifier sur le disque pendant l’exécution de votre programme.

Références

AppDomainProxy (GitHub)
https://github.com/samsonfr/AppDomainProxy

AppDomain Viewer
http://labs.workshare.com/2010/08/app-domain-viewer.html#comment-form

3 commentaires sur “Comment exécuter du code dans un Assembly sans le charger dans le AppDomain principal

Répondre à Comment faire de la .NET Reflexion sur un Assembly sans le charger dans le AppDomain principal | Frédérick Samson Annuler la réponse.