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

Historiquement, les données des applications dans les systèmes dit "monotâche", disposaient de la totalité de la mémoire et des ressources de l'ordinateur pour s'exécuter. Or, avec l'arrivée des systèmes dits "multitâche" cette situation devint dangereuse pour le système et surtout pour les applications résidant en mémoire. En effet, si chaque application se considère comme 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 apparue.

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 de la mémoire qui sont et qui peuvent être utilisées par le processus. En conséquence, 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éalise la transformation des adresses au moment de l'exé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 est introduite : les domaines d'application. Les domaines d'application fournissent le même niveau d'isolation que les processus mais à un coût de performance bien moindre. Les domaines d'application sont contrôlés par l'interpréteur en temps réel et en accord avec les règles de sécurité de la machine cible.

II. Les AppDomain : Pourquoi et comment ?

II-A. Pourquoi ?

II-A-1. La sécurité

De part la séparation des données, les domaines d'application amènent 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 pourra voir de données autres que celles que vous lui fournissez. Ainsi, par exemple dans le cas de plugins, l'utilisation des domaines d'application vous assure que les ceux-ci ne toucheront pas à d'autres données que celles fournies.

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

L'utilisation des domaines d'application peut dans une certaine mesure améliorer les performances de votre application. En effet, toujours dans le cas de plugins, si vous en chargez 200 pour un traitement spontané. Une fois ce traitement terminé, ceux-ci restent en mémoire tant que l'application n'est pas arrêtée. Par contre, si vous utilisez un domaine d'application pour les charger, vous pouvez à la fin du traitement les décharger et ainsi libérer la mémoire utilisée par les plugins.

II-B. Comment ?

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

Les domaines d'application ne sont pas tant complexe d'utilisation que peut paraître une lecture de la documentation officielle. Nous nous attacherons à expliquer le parcours de mise en oeuvre des 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ées au domaine principal.
- 2°) Vous chargez par l'intermédiaire de l'objet de chargement, les assemblies que vous souhaitez.
- 3°) Enfin, si besoin est, par l'intermédiaire de la classe de chargement, vous récupérez 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 de "Remoting".
Qu'est ce que le "Remoting" ?

C'est tout simplement le fait d'appeler des objets situés sur une autre machine ou dans un autre processus comme si c'était un objet local.
Pour faire cela, il faut définir des règles et utiliser un proxi qui va se charger de faire la liaison entre votre appel local et l'objet distant.
Les règles à définir, concernent toutes l'objet.
Pour passer des objets entre domaines d'application vous avez deux possiblités :
- Vous les passez par référence, grâce à MarshalByRefObject, c'est à dire que le domaine qui "reçoit" l'objet reçoit en fait une référence sur cet objet. Toute modification effectuée dans un domaine et repercutée dans l'autre puisque c'est le même objet dans les deux domaines.
- Vous les passez par copie, grâce à [Serializable], c'est à dire que l'objet est copié intégralement et que les deux domaines ne travaillent donc pas sur le même objet. Si des modifications sont effectuées sur l'objet dans un domaine, elles ne sont bien evidemment pas répercutées dans l'objet de l'autre domaine d'application.

Nous nous arrêterons à ces notions de base pour ce qui concerne le "Remoting".


Voyons maintenant comment implémenter ces 3 étapes. Pour ce faire, ecrivons:

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

 
Sélectionnez

interface
uses
  System.Collections, System.Reflection;
type
 // La classe Loader sert à charger les assemblies dans un domaine d'application particulier.
 // Elle garde également une trace des assemblies chargée.
 Loader = class(MarshalByRefObject)
  // Declarer un tableau dynamique pour toutes les assemblies à charger
  strict private
   var assemblies : ArrayList;
  public
   constructor Create;
   procedure Load(varassembly : string);
   function CreateInstanceOf(index : Integer; typeName : string):TObject;
  end;
 
implementation
  constructor Loader.Create;
  begin
    inherited Create;
    self.assemblies := ArrayList.Create;
  end;
  procedure Loader.Load(varassembly : string);
  begin
    //Charger l'assembly demandée et Ajouter à la collection.
    self.assemblies.Add(Assembly.Load(varassembly))
  end;
  function Loader.CreateInstanceOf(index : Integer; typeName : string):TObject;
  var ass : Assembly;
  begin
   ass := (self.assemblies[index]) as Assembly;
    //Fournir une instance de l'objet demandé .
    Result := ass.CreateInstance(typeName, True);
  end;
