كيف قمنا بأتمتة منتجات النقل من C # إلى C ++

مرحبا يا هابر. في هذا المنشور ، سأتحدث عن كيف تمكنا من تنظيم إصدار شهري للمكتبات للغة C ++ ، تم تطوير شفرة المصدر الخاصة بها في C #. لا يتعلق الأمر بإدارة C ++ ، أو حتى إنشاء جسر بين C ++ غير المُدار وبيئة CLR - إنه يتعلق بأتمتة إنشاء رمز C ++ الذي يكرر واجهة برمجة التطبيقات ووظائف رمز C # الأصلي.

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

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

خلفية


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

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

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

لصالح الجدوى الرئيسية لمثل هذه الخطة ، تحدث ما يلي:

  1. من الناحية الإيديولوجية ، تتشابه لغة C # و Java تمامًا - على الأقل ، مع بنية الأنواع وتنظيم العمل مع الذاكرة ؛
  2. كان الأمر يتعلق بنقل المكتبات ؛ ولم تكن هناك حاجة إلى نقل واجهة المستخدم الرسومية ؛
  3. , , - , System.Net System.Drawing;
  4. , .Net ( Framework, Standard Xamarin), .

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

حدد نجاح اتجاه جافا رغبة الشركة في التوسع في أسواق جديدة لنفسها ، وفي عام 2013 أثير سؤال حول إصدار منتجات للغة C ++ في سيناريو مماثل.

صياغة المشكلة


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

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

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

بالإضافة إلى العتال نفسه ، كان مطلوبًا أيضًا تطوير مكتبة في C ++ من شأنها حل المشكلات التالية:

  1. مضاهاة بيئة .Net إلى الحد الذي يكون من الضروري أن يعمل الكود المستخرج ؛
  2. تكييف كود C # المستخرج مع حقائق C ++ (بنية النوع ، إدارة الذاكرة ، رمز الخدمة الأخرى) ؛
  3. صقل الفروق بين "إعادة كتابة C #" و C ++ نفسها ، لتسهيل على المبرمجين الذين ليسوا على دراية بنماذج .Net لاستخدام التعليمات البرمجية المنقولة.

لأسباب واضحة ، لم يتم إجراء أية محاولة لتعيين أنواع .Net مباشرة لأنواع من المكتبة القياسية. بدلاً من ذلك ، تقرر دائمًا استخدام أنواع من مكتبته كبديل لأنواع Donnet.

سيتساءل العديد من القراء على الفور عن سبب عدم استخدامهم للتطبيقات الحالية مثل Mono . كانت هناك أسباب لذلك.

  1. من خلال جذب مثل هذه المكتبة النهائية ، سيكون من الممكن تلبية الشرط الأول فقط ، ولكن ليس الثاني وليس الثالث.
  2. Mono C# , , , .
  3. (API, , , C++, ) , .
  4. , .Net, . , , .

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

ونتيجة لذلك ، تقرر تطوير المكتبة كمجموعة من المحولات التي توفر الوصول إلى الوظائف التي تم تنفيذها بالفعل في مكتبات الجهات الخارجية ، ولكن من خلال واجهة برمجة تطبيقات تشبه .Net (على غرار Java). سيؤدي ذلك إلى تقليل العمل واستخدام مكونات C ++ الجاهزة ، والمُحسّنة بالفعل.

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

كان الشرط الآخر هو القدرة على نقل المكتبات ليس فقط ، ولكن أيضًا اختباراتها. تفتخر الشركة بثقافة عالية من التغطية الاختبارية لمنتجاتها ، والقدرة على تشغيل نفس الاختبارات التي تمت كتابتها للشفرة الأصلية في C ++ من شأنه أن يبسط بشكل كبير البحث عن المشاكل بعد الترجمة.

تتعلق المتطلبات المتبقية (تنسيق الإطلاق ، وتغطية الاختبار ، والتكنولوجيا ، وما إلى ذلك) بشكل أساسي بأساليب العمل مع المشروع وفي المشروع. لن أطيل عليهم.

قصة


قبل المتابعة ، يجب أن أقول بضع كلمات حول هيكل الشركة. تعمل الشركة عن بعد ، ويتم توزيع جميع الفرق فيها. عادة ما يكون تطوير منتج معين مسؤولية فريق ، توحده اللغة (دائمًا تقريبًا) والجغرافيا (بشكل أساسي).

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

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

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

أصدر الفريق ، الذي واصل العمل بشكل مستقل في إطار مماثل ، أول إصدار لـ C ++ في عام 2018.

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

تنظيم العمل في المشروع


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

لأسباب مستقلة إلى حد كبير عن حالة هذا التطور الخاص ، تقرر حل الفريق "المركزي" ونقل الأشخاص إلى فرق "المنتج" ، التي أصبحت الآن مسؤولة عن إصلاح الإطار لاحتياجاتهم. في هذه الحالة ، سيتخذ كل فريق نفسه قرارًا بشأن ما إذا كان سيتم استخدام أساسه المشترك أو إنشاء شوكة خاصة به للمشروع. كان بيان السؤال ذا صلة بإطار Java ، الذي كان رمزه مستقرًا في ذلك الوقت ، ولكن كان مطلوبًا توحيد الجهود لملء مكتبة C ++ في أقرب وقت ممكن ، لذلك لا تزال الفرق تعمل معًا.

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

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

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

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

