2009-09-01 10 views
11

Est-il possible de passer une expression lambda à un AppDomain secondaire en tant que flux d'octets IL, puis de l'assembler à l'aide de DynamicMethod être appelé?Passer un lambda à un AppDomain secondaire en tant que flux de IL et le réassembler à l'aide de DynamicMethod

Je ne suis pas sûr que ce soit la bonne façon de procéder en premier lieu, alors voici la raison (détaillée) Je pose cette question ...

Dans mes applications, il y a beaucoup de cas quand j'ai besoin de charger quelques assemblées pour la réflexion, je peux déterminer ce que je vais faire ensuite. Le problème est que je dois être capable de décharger les assemblages après avoir fini de réfléchir sur eux. Cela signifie que je dois les charger en utilisant un autre AppDomain.

Maintenant, la plupart de mes cas sont en quelque sorte similaires, mais pas tout à fait. Par exemple, parfois je dois retourner une confirmation simple, d'autres fois j'ai besoin de sérialiser un flux de ressources de l'assemblage, et encore d'autres fois j'ai besoin de faire un rappel ou deux.

Donc, je finis par écrire encore et encore le même code de création AppDomain semi-compliqué et mettre en œuvre des proxies MarshalByRefObject personnalisés pour communiquer entre le nouveau domaine et l'original.

Comme ce n'est pas vraiment plus acceptable, j'ai décidé de me coder une classe AssemblyReflector qui pourrait être utilisé de cette façon:

using (var reflector = new AssemblyReflector(@"C:\MyAssembly.dll")) 
{ 
    bool isMyAssembly = reflector.Execute(assembly => 
    { 
     return assembly.GetType("MyAssembly.MyType") != null; 
    }); 
} 

AssemblyReflector aurait automize le déchargement AppDomain en vertu de IDisposable et me permettent d'exécuter un Func<Assembly,object> -type lambda tenant le code de réflexion dans un autre AppDomain de manière transparente.

Le problème est que lambdas ne peut pas être passé si facilement à d'autres domaines. Donc, après avoir cherché autour, j'ai trouvé ce qui ressemble à une façon de faire: passer le lambda au nouveau AppDomain comme un flux IL - et cela m'amène à la question initiale.

Voici ce que j'ai essayé, mais ne fonctionnait pas (le problème a été BadImageFormatException être jeté en essayant d'appeler le nouveau délégué):

public delegate object AssemblyReflectorDelegate(Assembly reflectedAssembly); 

public class AssemblyReflector : IDisposable 
{ 
    private AppDomain _domain; 
    private string _assemblyFile; 
    public AssemblyReflector(string fileName) { ... } 
    public void Dispose() { ... } 

    public object Execute(AssemblyReflectorDelegate reflector) 
    { 
     var body = reflector.Method.GetMethodBody(); 
     _domain.SetData("IL", body.GetILAsByteArray()); 
     _domain.SetData("MaxStackSize", body.MaxStackSize); 
     _domain.SetData("FileName", _assemblyFile); 

     _domain.DoCallBack(() => 
     { 
      var il = (byte[])AppDomain.CurrentDomain.GetData("IL"); 
      var stack = (int)AppDomain.CurrentDomain.GetData("MaxStackSize"); 
      var fileName = (string)AppDomain.CurrentDomain.GetData("FileName"); 
      var args = Assembly.ReflectionOnlyLoadFrom(fileName); 
      var pars = new Type[] { typeof(Assembly) }; 

      var dm = new DynamicMethod("", typeof(object), pars, 
       typeof(string).Module); 
      dm.GetDynamicILInfo().SetCode(il, stack); 

      var clone = (AssemblyReflectorDelegate)dm.CreateDelegate(
       typeof(AssemblyReflectorDelegate)); 
      var result = clone(args); // <-- BadImageFormatException thrown. 

      AppDomain.CurrentDomain.SetData("Result", result); 
     }); 

     // Result obviously needs to be serializable for this to work. 
     return _domain.GetData("Result"); 
    } 
} 

Suis-je même près (ce qui manque?), Ou est-ce un exercice inutile dans l'ensemble?

NOTE: Je me rends compte que si cela fonctionnait, je devrais encore faire attention à ce que je mets dans lambda en ce qui concerne les références. Ce n'est pas un problème, cependant.

