لا أحترم التغليف ، أو كيفية استخدام methodtable من النوع الآخر للاتصال السريع بالطرق الخاصة

مرحبا. أود أن أوضح لك مثالًا على استخدام 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 إلى كائن CustomClass ، ولكنه يشير إلى كائن System.string (بما في ذلك جدول الطرق وفهرس وحدة المزامنة). لكن المترجم يرى أن الحقل لا يزال من نوع فئتنا.

لإثبات ، هنا لقطة صغيرة من WinDbg:



هنا يمكنك رؤية بعض الأشياء غير العادية.

  • في حقول الكائنات CustomStructWithLayout لها عناوين مختلفة من methodtables (متوقع للغاية) ، ولكن عناوين الكائنات هي نفسها.
  • والثاني هو أنه يمكنك أن ترى أن كلا الحقلين يقعان في الإزاحة 4. أعتقد أن معظمهم سوف يفهمون ، لكن فقط في الحالة ، سأشرح ، مباشرة ، إلى عنوان الكائن الذي وضع رابطًا لجدول الأساليب. تبدأ الحقول بإزاحة مكونة من 4 بايت (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 () ، بسبب الإزاحة المتطابقة لأعضاء الفئة هؤلاء بالنسبة إلى عنوان الكائن نفسه ، سيتم استدعاء طريقة السلسلة المقابلة (في هذه الحالة).

الوصول إلى أول طريقة خاصة من السلسلة التي يمكنني استخدامها كانت طويلة جدًا ، لذا توقفت عند واحدة عامة. لكن الحقل خاص ، كل شيء صادق. بالمناسبة ، يتم جعل الطرق افتراضية للحماية من أي تحسينات تتداخل مع العمل (على سبيل المثال ، التضمين) ، وكذلك بحيث يتم استدعاء الأسلوب بواسطة الإزاحة في جدول الطريقة.

لذلك ، الأداء. من الواضح أن المنافس المباشر في استدعاء الأشياء التي لا يجب استدعاءها وانتهاك التغليف يكون انعكاسًا. أعتقد أنه من الواضح أننا أسرع من هذا الشيء ، لأننا لا نحلل البيانات الوصفية. القيم الصحيحة:
طريقةوظيفةمتوسطخطأStdDevمتوسط
StructLayoutFieldCLR0.0597 ن0.0344 ن0.0396 ن0.0498 ن
ReflectionFieldCLR197.1257 ن1.9148 ن1.7911 ن197.4787 ن
StructLayoutMethodCLR3.5195 ن0.0382 ن0.0319 ن3.5285 ن
ReflectionMethodCLR743.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>(); } } 


النسخة الروسية

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


All Articles