14 votes

Obtenir les attributs d'un assemblage personnalisé sans le charger dans l'AppDomain actuel

J'ai créé une petite application pour charger récursivement des assemblages dans un répertoire fourni et lire leur collection d'attributs personnalisés. Principalement pour lire l'attribut DebuggableAttribute afin de déterminer les paramètres de IsJITTrackingEnabled et IsJITOptimizerDisabled pour déterminer si l'assemblage est optimisé pour la version.

Mon code actuel utilise Assembly.LoadFrom pour transmettre le chemin complet vers l'assemblage et le charger. Il effectue ensuite un GetCustomAttributes sur l'assemblage pour obtenir l'attribut à déboguer. Le problème est que chaque assembly est chargé dans le domaine d'application actuel. Ainsi, si un autre dossier utilise le même assemblage, il utilise simplement la référence chargée à l'origine. J'aimerais pouvoir charger l'assemblage, lire les propriétés dont j'ai besoin, puis le décharger. J'ai essayé de créer un nouveau domaine d'application, d'y charger les assemblages et de les décharger par la suite, mais sans succès.

Je sais que cela doit être possible, mais je suis perdue. Toute aide serait grandement appréciée. Je serais heureux de vous fournir toute autre information dont vous pourriez avoir besoin.

20voto

Michael Edenfield Points 15298

La réponse courte est, non, il n'y a aucun moyen de faire ce que vous demandez.

La réponse la plus longue est la suivante : Il existe une méthode spéciale de chargement des assemblages, Assembly.ReflectionOnlyLoad() qui utilise un contexte de chargement "reflection-only". Cela vous permet de charger des assemblages qui ne peuvent pas être exécutés, mais dont les métadonnées peuvent être lues.

