I. Introduction historique à la séparation des données d'une application

Historiquement les données des applications, dans les systèmes monotache, disposait de la totalité de la mémoire et des ressources de l'ordinateur pour s'éxécuter. Or avec l'arrivée des systèmes multitâche cette situation devint dangereuse pour le système et surtout pour les applications résidant en mémoire. En effet si chaque aaplication se considère comme la seule dans le système, elle peut à n'importe quel moment décider d'écrire dans la zone mémoire d'une autre application. Pour palier à ce problème la notion de processus et de séparation des données est apparut.

I-A. Les processus

Un processus est l'image mémoire d'un programme. Cette image contient les instructions du programme ainsi que les informations sur les zones mémoire qui sont et qui peuvent être utilisées par le processus.
A partir de là, la zone mémoire pour le processus ne correspond pas forcément (et même jamais) à la zone mémoire physique réelle, le système d'exploitation réalisant la transformation des adresses au moment de l'éxécution.

I-B. La séparation des données dans le framework .NET

Dans le framework .NET la séparation des données par les processus est conservée mais une autre couche de séparation des données à été introduite : les domaines d'application. Les domaines d'application fournissent le même niveau d'isolation que les processus mais à un cout de performance bien moindre. Les domaines d'application sont contrôlé par l'interpréteur en temps réel en accord avec les règles de sécurités de la machine cible.

II. Les AppDomain : Pourquoi et comment ?

II-A. Pourquoi ?

II-A-1. La sécurité

Les domaines d'application de part la séparation des données amène une notion de sécurité supplémentaire. En effet si vous utilisez une librairie chargée dans un domaine d'application différent de l'application principale, vous êtes assuré que cette librairie ne pourras pas voir les données autre que celle que vous lui fournissait. Ainsi dans le cas de plugins, l'utilisation des domaine d'application vous permet d'être assuré que les plugins ne toucheront pas à autre chose que les données que vous lui fournissait.

II-A-2. Les performances et la mémoire

L'utilisation des dommaines d'application peut dans une certaine mesure améliorer les performances de votre application. En effet toujours dans le cas des plugins, si vous chargez 200 plugins pour un traitement spontané, une fois ce traitement fini ils restent en mémoire tant que l'application n'est pas stoppée. Si vous utilisez un domaine d'application pour charger ces plugins vous pouvez à la fin du traitement le décharger et ainsi libérer la mémoire utilisé par les plugins.

II-B. Comment

II-B-1. Mettre en oeuvre les domaines d'application

Les domaines d'application ne sont pas si compliqués à utiliser que peut le laisser croire une lecture de la documentation officielle. Nous allons ici nous attacher à expliquer le cheminement à faire pour mettre en oeuvre les domaines d'application.
Tout d'abord vous devez comprendre que l'utilisation des domaines d'application ne remet pas en cause tout ce que vous connaissez sur la programmation.

L'utilisation des domaines d'application se fait en 3 temps.
- 1°) Vous devez créer le nouveau domaine d'application et y charger une assembly de "chargement". Cela est necessaire car ainsi vous ne liez pas les assemblies chargée au domaine principal.
- 2°) Vous allez alors charger par l'intermediaire de la classe défini au dessus les assemblies que vous voulez.
- 3°) Enfin par l'intermediaire de la classe de chargement, et si besoin est, vous allez récupérer une instance d'un objet contenu dans une assembly chargée dans le nouveau domaine d'application.

L'utilisation des domaines d'application fait intervenir certaines notions du "Remoting". Toutefois ces notions restant très basiques et ponctuelle si vous ne connaissez pas le Remoting ce n'est pas un problème.


Voyons maintenant comment au niveau du code nous allons réaliser ces 3 étapes pour cela nous allons écrire :

- 1°) une classe dont le rôle est de charger les assemblies dans le nouveau domaine d'application.

 
Sélectionnez

 
// La classe Loader sert à charger les assemblies dans un domaine d'application particulier. Elle garde également une trace des assemblies chargée.
public class Loader : MarshalByRefObject
{
	//On déclare un tableau dynamique pour contenir toute les assemblies que l'on va charger
	ArrayList assemblies;
 
	public Loader()
	{
		this.assemblies = new ArrayList();
	}
 
	public void Load(string assembly)
	{
		//On charge l'assembly demandée et on l'ajoute à la collection
		this.assemblies.Add(Assembly.Load(assembly));
	}
 
	public object CreateInstanceOf(int index, string typeName)
	{
		//On créer un instance de l'objet demandé et on le retourne
		return ((Assembly)assemblies[index]).CreateInstance(typeName,true);
 
	}
 
}

