Je souhaite charger dans une nouvelle AppDomain
une assemblée comportant une arborescence de références complexe (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll )
D'après ce que j'ai compris, lorsqu'un assemblage est chargé dans AppDomain
, ses références ne sont pas chargées automatiquement et je dois les charger manuellement . Alors, quand je le fais:
string dir = @"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
string path = System.IO.Path.Combine(dir, "MyDll.dll");
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = dir;
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);
domain.Load(AssemblyName.GetAssemblyName(path));
et j'ai FileNotFoundException
:
Impossible de charger le fichier ou l'assembly 'MyDll, version = 1.0.0.0, Culture = neutre, PublicKeyToken = null' ou l'une de ses dépendances. Le système ne peut pas trouver le fichier spécifié.
Je pense que la partie clé est une de ses dépendances .
Ok, je le fais avant avant domain.Load(AssemblyName.GetAssemblyName(path));
foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
domain.Load(refAsmName);
}
Mais j'ai à nouveau FileNotFoundException
, sur un autre assemblage (référencé).
Comment charger toutes les références de manière récursive?
Dois-je créer une arborescence de références avant de charger l'assemblage racine? Comment obtenir les références d'une assemblée sans la charger?
Vous devez appeler CreateInstanceAndUnwrap
avant que votre objet proxy s'exécute dans le domaine d'application étrangère.
class Program
{
static void Main(string[] args)
{
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);
Type type = typeof(Proxy);
var value = (Proxy)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
var Assembly = value.GetAssembly(args[0]);
// AppDomain.Unload(domain);
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
}
En outre, notez que si vous utilisez LoadFrom
, vous obtiendrez probablement une exception FileNotFound
car le résolveur d'assembly tentera de trouver l'assembly que vous chargez dans le GAC ou le dossier bin de l'application actuelle. Utilisez plutôt LoadFile
pour charger un fichier Assembly arbitraire - mais notez que si vous procédez ainsi, vous devrez charger vous-même les dépendances.
http://support.Microsoft.com/kb/837908/en-us
Version C #:
Créez une classe de modérateur et héritez-la de MarshalByRefObject
:
class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFrom(assemblyPath);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
}
appel du site client
ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);
Sur votre nouvelle AppDomain, essayez de définir un gestionnaire d’événements AssemblyResolve . Cet événement est appelé lorsqu'une dépendance est manquante.
Une fois que vous avez renvoyé l'instance d'assembly au domaine appelant, le domaine appelant essaiera de le charger! C'est pourquoi vous obtenez l'exception. Cela se produit dans votre dernière ligne de code:
domain.Load(AssemblyName.GetAssemblyName(path));
Ainsi, tout ce que vous voulez faire avec Assembly doit être fait dans une classe proxy - une classe qui hérite de MarshalByRefObject .
Tenez compte du fait que le domaine appelant et le nouveau domaine créé doivent tous deux avoir accès à la classe de proxy Assembly. Si votre problème n'est pas trop compliqué, pensez à laisser le dossier ApplicationBase inchangé, de sorte qu'il soit identique au dossier du domaine de l'appelant (le nouveau domaine chargera uniquement les assemblys dont il a besoin).
En code simple:
public void DoStuffInOtherDomain()
{
const string assemblyPath = @"[AsmPath]";
var newDomain = AppDomain.CreateDomain("newDomain");
var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);
asmLoaderProxy.GetAssembly(assemblyPath);
}
class ProxyDomain : MarshalByRefObject
{
public void GetAssembly(string AssemblyPath)
{
try
{
Assembly.LoadFrom(AssemblyPath);
//If you want to do anything further to that Assembly, you need to do it here.
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message, ex);
}
}
}
Si vous devez charger les assemblys à partir d'un dossier différent de celui de votre dossier de domaine d'application actuel, créez le nouveau domaine d'application avec un dossier de chemin de recherche spécifique.
Par exemple, la ligne de création de domaine d'application à partir du code ci-dessus doit être remplacée par:
var dllsSearchPath = @"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);
De cette façon, toutes les dlls seront automatiquement résolues à partir de dllsSearchPath.
Vous devez gérer les événements AppDomain.AssemblyResolve ou AppDomain.ReflectionOnlyAssemblyResolve (selon le chargement que vous effectuez) dans le cas où l'ensemble référencé ne se trouve pas dans le GAC ou sur le chemin de vérification du CLR.
Il m'a fallu un certain temps pour comprendre la réponse de @ user1996230. J'ai donc décidé de donner un exemple plus explicite. Dans l'exemple ci-dessous, je crée un proxy pour un objet chargé dans un autre AppDomain et appelle une méthode sur cet objet depuis un autre domaine.
class ProxyObject : MarshalByRefObject
{
private Type _type;
private Object _object;
public void InstantiateObject(string AssemblyPath, string typeName, object[] args)
{
Assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory
_type = Assembly.GetType(typeName);
_object = Activator.CreateInstance(_type, args); ;
}
public void InvokeMethod(string methodName, object[] args)
{
var methodinfo = _type.GetMethod(methodName);
methodinfo.Invoke(_object, args);
}
}
static void Main(string[] args)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = @"SomePathWithDLLs";
AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup);
ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject");
proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs});
proxyObject.InvokeMethod("foo",new object[] { "bar"});
}
La clé est l'événement AssemblyResolve déclenché par AppDomain.
[STAThread]
static void Main(string[] args)
{
fileDialog.ShowDialog();
string fileName = fileDialog.FileName;
if (string.IsNullOrEmpty(fileName) == false)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
if (Directory.Exists(@"c:\Provisioning\") == false)
Directory.CreateDirectory(@"c:\Provisioning\");
assemblyDirectory = Path.GetDirectoryName(fileName);
Assembly loadedAssembly = Assembly.LoadFile(fileName);
List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>();
foreach (var type in assemblyTypes)
{
if (type.IsInterface == false)
{
StreamWriter jsonFile = File.CreateText(string.Format(@"c:\Provisioning\{0}.json", type.Name));
JavaScriptSerializer serializer = new JavaScriptSerializer();
jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type)));
jsonFile.Close();
}
}
}
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string[] tokens = args.Name.Split(",".ToCharArray());
System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name);
return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"}));
}
J'ai dû le faire plusieurs fois et j'ai recherché de nombreuses solutions différentes.
La solution que je trouve la plus élégante et facile à réaliser peut être mise en œuvre telle quelle.
l'interface contiendra les signatures des membres que vous souhaitez appeler.
public interface IExampleProxy
{
string HelloWorld( string name );
}
Il est important de garder ce projet propre et éclairé. C’est un projet que les deux AppDomain
peuvent référencer et nous permettra de ne pas faire référence à Assembly
que nous souhaitons charger dans un domaine distinct de notre client Assembly.
AppDomain
.Ce projet comme avec le projet client référencera le proxy proj et vous implémenterez l'interface.
public interface Example : MarshalByRefObject, IExampleProxy
{
public string HelloWorld( string name )
{
return $"Hello '{ name }'";
}
}
AppDomain
. Alors, maintenant nous créons un nouveau AppDomain
. Peut spécifier l'emplacement de base pour les références d'assemblage. La vérification vérifiera les assemblys dépendants dans GAC et dans le répertoire en cours, ainsi que le locus de base AppDomain
.
// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
ApplicationBase = System.Environment.CurrentDirectory
};
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);
// Assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";
// Optional - get a reflection only Assembly type reference
var @type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName );
// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );
// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( @type.Assembly.Name, @type.Name );
// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );
// unload the `AppDomain`
AppDomain.Unload( exampleDomain );
si vous en avez besoin, il existe une tonne de façons différentes de charger une assemblée. Vous pouvez utiliser une manière différente avec cette solution. Si vous avez le nom qualifié Assembly, j'aime utiliser CreateInstanceAndUnwrap
car il charge les octets Assembly, puis instancie votre type et renvoie un object
que vous pouvez transtyper simplement en votre type de proxy ou si vous ne l'utilisez pas en code fortement typé vous pouvez utiliser le langage d'exécution dynamique et attribuer l'objet renvoyé à une variable typée dynamic
, puis simplement appeler les membres directement sur celle-ci.
Cela permet de charger une assemblée à laquelle votre projet client n'a pas de référence dans un fichier séparé AppDomain
et d'appeler des membres dessus depuis le client.
La clé est de vous assurer que votre code dérive MarshalByRefObject
ou est sérialisable.
`MarshalByRefObject vous permettra de configurer la durée de vie du domaine. Entrez, par exemple, si vous voulez que le domaine soit détruit si le proxy n'a pas été appelé depuis 20 minutes.