iniezione di codice runtime utilizzando DynamicMethod?
-
08-10-2019 - |
Domanda
Si consideri il seguente codice banale:
using System;
class Test
{
delegate int FooDelegate(int i);
FooDelegate Foo = FooImplementation;
static int FooImplementation(int i)
{
return i + 1;
}
public static void Main()
{
Foo(1);
}
}
Quello che vorrei fare è iniettare un po 'di codice di debug nella delegato Foo, che sarebbe equivalente:
FooDelegate Foo = delegate(int i)
{
try
{
DebugPrologue();
return FooImplementation(i);
}
finally
{
DebugEpilogue();
}
};
La torsione è che devo essere in grado di farlo a runtime , quindi in fase di compilazione e di metodi di post-processing sono fuori questione.
Il mio primo approccio utilizzato Delegate.Combine () per aggiungere i metodi di prologo ed epilogo al delegato Foo. Purtroppo, questo non funzionerà come munges valori restituiti.
La mia idea attuale è quella di utilizzare System.Reflection.Emit e DynamicMethod come una potenziale soluzione. Per quanto posso dire, ho bisogno di ottenere il MethodInfo per FooImplementation, ottenere il suo MethodBody, convertito che ad un DynamicMethod e iniettare il try-blocco finally in quello.
Purtroppo, non ho assolutamente idea di come fare questo. Chiunque abbia voglia di dare una mano? O avete un'altra idea (preferibilmente più semplice)?
Edit: il caso d'uso qui è il debug di un OpenGL vincolante (http://www.opentk.com). Dobbiamo iniettare 2226 metodi con parametri molto diversi, quindi un approccio generale è necessario.
Soluzione 6
Quello che ho finalmente finito per fare è stato quello di implementare la mia soluzione riscrittura utilizzando Mono.Cecil. Semplice, veloce e funziona bene. Purtroppo, questo deve essere fatto come un evento post-generazione, quindi questo non è in realtà l'inserimento di codice runtime.
Altri suggerimenti
È possibile utilizzare le espressioni.
var param = Expression.Parameter(typeof(int), "i");
Foo = Expression.Lambda(
Expression.TryFinally(
Expression.Block(
<expression for DebugPrologue>,
Expression.Invoke(<expression for FooImplementation>, param)),
<expression for DebugEpilogue>),
param)
.Compile();
In questo modo, è possibile costruire le espressioni per il prologo ed epilogo in fase di esecuzione.
Nota sicuro di quello che stai cercando di fare, ma se è semplicemente reindirizzando l'FooImplementation
che ha sottolineato al Foo
Field allora si può semplicemente fare:
class Test
{
delegate int FooDelegate(int i);
static FooDelegate Foo = FooImplementation;
static int FooImplementation(int i)
{
return i + 1;
}
public static void Main()
{
Inject();
Foo(1);
}
public static void Inject()
{
var oldImpl = Foo;
Foo = i =>
{
try
{
BeginDebug();
return oldImpl(i);
}
finally
{
EndDebug();
}
};
}
public static void BeginDebug()
{
Console.WriteLine("Begin Foo");
}
public static void EndDebug()
{
Console.WriteLine("End Foo");
}
}
Naturalmente l'Inject non deve essere nella stessa classe, se il campo / proprietà non è accessibile al pubblico si dovrà utilizzare la reflection per farlo, ancora non è che anche se è difficile.
Hai pensato di usare qualcosa di simile a: PostSharp? http://www.sharpcrafters.com/postsharp
o politica iniezione Enterprise Library? http://msdn.microsoft.com/en-us/library/ff650672.aspx
o Mono.Cecil dispari? http://www.mono-project.com/Cecil
TypeMock anche forse una soluzione praticabile al problema in questione: http://www.typemock.com/
Si potrebbe anche utilizzare la classe RealProxy (http://msdn.microsoft.com/en-us/library/system.runtime.remoting.proxies.realproxy.aspx) per generare la propria delega in fase di esecuzione che sarà prima chiamata il codice iniettato e quindi chiamare il metodo effettivo.
È anche possibile provare CInject su CodePlex in codice iniettare in codice gestito (C # o VB.NET) con grande facilità.