end.

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 signifie que l'on souhaite 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.

Le constructeur ne présente aucun intérêt particulier. Par contre, la méthode Load va effectué proprement dit le chargement d'une assembly . La classe Assembly définie dans System.reflection nous permet de le faire très simplement. Il suffit de donner le nom de l'assembly que l'on veux charger. Toutefois, attention 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 du 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 où s'exécute l'objet Loader.
La classe, dont on crée l'objet, doit être marquée 'sérializable' ou étendre MarshalByRefObject, sinon une exception sera levée. Pourquoi ? Tout simplement parce que .NET a besoin de savoir comment effectuer le transfert de l'objet.

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

 
Sélectionnez

var setup : AppDomainSetup;
begin
 setup := AppDomain.CurrentDomain.SetupInformation;
 setup.PrivateBinPath := path;
 setup.ShadowCopyFiles := 'false';
 self.pluginsDomain := AppDomain.CreateDomain('pluginsDomain', nil, setup);
  self.loader := (self.pluginsDomain.CreateInstanceFromAndUnwrap('AssemblyLoader.dll', 'AssemblyLoader.Loader')) as Loader;
end;

Ce code peut être écrit 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 sont :
- ApplicationBase : Contient le chemin du répertoire où se trouve l'assembly principale.
- PrivateBinPath : Contient la liste des répertoires où 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 travaille dessus.

ShadowCopyFiles n'est pas comme on pourrait s'y 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 static : AppDomain.CreateDomain(). Cette méthode possède plusieurs surcharges, la plus interressante prend en argument un nom pour le domaine créé, une Evidence (un ensemble de règle de sécurité) , et un AppDomainSetup .
Après avoir créé le domaine, il faut charger l'assembly de chargement. 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 renvoie. Seule restriction à cette méthode, le type créé doit posséder un constructeur sans argument.


- 3°) rien ;-)
En effet à ce stade, un simple appel à notre fonction Load 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 restent cependant théoriques. Les cas d'utilisation decrits ci-dessous devraient mieux vous éclairer.
Avant de passer aux exemples d'utilisation je fais 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 static 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 du domaine que vous allez créer.

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

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

II-B-2-d. MonEspace.Loader

Cette classe est utilisée 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 vos données en mémoire, à l'abri de traitement incontrôlé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 disposent d'aucune forme de signature permettant d'en assurer la provenance.
Voyons tout de suite le code de chargement des plugins (MainForm.cs):

 
Sélectionnez

procedure TWinForm.LoadPlugins;
 var dir : DirectoryInfo;
 var i : Integer;
 var iplug : IPlugins;
 var menuI : MenuItem;
 var path : string;
 var setup : AppDomainSetup;
 var vartype : string;
 var varfile: FileInfo;
 begin
  path := Application.StartupPath + '/Plugins/';
 
  {$REGION 'Création du domaine d''application et chargement de la librairie de chargement '}
  setup := AppDomain.CurrentDomain.SetupInformation;
  setup.PrivateBinPath := path;
  setup.ShadowCopyFiles := 'false';
  self.pluginsDomain := AppDomain.CreateDomain('pluginsDomain', nil, setup);
  self.loader := (self.pluginsDomain.CreateInstanceFromAndUnwrap('AssemblyLoader.dll', 'AssemblyLoader.Loader')) as Loader;
  {$ENDREGION}
  dir := DirectoryInfo.Create(path);
  i := 0;
  for varfile in dir.GetFiles('*.dll') do
    begin
      vartype := varfile.Name.Replace(varfile.Extension, '');
      self.loader.Load(vartype);
      iplug := self.loader.CreateInstanceOf(i, vartype + '.' + vartype) as IPlugins;
      menuI := MenuItem.Create(iplug.Text);
      Include(menuI.Click,self.MenuItemClick);
      if (iplug.IndexMenuItem = 0) then
        begin
          self.menuItem1.MenuItems.Add(menuI);
        end
      else
        begin
          self.menuItem2.MenuItems.Add(menuI);
        end;
      self.plugins.Add(menuI, iplug);
      Math.Min(Interlocked.Increment(i), i - 1);
    end;
 end;