Voyons en détail le code de l'assembly de chargement et ses spécificités.

Commençons par l'explication de l'extension de la classe. En effet vous avez certainement remarqué que la classes Loader étend MarshalByRefObject.
Cette extension ne signifie rien d'autre que le fait que l'on veux passer cette classe par référence dans un autre domaine d'application. Nous ne redéfinissons aucune méthode et nous ne nous préoccuperons pas de savoir comment fonctionne cette classe.

Sachez seulement que pour passer des objets entre domaines d'application vous avez deux choix :
- Vous pouvez les passer par référence, grâce à MarshalByRefObject, c'est à dire que le domaine qui "reçoit" l'objet ne reçoit enfait qu'une référence sur cet objet. Toute modification effectué dans un domaine et reporté dans l'autre ce qui est normale car c'est le même objet dans les deux domaines.
- Vous pouvez les passer par copie, grâce à [Serializable], c'est à dire que l'objet est copié intégralement que les deux domainesne travail pas sur la même copie de l'objet. Si des modifications sont faite dans un domaine elles ne se répercuteront pas dans l'autre.


Le constructeur ne présente aucun intéret particulier, par contre la méthode Load va plus nous interresser. C'est dans cette méthode que le chargement proprement dit d'une assembly va être effectué. La classe Assembly défini dans System.reflection nous permet de faire cela très simplement. Il suffit de donner le nom de l'assembly que l'on veux charger. Toutefois attention à ne pas tomber dans le piège de cette fonction. En effet ce n'est pas le nom tel que nous le voyons dans le système de fichier mais bien le nom de l'assembly, qui pratiquement tout le temps est le nom visible à partir système de fichier sans l'extension.
L'appel à Assembly.Load() charge donc l'assembly cible en mémoire dans le domaine d'application courant.

La méthode CreateInstanceOf nous permet de demander la création d'une instance d'un type particulier dans le domaine ou s'éxécute l'objet Loader.
L'appel de cette méthode necessite que l'on connaisse le type que l'on veux créer. A propos du type il faut savoir que c'est le type complet qui est demandé. C'est à dire que, par exemple, Assembly devient System.Reflection.Assembly.
La classe dont on créer l'objet doit être marquée 'sérializable' ou étendre MarshalByRefObject ou vous aurez une belle exception.
Pourquoi ? Tout simplement car l'objet va être transféré d'un domaine à l'autre et que .NET doit savoir comment faire ce transfert.

- 2°) une classe qui va charger l'assembly de chargement dans le domaine qu'elle va créer.

 
Sélectionnez

AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.PrivateBinPath = path;
setup.ShadowCopyFiles = "false";
 
this.pluginsDomain = AppDomain.CreateDomain("pluginsDomain",null,setup);
 
this.loader = (Loader)this.pluginsDomain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.Loader");

Ce code peut être mis dans n'importe quelle méthode.
Voyons comment il fonctionne :
Tout d'abord nous créons un objet de type AppDomainSetup. Cet objet défini les paramètres de notre nouveau domaine d'application. Les plus importants étant :
- ApplicationBase : Contient le chemin du répertoire ou se trouve l'assembly principale.
- PrivateBinPath : Contient la liste des répertoires ou doivent être cherché les assemblies privées. Ces répertoires sont combinés à ApplicationBase.
- ShadowCopyFiles : Obtient ou défini si les copies fantômes sont activées. Les copies fantômes sont des copies des assemblies utilisées dans un répertoire externe ce qui fait que l'on a l'impression de pouvoir modifier les assemblies alors que l'application travail dessus.

ShadowCopyFiles n'est pas comme on pourrait si attendre de type bool mais de type string

Une fois le setup créé et configuré nous pouvons créer à proprement parlé le domaine d'application. Cela est fait tout simplement grâce à la méthode statique AppDomain.CreateDomain(). Cette méthode possède plusieurs forme, la plus interressante étant celle présenté au dessus : Elle prend en argument un nom pour le domaine créé, une Evidence, en gros un ensemble de règle de sécurité, et un AppDomainSetup.
Après avoir créé le domaine il faut charger l'assembly de chargement dedans. Cela est fait grâce à la méthode CreateInstanceFromAndUnwrap. Cette méthode charge l'assembly qu'elle prend en argument et créer un objet du type du second argument qu'elle renvois. La seule restriction de cette méthode est que le type qui est créé doit posséder un constructeur sans argument.


- 3°) rien ;-)
En effet à ce stade un simple appel à notre fonction Load défini nous permet de charger une assembly dans le domaine nouvellement créé.

