Eu não respeito o encapsulamento, ou como usar methodtable do outro tipo para chamada rápida dos métodos privados

Oi Gostaria de mostrar um exemplo do uso de StructLayout para algo mais interessante do que exemplos com bytes, ints e outros tipos primitivos, quando tudo acontece de maneira óbvia.



Antes de prosseguir com a violação extremamente rápida do encapsulamento, vale a pena relembrar brevemente o que é o StructLayout. A rigor, é mesmo o StructLayoutAttribute , um atributo que permite criar estruturas e classes semelhantes à união em C ++. Este atributo permite que você assuma o controle da colocação dos membros da classe na memória (usando deslocamentos). 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 permite que você especifique que o local dos campos será definido não pelo ambiente, mas pelo usuário. Para especificar explicitamente o deslocamento dos campos, devemos usar o parâmetro LayoutKind.Explicit .

Para indicar qual deslocamento do início da classe / estrutura (daqui em diante apenas "classe") queremos colocar o campo, precisamos colocar o atributo FieldOffset nele. Este atributo assume como parâmetro o número de bytes - o deslocamento desde o início da classe. É impossível passar um valor negativo, para não estragar os ponteiros para a tabela de métodos ou o índice do bloco de sincronização. Entã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 a seguinte classe:

public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); } 

Em seguida, usamos o mecanismo descrito acima para especificar 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(); } } 

Chamar o método GetType () retorna uma string, o método ToString () é impertinente e nos fornece a string "4564".

Descarga cerebral: o que será exibido depois de chamar a propriedade virtual CustomClass ?

Como você já adivinhou, inicializamos CustomStructWithLayout , os dois links são nulos, inicializamos o campo do nosso tipo e atribuímos a string ao campo Str. Como resultado, o link CustomClass não aponta para o objeto CustomClass , mas para o objeto System.string (incluindo a tabela de métodos e o índice da unidade de sincronização). Mas o compilador vê que o campo ainda é do tipo de nossa classe.

Para prova, aqui está um pequeno recorte do WinDbg:



Aqui você pode ver algumas coisas incomuns.

  • No objeto CustomStructWithLayout, os campos têm endereços diferentes de tabelas de métodos (altamente esperados), mas os endereços dos objetos são os mesmos.
  • A segunda é que você pode ver que os dois campos estão localizados no deslocamento 4. Acho que a maioria entenderá, mas, apenas no caso, explicarei, diretamente para o endereço do objeto colocado um link para a tabela de métodos. Os campos começam com um deslocamento de 4 bytes (para 32 bits) e o índice do bloco de sincronização está localizado com um deslocamento de -4. Assim, ambos os objetos estão no mesmo deslocamento.

Agora que você descobriu o que está acontecendo, tente usar compensações para chamar o que não deveria ter sido chamado.

Para isso, repeti a estrutura da classe string em uma das minhas classes. Mas repeti apenas o começo, já que a série é bastante volumosa e sou muito preguiçosa.

Nota: consts e estática não são necessários, apenas por diversão.

  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 o layout será alterada um pouco.

  [StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; } 

Além disso, ao chamar o método FakeLength ou 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 será chamado (neste caso).

O acesso ao primeiro método privado da string que eu posso usar foi muito longo, então parei em um método público. Mas o campo é privado, tudo é honesto. A propósito, os métodos são virtualizados para proteger contra otimizações que interferem no trabalho (por exemplo, incorporação) e também para que o método seja chamado pelo deslocamento na tabela de métodos.

Então, desempenho. É claro que um concorrente direto chamando coisas que não devem ser chamadas e violando o encapsulamento é reflexo. Eu acho que está claro que somos mais rápidos que isso, porque 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>(); } } 


Versão russa

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


All Articles