procedure TWinForm.MenuItemClick(o : System.Object; e : System.EventArgs);
 begin
  IPlugins(self.plugins[o]).Traitement(self.data);
  //Pour l'actualisation des données dans le propertyDataGrid
  self.propertyGrid1.SelectedObject := self.data
 end;
procedure TWinForm.MainFormClosing(sender: System.Object; e: System.ComponentModel.CancelEventArgs);
begin
  self.loader := nil;
  self.plugins := nil;
  AppDomain.Unload(self.pluginsDomain);
end; 

Cette fonction est appellée par l'évènement OnLoad du formulaire. Elle se charge de créer le domaine d'application et d'y charger tous les plugins présents dans le répertoire Plugins. Elle crée ensuite le menuItem correspondant et l'ajoute au menu en fonction de la valeur définie par l'interface.

Voyons maintenant le code de l'interface des plugins :

 
Sélectionnez

unit appDomainSecureData;
 
interface
uses
   datasecure;
type
   IPlugins = interface
     function  get_IndexMenuItem : Integer;
     function  get_Text : string;
     function Traitement(data : VeryImportanteData) : boolean;
     property  IndexMenuItem : Integer read get_IndexMenuItem;
     property  Text : string read get_Text;
   end;
implementation
end. 

Là aussi rien de compliqué.
Ensuite le code de la classe de données :

 
Sélectionnez

unit  datasecure;
 
interface
type
  [Serializable]
  VeryImportanteData = class
  strict private
    var varname : string;
    var varfirstName : string;
    var vartel : string;
  public
    function get_Name : string;
    function get_FirstName : string;
    function get_Tel : string;
    procedure set_Name(value : string);
    procedure set_FirstName(value : string);
    procedure set_Tel(value : string);
    constructor Create;
    property Name : string read get_Name write set_Name;
    property FirstName : string read get_FirstName write set_FirstName;
    property Tel : string read get_Tel write set_Tel;
  end;
 
implementation
 constructor VeryImportanteData.Create;
 begin
  inherited Create()
 end;
 function VeryImportanteData.get_Name : string;
 begin
      Result := self.varname
 end;
 function VeryImportanteData.get_FirstName : string;
 begin
      Result := self.varfirstName
 end;
 function VeryImportanteData.get_Tel : string;
 begin
      Result := self.vartel
 end;
 procedure VeryImportanteData.set_Name(value: string);
 begin
      self.varname := value
 end;
 procedure VeryImportanteData.set_FirstName(value: string);
 begin
      self.varfirstName := value
 end;
 procedure VeryImportanteData.set_Tel(value: string);
 begin
      self.vartel := value
 end;
end. 

La seule chose importante dans cette classe est l'instruction [Serializable] qui défini explicitement que le transfert de l'objet dans un autre domaine d'application se fera par copie.

Voyons enfin le code du (méchant) plugins :

 
Sélectionnez

unit appPluginsInconnu;
 
interface
uses
  appDomainSecureData, dataSecure;
type
  PluginsInconnu =  Class(MarshalByRefObject, IPlugins)
  public
    constructor Create;
    function get_IndexMenuItem : Integer;
    function get_Text : string;
    function Traitement(data : VeryImportanteData) : Boolean;
    property IndexMenuItem:Integer read get_IndexMenuItem;
    property Text:string read get_Text;
  end;
 
implementation
constructor PluginsInconnu.Create;
begin
  inherited Create;
end;
{$REGION ' appDomainSecureData.IPlugins interface implementation '}
function PluginsInconnu.get_IndexMenuItem : Integer;
begin
   Result := 1
end;
function PluginsInconnu.get_Text : string;
begin
   Result := 'PluginsInconnu'
end;
function PluginsInconnu.Traitement(data : VeryImportanteData) : Boolean;
begin
  data.Name := 'Je fais n''importe quoi avec le nom';
  Console.WriteLine('Traitement effectué');
  Result := true;
end;
{$ENDREGION}
end. 

