GObject: التغليف ، الاستنساخ ، الاستبطان

... وكذلك كلمات مخيفة أخرى! (ج)

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

الصورة

الدورة الكاملة حول GObject:


GObject: الأساسيات
GObject: الميراث والواجهات
GObject: التغليف ، الاستنساخ ، الاستبطان

الهياكل الكثير من الهياكل.


كما نعلم ، يمكن أن يرث أحفاد GObject - مشتق وغير موروث - نهائي. بشكل عام ، تتكون GObject القابلة للاشتقاق من مجموعة من ثلاثة كائنات: بنية فئة ، وهيكل مثيل ، وهيكل مع بيانات خاصة.

مع بنية الفصل ، كل شيء أكثر أو أقل بساطة - يتم وصفه في ملف الرأس ويحتوي على مثيل من بنية فئة الوالدين ومؤشرات الوظائف - "الأساليب الافتراضية". من الممارسات الجيدة إضافة مجموعة صغيرة من مؤشرات الفراغ إلى الحقل الأخير من الهيكل لضمان توافق ABI. يتم إنشاء مثيل لمثل هذه البنية في مثيل واحد عند إنشاء المثيل الأول من هذا النوع.

/* animalcat.h */ /*   ,      :) */ typedef struct _AnimalCat AnimalCat; typedef struct _AnimalCatClass AnimalCatClass; typedef struct _AnimalCatPrivate AnimalCatPrivate; struct _AnimalCatClass { GObjectClass parent_class; /*    */ void (*say_meow) (AnimalCat*); /*   */ gpointer padding[10]; /*  ; gpointer -  void* */ }; 

بالنسبة للأنواع النهائية ، ليست هناك حاجة لتعريف البنية الطبقية.

هناك حاجة إلى بنية مع بيانات خاصة للكائنات القابلة للاشتقاق. يتم تعريفه في ملف شفرة المصدر ، ويمكن الوصول إليه من خلال وظيفة يتم إنشاؤها تلقائيًا من النموذج animal_cat_get_instance_private (). في هذه الحالة ، يجب أن يبدو الماكرو في بداية ملف .s مثل G_DEFINE_TYPE_WITH_PRIVATE (NamespaceObject، namespace_object، PARENT_TYPE). يمكنك استخدام الماكرو G_DEFINE_TYPE_WITH_CODE (مع الماكرو G_ADD_PRIVATE المتضمن).

 /* animalcat.c */ #include "animalcat.h" G_DEFINE_TYPE_WITH_PRIVATE(AnimalCat, animal_cat, G_TYPE_OBJECT) /* G_DEFINE_TYPE_WITH_CODE(AnimalCat, animal_cat, G_TYPE_OBJECT, G_ADD_PRIVATE (AnimalCat)) */ struct _AnimalCatPrivate { char* name; double weight; int age; }; static void animal_cat_init(AnimalCat* self) { AnimalCatPrivate* priv = animal_cat_get_instance_private(self); priv->age = 0; priv->name = "Barsik"; /*    */ } 

يفترض أن تكون جميع البيانات مغلفة. للوصول إليها ، يمكنك استخدام الأغلفة المعتادة - getters و الإعدادية ، ولكن ، كما سنرى لاحقًا ، يوفر GObject أداة أكثر قوة لهذا - الخصائص.

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

 /* animaltiger.c */ struct _AnimalTiger { AnimalCat parent; /*         */ int speed; /*   */ }; 

أما بالنسبة للواجهات ، فمن الضروري لتطبيقها تحديد بنية الواجهة فقط ، وهي مشابهة جدًا للفئة المعتادة. سيتم إنشاء بنية كائن عرض _AnimalPredator نفسه تلقائيًا.

 /* animalpredator.h */ typedef struct _AnimalPredatorInterface AnimalPredatorInterface; struct _AnimalPredatorInterface { GTypeInterface parent; /*     GTypeInterface */ void (*hunt) (AnimalPredator* self); /*   */ }; 


