المساعدة - البحث - قائمة الأعضاء - التقويم
نسخة كاملة: طلب بسيط عن vTable
برمجة - شبكات - كمبيوتر - منتديات الفريق العربي للبرمجة > منتديات لغات البرمجة العام > منتدى مبرمجي C و ++C و C++.NET > أرشيف منتدى قسم السي ++
Smart Star
السلام عليكم و رحمة الله و بركاته :-

طبعا كما ترون في عنوان الموضع أنا أتكلم أعن Virtual Tables ، طبعا كل مبرمج لغى سي ++ درس نظم الوراثه يجب أن تكون لديه معلومات بسيطه عن هذه الجداول أو بالأحرى المصفوفات .

لكن أين تكمن المشكله ، إنها تكمن أن أكثر الكتب لا تعطي معلومات مفصله عنه و كما تعلم أن تقنية COM تعتبر أحد أكبر نواتج هذه الميزه في لغة سي++ . ولكن قد نجبر في كثيير من المواضع أن نزيل هذا الجدول أو نجعل المصرف بعدم عمله .مثلا في afx_msg و AFX_NOVTABLE .

ما أريده بإختصار أحد المواضيع التي تتكلم عن هذه الجداول تفصييل كبير مع بعض الأمثله .


شكرا................
هاني الأتاسي
ال vTable من أهم الأمور في الوراثة في السي++ . وهي عبارة بكل بساطة عن مصفوفة في الذاكرة كل عنصر فيها يعتبر عن مؤشر لتابع داخل الكلاس . مؤشر هذه المصفوفة موجود في أول ال Object لهذا الكلاس في الذاكرة .

عندما تقوم بكتابة كلاس يحتوي على هدة توابع . فرضا اعتبر الكلاس التالي :
CODE2

class base {
public:

void func1() {
printf("I am base::func1n");
}
void func2() {
printf("I am base::func2n");
}
void func3() {
printf("I am base::func3n");
}
};

base obj;

طبعا أنت تقر أن التابع func1 و func2 موجودين في الذاكرة بازاحات معينة ولتكن 400 و 500 على الترتيب .
الآن عندما تكتب مثلا :
CODE2

obj.func1();

فإن المترجم يفهم مباشرة العبارة السابقة على أنها استدعاء للتابع func1 من base ويقوم باستدعاء العنوان 400 . وسوف يطبع النتيجة :
I am base::func1

الآن إذا قمنا بوراثة الكلاس son من base بحيث يعيد الكلاس son تعريف التوابع func1 و func2 أما func3 فورثناه بدون تغيير :
CODE2

class son : public base {
public :

void func1() {
printf("I am son::func1n");
}
void func2() {
printf("I am son::func2n");
}

};

son obj;
base *ptr = &obj; // this is valid because base is a father of son

لاحظ كيف أصبح لدينا كائن في الذاكرة من نوع son ومن ثم حجزنا متحول من نوع مؤشر إلى base وأعيطناه عنوان ال son .
الآن إذا قمنا باستدعاء التابع func1 عن طريق obj فإننا نحصل على النتيجة :
I am son::func1
ومن ثم من ptr فإننا نحصل على النتيجة :
I am base::func1

النتيجة الاولى منطقية أما الثانية فلماذا نفذ التابع الخاطئ مع أنه لدينا كائن من son وليس base ..
في الحقيقة قام المترجم بفهم العبارة
Ptr->func1()
على أنها استدعاء للتابع func1 الذي داخل الكلاس base وقام بتكوين لغة الآلة التي تقوم باستدعاء الموقع 400 مباشرة .
في الحقيقة بما ان التابع func1 لا يصل إلى أي متحولات داخل الكلاس base فإنك حتى لو وضعت قيمة المؤشر ptr تساوي NULL فإن الكود يعمل بنجاح .

