Chcę zmienić sposób wykonywania metody C #, gdy jest wywoływana, aby móc napisać coś takiego:
[Distributed]
public DTask<bool> Solve(int n, DEvent<bool> callback)
{
for (int m = 2; m < n - 1; m += 1)
if (m % n == 0)
return false;
return true;
}
W czasie wykonywania muszę być w stanie analizować metody, które mają atrybut Distributed (co już mogę zrobić), a następnie wstawiać kod przed wykonaniem treści funkcji i po jej powrocie. Co ważniejsze, muszę być w stanie to zrobić bez modyfikowania kodu w miejscu wywołania funkcji Solve lub na początku funkcji (w czasie kompilacji; celem jest zrobienie tego w czasie wykonywania).
W tej chwili próbowałem tego fragmentu kodu (załóżmy, że t jest typem, w którym jest przechowywany Solve, a m to MethodInfo of Solve) :
private void WrapMethod(Type t, MethodInfo m)
{
// Generate ILasm for delegate.
byte[] il = typeof(Dpm).GetMethod("ReplacedSolve").GetMethodBody().GetILAsByteArray();
// Pin the bytes in the garbage collection.
GCHandle h = GCHandle.Alloc((object)il, GCHandleType.Pinned);
IntPtr addr = h.AddrOfPinnedObject();
int size = il.Length;
// Swap the method.
MethodRental.SwapMethodBody(t, m.MetadataToken, addr, size, MethodRental.JitImmediate);
}
public DTask<bool> ReplacedSolve(int n, DEvent<bool> callback)
{
Console.WriteLine("This was executed instead!");
return true;
}
Jednak MethodRental.SwapMethodBody działa tylko na modułach dynamicznych; nie te, które zostały już skompilowane i zapisane w zestawie.
Dlatego szukam sposobu, aby skutecznie wykonać SwapMethodBody w metodzie, która jest już przechowywana w załadowanym i wykonującym się zestawie .
Zauważ, że nie jest problemem, jeśli muszę całkowicie skopiować metodę do modułu dynamicznego, ale w tym przypadku muszę znaleźć sposób na skopiowanie przez IL, a także zaktualizować wszystkie wywołania do Solve (), tak aby były wskazywałby na nową kopię.
Odpowiedzi:
Harmony 2 to biblioteka open source (licencja MIT) zaprojektowana do zastępowania, dekorowania lub modyfikowania istniejących metod języka C # dowolnego rodzaju w czasie wykonywania. Jej głównym celem są gry i wtyczki napisane w Mono lub .NET. Dba o wiele zmian w tej samej metodzie - kumulują się, a nie zastępują się nawzajem.
Tworzy dynamiczne metody zastępcze dla każdej oryginalnej metody i emituje do nich kod, który wywołuje metody niestandardowe na początku i na końcu. Umożliwia także pisanie filtrów do przetwarzania oryginalnego kodu IL i niestandardowych procedur obsługi wyjątków, co pozwala na bardziej szczegółowe manipulowanie oryginalną metodą.
Aby zakończyć proces, zapisuje prosty asemblerowy skok do trampoliny oryginalnej metody, który wskazuje na asemblera wygenerowanego z kompilacji metody dynamicznej. Działa to dla 32 / 64Bit w systemie Windows, macOS i dowolnym systemie Linux, który obsługuje Mono.
Dokumentację można znaleźć tutaj .
Przykład
( Źródło )
Oryginalny kod
Poprawianie adnotacjami Harmony
Alternatywnie, ręczne łatanie z refleksją
źródło
Memory.WriteJump
)?48 B8 <QWord>
przesuwa natychmiastową wartość QWord narax
, a następnieFF E0
jestjmp rax
- wszystko jasne! Moje pozostałe pytanie dotyczyE9 <DWord>
przypadku (skok z bliska): wydaje się, że w tym przypadku skok z bliska jest zachowany, a modyfikacja jest na celu skoku; kiedy Mono generuje taki kod w pierwszej kolejności i dlaczego otrzymuje to specjalne traktowanie?Dla .NET 4 i nowszych
źródło
this
wewnątrzinjectionMethod*()
, będzie on odwoływał się doInjection
instancji w czasie kompilacji , aleTarget
instancji w czasie wykonywania (dotyczy to wszystkich odwołań do elementów instancji, których używasz wewnątrz wstrzykniętego metoda). (2) Z jakiegoś powodu#DEBUG
część działała tylko podczas debugowania testu, ale nie podczas uruchamiania testu, który został skompilowany przez debugowanie. Skończyło się na tym, że zawsze korzystałem z tej#else
części. Nie rozumiem, dlaczego to działa, ale działa.Debugger.IsAttached
zamiast#if
preprocesoraMożesz modyfikować zawartość metody w czasie wykonywania. Ale nie powinieneś tego robić i zdecydowanie zaleca się zachowanie tego do celów testowych.
Wystarczy spojrzeć na:
http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time
Zasadniczo możesz:
Zadzieraj z tymi bajtami.
Jeśli chcesz po prostu dodać lub dołączyć jakiś kod, po prostu poprzedzaj / dołączaj opkody, które chcesz (jednak uważaj na pozostawienie stosu czystego)
Oto kilka wskazówek, jak „zdekompilować” istniejący IL:
Po zmodyfikowaniu tablica bajtów IL może zostać ponownie wprowadzona za pomocą InjectionHelper.UpdateILCodes (metoda MethodInfo, byte [] ilCodes) - patrz link wspomniany powyżej
To jest ta „niebezpieczna” część ... Działa dobrze, ale polega to na włamywaniu się do wewnętrznych mechanizmów CLR ...
źródło
możesz go zastąpić, jeśli metoda jest niewirtualna, nie generyczna, nie jest w typie ogólnym, nie jest w wierszu i na platformie x86:
źródło
Istnieje kilka frameworków, które pozwalają dynamicznie zmieniać dowolną metodę w czasie wykonywania (używają interfejsu ICLRProfiling wspomnianego przez user152949):
Istnieje również kilka frameworków, które kpią z wewnętrznych elementów .NET, są one prawdopodobnie bardziej delikatne i prawdopodobnie nie mogą zmienić wbudowanego kodu, ale z drugiej strony są w pełni samodzielne i nie wymagają używania niestandardowy program uruchamiający.
źródło
Rozwiązanie Logmana , ale z interfejsem do zamiany treści metod. Również prostszy przykład.
źródło
Opierając się na odpowiedzi na to i inne pytanie, wymyśliłem tę uporządkowaną wersję:
źródło
Metodę można zastąpić w czasie wykonywania przy użyciu interfejsu ICLRPRofiling .
Zobacz ten blog, aby uzyskać więcej informacji.
źródło
Wiem, że to nie jest dokładna odpowiedź na twoje pytanie, ale zwykle można to zrobić przy użyciu podejścia opartego na fabrykach / proxy.
Najpierw deklarujemy typ podstawowy.
Następnie możemy zadeklarować typ pochodny (nazwijmy go proxy).
Typ pochodny można również wygenerować w czasie wykonywania.
Jedyna utrata wydajności występuje podczas konstruowania obiektu pochodnego, za pierwszym razem jest dość powolny, ponieważ zużywa dużo odbić i emituje odbicia. We wszystkich innych przypadkach jest to koszt współbieżnego wyszukiwania w tabeli i konstruktora. Jak już powiedziano, możesz zoptymalizować konstrukcję za pomocą
źródło