I don’t respect encapsulation, or use another type of table of methods to quickly call private methods.

Published on September 18, 2018

I don’t respect encapsulation, or use another type of table of methods to quickly call private methods.

Hello. I would like to share an example of using StructLayout for something more interesting than examples with bytes, inta, and other numbers, in which everything happens a little more than expected.

Before proceeding to lightning-fast violation of encapsulation, it is worth recalling briefly what StructLayout is. Strictly speaking, it is even a StructLayoutAttribute, that is, an attribute that allows you to create structures and classes similar to a union in C ++. If we talk in more detail, this attribute allows you to take control of the placement of class members in memory. Accordingly, it is placed above the class. Usually, if a class has 2 fields, we expect them to be arranged sequentially, that is, they will be independent of each other (do not overlap). However, StructLayout allows you to specify that the location of the fields will not be determined by the environment, but by the user. To explicitly specify the field offset, use the LayoutKind.Explicit parameter. To indicate at what offset relative to the beginning of the class / structure (hereinafter class) we want to place the field, above it should put the attribute FieldOffset, which takes as a parameter the number of bytes - indent from the beginning of the class. It is impossible to transfer a negative value, so that spoiling the pointers to the method table or the synchronization block index, don’t even think, everything will be a little more complicated.

Let's start writing the code. To begin with, I propose to start with a simple example. Create a class of the following form:
    public class CustomClass
    {
        public override string ToString()
        {
            return "CUSTOM";
        }
        public virtual object Field { get; } = new object();
    }

Next, we use the above mechanism for explicitly specifying field offsets.
[StructLayout(LayoutKind.Explicit)]
    public class CustomStructWithLayout
    {
        [FieldOffset(0)]
        public string Str;
        [FieldOffset(0)]
        public CustomClass SomeInstance;
    }

For now, I'll postpone the explanations and use the written class as follows:
    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 Calling the GetType () method returns a string, the ToString () method plays pranks and gives us the string "4564".
Brain Discharge: What will be displayed when calling the CustomClass virtual property?

As you have already guessed, we initialized CustomStructWithLayout, both links are null, then we initialize the field of our type, and then assign the string to the Str field. As a result, a little more remains from CustomClass than nothing. Over it was written a string with its entire internal structure, including the table of methods and the index of the synchronization unit. But the compiler sees the field is still of the type of our class.
For proof, here is a small clipping from WinDbg:

Here you can see some unusual things. The first is that in the object of the address on the method tables, the class fields are different, as expected, but the address of the field value is one. The second is that you can see that both fields are located at offset 4. I think most will understand, but just in case I will explain, the link to the method table is located directly at the address of the object. The fields start with an offset of 4 bytes (for 32bit bits), and the index of the synchronization unit is located at an offset of -4.

Now that you have figured out what is happening, you can try using offsets to call what should not have been called.
For this, I repeated the structure of the string class in one of my classes. True, I repeated only the beginning, since the class string is quite voluminous.
    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;
        }
    }

Well, the structure changes a little with the layout
    [StructLayout(LayoutKind.Explicit)]
    public class CustomStructWithLayout
    {
        [FieldOffset(0)]
        public string Str;
        [FieldOffset(0)]
        public CustomClassLikeString SomeInstance;
    }

Further, when calling FakeLength or the CompareTo () method, due to the identical offset of these class members relative to the address of the object itself, the corresponding string method will be called (in this case). Getting to the first private method in a row that I can use was quite long, so I stopped at a public one. But the field is private, everything is honest. By the way, the methods are made virtual to protect against any optimizations that interfere with the work (for example, embedding), and also so that the method is called by the offset in the table of methods.

So, performance. It’sa matter that a direct competitor in calling what is not needed and in violation of encapsulation is reflection. I think that it is clear that we are faster than this thing, all of us do not analyze metadata. Exact values:
Method Job job Mean Error Stddev Median
StructLayoutField Clr 0.0597 ns 0.0344 ns 0.0396 ns 0.0498 ns
Reflectionfield 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
ReflectionMethod Clr 743.9793 ns 13.7378 ns 12.8504 ns 743.8471 ns
Here is a long piece of code with how I measured performance (If someone needs it):
Code
    [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>();
        }
    }