Gestion du cycle de vie des DLL via AppDomain en C#

Dans le développement d'applications .NET, notamment pour des systèmes nécessitant une mise à jour à chaud (Hot Update) comme Unity ou des serveurs longue durée, la gestion dynamique des dépendances est cruciale. Comme le CLR ne permet pas de décharger une DLL individuellement d'un domaine d'application, la solution consiste à isoler ces bibliothèques dans un AppDomain séparé que l'on peut détruire intégralement.

1. Architecture de la solution

Pour implémenter cette isolation, nous divisons le projet en trois composants distincts :

  • CoreInterfaces : Une bibliothèque de classes contenant les contrats (interfaces) partagés.
  • PluginModule : Une bibliothèque implémentant la logique métier, destinée à être chargée/déchargée.
  • HostRunner : L'application principale qui orchestre les domaines d'application.

2. Définition du contrat partagé

L'interface doit être connue à la fois par l'hôte et par le module chargé. Elle sert de pont de communication.

using System;

namespace CoreInterfaces
{
    public interface IComputationTask
    {
        float Execute(float x, float y);
    }
}

3. Implémentation du module dynamiqeu

Le point critique ici est l'héritage de MarshalByRefObject. Cela permet à l'objet de franchir les frontières du domaine d'application par référence plutôt que par valeur.

using System;
using CoreInterfaces;

namespace PluginLibrary
{
    public class AdditionTask : MarshalByRefObject, IComputationTask
    {
        public float Execute(float x, float y)
        {
            float sum = x + y;
            Console.WriteLine($"[Domaine: {AppDomain.CurrentDomain.FriendlyName}] Calcul : {x} + {y} = {sum}");
            return sum;
        }
    }
}

4. Orchestration dans l'application hôte

Le code suivant illustre comment créer un domaine, instancier l'objet via un proxy, et libérer les ressources pour permettre, par exemple, la suppression ou le remplacement du fichier DLL sur le disque.

using System;
using System.IO;
using CoreInterfaces;

namespace HostRunner
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Hôte démarré dans : {AppDomain.CurrentDomain.FriendlyName}");

            // Paramètres de test
            float valA = 12.5f;
            float valB = 7.5f;

            // 1. Création d'un domaine d'application isolé
            AppDomain taskDomain = AppDomain.CreateDomain("Isolator_Domain");

            try
            {
                // 2. Instanciation via Proxy
                // Note : "PluginLibrary" est le nom de l'assembly, "PluginLibrary.AdditionTask" est le type complet
                IComputationTask proxy = (IComputationTask)taskDomain.CreateInstanceAndUnwrap(
                    "PluginLibrary", 
                    "PluginLibrary.AdditionTask"
                );

                float result = proxy.Execute(valA, valB);
                Console.WriteLine($"Résultat obtenu par l'hôte : {result}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Erreur : {ex.Message}");
            }

            // 3. Gestion des commandes utilisateur
            bool running = true;
            while (running)
            {
                Console.WriteLine("\nCommandes : [u] Décharger le domaine, [d] Supprimer DLL, [q] Quitter");
                string input = Console.ReadLine()?.ToLower();

                switch (input)
                {
                    case "u":
                        if (taskDomain != null)
                        {
                            string name = taskDomain.FriendlyName;
                            AppDomain.Unload(taskDomain);
                            taskDomain = null;
                            Console.WriteLine($"Domaine {name} déchargé avec succès.");
                        }
                        break;

                    case "d":
                        string dllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PluginLibrary.dll");
                        try
                        {
                            File.Delete(dllPath);
                            Console.WriteLine("Fichier DLL supprimé du disque.");
                        }
                        catch (IOException)
                        {
                            Console.WriteLine("Impossible de supprimer la DLL : le fichier est toujours verrouillé (déchargez le domaine d'abord).");
                        }
                        break;

                    case "q":
                        running = false;
                        break;
                }
            }
        }
    }
}

Points clés à retenir

  • Isolatoin : Un AppDomain agit comme un conteneur léger. En déchargeant le domaine, le CLR libère toute la mémoire associée, y compris les types chargés.
  • Proxying : L'interface IComputationTask permet à l'hôte de manipuler l'objet distant sans avoir besoin de charger l'implémentation concrète dans le domaine principle.
  • Verrouillage de fichier : Tant que le domaine secondaire est actif, la DLL est verrouillée par le système de fichiers. L'appel à AppDomain.Unload est impératif pour libérer ce verrou.

Étiquettes: C-Sharp AppDomain Hot-Update DLL-Loading Reflection

Publié le 4 août à 09h26