Le plugins est marqué MarshalByRefObject, car le domaine principal communique avec lui, il faut donc indiquer au framework .Net que nous voulons travailler sur la référence de l'objet et non pas sur sa copie. Voyons maintenant le résultat de l'exécution de ce premier programme utilisant les domaines d'application :

Informations remplies avant l'action du plugins
Informations remplies avant l'action du plugins
Le plugins a fait son traitement
Le plugins a fait son traitement
Les données sont toujours les même bien que le plugins les ai apparament modifiées
Les données sont toujours les même bien que le plugins les ai apparament modifiées

Explications : Bien que vous ayez l'impression de passer l'objet data par référence (comme vous le faites d'habitude avec les objets), celui-ci est copié par .NET et c'est la copie qui est envoyé au plugins. C'est donc cette copie que le plugins modifient et non l'objet original ce qui fait que les informations restent les mêmes après exécution du plugins.
Si vous voulez vous amuser un peu vous pouvez marquer la classe VeryImportanteData comme héritant de MarshalByRefObject à la place de [Serializable] et constater les dégâts.

On réentre les informations
On réentre les informations
Les informations ont été modifiées par le plugins
Les informations ont été modifiées par le plugins

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

Dans cette partie, nous revenons sur l'article de Sébastien Curutchet sur le développement d'une application Winform de façon modulaire. (Disponible ici ).
Un problème soulevé par cet article est qu'une fois les plugins chargés, on ne peut les décharger sans fermer l'application.
Nous allons donc voir comment palier à ce problème grâce aux domaines d'application.

Tout d'abord il faut savoir que toute la base reste la même. Ainsi nous ne toucherons pas à l'interface défini et nous ne ferons qu'une petite modification du plugin exemple.
Le gros de la modification sera donc dans la façon de charger les plugins dans notre application.

Voyons le code ainsi modifié :

Code du plugins :

 
Sélectionnez

type
  PluginClass = class(System.Windows.Forms.UserControl, IPlugin)
  //Nous ne modifions rien au reste de la classe ... 

Code de la classe principale :

 
Sélectionnez

//...
uses
 //...
  datasecure;
type
  frmMain = class(System.Windows.Forms.Form)
  strict private
     Components: System.ComponentModel.Container;
     tabControl1 : TabControl;
     procedure InitializeComponent;
     var pluginsDomain: AppDomain;
     procedure UnLoadAppDomain;
     //...
  public
    constructor Create;
  end;
//...
constructor frmMain.Create;
var loader : appLoader.Loader;
var obj : TObject;
var setup : AppDomainSetup;
var tp : TabPage;
begin
  inherited Create;
  InitializeComponent;
  setup := AppDomain.CurrentDomain.SetupInformation;
  setup.ShadowCopyFiles := 'false';
  //Creer un nouveau domaine d'application.
  self.pluginsDomain := AppDomain.CreateDomain('PluginsDomain', nil, setup);
  // Créer une instance du plugin dans le domaine principal
  // CreateInstanceFromAndUnWrap retourne un objet de type object.
  loader := (pluginsDomain.CreateInstanceFromAndUnwrap('AssemblyLoader.dll', 'AssemblyLoader.Loader')) as Loader;
  loader.Load('Plugin1.dll');
  obj := loader.CreateInstanceOf(0,'PluginApp.PluginClass');
  //Exécuter la fonction de traitement..
  MessageBox.Show('Chargement de ' + IPlugin(obj).Traitement);
  //Instancier un tabPage ayant comme titre "Ma page".
  tp := TabPage.Create('Ma page');
  //Ajout au tabPage d'un composant visuel du plugin
  // obj étant de type object, le transTypage en IPlugin est nécessaire.
  tp.Controls.Add(IPlugin(obj).VisualComponent);
  // Ajout du tabPage au TabControl.
  tabControl1.TabPages.Add(tp);
end;
 
procedure  frmMain.UnLoadAppDomain;
begin
    AppDomain.Unload(self.pluginsDomain);
end; 

Voila, avec ces quelques modifications vous avez un système qui vous permez de décharger les plugins quand vous le souhaitez. Une petite restriction tout de même : Veillez à correctement supprimer toutes les instances des classes définies dans les plugins avant de décharger le domaine d'application.

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 besoin d'aucune intervention de l'utilisateur pour se mettre à jour et cela sans perte de données. En effet, si au moment de la mise à jour, l'utilisateur est justement en train 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 que l'on connait la technique, reste encore d'utiliser 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 domaine 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

[STAThread]
procedure Main(args : array of string);
 //Declarer un objet de données pour les tests de transfert entre les deux versions de l'application
 var data : DataObject;
 //Declarer un setup de domaine
 var setup : AppDomainSetup;
 var loader : Loader;
 begin
  data := DataObject.Create('text1', 4 as TObject);
  //Initialiser setup à partir du domaine courant car les assemblies sont dans le même répertoire
  setup := AppDomain.CurrentDomain.SetupInformation;
  //Interdire les copies fantômes des assemblies.
  setup.ShadowCopyFiles := 'false';
  //Creer le nouveau domaine d'application qui va charger la form.
  MainClass.formDomain := AppDomain.CreateDomain('formDomain', nil, setup);
  //Instancier un loader dans le nouveau domaine d'application.
  loader := (MainClass.formDomain.CreateInstanceFromAndUnwrap('AssemblyLoader.dll','AssemblyLoader.Loader')) as Loader;
  //Executer le chargement dans le domaine d'application de formDomain.
  loader.Load('appDomainAutoPatchable');
  //Recuperer une référence sur une instance de IForm a travers les appDomains
  iform := (loader.CreateInstanceOf(0, 'appDomainAutoPatchable.MainForm')) as IForm;
  //Passer l'objet data.
  iform.Data := data;
  //Lancer la form dans l'autre appDomain.
  iform.Run;
  //Décharger l'appDomain formDomain.
  AppDomain.Unload(MainClass.formDomain);
  //Mettre à jour la librairie contenant la form.
  MainClass.Update;
  //Recréer le domaine d'application.
  MainClass.formDomain := AppDomain.CreateDomain('formDomain', nil, setup);
  loader := (MainClass.formDomain.CreateInstanceFromAndUnwrap('AssemblyLoader.dll', 'AssemblyLoader.Loader')) as Loader;
  loader.Load('appDomainAutoPatchable');
  iform := (loader.CreateInstanceOf(0, 'appDomainAutoPatchable.MainForm')) as IForm;
  iform.Data := data;
  iform.Run;
  //La form à été mise à jour sans perte de données.
end; 

Ce code est le plus complexe de l'application.
Revenons point par point sur les morceaux un peu complexe de ce code.
Il faut bien comprendre que ce type d'application doit être pensé dès le départ. En effet, cette approche implique que votre application effectue clairement la distinction entre les données et leurs traitements. De plus, il faut tenir compte des contraintes, comme le fait d'avoir des assemblies séparées pour le chargeur, le traitement et le "main".
Sur le plan du code, il n'y a rien de nouveau concernant les domaines d'applications, il faut juste remarquer que l'on utilise une interface pour accéder à la form. Pourquoi ? Tout simplement pour ne pas lier l'assembly chargée dans le nouveau domaine et l'assembly "Main". En effet, si vous définissez un objet d'un type contenu dans l'assembly à charger, vous serez alors obligé de référencer cette dernière dans l'assembly "Main". Or, pour des questions de versionning, il est préférable d'éviter cela car vous ne seriez plus en mesure de lancer votre assembly "Main".

Le versionning n'est pas l'objet de cet article, cependant sachez que le framework .NET considère comme compatible des librairies dont les deux premiers nombres sont identiques. Le numéro de build (le troisième) n'est là que pour distinguer les versions compatible entre elles. Ainsi, la version 1.0.3201 et la version 1.0.9023 sont compatibles mais pas les versions 1.0.3985 et 1.1.4858

La mise à jour est faite par la méthode Update. Elle est une simple copie de librairie du répertoire update vers le répertoire de l'application.
Après la mise à jour, on recrée le domaine d'application et on recharge l'assembly. Une fois qu'une référence est récupérée, on lui rend les données afin de poursuivre le traitement.

IV. Bibliographie et remerciements

IV-A. Bibliographie

IV-B. Remerciements

Merci à neguib pour les versions VB.NET et Deplhi.NET et la (grosse) relecture.

IV-C. Téléchargement