Je vous ai présenté ici les grands principes de la mise en oeuvre des domaines d'application. Ces explications sont assez théorique mais les cas d'utilisation au dessus devrait finir de vous éclairer.
Avant de passer aux exemples d'utilisation je vais faire un récapitulatif des classes utilisées pour la mise en oeuvre des domaines d'application.

II-B-2. Les classes utilisées

II-B-2-a. System.AppDomain

Cette classe est la classe de base des domaines d'application. Elle vous permet grâce à ses méthodes statiques de créer et décharger un domaine d'application. Elle permet également d'obtenir toutes les informations sur un domaine d'application.

II-B-2-b. System.AppDomainSetup

Cette classe vous permez de déterminer la configuration de du domaine que vous allez créer.

II-B-2-c. System.Reflection.Assembly

Cette classe est utilisé pour le chargement des assemblies dans le nouveau domaine d'application.

II-B-2-d. MonEspace.Loader

Cette classe est utilisé comme chargeur d'assembly dans le nouveau domaine. Elle doit être MarshalByRefObject et communique entre les domaines d'application.

III. Cas concret d'utilisation des domaines d'application

III-A. Cas de la sécurisation des données

Dans cette partie nous allons voir comment les domaines d'application peuvent vous aider à mettre à l'abri vos données en mémoire de traitement incontrolés.
Nous partirons d'une application qui manipule un certain nombre de données et qui est facilement extensible grâce à des plugins. Les plugins sont chargés au démarrage de l'application et ne dispose d'aucune forme de signature permettant d'en assurer la provenance.
Voyons tout de suite le code du formulaire principal :

III-B. Retour sur l'article de la création d'une application modulaire et amélioration de celui-ci

III-C. Application autopatchable

Dans cette partie nous allons voir comment créer une application autopatchable. Une application autopatchable est une application qui n'a besion d'aucune intervention de l'utilisateur pour ce mettre à jour et cela sans perte de données. En effet si au moment de la mise à jour l'utilisateur est entrain de travailler sur l'application, il ne doit rien perdre de son travail. Ce comportement peut être atteint par différent moyens mais le plus simple, une fois qu l'on connait la technique, reste encore de passer par les domaines d'application.

Cette application se compose de 3 modules distincts.
- Le premier, MainAssembly, est le coeur de l'application. C'est cette assembly qui contient le main et qui s'occupe de charger et décharger les domaines d'application. Elle contient également le code des classes de "données".
- Le deuxième, AssemblyLoader, est le module de chargement des assemblies dans le domain d'application.
- Le troisième, appDomainAutoPatchable, est le module contenant le code du formulaire. C'est le module qui va être mis à jour par l'application.

Voyons le code du premier module :

 
Sélectionnez

public static void Main(string[] args)
{
	//On déclare un objet de données pour les tests de transfert entre les deux versions de l'application
	DataObject data = new DataObject("text1",4);
 
	//On créer un nouveau setup de domain à partir du domain courant car les assemblies sont dans le meme répertoire
	AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
	//On interdit les copies fantome des assemblies
	setup.ShadowCopyFiles = "false";
 
	//On creer le nouveau domain d'application qui va charger la form
	MainClass.formDomain = AppDomain.CreateDomain("formDomain",null, setup);
 
	//On cré une instance de Loader dans le nouveau domaine d'application
	Loader loader = (Loader)MainClass.formDomain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.Loader");
 
	//On exécute la fonction load dans le domain d'application de formDomain
	loader.Load("appDomainAutoPatchable");
 
	//On récupere une référence sur une instance de IForm a travers les appDomains
	IForm iform = (IForm)loader.CreateInstanceOf(0,"appDomainAutoPatchable.MainForm");
 
	//on passe l'objet data
	iform.Data = data;
 
	//On lance la form dans l'autre appDomain
	iform.Run();
 
	//Une fois la form fermée on décharge l'appDomain formDomain
	AppDomain.Unload(MainClass.formDomain);
 
 
	//On update la librairie contenant la form mise à jour
	MainClass.Update();
 
 
	//On recrer le domain d'application
	MainClass.formDomain = AppDomain.CreateDomain("formDomain",null, setup);
	loader = (Loader)MainClass.formDomain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.Loader");
 
	loader.Load("appDomainAutoPatchable");
 
	iform = (IForm)loader.CreateInstanceOf(0,"appDomainAutoPatchable.MainForm");
 
	iform.Data = data;
	iform.Run();
 
	//La form à été mise à jour sans perte de données.
}

Ce code est le plus complexe de l'application.
Revenons point par point sur les morceaux un peu complexe de ce code.