MISE À JOUR: J'ai réussi à aller un peu plus loin. Il semble qu'il suffit d'appeler SetCode(...) pour reconstruire la méthode. Voici ce qui est nécessaire:

// Build a method signature. Since we know which delegate this is, this simply 
// means adding its argument types together. 
var builder = SignatureHelper.GetLocalVarSigHelper(); 
builder.AddArgument(typeof(Assembly), false); 
var signature = builder.GetSignature(); 

// This is the tricky part... See explanation below. 
di.SetCode(ILTokenResolver.Resolve(il, di, module), stack); 
dm.InitLocals = initLocals; // Value gotten from original method's MethodInfo. 
di.SetLocalSignature(signature); 

L'astuce est la suivante. Original IL contient certains jetons de métadonnées qui ne sont valides que dans le contexte de la méthode d'origine. J'avais besoin d'analyser l'IL et de remplacer ces jetons par ceux qui sont valides dans le nouveau contexte. Je l'ai fait en utilisant une classe spéciale, ILTokenResolver, que j'ai adapté à partir de ces deux sources: Drew Wilson et Haibo Luo.

Il y a toujours un petit problème avec ceci - la nouvelle IL ne semble pas être exactement valide.En fonction du contenu exact du lambda, il peut ou non lancer une exception InvalidProgramException à l'exécution.

Comme un exemple simple, cela fonctionne:

reflector.Execute(a => { return 5; }); 

alors que cela ne:

reflector.Execute(a => { int a = 5; return a; }); 

Il y a aussi des exemples plus complexes qui soit travaillent ou non, en fonction de certaines yet- différence à déterminer. Il se pourrait que j'ai manqué un détail petit mais important. Mais je suis raisonnablement confiant que je le trouverai après une comparaison plus détaillée des sorties d'ildasm. Je posterai mes résultats ici, quand je le ferai.

EDIT: Oh, man. J'ai complètement oublié cette question était toujours ouverte. Mais comme cela est probablement devenu évident en soi, j'ai renoncé à résoudre cela. Je ne suis pas content, c'est certain. C'est vraiment dommage, mais je suppose que j'attendrai un meilleur support du framework et/ou du CLR avant de recommencer. Il y a juste beaucoup de hacks à faire pour que cela fonctionne, et même alors ce n'est pas fiable. Toutes mes excuses à tous ceux qui sont intéressés.

Répondre

1

Probablement pas, car un lambda est plus qu'une simple expression dans le code source. Les expressions lambda créent également des fermetures qui capturent/hissent les variables dans leurs propres classes cachées. Le programme est modifié par le compilateur donc partout où vous utilisez ces variables, vous êtes en train de parler à la classe. Donc, vous devez non seulement passer le code pour le lambda, mais aussi tous les changements aux variables de fermeture au fil du temps.

+0

J'en étais conscient. Mais je pensais que ce ne serait pas un problème si je n'utilisais pas de variables externes dans lambda? Que diriez-vous si j'ai utilisé un vieux délégué simple au lieu de lambda? – aoven

+0

Une partie du point est qu'un lambda n'est pas seulement le code dans l'expression. Il transforme également d'autres codes dans votre application au moment de la compilation. Vous pouvez transférer des modifications de compilation à un autre assembly déjà compilé. –

2

Je n'ai pas eu exactement quel est le problème que vous essayez de résoudre, mais j'ai fait un composant dans le passé qui pourrait le résoudre. Fondamentalement, son but était de générer une expression lambda à partir de string. Il utilise un AppDomain distinct pour exécuter le compilateur CodeDOM. Le IL d'une méthode compilée est sérialisé à AppDomain d'origine, puis reconstruire à un délégué avec DynamicMethod. Ensuite, le délégué est appelé et une expression lambda est renvoyée. J'ai publié une explication complète de mon blog. Naturellement, c'est open source. Donc, si vous arrivez à l'utiliser, s'il vous plaît envoyez-moi tous les commentaires que vous pensez être raisonnable.

+0

Je voulais injecter du code de mon AppDomain dans mon extension VS principale AppDomain et cela a fonctionné magnifiquement! Pour toute personne souhaitant utiliser ce code pour le code existant, vous devrez apporter des modifications pour reconstruire le DynamicMethod à partir du MethodIL en passant le module qui contient tous vos types pour éviter les exceptions d'accès/sécurité. – mstrange

+0

@mstrange Je suis content de savoir que cela reste utile après plus de 6 ans. – jpbochi