Dans votre cas (et, apparemment, dans tous les cas d'utilisation que je pourrais moi-même imaginer), ce n'est pas vraiment utile. Vous ne pouvez pas obtenir d'attributs typés à partir de ce type d'assemblage, seulement CustomAttributeData . Cette classe ne permet pas de filtrer un attribut spécifique (le mieux que j'ai pu trouver est de le transformer en chaîne de caractères et d'utiliser la fonction StartsWith("[System.Diagnostics.Debuggable");

Pire encore, un chargement en réflexion seule ne charge aucun assemblage de dépendance, mais cela vous oblige à le faire manuellement . Cela est objectivement pire que ce que vous faites maintenant ; au moins, le chargement des dépendances est automatique.

(De plus, ma réponse précédente faisait référence au MEF ; je me suis trompé, il semble que le MEF inclut toute une tonne de code de réflexion personnalisé pour que cela fonctionne).

En définitive, vous ne pouvez pas décharger un assemblage une fois qu'il a été chargé. Vous devez décharger le tout le domaine des applications comme décrit dans cet article de MSDN.

UPDATE :

Comme indiqué dans les commentaires, j'ai pu obtenir les informations sur les attributs dont vous aviez besoin par le biais du chargement par réflexion uniquement (et d'un chargement normal), mais l'absence de métadonnées d'attributs typés rend la tâche très difficile.

S'il est chargé dans un contexte d'assemblage normal, vous pouvez obtenir les informations dont vous avez besoin assez facilement :

var d = a.GetCustomAttributes(typeof(DebuggableAttribute), false) as DebuggableAttribute;
var tracking = d.IsJITTrackingEnabled;
var optimized = !d.IsJITOptimizerDisabled;

S'il est chargé dans un contexte de réflexion uniquement, vous devez faire un peu de travail ; vous devez déterminer la forme que le constructeur d'attributs a prise, savoir quelles sont les valeurs par défaut et combiner ces informations pour obtenir les valeurs finales de chaque propriété. Vous obtenez les informations dont vous avez besoin comme ceci :

var d2 = a.GetCustomAttributesData()
         .SingleOrDefault(x => x.ToString()
                                .StartsWith("[System.Diagnostics.DebuggableAttribute"));

A partir de là, vous devez vérifier le ConstructorArguments pour voir quel constructeur a été appelé : celui-ci avec un seul argument ou celui-ci avec deux arguments. Vous pouvez alors utiliser les valeurs des paramètres appropriés pour déterminer les valeurs qu'auraient prises les deux propriétés qui vous intéressent :

if (d2.ConstructorArguments.Count == 1)
{
  var mode = d2.ConstructorArguments[0].Value as DebuggableAttribute.DebuggingModes;
  // Parse the modes enumeration and figure out the values.
}
else
{
  var tracking = (bool)d2.ConstructorArguments[0].Value;
  var optimized = !((bool)d2.ConstructorArguments[1].Value);
}

Enfin, vous devez vérifier si NamedArguments qui pourraient remplacer celles définies sur le constructeur, en utilisant par exemple :

var arg = NamedArguments.SingleOrDefault(x => x.MemberInfo.Name.Equals("IsJITOptimizerDisabled"));
var optimized = (arg == null || !((bool)arg.TypedValue.Value));

Enfin, si vous exécutez ce programme sous .NET 2.0 ou plus, et que vous ne l'avez pas encore vu, MSDN le signale dans la page d'accueil du site. DebuggingModes documentation :

Dans la version 2.0 de .NET Framework, les informations de suivi JIT sont toujours générées, et cet indicateur a le même effet que Default, à l'exception de la propriété IsJITTrackingEnabled qui est fausse, ce qui n'a aucune signification dans la version 2.0.

11voto

Preet Sangha Points 39414

Vous devez utiliser Assembly.ReflectionOnlyLoad .

Voici quelques Notes MSDN qui montre comment l'utiliser :

using System;
using System.IO;
using System.Reflection;

public class ReflectionOnlyLoadTest
{
    public ReflectionOnlyLoadTest(String rootAssembly) {
        m_rootAssembly = rootAssembly;
    }

    public static void Main(String[] args)
    {
        if (args.Length != 1) {
            Console.WriteLine("Usage: Test assemblyPath");
            return;
        }

        try {
            ReflectionOnlyLoadTest rolt = new ReflectionOnlyLoadTest(args[0]);
            rolt.Run();
        }

        catch (Exception e) {
            Console.WriteLine("Exception: {0}!!!", e.Message);
        }
    }

    internal void Run() {
        AppDomain curDomain = AppDomain.CurrentDomain;
        curDomain.ReflectionOnlyPreBindAssemblyResolve += new ResolveEventHandler(MyReflectionOnlyResolveEventHandler);
        Assembly asm = Assembly.ReflectionOnlyLoadFrom(m_rootAssembly);

        // force loading all the dependencies
        Type[] types = asm.GetTypes();

        // show reflection only assemblies in current appdomain
        Console.WriteLine("------------- Inspection Context --------------");
        foreach (Assembly a in curDomain.ReflectionOnlyGetAssemblies())
        {
            Console.WriteLine("Assembly Location: {0}", a.Location);
            Console.WriteLine("Assembly Name: {0}", a.FullName);
            Console.WriteLine();
        }
    }

    private Assembly MyReflectionOnlyResolveEventHandler(object sender, ResolveEventArgs args) {
        AssemblyName name = new AssemblyName(args.Name);
        String asmToCheck = Path.GetDirectoryName(m_rootAssembly) + "\\" + name.Name + ".dll";
        if (File.Exists(asmToCheck)) {
            return Assembly.ReflectionOnlyLoadFrom(asmToCheck);
        }
        return Assembly.ReflectionOnlyLoad(args.Name);
    }

    private String m_rootAssembly;
}

5voto

tyranid Points 7996

Il n'est pas possible de décharger un assemblage dans le domaine d'application actuel, c'est malheureusement la façon dont .NET est conçu pour fonctionner. C'est même le cas avec les chargements ReflectionOnly. Il y a aussi une petite difficulté à faire cela, car vous devez alors utiliser la fonction GetCustomAttributesData au lieu de la méthode normale GetCustomAttributes, car cette dernière doit exécuter du code dans le constructeur de l'attribut. Cela peut rendre la vie plus difficile.

Une alternative qui devrait fonctionner est d'utiliser Cecil qui vous permet d'inspecter l'assemblage sans le charger au sens normal du terme. Mais cela représente beaucoup de travail supplémentaire.

1voto

Jason Hernandez Points 1461

Je pense que Assembly.ReflectionOnlyLoad est ce que vous recherchez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X