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:
publicclassCustomClass
    {
        publicoverridestringToString()
        {
            return"CUSTOM";
        }
        publicvirtualobject Field { get; } = newobject();
    }

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

For now, I'll postpone the explanations and use the written class as follows:
classProgram
    {
        static void Main(string[] args)
        {
            CustomStructWithLayoutinstance = new CustomStructWithLayout();
            instance.SomeInstance = new CustomClass();
            instance.Str = "4564";
            Console.WriteLine(instance.SomeInstance.GetType()); //System.StringConsole.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.
publicclassCustomClassLikeString
    {
        publicconstint FakeAlignConst = 3;
        publicconstint FakeCharPtrAlignConst = 3;
        publicstaticreadonlyobject FakeStringEmpty;
        publicchar FakeFirstChar;
        publicint FakeLength = 3;
        publicconstint FakeTrimBoth = 3;
        publicconstint FakeTrimHead = 3;
        publicconstint FakeTrimTail = 3;
        publicCustomClassLikeString(){}
        publicCustomClassLikeString(int a){}
        publicCustomClassLikeString(byte a){}
        publicCustomClassLikeString(short a){}
        publicCustomClassLikeString(string a){}
        publicCustomClassLikeString(uint a){}
        publicCustomClassLikeString(ushort a){}
        publicCustomClassLikeString(long a){ }
        publicvoidStub1(){}
        publicvirtualintCompareTo(objectvalue)
        {
            return800;
        }
        publicvirtualintCompareTo(stringvalue)
        {
            return801;
        }
    }

Well, the structure changes a little with the layout
    [StructLayout(LayoutKind.Explicit)]
    publicclassCustomStructWithLayout
    {
        [FieldOffset(0)]
        publicstring 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:
MethodJob jobMeanErrorStddevMedian
StructLayoutFieldClr0.0597 ns0.0344 ns0.0396 ns0.0498 ns
ReflectionfieldClr197.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
Here is a long piece of code with how I measured performance (If someone needs it):
Code
    [ClrJob]
    [RPlotExporter, RankColumn]
    [InProcessAttribute]
    publicclassBenchmarking
    {
        private CustomStructWithLayout instance;
        privatestring str;
        [GlobalSetup]
        publicvoidSetup()
        {
            instance = new CustomStructWithLayout();
            instance.SomeInstance = new CustomClassLikeString();
            instance.Str = "4564";
            str = "4564";
        }
        [Benchmark]
        publicintStructLayoutField()
        {
            return instance.SomeInstance.FakeLength;
        }
        [Benchmark]
        publicintReflectionField()
        {
            return (int)typeof(string).GetField("m_stringLength", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(str);
        }
        [Benchmark]
        publicintStructLayoutMethod()
        {
            return instance.SomeInstance.CompareTo("4564");
        }
        [Benchmark]
        publicintReflectionMethod()
        {
            return (int)typeof(string).GetMethod("CompareTo", new[] { typeof(string) }).Invoke(str, new[] { "4564" });
        }
    }
    classProgram
    {
        staticvoidMain(string[] args)
        {
            var summary = BenchmarkRunner.Run<Benchmarking>();
        }
    }


Also popular now: