Lors de la création d'un outil de Benchmark pour un projet en cours, je me suis retrouvé devant un sérieux problème au sujet du chargement dynamique d'assemblies.
En effet, le fait de charger une Assembly via System.Reflection.Assembly.LoadFile("myAssembly.dll"); permet de manipuler les types de cette assemblies, mais ne la charge pas dans l'AppDomain de façon à ce qu'elle soit utilisable par d'autres assemblies chargées dynamiquement.
Voici un petit code d'exemple (qui ne fonctionne donc que lorsque la méthode à tester n'utilise pas de types déclarés dans des assemblies référencées) :
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace Tester
{
class Program
{
static void Main(string[] args)
{
if(args.Length != 3)
{
Console.WriteLine("Usage : \nTester \"Assembly path\" ClassName MethodName");
return;
}
// Retrieve the absolute path of the assembly
string absolutePath = System.IO.Path.GetFullPath(args[0]);
// Load the assembly using Reflection API
Assembly assToTest = Assembly.LoadFile(absolutePath);
// Load the type and create an instance of the type to test
Type typeToTest= assToTest.GetType(args[1]);
object objToTest = typeToTest.GetConstructor(new Type[]{}).Invoke(null);
// Call the method to test using reflection
typeToTest.GetMethod(args[2]).Invoke(objToTest,null);
}
}
}
Si nous le lançons sur une méthode utilisant des types définis dans des assemblies externes, nous recevons une exception de de ce style :
Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.FileNotFoundException: Could not load file or assembly 'ReferencedAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. File name: 'ReferencedAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at AssemblyToTest.ClassToTest.MethodToTest()
Il existe cependant une méthode permettant d'aider la CLR à localiser les assemblies référencées, mais non chargées dans l'AppDomain. En effet la classe AppDomain possède un évènement AssemblyResolve appelée à chaque fois que la CLR n'arrive pas à résoudre elle même le nom d'une Assembly. Voici comment l'on pourrait améliorer notre petit programme afin qu'il charge automatiquement les assemblies qui se trouvent dans le répertoire de l'assembly testée :
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
namespace Tester
{
class Program
{
static void Main(string[] args)
{
if(args.Length != 3)
{
Console.WriteLine("Usage : \nTester \"Assembly path\" ClassName MethodName");
return;
}
// Retrieve the absolute path of the assembly
string absolutePath = System.IO.Path.GetFullPath(args[0]);
// Load the assembly using Reflection API
Assembly assToTest = Assembly.LoadFile(absolutePath);
string directoryPath = System.IO.Path.GetDirectoryName(absolutePath);
AppDomain.CurrentDomain.AssemblyResolve+=delegate(object sender,ResolveEventArgs arg)
{
string assemblySimpleName =arg.Name.Substring(0,arg.Name.IndexOf(','));
string path;
if(File.Exists((path = Path.Combine(directoryPath, assemblySimpleName+".dll"))))
{
return Assembly.LoadFile(path);
}
else if(File.Exists((path = Path.Combine(directoryPath, assemblySimpleName+".exe"))))
{
return Assembly.LoadFile(path);
}
else
{
throw new InvalidOperationException(string.Format("The referenced assembly \"{0}\" is not in the same directory as the tested assembly",arg.Name));
}
};
// Load the type and create an instance of the type to test
Type typeToTest= assToTest.GetType(args[1]);
object objToTest = typeToTest.GetConstructor(new Type[]{}).Invoke(null);
// Call the method to test using reflection
typeToTest.GetMethod(args[2]).Invoke(objToTest,null);
}
}
}