جدول سرير بصري:
الصورة

كشف النوع الديناميكي في الممارسة


في ملفات الرأس ، بدأنا وصف النوع الجديد باستخدام وحدتي ماكرو ، والتي بدورها يتم تحويلها إلى مجموعة كاملة من تعريفات الماكرو. في الإصدارات القديمة من GLib ، كان من الضروري وصف كل مجموعة الأدوات هذه يدويًا. دعونا نرى أيًا من هذه يمكننا استخدامها.

ANIMAL_TYPE_CAT: إرجاع معرف صحيح من نوع GType. يرتبط هذا الماكرو ارتباطًا وثيقًا بنظام نوع GType الذي يقوم عليه GObject. سوف تقابله بالتأكيد ، لقد ذكرته فقط حتى يتضح من أين أتى. يتم إنشاء وظائف النموذج animal_cat_get_type () الذي يستخدم تعريف الماكرو هذا تلقائيًا في الملف المصدر عند توسيع وحدات الماكرو من عائلة G_DEFINE_TYPE.

ANIMAL_CAT (كائن): إرسال إلى مؤشر من هذا النوع. يوفر طبقات آمنة ، ويؤدي أيضًا فحوصات وقت التشغيل. كما ترى ، يعتمد نظام الوراثة في GObject بشكل عام على حقيقة أن الهياكل تحتوي على الحقل الأول كمثيل للبنية الأم ، مما يعني أنه ، وفقًا لاتفاقيات استدعاء C ، يتطابق المؤشر مع الكائن مع المؤشر مع جميع الأسلاف التي ورثها. على الرغم من ذلك ، من المستحسن استخدام الماكرو المقدم ، بدلاً من C-cast المعتاد. بالإضافة إلى ذلك ، في بعض الحالات (على سبيل المثال ، عند الإرسال إلى نوع واجهة مطبقة) ، لن يعمل الإرسال على غرار C على الإطلاق.

ANIMAL_CAT_CLASS (klass): ماكرو مشابه لهياكل الصف. تنص الاتفاقية على عدم استخدام فئة الكلمة للتوافق مع برامج التحويل البرمجي لـ C ++.

ANIMAL_IS_CAT (obj): كما يوحي الاسم ، يحدد هذا الماكرو ما إذا كان obj هو مؤشر لهذا النوع (وما إذا كان مؤشر NULL). من الجيد أن تبدأ طرق الكائن بمثل هذا الفحص.

 void animal_cat_run (AnimalCat *self) { assert(ANIMAL_IS_CAT (self)); g_return_if_fail (ANIMAL_IS_CAT (self)); /*   GLib */ /*   */ } 

ANIMAL_IS_CAT_CLASS (klass): نفس الشيء بالنسبة لهياكل الصف.

ANIMAL_CAT_GET_CLASS (obj): إرجاع مؤشر إلى بنية الفئة المقابلة.

يتم أيضًا إنشاء مجموعة مماثلة من تعريفات الماكرو للواجهات.

ANIMAL_PREDATOR (الهدف): إرسال إلى نوع الواجهة.
ANIMAL_IS_PREDATOR (الهدف): تدقيق النوع.
ANIMAL_PREDATOR_GET_IFACE (الهدف): الحصول على بنية الواجهة.

يمكن الحصول على اسم الكائن باستخدام الماكرو G_OBJECT_TYPE_NAME (obj) ، الذي يُرجع سلسلة si باسم النوع.

تقوم وحدات الماكرو الموجودة في بداية الملف المصدر G_DEFINE_TYPE وإصداراته الموسعة بإنشاء مؤشر للنموذج animal_cat_parent_class ، والذي يعيد المؤشر إلى بنية فئة الكائن الأصل ، بالإضافة إلى دالة للنموذج animal_cat_get_instance_private () ، إذا استخدمنا الماكرو المقابل.

المدمرات والوظائف الافتراضية الأخرى