من أجل حل المشكلة السابقة نلجأ إلى ال vtable .. فكما لاحظت في الأمثلة السابق فإن عملية تحديد أيها تابع يجب أن يستدعى هي مسؤلية المترجم أي ال compiler وهذا يسمى ب early binding . أما في ال vtable كما سوف ترى فإن المترجم ليس له علاقة ويتم تحديد التابع الذي سوف يستدعى أثناء التنفيذ وهذه تسمى late binding ..
يتم بناء ال vtable لأي كلاس إذا تم اعطاء أي تابع ضمني داخل الكلاس الخاصية virtual . أي يكفي تابع واحد فقط من أجل بناء مصفوفة ال vtable .
لو فرضنا أننا قمنا بتغيير الكلاسين السابقين بحيث يصبحا يحتويان على مصفوفة ال vtable .. كالتالي :
CODE2

class base {
public:

virtual void func1() {
printf("I am base::func1n");
}
virtual void func2() {
printf("I am base::func2n");
}
virtual void func3() {
printf("I am base::func3n");
}
};

class son : public base {
public :

virtual void func1() {
printf("I am son::func1n");
}
virtual void func2() {
printf("I am son::func2n");
}

};

base obj_base;
son obj_son;

الآن ماذا أصبح لدينا : لو قمت بفحص المتحول obj في بيئة ال Visual C++ أثناء ال Debug فإنك سوف تحصل على التالي :

لاحظ أن أول حجرة في الكائن obj_base في الذاكرة هي عبارة عن متحول ضمني من نوع مؤشر إلى مصفوفة هذه المصفوفة تحتوي على ثلاثة عناصر بعدد توابع الvirtual في الكلاس . وأيضا لاحظ إلى ماذا يؤشر كل عنصر في المصفوفة .
ولاحظ الكائن obj_son الذي يحتوي أيضا على ثلاث عناصر لكنه يشارك نفس الكود تبع func3 مع الكلاس base .
ويمكن توضح الرسمة التالية أكثر :

يجب أن تنتبه إلى أمر وهو أن ترتيب وجود المؤشرات في ال vtable هو نفس ترتيب وجودها في ال class أي الذي يأتي أولا يتوضع أولا وهكذا .
الآن إذا قمنا بكتابة الكود التالي:
CODE2

base *father = &obj_son;
father->func2();

بما أن التابع func2 هو عبارة عن virtual فالمترجم لن يعرف أثناء الترجمة ألى أين يسند الاستدعاء ويقوم بتوليد الكود المناسب الذي يستخلص عنوان التابع من ال vtable . obj_son يحتوي على ال vtable التي رأيتها سابقا وعندما نقوم بأخذ عنوان هذا الكائن واسناده إلى متحول father الذي من نوع مؤشر إلى base فإن father في هذه الحالة يؤشر إلى vtable تبعة obj_son وبالتالي التابع func2 في هذه الحالة سوف ينفذ بطريقة صحيحة ويظهر النتيجة :
I am son::func2

بالنسبة لل COM فإنه أساس تعامل الCOM هو ال vtable .. إن كائنات الCOM تحتوي على Interfaces كل Interface هو عبارة عن Classe هذا الكلاس يحتوي على العديد من التوابع أو Methods . وعند استخدام هذا الCOM في أحد برامج ال client فإنها تحصل على أحد ال Interfaces التي تريد التعامل معها ومن هذه الواجهة تبدأ باستدعاء التوابع . ولكن كيف تعرف ال client أين موجود التابع من أجل استدعائه .. يتم هذا بأن تحتوي ال Interface أو الClass عند بنائه في الذاكرة على vtable . وال Client فقط تعرف ال prototype تبعة التوابع وأماكن وجودها أو ترتيبها في ال Class وبالتالي تعرف أين سوف تأخذ قيمة مؤشر التابع من ال vtable ..
طبعا بعض ال clients لا تدعم ال vtable مثل ال vbscript و jscript و ال asp و و و. ففي هذه الحالة يستخدم IDispatch من أجل تنفيذ التوابع .

