Comment faire de la .NET Reflection sur un Assembly sans le charger dans le AppDomain principal

AppDomain 101

Mon premier article indiquait la marche à suivre afin d’exécuter du code d’un Assembly sans le charger dans le AppDomain principal. Il fallait définir une interface connue des deux parties et la solution était plutôt simple.

AppDomain 102

Maintenant, on va augmenter la complexité d’un cran. Supposons que vous ne pouvez pas utiliser d’interface et que vous devez rechercher des classes / méthodes respectant une certaine convention de nommage et acceptant certains paramètres.

Lorsque l’on fait de la .NET Reflection, on utilise des méthodes comme Assembly.LoadFrom / Assembly.Load pour obtenir un Assembly, et des instances de Assembly, Type et MethodInfo pour obtenir de l’information dynamique sur les classes et leurs méthodes.

Ces classes n’héritent pas de MarshalByRefObject et ne peuvent donc pas être utilisées en Remoting. Dès que vous tenterez d’obtenir une instance à ces types dans votre AppDomain principal afin d’invoker une méthode par Reflection, cela aura pour effet de charger l’Assembly dans l’AppDomain. Et c’est ce qu’on tente d’éviter!

La solution? Il faut utiliser le design pattern Proxy et se créer des classes Proxy sur les classes Type, Assembly et MethodInfo.

Un peu comme Interfaces.dll était partagé par les deux AppDomain, ReflectionProxy.dll sera partagé lui aussi:

image

Dans le AppDomain principal, il faudrait se créer un nouveau AppDomain et instancier toutes les classes au sein de celui-ci afin d’éviter de charger les Assembly dans le AppDomain principal.

La classe AppDomainProxy va se charger de créer un nouvel AppDomain et va permettre d’obtenir une instance de la classe AssemblyProxy (GetAssemblyProxy) et de créer n’importe quelle autre classe (CreateInstance) à partir du nom complet de l’assembly et de la classe.

Elle utilise la classe AssemblyResolver qui permet de spécifier des répertoires où rechercher les Assemblies si ceux-ci ne sont pas dans le repertoire de l’application ou le GAC.

Il est possible d’effectuer de la Reflection à partir de la classe AssemblyProxy. Par exemple, la méthode FindClassInAssemblies permet de rechercher une classe portant certain nom dans une liste d’Assemblies. Cette méthode retourne un TypeProxy qui permet de rechercher des méthodes via la méthode FindMethod. TypeProxy pourrait être étendue pour supporter les propriétés, selon vos besoins.

L’instance de MethodInfo retournée permet d’invoker des méthodes statiques ou d’instance via Invoke ou InvokeStatic. Pour utiliser Invoke, il faudra obtenir une instance de la classe en passant par AppDomainProxy.CreateInstance.

Voici un extrait de mon exemple complet:

try
{
  using (AppDomainProxy pAppDomainProxy = new AppDomainProxy())
  {
    AssemblyProxy pAssemblyProxy = pAppDomainProxy.GetAssemblyProxy();
    TypeProxy pTypeProxy = pAssemblyProxy.FindClassInAssemblies(new string[] { cboAssemblyName.Text }, cboClassName.Text);

    if (pTypeProxy != null)
    {
      MethodInfoProxy pMethodInfoProxy = pTypeProxy.FindMethod("GetValue", new string[] { "System.String" });

      if (pMethodInfoProxy != null)
      {
        object pInstance = pAppDomainProxy.CreateInstance(cboAssemblyName.Text, cboClassName.Text);
        object pResult = pMethodInfoProxy.Invoke(pInstance, new object[] { txtParameter.Text });
        txtOutput.Text = Convert.ToString(pResult);
        MessageBox.Show("Now is the time to look both AppDomain before it gets deleted!");
      }
    }
  }
}
catch (Exception pException)
{
  MessageBox.Show(pException.Message);
}

Dans cet exemple, cboAssemblyName.Text contiendrait par exemple PluginA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null et cboClassName.Text vaudrait PluginA.ClassA.

Si si on exécute mon programme d’exemple:

image

Avant d’utiliser le bouton Execute, on voit qu’il y aura un seul AppDomain principal et que celui-ci ne chargera aucun plugin:

image

Puis, en cliquant sur le bouton, une instance de la classe est créée et une méthode est appelée. Avant de fermer la boîte de dialogue:

image

On va rafraîchir l’Application Domain Viewer et confirmer qu’un nouveau AppDomain a été créé et qu’il a chargé le plugin:

image

En fermant la boîte de dialgue, cet AppDomain temporaire sera détruit:

image

Et on voit que ReflectionProxy reste dans le AppDomain principal.

La meilleure manière de comprendre ce qui se passe est d’utiliser mon exemple complet publié sur GitHub.

Références

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

AppDomainProxyReflection (Zip)
Téléchargez le fichier Zip

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

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s