كما نتذكر ، عند إنشاء أي سلالة GObject ، يتم تشغيل وظائف الشكل animal_cat_init (). إنهم يؤدون نفس دور C ++ و Java. مع المدمرين ، فإن الوضع أكثر تعقيدًا.

يتم تنفيذ إدارة الذاكرة في GObject باستخدام الحساب المرجعي. عندما يتم استدعاء الدالة g_object_new () ، يتم تعيين عدد الارتباطات على واحد. في المستقبل ، يمكننا زيادة عددهم باستخدام g_object_ref () وخفضه مع g_object_unref (). عندما يصبح عدد الوصلات صفرًا ، ستبدأ عملية تدمير الكائن المكون من مرحلتين. أولاً ، يتم استدعاء دالة التخلص () ، والتي يمكن استدعاؤها عدة مرات. وتتمثل مهمتها الرئيسية في حل المراجع الدائرية إذا لزم الأمر. بعد ذلك ، يتم استدعاء وظيفة finalize () مرة واحدة ، حيث يتم تنفيذ كل ما تستخدمه المدمرات عادةً - يتم تحرير الذاكرة وإغلاق واصفات الملفات المفتوحة ، وما إلى ذلك.

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

تتصرف الدالات () وإنهاء () ، بالإضافة إلى عدد من الوظائف الأخرى ، التي سنتحدث عنها لاحقًا ، وهي افتراضية ويتم تعريفها في GObjectClass.

 static void animal_cat_finalize(GObject* obj) { g_print("Buy!\n"); /*  printf()  GLib */ /*    . . */ G_OBJECT_CLASS (animal_cat_parent_class)->finalize(obj); /*         */ } static void animal_cat_class_init(AnimalCatClass* klass) { GObjectClass* obj_class = G_OBJECT_CLASS (klass); obj_class->finalize = animal_cat_finalize; /*   */ } 

قد يبدو أن السطر الأخير من دالة animal_cat_finalize () يتطلب مزيدًا من التوضيح. يتم إنشاء مؤشر animal_cat_parent_class للفئة الرئيسية عندما يتم توسيع الماكرو G_DEFINE_TYPE وإصداراته الموسعة. نحن نسمي الوظيفة المقابلة من الفئة الرئيسية ، والتي في هذه الحالة هي بنية GObjectClass مباشرة ، وهي بدورها تستدعي إنهاء () من الفئة السابقة في السلسلة. لا داعي للقلق من أن الفصل الأصل قد لا يحتوي على تجاوزات () النهائية ؛ ستهتم GObject بهذا.

يبقى فقط أن نتذكر أن المدمر يتم استدعاؤه فقط عندما يكون العداد المرجعي صفرًا:

 int main(int argc, char** argv) { AnimalCat* cat = animal_cat_new(); g_object_unref(cat); /*      */ } 

بالإضافة إلى مدمرين ، يحتوي GObjectClass على مُنشئين افتراضيين إضافيين. يُدعى مُنشئ () قبل الحيوان_المعروف بالفعل () ويقوم بإنشاء مثيل من هذا النوع مباشرة () - بعد. ليس من السهل التوصل إلى موقف تحتاج فيه إلى إعادة تعريف هذه الوظائف ، ما لم تقرر بالطبع تصحيح GLib نفسه. في الوثائق ، يعطي المطورون مثالًا على تنفيذ المفرد ، ولكن في الكود الحقيقي لم أر مثل هذه الحالات. ومع ذلك ، لتحقيق أقصى قدر من المرونة في جميع مراحل دورة حياة مثيل المنشأة ، اعتبر المطورون أنه من الضروري جعل هذه الوظائف افتراضية.

بالإضافة إلى ذلك ، يحتوي GObjectClass على وظائف افتراضية get_property () و set_property () ، والتي يجب إعادة تعريفها لاستخدام ميزات قوية من نوع GObject الأساسي ونسله كخصائص في كائناتهم الخاصة. سنتحدث عن هذا في المقالة القادمة.

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


All Articles