O que eu quero fazer é mudar a forma como um método C # é executado quando chamado, para que eu possa escrever algo assim:
[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;
}
Em tempo de execução, preciso ser capaz de analisar métodos que tenham o atributo Distribuído (o que já posso fazer) e inserir o código antes que o corpo da função seja executado e depois que a função retornar. Mais importante, eu preciso ser capaz de fazer isso sem modificar o código onde Solve é chamado ou no início da função (em tempo de compilação; fazer isso em tempo de execução é o objetivo).
No momento, tentei este trecho de código (suponha que t seja o tipo em que Solve está armazenado e m é um MethodInfo de 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;
}
No entanto, MethodRental.SwapMethodBody funciona apenas em módulos dinâmicos; não aqueles que já foram compilados e armazenados na montagem.
Portanto, estou procurando uma maneira de fazer SwapMethodBody com eficácia em um método que já está armazenado em um assembly carregado e em execução .
Observe, não é um problema se eu tiver que copiar completamente o método em um módulo dinâmico, mas, neste caso, preciso encontrar uma maneira de copiar através do IL, bem como atualizar todas as chamadas para Solve () de forma que elas apontaria para a nova cópia.
Respostas:
Harmony 2 é uma biblioteca de código aberto (licença MIT) projetada para substituir, decorar ou modificar métodos C # existentes de qualquer tipo durante o tempo de execução. Seu foco principal são jogos e plug-ins escritos em Mono ou .NET. Ele cuida de várias alterações no mesmo método - elas se acumulam em vez de se sobrescrever.
Ele cria métodos de substituição dinâmicos para cada método original e emite código para eles que chama métodos personalizados no início e no final. Ele também permite que você escreva filtros para processar o código IL original e manipuladores de exceção personalizados que permitem uma manipulação mais detalhada do método original.
Para completar o processo, ele grava um salto simples do montador no trampolim do método original que aponta para o montador gerado a partir da compilação do método dinâmico. Isso funciona para 32/64 bits no Windows, macOS e qualquer Linux compatível com o Mono.
A documentação pode ser encontrada aqui .
Exemplo
( Fonte )
Código Original
Patching with Harmony Annotations
Alternativamente, remendo manual com reflexão
fonte
Memory.WriteJump
)?48 B8 <QWord>
move um valor imediato de QWord pararax
, entãoFF E0
estájmp rax
- tudo limpo aí! Minha pergunta restante é sobre oE9 <DWord>
caso (um salto para perto): parece que neste caso o salto para perto é preservado e a modificação está no alvo do salto; Quando o Mono gera esse código em primeiro lugar, e por que ele recebe esse tratamento especial?Para .NET 4 e superior
fonte
this
dentroinjectionMethod*()
, fará referência a umaInjection
instância durante o tempo de compilação , mas umaTarget
instância durante o tempo de execução (isso é verdadeiro para todas as referências a membros de instância que você usa dentro de um injetado método). (2) Por alguma razão, a#DEBUG
parte estava funcionando apenas ao depurar um teste, mas não ao executar um teste que foi compilado para depuração. Acabei sempre usando a#else
peça. Eu não entendo por que isso funciona, mas funciona.Debugger.IsAttached
vez do#if
préVocê PODE modificar o conteúdo de um método em tempo de execução. Mas você não deve fazer isso, e é altamente recomendável mantê-lo para fins de teste.
Basta dar uma olhada em:
http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time
Basicamente, você pode:
Mexa com esses bytes.
Se você deseja apenas pré-anexar ou anexar algum código, apenas preprend / anexar opcodes que você deseja (no entanto, tenha cuidado ao deixar a pilha limpa)
Aqui estão algumas dicas para "descompilar" IL existente:
Uma vez modificado, sua matriz de bytes IL pode ser reinjetada via InjectionHelper.UpdateILCodes (método MethodInfo, byte [] ilCodes) - consulte o link mencionado acima
Esta é a parte "insegura" ... Funciona bem, mas consiste em hackear mecanismos CLR internos ...
fonte
você pode substituí-lo se o método for não virtual, não genérico, não for do tipo genérico, não embutido e em formato de placa x86:
fonte
Existem algumas estruturas que permitem alterar dinamicamente qualquer método em tempo de execução (eles usam a interface ICLRProfiling mencionada pelo usuário 152949):
Existem também alguns frameworks que brincam com a parte interna do .NET, estes são provavelmente mais frágeis e provavelmente não podem alterar o código embutido, mas por outro lado, eles são totalmente independentes e não exigem que você use um lançador personalizado.
fonte
Solução do Logman , mas com interface para troca de corpos de métodos. Além disso, um exemplo mais simples.
fonte
Com base na resposta a esta e outra pergunta, ive veio com esta versão organizada:
fonte
Você pode substituir um método em tempo de execução usando a interface ICLRPRofiling .
Veja este blog para mais detalhes.
fonte
Eu sei que não é a resposta exata para sua pergunta, mas a maneira usual de fazer isso é usando a abordagem de fábricas / proxy.
Primeiro, declaramos um tipo base.
Então, podemos declarar um tipo derivado (chamá-lo de proxy).
O tipo derivado também pode ser gerado em tempo de execução.
A única perda de desempenho é durante a construção do objeto derivado, a primeira vez é bem lento porque vai usar muita reflexão e emissão de reflexão. Todas as outras vezes, é o custo de uma consulta de tabela simultânea e de um construtor. Como disse, você pode otimizar a construção usando
fonte