Hola Me gustaría mostrarle un ejemplo del uso de
StructLayout para algo más interesante que ejemplos con bytes, ints y otros tipos primitivos, cuando todo sucede de manera bastante obvia.
Antes de proceder a la rápida violación de la encapsulación, vale la pena recordar brevemente qué es StructLayout. Estrictamente hablando, incluso es
StructLayoutAttribute , un atributo que le permite crear estructuras y clases similares a la unión en C ++. Este atributo le permite tomar el control de la ubicación de los miembros de la clase en la memoria (usando compensaciones). En consecuencia, se coloca por encima de la clase.
Por lo general, si una clase tiene 2 campos, esperamos que se organicen secuencialmente, es decir, serán independientes entre sí (no se superpongan). Sin embargo,
StructLayout le permite especificar que la ubicación de los campos será establecida no por el entorno, sino por el usuario. Para especificar el desplazamiento de los campos explícitamente, debemos usar el parámetro
LayoutKind.Explicit .
Para indicar qué desplazamiento desde el comienzo de la clase / estructura (en adelante, solo "clase") queremos colocar el campo, debemos ponerle el atributo
FieldOffset . Este atributo toma como parámetro el número de bytes, el desplazamiento desde el comienzo de la clase. Es imposible pasar un valor negativo para no estropear los punteros a la tabla de métodos o al índice de bloque de sincronización. Entonces será un poco más complicado.
Comencemos a escribir el código. Para empezar, sugiero comenzar con un ejemplo simple. Crea la siguiente clase:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); }
A continuación, utilizamos el mecanismo descrito anteriormente para especificar explícitamente las compensaciones de campo.
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClass SomeInstance; }
Por ahora, pospondré las explicaciones y usaré la clase escrita de la siguiente manera:
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(); } }
Llamar al método
GetType () devuelve una cadena, el método
ToString () es travieso y nos da la cadena "4564".
Descarga cerebral: ¿Qué se mostrará después de llamar a la propiedad virtual
CustomClass ?
Como ya adivinó, inicializamos
CustomStructWithLayout , ambos enlaces son nulos, luego inicializamos el campo de nuestro tipo y luego asignamos la cadena al campo Str. Como resultado, el enlace
CustomClass no apunta al objeto
CustomClass , sino al objeto System.string (incluida la tabla de métodos y el índice de la unidad de sincronización). Pero el compilador ve que el campo todavía es del tipo de nuestra clase.
Como prueba, aquí hay un pequeño recorte de WinDbg:

Aquí puedes ver algunas cosas inusuales.
- En CustomStructWithLayout los campos de objeto tienen diferentes direcciones de tablas de métodos (muy esperadas), pero las direcciones de los objetos son las mismas.
- El segundo es que puede ver que ambos campos están ubicados en el desplazamiento 4. Creo que la mayoría lo entenderá, pero por si acaso, lo explicaré, directamente a la dirección del objeto coloca un enlace a la tabla de métodos. Los campos comienzan con un desplazamiento de 4 bytes (para 32 bits), y el índice del bloque de sincronización se encuentra con un desplazamiento de -4. Por lo tanto, ambos objetos están en el mismo desplazamiento.
Ahora que ha descubierto lo que está sucediendo, puede intentar usar compensaciones para llamar a lo que no debería haberse llamado.
Para esto, repetí la estructura de la clase de cadena en una de mis clases. Pero repetí solo el principio, ya que la cadena de clase es bastante voluminosa y soy muy vago.
Nota: no se requieren concursos y estadísticas, solo por diversión.
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; } }
Bueno, la estructura con el diseño cambiará un poco.
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; }
Además, al llamar a
FakeLength o al método
CompareTo () , debido al desplazamiento idéntico de estos miembros de la clase en relación con la dirección del objeto en sí, se llamará al método de cadena correspondiente (en este caso).
Llegar al primer método privado de la cadena que puedo usar fue demasiado largo, por lo que me detuve en uno público. Pero el campo es privado, todo es honesto. Por cierto, los métodos se hacen virtuales para proteger contra cualquier optimización que interfiera con el trabajo (por ejemplo, incrustación), y también para que el método sea llamado por el desplazamiento en la tabla de métodos.
Entonces, rendimiento. Está claro que un competidor directo en llamar a cosas que no deberían llamarse y en violación de la encapsulación es reflejo. Creo que está claro que somos más rápidos que esto, porque no analizamos metadatos. Valores exactos:
Método | Trabajo | Media | Error | Stddev | Mediana |
---|
StructLayoutField | Clr | 0,0597 ns | 0,0344 ns | 0,0396 ns | 0,0498 ns |
Campo de reflexión | Clr | 197.1257 ns | 1.9148 ns | 1.7911 ns | 197,4787 ns |
StructLayoutMethod | Clr | 3.5195 ns | 0,0382 ns | 0.0319 ns | 3.5285 ns |
Método de reflexión | Clr | 743.9793 ns | 13.7378 ns | 12.8504 ns | 743.8471 ns |
Aquí hay un largo código con cómo medí el rendimiento (si alguien lo necesita):
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>(); } }
Versión rusa