È possibile passare un'espressione lambda a un AppDomain secondario come un flusso di byte IL e quindi assemblarlo di nuovo utilizzando DynamicMethod in modo che possa essere chiamato?Passare un lambda a un AppDomain secondario come un flusso di IL e riassemblarlo utilizzando DynamicMethod
io non sono troppo sicuro che questo sia il modo giusto per andare in primo luogo, quindi ecco la (dettagliata) motivo che mi chiedo questa domanda ...
Nelle mie applicazioni, ci sono un sacco di casi quando ho bisogno di caricare un paio di assiemi per la riflessione, così posso determinare cosa fare con loro dopo. La parte problematica è che devo essere in grado di scaricare gli assembly dopo che ho finito di rifletterci sopra. Ciò significa che ho bisogno di caricarli utilizzando un altro AppDomain
.
Ora, la maggior parte dei miei casi sono simili, tranne che non del tutto. Ad esempio, a volte ho bisogno di restituire una semplice conferma, altre volte ho bisogno di serializzare un flusso di risorse dall'assembly, e altre volte ho bisogno di fare una callback o due.
Così alla fine scrivo più e più volte lo stesso codice di creazione temporaneo AppDomain
temporaneo e l'implementazione di proxy personalizzati MarshalByRefObject
per comunicare tra il nuovo dominio e quello originale.
Dato che questo non è più davvero accettabile, ho deciso di codificare me una classe AssemblyReflector
che potrebbe essere utilizzato in questo modo:
using (var reflector = new AssemblyReflector(@"C:\MyAssembly.dll"))
{
bool isMyAssembly = reflector.Execute(assembly =>
{
return assembly.GetType("MyAssembly.MyType") != null;
});
}
AssemblyReflector
sarebbe automatizzare il AppDomain
scarico in virtù della IDisposable
, e mi permette di eseguire a Func<Assembly,object>
- tipo lambda che tiene il codice di riflessione in un altro AppDomain
in modo trasparente.
Il problema è che lambda non può essere passato ad altri domini così semplicemente. Quindi, dopo aver cercato in giro, ho trovato quello che sembra un modo per farlo: passare il lambda al nuovo AppDomain
come un flusso di IL - e questo mi porta alla domanda originale.
Ecco quello che ho provato, ma non ha funzionato (il problema è stato BadImageFormatException
viene generata quando si cerca di chiamare il nuovo delegato):
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");
}
}
sono io nemmeno vicino (cosa manca?), O è questo un esercizio inutile tutto sommato?
NOTA: mi rendo conto che se questo ha funzionato, dovrei ancora fare attenzione a ciò che ho messo in lambda in riferimento ai riferimenti. Questo non è un problema, però.
AGGIORNAMENTO: sono riuscito a ottenere un po 'di più. Sembra che chiamare semplicemente SetCode(...)
non sia abbastanza per ricostruire il metodo. Ecco ciò che è necessario:
// 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);
Il trucco è il seguente. L'IL originale contiene determinati token di metadati validi solo nel contesto del metodo originale. Avevo bisogno di analizzare l'IL e sostituire quei token con quelli che sono validi nel nuovo contesto. L'ho fatto utilizzando una classe speciale, ILTokenResolver
, che ho adattato da queste due fonti: Drew Wilson e Haibo Luo.
C'è ancora un piccolo problema con questo - il nuovo IL non sembra essere esattamente valido.A seconda del contenuto esatto del lambda, può o meno lanciare un InvalidProgramException in fase di esecuzione.
Come semplice esempio, questo funziona:
reflector.Execute(a => { return 5; });
mentre questo non:
reflector.Execute(a => { int a = 5; return a; });
Ci sono anche esempi più complessi che sono o funziona o non, secondo alcuni ancora- differenza da determinare. Potrei aver perso qualche piccolo ma importante dettaglio. Ma sono abbastanza sicuro che lo troverò dopo un confronto più dettagliato delle uscite ildasm. Pubblicherò i miei risultati qui, quando lo farò.
MODIFICA: Oh, amico. Ho completamente dimenticato che questa domanda era ancora aperta. Ma come probabilmente è diventato ovvio in sé, ho rinunciato a risolvere questo. Non ne sono felice, questo è sicuro. È davvero un peccato, ma suppongo che aspetterò un miglior supporto dal framework e/o CLR prima di tentare di nuovo. Ci sono solo molti hack che devi fare per farlo funzionare, e anche in questo caso non è affidabile. Scuse a tutti gli interessati.
Ne ero consapevole. Ma pensavo che non sarebbe stato un problema se non avessi usato variabili esterne in lambda? E se usassi un vecchio delegato semplice invece di lambda? – aoven
Parte del punto è che un lambda non è solo il codice nell'espressione. Trasforma anche altri codici nella tua app in fase di compilazione. È possibile trasferire le modifiche in fase di compilazione a un altro assembly già compilato. –