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.
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
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é. –