web-dev-qa-db-fra.com

Comment charger un assembly sur AppDomain avec toutes les références de manière récursive?

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?

102
abatishchev

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.

61
Jduv

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);
16
rockvista

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.

11
David

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.

10
Nir

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.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

5
Dustin Campbell

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"});
}
3
grouma

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"}));
}
3
Leslie Marshall

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.

1. Créez un projet que vous pouvez créer une interface simple

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.

2. Créez maintenant un projet contenant le code que vous souhaitez charger séparément 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 }'";
    }
}

3. Ensuite, dans le projet client, chargez le code dans un autre 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.  

Voilà.

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.

Pour tester, j'aime utiliser la fenêtre Modules dans Visual Studio. Il vous montrera votre domaine d'assembly client et quels sont les modules chargés dans ce domaine, ainsi que votre nouveau domaine d'applications, ainsi que les assemblys ou modules chargés dans ce domaine.

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.

J'espère que ça aide.

0
SimperT