تحليل الهيكل والدمار في دلفي
نموذج كائن 1 في دلفي: 2
1.1 ماذا يعني اسم الكائن؟ 2
1.2 أين يتم تخزين الكائنات؟ 2
1.3 ما هو مخزن في الكائن؟ كيف يتم تخزينها؟
2 مُنشئ وإنشاء كائن 5
2.1 ما هو مُنشئ؟ (طريقة الطبقة "الخاصة") 5
2.2 العملية برمتها لإنشاء كائن 5
2.3 الاستخدام البديل للمصممون (باستخدام المراجع الطبقية لتنفيذ تعدد الأشكال للمصممين) 6
3 Destructor and Destruction Comple 7
3.1 ما هو المدمار (الطريقة الطبيعية "الافتراضية) 7
3.2 العملية برمتها لتدمير الكائن 7
3.3 تدمير ، حرة ، freeandnil ، استخدام الإفراج والفرق 7
4 هندسة البناء والتدمير VCL 8
5 استخدم المُنشرين والمدمرين 9 بشكل صحيح
تحليل الهيكل والدمار في دلفي
الخلاصة: من خلال دراسة VCL/RTL ، تحلل هذه الورقة آلية تنفيذ المُنشئين والمدمرات وبنية الكائنات في VCL ، وتشرح كيفية إنشاء كائنات وإطلاقها بشكل صحيح.
الكلمات الرئيسية: بناء ، تدمير ، إنشاء كائنات ، تدمير الكائنات ، كومة ، كومة ، تعدد الأشكال.
المؤلف: Majorsoft
سؤال
ما هي آلية تنفيذ المُنشئين والمدمرين في دلفي؟ كيفية إنشاء الكائنات وإطلاقها بشكل صحيح؟
حل
كيفية استخدام البناء والتدمير بشكل صحيح هي مشكلة شائعة في عملية استخدام Delphi. فيما يلي فهم آلية تنفيذ المنشئين والمدمرين من خلال دراسة رمز مصدر VCL/RTL.
نموذج كائن واحد في دلفي:
1.1 ماذا يعني اسم الكائن؟
على عكس C ++ ، يمثل اسم الكائن (يسمى أيضًا متغير) في Delphi مرجع كائن ما ، ولا يمثل الكائن نفسه ، وهو ما يعادل مؤشر إلى الكائن ، والذي يسمى "نموذج مرجع الكائن". كما هو مبين في الشكل:
OBJ (اسم الكائن) الكائن الفعلي
عنوان إدخال VMT
أعضاء البيانات
يشير اسم الكائن الشكل 1 إلى كائن في الذاكرة
1.2 أين يتم تخزين الكائنات؟
يقسم كل تطبيق الذاكرة المخصصة لها للركض إلى أربعة مجالات:
منطقة الكود
منطقة البيانات العالمية
منطقة كومة
منطقة المكدس
الشكل 2 مساحة ذاكرة البرنامج
منطقة الرمز: رمز البرنامج في البرنامج ، بما في ذلك جميع رموز الوظائف
منطقة البيانات العالمية: تخزن البيانات العالمية.
منطقة الكومة: تُعرف أيضًا باسم "منطقة التخزين المجانية" ، التي تخزن البيانات الديناميكية (بما في ذلك الكائنات والسلاسل في دلفي). النطاق هو دورة حياة التطبيق بأكملها حتى يتم استدعاء المدمر.
منطقة المكدس: تُعرف أيضًا باسم "منطقة التخزين التلقائية" لتخزين البيانات المحلية في البرنامج. يقع النطاق داخل الوظيفة ، ويعيد النظام على الفور إعادة تدوير مساحة المكدس بعد استدعاء الوظيفة.
في C ++ ، يمكن إنشاء الكائنات على الكومة ، أو على المكدس ، أو في البيانات العالمية. في Delphi ، تم تصميم جميع الكائنات على منطقة تخزين الكومة ، لذلك لا يمكن استدعاء مُنشئ Delphi تلقائيًا ، ولكن يجب أن يطلق عليه المبرمج نفسه (اسحب المكون في المصمم ، ويتم إنشاء الكائن بواسطة Delphi). يشرح البرنامج التالي الفرق بين إنشاء الكائنات في Delphi و C ++:
في دلفي:
الإجراء createObject (var fooobjref: tfoooBject) ؛
يبدأ
fooobjref: = tfoooBject.create ؛
// يسمى المبرمج ، بعد أن يتم استدعاء الإجراء ، لا يزال هناك حاجة لنسخه.
fooobject.caption = 'أنا تم إنشاؤه في كومة من createObject ()' ؛
نهاية؛
وفي C ++:
tfoooBject createObject (void) ؛
{
tfoooBject fooobject ؛ // إنشاء كائنات محلية
// static tfoooBject fooobject ؛ // إنشاء كائن محلي ثابت
// يتم إنشاء الكائن تلقائيًا عن طريق استدعاء المُنشئ الافتراضي ، ويتم إنشاء الكائن في مكدس الدالة في هذا الوقت.
fooobject.caption = 'أنا تم إنشاؤه في كومة من createObject ()' ؛
إرجاع foobject ؛
// يتم نسخ الكائن عند العودة ، وسيتم تدمير الكائن الأصلي الذي تم إنشاؤه تلقائيًا بعد الانتهاء من استدعاء الوظيفة}
tfoooBject fooobject2 ؛ // إنشاء كائن عالمي.
void main () ؛
{tfoooBject* pfoooBject = new tfoooBject ؛
// إنشاء كائن كومة. بعد استدعاء الوظيفة ، لا يزال الكائن موجودًا ولا يلزم نسخه. }
1.3 ما هو مخزن في الكائن؟ كيف يتم تخزينها؟
على عكس C ++ ، تقوم الكائنات في Delphi بتخزين عناوين الإدخال لأعضاء البيانات وجداول الأسلوب الافتراضية (VMTS) فقط ، ولكن لا تخزن الأساليب ، كما هو موضح في الشكل:
شريحة رمز جدول الأسلوب الافتراضي الكائن
عنوان VMT
الاسم: سلسلة
العرض: عدد صحيح ؛
CH1: Char ؛
...
PROC1
FUNC1
...
بروكن
funcn
...
الشكل 3 هيكل الكائن ...
ربما لديك بعض الأسئلة حول البيان أعلاه ، يرجى الاطلاع على الإجراء التالي:
tsizealigntest = الفصل
خاص
أنا: عدد صحيح.
CH1 ، CH2: Char ؛
J: عدد صحيح ؛
عام
الإجراءات showmsg ؛
الإجراء VIRTMTD ؛
نهاية؛
memo1.lines.Add (inttoStr (sizetest.instancesize)+': cinstancize') ؛
memo1.lines.add (inttoStr (integer (sizetest))+'<-start addr') ؛
memo1.lines.add (inttoStr (integer (@(sizetest.i)))+'<-sizetest.i') ؛
memo1.lines.add (inttoStr (integer (@(sizetest.ch1)))+'<-sizetest.ch1') ؛
memo1.lines.add (inttoStr (integer (@(sizetest.ch2)))+'<-sizetest.ch2') ؛
memo1.lines.add (inttoStr (integer (@(sizetest.j)))+'<-sizetest.j') ؛
تظهر النتائج:
16: تحديد الحدود
14630724 <-start addr
14630728 <-sizetest.i
14630732 <-sizetest.ch1
14630733 <-sizetest.ch2
14630736 <-sizetest.j
لا يشغل عضو DATION وعنوان إدخال VMT 16 بايت!
إذن أين يتم تخزين وظائف العضو؟ نظرًا لأن Delphi يعتمد على RTL (مكتبة نوع وقت التشغيل) ، يتم تخزين جميع وظائف الأعضاء في الفصل. إذن كيف تجد عنوان دخول وظيفة العضو؟ بالنسبة للوظائف الثابتة ، يتم هذا العمل بواسطة المترجم. هذه المرة). يجب أن يكون موجودًا في هذا الوقت.
يلاحظ
كما ذكر أعلاه ، يتم تخزين جميع وظائف الأعضاء في الفصل ، وتشمل في الواقع جدول الطريقة الافتراضية VMT. من وظيفة الإكمال التلقائي من رمز Delphi (يعتمد على معلومات التجميع) ، يمكننا أن نرى أنه بعد إدخال اسم الكائن ثم الإدخال "." الطرق ، جميع المنشئين والمدمرين ، يمكنك تجربتها إذا كان هذا هو الحال.
عنوان إدخال VMT لجدول الطريقة الافتراضية للفئة
معلومات قالب عضو البيانات
جدول طريقة ثابتة ، إلخ.
جدول الطريقة الافتراضية VMT
هدف
عنوان إدخال VMT
أعضاء البيانات
يوضح البرنامج أعلاه أيضًا محاذاة أعضاء بيانات الكائن (بنية البيانات المادية) ، والتي تتم محاذاة في 4 بايت (محاذاة Windows الافتراضية) ، كما هو موضح في الشكل أدناه:
مدخل VMT Addr
أنا
CH1CH2
ي
2 مُنشئ وإنشاء كائنات
2.1 ما هو مُنشئ؟ (طريقة "خاصة")
من دلالات فكرة OO (الموجهة نحو الكائن) ، يكون المنشئ مسؤولاً عن إنشاء الكائن ، ولكن من حيث تنفيذ لغة OOP ، سواء كان Delphi أو C ++ ، يقوم المُنشئ فقط بتهيئة الكائن (بما في ذلك استدعاء الكائنات الفرعية الداخلية).
بالإضافة إلى ذلك ، على عكس C ++ ، تعرّف Delphi نوع طريقة أخرى للمشارك (MKConstructor ، انظر Line/Source . لا يمكن استدعاؤه إلا من خلال الفئة (اسم الفئة/مؤشر الفئة) ، في حين يمكن استدعاء أساليب الفئة العامة من خلال كل من الفئة والكائنات ؛ ، وفي أساليب الفصل ، يشير الذات إلى الفصل ، حيث نهيئة أعضاء البيانات عادة لجعله كائنًا حقيقيًا ، وهو كل شيء بفضل المعلمة الذاتية.
افتراضيًا ، يمكن أن يدركه هذا المُنشئ. يخلق مُنشئًا متعددًا ، ويمكنه أيضًا تراكب مُنشئ الفئة الأم في الفئة المشتقة. الهيكل والتدمير (انظر 4)
2.2 عملية إنشاء الكائن بأكملها
يجب أن تتضمن العملية الكاملة لإنشاء كائن تخصيص مساحة ، وبناء هياكل البيانات المادية ، وتهيئة ، وإنشاء كائنات فرعية داخلية. كما ذكر أعلاه ، فإن المنشئ مسؤول فقط عن التهيئة والاتصال بمنشئات الفرعية الداخلية. وذلك لأن المترجم يقوم بأشياء إضافية ، لا نعرف. عند التجميع إلى المُنشئ ، سيتم إدراج سطر من رمز "call classcreat". :
دالة _classcreate (ACLASS: tclass ؛ تخصيص: منطقية): TOBJECT ؛
ASM
{ -> eax = مؤشر إلى vmt}
{<- eax = مؤشر إلى مثيل}
...
اتصل بـ Dword Ptr [eax] .vmtnewinstance // استدعاء newinstance
...
End ؛
vmtnewinstance = -12 ؛
وظيفة الطبقة newinstance: tooject ؛
وظيفة فئة toopject.newinstance: tobject ؛
يبدأ
النتيجة: = initinstance (_getMem (instancize)) ؛
نهاية؛
"initinstance (_getMem (instancize))" يستدعي ثلاث وظائف بدوره:
1) أول استدعاء instancize () لإرجاع حجم كائن الفئة الفعلية
وظيفة الفئة tooject.
يبدأ
النتيجة: = pinteger (integer (self) + vmtinstancesize)^؛ // إرجاع حجم كائن الفئة الفعلية
نهاية؛
2) اتصل على _getMem () لتخصيص الذاكرة بحجم المثيل على الكومة وإرجاع مرجع الكائن
3) استدعاء initInstance () لإنشاء بنية البيانات الفعلية وتعيين القيمة الافتراضية للعضو ، مثل تعيين قيمة عضو بيانات عدد صحيح إلى 0 ، وتعيين المؤشر على لا شيء ، إلخ. إذا كانت هناك طريقة افتراضية ، قم بتعيين عنوان إدخال جدول الطريقة الافتراضية VMT إلى بايت الأربعة الأولى للكائن.
بعد استدعاء NewInstance ، يحتوي الكائن في هذا الوقت فقط على "قذيفة فارغة" وليس "محتوى" فعليًا ، لذلك من الضروري استدعاء مُنشئ مخصص لتهيئة الكائن بشكل مفيد ، واتصل بمنشئ الكائن الفرعي الداخلي ، وتمكينه الكائنات في البرنامج لتعكس حقًا الأشياء في العالم الحقيقي. هذه هي العملية الكاملة لإنشاء الكائن.
2.3 الاستخدام البديل للمصمم (باستخدام المراجع الطبقية لتنفيذ تعدد الأشكال للمصممين)
في دلفي ، يتم تخزين الفصول أيضًا ككائنات ، لذلك هناك تعدد الأشكال أيضًا. قم بتعيين طريقة الفصل كطريقة افتراضية ، وتجاوزها في فئتها المشتقة ، ثم نسميها من خلال المرجع/المؤشر للفئة الأساسية ، بحيث يتم إنشاء الكائن بناءً على مرجع الفئة/المؤشر الذي يشير إلى الفئة الفعلية. يرجى الاطلاع على البرنامج التالي:
tmyclass = فئة
إنشاء مُنشئ ؛ افتراضي ؛
نهاية؛
ttmyclass = فئة من tmyclass ؛ // مرجع فئة الفئة الأساسية
tmyclasssub = فئة (tmyclass)
مُنشئ.
نهاية؛
الإجراء createObj (aclass: ttmyclass ؛ var ref) ؛
يبدأ
TOBJECT (المرجع): = aclass.create ؛
// المرجع لا يوجد نوع ولا يتوافق مع أي نوع ، لذلك يجب أن يتم إلقاؤه بشكل صريح عند استخدامه (يلقي)
// ACLASS هو مرجع فئة ، وواجهة وظيفة موحدة ، وتطبيقات مختلفة.
// ستقوم ببناء كائنات بناءً على الفئة الفعلية المشار إليها/المشار إليها بواسطة ACLASS.
نهاية؛
...
CreateObj (tmyclass ، obj) ؛
createObj (tmyclasssub ، subobj) ؛
3 المدمرة وتدمير الأشياء
3.1 ما هو المدمار (الطريقة الطبيعية "الافتراضية)
من الناحية الدلالية ، فإن المدمر مسؤول عن تدمير الأشياء وإطلاق الموارد. في دلفي ، مرادف.
تعرّف دلفي أيضًا نوع طريقة للمدمر (mkcontructor ، انظر Line/source/rtl/common/typinfo.pas ، 125 في دليل تثبيت Delphi). ؛ لماذا يفعل VCL هذا؟ لأنه يضمن أنه يمكن تدمير الكائن بشكل صحيح في حالات متعددة الأشكال. إذا لم يتم استخدام الأساليب الافتراضية ، فيمكنك فقط تدمير المشروع الفرعي للفئة الأساسية ، مما يؤدي إلى ما يسمى "تسرب الذاكرة". لذلك ، من أجل ضمان المدمر الصحيح ، يحتاج المدمر إلى إضافة إعلان تجاوز.
3.2 عملية تدمير الكائن بأكملها
أولا تدمير كائن SubObject من الفئة المشتقة ، ثم تدمير كائن SubObject الفئة الأساسية.
تَلمِيح
في فئة مشتقة ، يشير الكائن الفرعي من الفئة الأساسية إلى الجزء الموروث من الفئة الأساسية ، ويشير الكائن الفرعي في الفئة المشتقة إلى الجزء المضافة حديثًا.
3.3 تدمير ، حرة ، freeandnil ، استخدام الإفراج والاختلافات
تدمير: الطريقة الافتراضية
تحرير الذاكرة ، وأعلن أنها افتراضية في TOBJECT ، وعادة ما تتجاوزها في فئة الفئة الفرعية الخاصة بها ، وأضف الكلمة الرئيسية الموروثة لضمان تدمير كائن الفئة المشتقة بشكل صحيح ؛
ولكن لا يمكن استخدام التدمير مباشرة ، لماذا؟
إذا كان الكائن لا شيء ، فلا نزال ندعو إلى التدمير ، فسيحدث خطأ. نظرًا لأن التدمير هو طريقة افتراضية ، فإنه يحتاج إلى العثور على عنوان مدخل جدول الطريقة الافتراضية VMT استنادًا إلى البايتات الأربعة الأولى في الكائن ، وذلك للعثور على عنوان مدخل التدمير ، لذلك يجب أن يكون الكائن موجودًا في هذا الوقت. ولكن هناك طريقة ثابتة. استخدام مجاني أكثر أمانًا من استخدام التدمير.
2) مجانا: طريقة ثابتة
اختبار ما إذا كان الكائن لا شيء ، ويتم تدمير ما إذا لم يكن الأمر لا شيء. هنا رمز دلفي مجانًا:
الإجراء tooject.free ؛
يبدأ
إذا كانت الذات <> nil ثم
تدمر
نهاية؛
أليس من الرائع أن نتعلم من نقاط القوة والضعف؟
ومع ذلك ، فإن استدعاء التدمير يدمر فقط الكائن ، لكنه لا يضع مرجع الكائن إلى NIL ، والذي يجب القيام به من قبل المبرمج.
3) Freeandnil طريقة عامة ، طريقة غير طابق.
تعريف Freeandnil في وحدة Sysutils
الإجراء freeandnil (var obj) ؛
var
درجة الحرارة: toojject ؛
يبدأ
درجة الحرارة: = tobject (OBJ) ؛
مؤشر (OBJ): = nil ؛
temp.free ؛
نهاية؛
نوصي باستخدامه بدلاً من مجاني/تدمير للتأكد من إطلاق الكائن بشكل صحيح.
4) الإفراج ؛
تسمى الوظيفة الحرة بعد معالجة جميع الأحداث في النافذة. غالبًا ما يتم استخدامه لتدمير النوافذ ، وعندما تستغرق معالجة الأحداث وقتًا معينًا في هذه النافذة ، يمكن لهذه الطريقة التأكد من تدمير النافذة فقط بعد معالجة حدث النافذة. فيما يلي رمز مصدر دلفي لـ tcustomform.Release:
الإجراء tcustomform.Release ؛
يبدأ
postmessage (مقبض ، cm_release ، 0 ، 0) ؛
// إرسال رسالة CM_RELEASE إلى النافذة إلى قائمة انتظار الرسائل.
// استدعاء عملية معالجة الرسائل cm_release cmrelease مرة أخرى
نهاية؛
دعونا نلقي نظرة على التعريف التالي لـ cmrelease لمعالجة رسائل CM_Release:
الإجراء cmrelease (var message: tmessage) ؛
الإجراء tcustomform.cmrelease ؛
يبدأ
مجاني ؛
نهاية؛
4 هندسة البناء والتدمير VCL
لاعب
إنشاء مُنشئ ؛ // طريقة ثابتة
المدمرة تدمير.
tpersence
المدمرة تدمير.
tcomponent
إنشاء مُنشئ (مالك: tcomponent) ؛
المدمرة تدمير.
tcontrol
إنشاء مُنشئ (مالك: tcomponent) ؛
المدمرة تدمير.
...
يحلل ما يلي رمز المصدر للبناء والتدمير في VCL ، مع أخذ tcontrol كمثال:
مُنشئ tcontrol.create (aleener: tcomponent) ؛
يبدأ
CREATE الموروثة (ANNERER) ؛ // قم بإنشاء كائن فرعي من فئة الأساس وتسليم حقوق التدمير إلى Oenler. ضعه في المقدمة
// هذا يضمن ترتيب "إنشاء Base Class SuboBject أولاً ، ثم إنشاء مشتقة من الفئة الفرعية"
...//تهيئة ، واستدعاء مُنشئ الفرعية الداخلية
نهاية؛
Destructor tcontrol.destroy ؛
يبدأ
...//تدمير المشروبات الفرعية الداخلية في الفصول المشتقة
تدمير موروثة ؛ // تدمير كائن الطبقة الأساسية ووضعه في النهاية
// هذا يضمن ترتيب "التدمير الأول للفرعية المشتقة من الطبقة المشتقة ، ثم تدمير المشروعات الفرعية من الفئة الأساسية"
نهاية؛
5 استخدم المُنشئين والمدمرين بشكل صحيح
بعد التحليل أعلاه ، يلخص ما يلي مبادئ استخدام المنشئين والمدمرين:
قبل استخدام كائن ، يجب عليك أولاً إنشاء كائن وتدمير الكائن في الوقت المناسب لتحرير الموارد.
عندما تشير كائنين إلى الواجبات ، تأكد من أن الكائن NAMLOEN (في إشارة إلى الكائنات غير المشار إليها) يمكن إطلاقها.
عند إنشاء مكون ، يوصى بإعداد مكون مضيف (أي ، استخدم المعلمة المملوكة ، وعادة ما يدير مالك الأشياء ، لذلك لا داعي للقلق بشأن تدمير المكون Delphi على تصميم وحدة النموذج/البيانات وإنشاء مكونات هي الطريقة التي يتم اتخاذها. لذلك ليس علينا أن نكتب المدمر الذي يدعو المكون.
عندما يكون نوع الإرجاع للوظيفة كائنًا ، تكون النتيجة أيضًا مرجعًا للكائن ، مما يضمن وجود الكائن المشار إليه بالنتيجة.
لاستخدام OBJ <> nil أو تعيين (لا شيء) لاختبار أن الكائن موجود ، OBJ: = يجب أن يتم استدعاء NIL أيضًا بعد OBJ: = NIL.
يرجى الرجوع إلى الكود المصدري لبرنامج العرض التوضيحي
التعليمات (الموصى بها)
تم تمرير جميع برامج Delphi على Win2K+Delphi6 SP2. من أجل تعميق فهمك لهذه المقالة ، يوصى بالإشارة إلى برنامج العرض التوضيحي.
يتضمن هذا المقال بعضًا من تجاربي وخبراتي في تعلم VCL/RTL.
قبل قراءة هذا المقال ، يحتاج القراء إلى فهم معين للغة الباسكال الموجهة نحو فهم تعدد الأشكال.
من خلال هذه المقالة ، يجب أن تكون قادرًا على فهم نموذج الكائن ، وآلية تنفيذ البناء والتدمير في دلفي ، وبنية البناء والتدمير في VCL ، وتكون قادرًا على إتقان استخدام أساليب البناء والتدمير. الهيكل والتدمير في دلفي أبسط بكثير من تلك الموجودة في C ++ ، ويجب أن نكون قادرين على إتقانه.