Não respeito o encapsulamento ou uso um tipo diferente de tabela de método para chamar rapidamente métodos privados

Olá pessoal. Gostaria de compartilhar um exemplo de uso do StructLayout para algo mais interessante do que exemplos com bytes, ints e outros números, nos quais tudo acontece um pouco mais do que o esperado.

Antes de prosseguir com uma violação de encapsulamento extremamente rápida, vale lembrar em poucas palavras o que é o StructLayout. A rigor, esse é mesmo um StructLayoutAttribute, ou seja, um atributo que permite criar estruturas e classes semelhantes à união no C ++. Mais detalhadamente, esse atributo permite que você assuma o controle da colocação dos alunos na memória. Por conseguinte, é colocado acima da classe. Normalmente, se uma classe tiver 2 campos, esperamos que sejam organizados sequencialmente, ou seja, eles serão independentes um do outro (não se sobrepõem). No entanto, o StructLayout possibilita especificar que a localização dos campos será determinada não pelo ambiente, mas pelo usuário. Para indicar explicitamente deslocamentos de campo, use o parâmetro LayoutKind.Explicit. Para indicar em qual deslocamento em relação ao início da classe / estrutura (a seguir denominada classe) queremos colocar o campo, devemos colocar o atributo FieldOffset acima dele, que toma como parâmetro o número de bytes - o recuo do início da classe. Você não poderá transmitir um valor negativo; portanto, nem pense em estragar os ponteiros para a tabela de métodos ou o índice do bloco de sincronização, será um pouco mais complicado.

Vamos começar a escrever o código. Para começar, sugiro começar com um exemplo simples. Crie uma classe do seguinte formulário:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); } 

Em seguida, usamos o mecanismo acima para definir explicitamente as compensações de campo.
 [StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClass SomeInstance; } 

Por enquanto, vou adiar as explicações e usar a aula escrita da seguinte maneira:
  class Program { static void Main(string[] args) { CustomStructWithLayout instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClass(); instance.Str = "4564"; Console.WriteLine(instance.SomeInstance.GetType()); //System.String Console.WriteLine(instance.SomeInstance.ToString()); //4564 Console.Read(); } } 

Total A chamada para o método GetType () retorna uma string, o método ToString () é impertinente e nos fornece a string "4564".
Descarga para o cérebro: o que será exibido quando a propriedade virtual CustomClass for chamada?

Como você deve ter adivinhado, inicializamos CustomStructWithLayout, os dois links são nulos, inicializamos um campo do nosso tipo e depois atribuímos uma string ao campo Str. Como resultado, resta um pouco mais da CustomClass do que nada. No topo, havia uma linha com toda a sua estrutura interna, incluindo uma tabela de métodos e um índice de um bloco de sincronização. Mas o compilador vê que o campo ainda é do tipo de nossa classe.
Como prova, darei um pequeno recorte do WinDbg:

Aqui você pode ver algumas coisas incomuns. A primeira é que, no objeto, os endereços nas tabelas de métodos possuem campos diferentes para os campos da classe, conforme o esperado, mas o endereço do valor do campo é um. A segunda - você pode ver que os dois campos estão localizados no deslocamento 4. Acho que a maioria entenderá, mas, apenas no caso, explicarei, diretamente no endereço do objeto, há um link para a tabela de métodos. Os campos começam com um deslocamento de 4 bytes (para z 32 bits) e o índice do bloco de sincronização está localizado com um deslocamento de -4.

Agora que você descobriu o que está acontecendo, tente usar compensações para chamar algo que não deve chamar.
Para fazer isso, repeti a estrutura da classe string em uma das minhas classes. É verdade que repeti apenas o começo, já que a classe de cordas é muito volumosa.
  public class CustomClassLikeString { public const int FakeAlignConst = 3; public const int FakeCharPtrAlignConst = 3; public static readonly object FakeStringEmpty; public char FakeFirstChar; public int FakeLength = 3; public const int FakeTrimBoth = 3; public const int FakeTrimHead = 3; public const int FakeTrimTail = 3; public CustomClassLikeString(){} public CustomClassLikeString(int a){} public CustomClassLikeString(byte a){} public CustomClassLikeString(short a){} public CustomClassLikeString(string a){} public CustomClassLikeString(uint a){} public CustomClassLikeString(ushort a){} public CustomClassLikeString(long a){ } public void Stub1(){} public virtual int CompareTo(object value) { return 800; } public virtual int CompareTo(string value) { return 801; } } 

Bem, a estrutura com Layout muda um pouco
  [StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; } 

Além disso, ao chamar o FakeLength ou o método CompareTo (), devido ao deslocamento idêntico desses membros da classe em relação ao endereço do próprio objeto, o método de string correspondente (neste caso) será chamado. Demorou um pouco para chegar ao primeiro método privado na linha que eu posso usar, então decidi pelo método público. Mas o campo é privado, tudo é justo. A propósito, os métodos são virtualizados para proteger contra todos os tipos de otimizações, o que interfere no trabalho (por exemplo, incorporação) e também para que o método seja chamado em um deslocamento na tabela de métodos.

Então, desempenho. É fato que um concorrente direto no desafio do que não é necessário causar e na violação do encapsulamento é reflexo. Eu acho que já está claro que somos mais rápidos do que isso, todos nós não analisamos metadados. Valores exatos:
MétodoJobMeanErroStddevMediana
StructLayoutFieldClr0,0597 ns0,0344 ns0,0396 ns0,0498 ns
Campo de reflexãoClr197.1257 ns1.9148 ns1,7911 ns197.4787 ns
StructLayoutMethodClr3.5195 ns0,0382 ns0,0319 ns3.5285 ns
ReflectionMethodClr743.9793 ns13.7378 ns12.8504 ns743.8471 ns
Aqui está um longo pedaço de código com a forma como medi o desempenho (se alguém precisar):
Código
  [ClrJob] [RPlotExporter, RankColumn] [InProcessAttribute] public class Benchmarking { private CustomStructWithLayout instance; private string str; [GlobalSetup] public void Setup() { instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClassLikeString(); instance.Str = "4564"; str = "4564"; } [Benchmark] public int StructLayoutField() { return instance.SomeInstance.FakeLength; } [Benchmark] public int ReflectionField() { return (int)typeof(string).GetField("m_stringLength", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(str); } [Benchmark] public int StructLayoutMethod() { return instance.SomeInstance.CompareTo("4564"); } [Benchmark] public int ReflectionMethod() { return (int)typeof(string).GetMethod("CompareTo", new[] { typeof(string) }).Invoke(str, new[] { "4564" }); } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarking>(); } } 

Source: https://habr.com/ru/post/pt423657/


All Articles