بالنسبة إلى afx_msg فليس لها أي علاقة .. وهي لا شيئ .. أي تستبدل بلا شئ .. انظر تعريفها في الملف afxwin.h .
الآن بالنسبة إلى AFX_NOVTABLE فهي معرفة على أنها __declspec(novtable) إلى __declspec مستخدمة من أجل توسع للغة أو بيئة معينة ف Microsoft لها العديد من الأضافات على لغة السي++ أحدها novtable . وهي تستخدم بعد الأمر class من أجل عدم تكوين vtable للobject .. وهذا لا يعني أن طريقة التعامل هنا أصبحت early binding لا بل بقيت late binding ولكن لا يتم توليد vtable فقط . وهذا مفيد في حالة لدينا class في سلسلة الوراثة لا نستخدمه مباشرة وبالتالي لا يتم استخدام ال vtable خاصته وهذا يعني توفير في الذاكرة في حالة تم الغاء تكوين ال vtable لذاك الكلاس .

لشرح ذلك قم بوراثة كلاس آخر من البرنامج السابق ليصبح البرنامج كالتالي :
CODE2

class base {
public:

virtual void func1() {
printf("I am base::func1n");
}
virtual void func2() {
printf("I am base::func2n");
}
void func3() {
printf("I am base::func3n");
}

};

class __declspec(novtable) son : public base {
public :

virtual void func1() {
printf("I am son::func1n");
}
virtual void func2() {
printf("I am son::func2n");
}

};

class grandson : public son {
public :

virtual void func1() {
printf("I am grandson::func1n");
}
virtual void func2() {
printf("I am grandson::func2n");
}
};

base obj_base;
son obj_son;
grandson obj_grandson;

int main(int argc, char* argv[])
{
base *ptr;

ptr = &obj_son;
ptr->func1();

ptr = &obj_grandson;
ptr->func1();

return 0;
}


لاحظ كيفية وضع __declspec(novtable) بعد كلمة class .. وهذا يعني عدم تكوين مصفوفة ال vtable ..
قم بوضع break point في بداية البرنامج وادرس ال watch :



لاحظ أن المترجم عامل الكلاس son على أنه لديه vtable ولكن لاحظ مؤشر ال vtable يؤشر إلى vtable الأب وبالتالي لا يوجد مصفوفة جديدة تم حجزها خصوصي من أجل الكلاس son أما الكلاس الجديد فله vtable مثل العادة . في هذه الحالة يجب أن لا نستخدم الكلاس son أبدا لأنه قد يؤدي إلى مشاكل مثل المثال السابق في ال main ..

عندما تكتب أي COM باستخدام ال ATL فإنك سوف تجد أن الويزارد قام بوضع AFX_NOVTABLE عند الكلاس الذي تكتب فيه كود ال Interface لأنه في الحقيقة لانحتاج إلى vtable هنا وذلك بسبب أنك مطلقا وأبدا سوف تستخدم هذا الكلاس كobject وذلك بسبب أن ال ATL تسخدم كلاس آخر اسمه CComObject يرث من الكلاس تبعك والكلاس الناتج يتم منه بناء object بالذاكرة وبتم ارساله إلى ال client وهذا كله يتم في IClassFactory ... ومكنك الرجوع إلى كود ال Factory في ال ATL ...

هذا مالدي .. إذا عندك أي أسئلة فأنا جاهز ..
Adel Khayata
السلام عليكم.
الله ....الله .... الله .... الله regular_smile.gifregular_smile.gifregular_smile.gif
الله يسلم ايديك يا أخي هاني على هالشرح الممتاز و ال Professional يعني ما بعرف شو بدي قلك ؟؟ بس الله يوفقك و يبيض وجهك.
مع التحيات.
Smart Star
صراحه يا أخ هاني ، أنا لست بشاعر لكي أوفيك حقك في الشكر إن كانت الكلمات توفي بذلك أصلا .

جزاك الله خيرا.................
هاني الأتاسي
لا شكر على واجب أخي سمارت .. والله يوفقك في دراستك وفي علمك .. وإحنا كلنا لبعض ياشيخ ... regular_smile.gif
زهرة الكاميليا
شكراً كتير عن جد مواضيع مهمة و أنا كتير لح استفيد منهم أنا عم أدرس معلوماتية و كل شي عنا هي السنة بال c++
هذه "نسخة - خفيفة" من محتويات الرئيسية للإستعراض الكامل مع المزيد من الصور والخيارات الرجاء إضغط هنا.
Invision Power Board © 2001-2009 Invision Power Services, Inc.