مرحبا بالجميع. أود أن أشارك مثالاً على استخدام StructLayout لشيء أكثر إثارة للاهتمام من الأمثلة مع وحدات البايت والعلامات الداخلية والأرقام الأخرى ، حيث يحدث كل شيء أكثر قليلاً من المتوقع.
قبل الشروع في انتهاك التغليف بسرعة البرق ، باختصار ، تجدر الإشارة إلى ما هو StructLayout. بالمعنى الدقيق للكلمة ، هذه حتى StructLayoutAttribute ، وهي سمة تسمح لك بإنشاء هياكل وفئات مماثلة للتوحيد في C ++. بمزيد من التفصيل ، تسمح لك هذه السمة بالتحكم في وضع أعضاء الفصل في الذاكرة. وفقا لذلك ، يتم وضعه فوق الفصل. عادة ، إذا كان للفصل حقلين ، نتوقع أن يتم ترتيبهما بالتسلسل ، أي أنهم سيكونون مستقلين عن بعضهم البعض (لا تتداخل). ومع ذلك ، يتيح برنامج StructLayout تحديد أن موقع الحقول لن يتم تحديده بواسطة البيئة ، ولكن بواسطة المستخدم. للإشارة صراحة إلى إزاحة الحقول ، استخدم المعلمة LayoutKind.Explicit. للإشارة إلى الإزاحة المتعلقة ببداية الفئة / البنية (المشار إليها فيما يلي باسم الفئة) ، نرغب في وضع الحقل ، يجب أن نضع سمة FieldOffset فوقه ، والتي تأخذ كمعلمة عدد وحدات البايت - المسافة البادئة من بداية الفصل. لا يمكن تمرير قيمة سالبة ، لذلك لا تفكر حتى في إفساد المؤشرات إلى جدول الأسلوب أو فهرس كتلة المزامنة ، فستكون أكثر تعقيدًا قليلاً.
لنبدأ في كتابة الرمز. في البداية ، أقترح البدء بمثال بسيط. قم بإنشاء فئة من النموذج التالي:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); }
بعد ذلك ، نستخدم الآلية المذكورة أعلاه لضبط الإزاحات الميدانية بشكل صريح.
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClass SomeInstance; }
في الوقت الحالي ، سأؤجل التفسيرات واستخدم الصف المكتوب على النحو التالي:
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(); } }
المجموع إرجاع استدعاء الأسلوب GetType () سلسلة ، أسلوب ToString () غير مطيع ويعطينا السلسلة "4564".
إفرازات الدماغ: ما الذي سيتم عرضه عندما يتم استدعاء الخاصية الافتراضية CustomClass؟
كما قد تكون خمنت ، قمنا بتهيئة CustomStructWithLayout ، وكلا الرابطين فارغين ، ثم نقوم بتهيئة حقل من نوعنا ، وبعد ذلك نقوم بتعيين سلسلة إلى حقل Str. ونتيجة لذلك ، لا يزال هناك القليل من CustomClass من لا شيء. علاوة على ذلك ، كان هناك خط بكل بنيته الداخلية ، بما في ذلك جدول الأساليب وفهرس كتلة التزامن. لكن المترجم يرى أن الحقل لا يزال من نوع فئتنا.
كإثبات ، سأعطي لقطة صغيرة من WinDbg:

هنا يمكنك أن ترى بعض الأشياء غير العادية. الأول هو أنه في الكائن ، تحتوي العناوين في جداول الطريقة على حقول مختلفة لحقول الفئة ، كما هو متوقع ، ولكن عنوان قيمة الحقل واحد. الثاني - يمكنك أن ترى أن كلا الحقلين يقعان في الإزاحة 4. أعتقد أن معظمهم سيفهمون ، ولكن فقط في حالة ، سأوضح ، مباشرة على عنوان الكائن هناك ارتباط إلى جدول الأساليب. تبدأ الحقول بإزاحة 4 بايت (لـ z 32 بت) ، ويقع فهرس كتلة المزامنة مع إزاحة -4.
الآن بعد أن اكتشفت ما يحدث ، يمكنك محاولة استخدام الإزاحات لاستدعاء شيء لا ينبغي استدعاؤه.
للقيام بذلك ، كررت بنية فئة الأوتار في إحدى فصولي. صحيح ، لقد كررت البداية فقط ، لأن فئة الأوتار ضخمة جدًا.
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; } }
حسنًا ، تتغير البنية مع التنسيق قليلاً
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; }
علاوة على ذلك ، عند استدعاء FakeLength أو أسلوب CompareTo () ، نظرًا للإزاحة المتطابقة لهؤلاء الأعضاء من الفئة بالنسبة إلى عنوان الكائن نفسه ، سيتم استدعاء طريقة السلسلة المقابلة (في هذه الحالة). استغرق الأمر بعض الوقت للوصول إلى أول طريقة خاصة في الخط الذي يمكنني استخدامه ، لذلك استقرت على الطريقة العامة. لكن المجال خاص وكل شيء عادل. بالمناسبة ، يتم جعل الأساليب ظاهرية للحماية من جميع أنواع التحسينات ، والتي تتداخل مع العمل (على سبيل المثال ، التضمين) ، وكذلك حتى يتم استدعاء الطريقة عند الإزاحة في جدول الطريقة.
لذا ، الأداء. والواقع أن المنافس المباشر في تحدي ما هو غير ضروري للتسبب وفي انتهاك التغليف هو التفكير. أعتقد أنه من الواضح بالفعل أننا أسرع من هذا الشيء ، جميعنا لا نحلل البيانات الوصفية. القيم الدقيقة:
الطريقة | الوظيفة | يعني | خطأ | ستديف | متوسط |
---|
StructLayoutField | Clr | 0.0597 نانوثانية | 0.0344 نانوثانية | 0.0396 نانوثانية | 0.0498 نانوثانية |
حقل التأمل | Clr | 197.1257 نانوثانية | 1.9148 نانوثانية | 1.7911 نانوثانية | 197.4787 نانوثانية |
طريقة التركيب | Clr | 3.5195 نانوثانية | 0.0382 نانوثانية | 0.0319 نانوثانية | 3.5285 نانوثانية |
طريقة التفكير | Clr | 743.9793 نانوثانية | 13.7378 نانوثانية | 12.8504 نانوثانية | 743.8471 نانوثانية |
في ما يلي جزء طويل من الكود مع كيفية قياس الأداء (إذا احتاجه شخص ما):
كود [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>(); } }