التقنيات


حان الوقت للحديث عن التقنيات المستخدمة في المشروع. Porter هو تطبيق وحدة تحكم مكتوب بلغة C # ، لأنه في هذا النموذج من الأسهل تضمينه في البرامج النصية التي تؤدي مهام مثل "اختبارات تشغيل ترجمة المنفذ." بالإضافة إلى ذلك ، هناك مكون واجهة المستخدم الرسومية الذي يسمح لك بتحقيق نفس الأهداف من خلال النقر على الأزرار.

مكتبة NRefactory القديمة هي المسؤولة عن تحليل التعليمات البرمجية وحل دلالات . لسوء الحظ ، في الوقت الذي بدأ فيه المشروع ، لم تكن Roslyn متاحة بعد ، على الرغم من أن الهجرة إليه ، بالطبع ، في خططنا.

يستخدم بورتر ممرات الخشب ASTلجمع المعلومات وإنشاء رمز إخراج C ++. عند إنشاء رمز C ++ ، لا يتم إنشاء تمثيل AST ، ويتم حفظ كافة التعليمات البرمجية كنص عادي.

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

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

وبالتالي ، يتم تحويل تجميع C # الأصلي إلى مشروع C ++ ، والذي يعتمد بدلاً من مكتبات .Net على مكتبتنا المشتركة. يظهر هذا في الرسم التخطيطي التالي:



يتم استخدام cmake لبناء المكتبة والمشاريع المنقولة. المترجمون VS 2017 و 2019 (Windows) و GCC و Clang (Linux) مدعومون حاليًا.

كما ذكر أعلاه ، فإن معظم عمليات تنفيذ .Net الخاصة بنا عبارة عن طبقات رقيقة من مكتبات الجهات الخارجية التي تقوم بمعظم العمل. ويشمل:

  • Skia - للعمل مع الرسومات ؛
  • Botan - لدعم وظائف التشفير ؛
  • ICU - للعمل مع الأوتار والترميزات والثقافات ؛
  • Libxml2 - للعمل مع XML ؛
  • PCRE2 - للعمل مع التعبيرات العادية ؛
  • zlib - لتنفيذ وظائف الضغط ؛
  • زيادة - لأغراض مختلفة ؛
  • عدة مكتبات أخرى.

يتم تغطية كل من البواب والمكتبة في العديد من الاختبارات. تستخدم اختبارات المكتبة إطار عمل gtest. اختبارات بورتر مكتوبة بشكل أساسي في NUnit / xUnit وتنقسم إلى عدة فئات ، مما يشهد على ما يلي:

  • يطابق إخراج الحمال في ملفات الإدخال هذه الهدف ؛
  • يتطابق إخراج البرامج المنقولة بعد التجميع والإطلاق مع الهدف ؛
  • يتم تحويل اختبارات NUnit من مشاريع المدخلات بنجاح إلى اختبارات gtest في المشاريع المنقولة وتمريرها ؛
  • يعمل API Ported Projects بنجاح في C ++؛
  • تأثير الخيارات والسمات الفردية على عملية الترجمة كما هو متوقع.

نستخدم GitLab لتخزين شفرة المصدر . تم اختيار Jenkins كبيئة CI . تتوفر المنتجات المنقولة في شكل حزم Nuget وأرشيفات للتنزيل.

مشاكل


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

  1. .Net C++.
    , C++ Object, RTTI. .Net STL.
  2. .
    , , . , C# , C++ — .
  3. .
    — . , . , .
  4. .
    C++ , , .
  5. C#.
    C# , C++. , :

    • , ;
    • , (, yeild);
    • , (, , , C#);
    • , C++ (, C# foreground-).
  6. .
    , .Net , .
  7. .
    - , , «» , . , , , , using, -. . , .
  8. .
    , , , , , / - .
  9. .
    . , . , , , .
  10. صعوبات في حماية الملكية الفكرية.
    إذا تم تشفير رمز C # بسهولة إلى حد ما عن طريق الحلول المعبأة ، فيجب عليك بذل جهود إضافية في C ++ ، حيث لا يمكن حذف العديد من أعضاء الفصل من ملفات الرأس دون عواقب. إن ترجمة الفئات والأساليب العامة إلى قوالب يخلق أيضًا نقاط ضعف من خلال كشف الخوارزميات.

على الرغم من ذلك ، فإن المشروع مثير للاهتمام للغاية من الناحية الفنية. يتيح لك العمل عليها أن تتعلم الكثير وتتعلم الكثير. كما تساهم الطبيعة الأكاديمية للمهمة في ذلك.

ملخص


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

ومن المقرر قريبا نشر مقالتين أخريين. سيصف أحدهم بالتفصيل ، مع أمثلة ، كيف يعمل الحمال وكيف يتم عرض تصميمات C # في C ++. في خطاب آخر ، سنتحدث عن كيفية تمكننا من ضمان توافق نماذج الذاكرة للغتين.

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

All Articles