تمديد PHP و Kotlin الأصلي. الجزء الثالث ، ربما نهائي

في الجزء الأول ، يتم سرد أشياء أساسية تمامًا حول إعداد الأدوات والمفاهيم العامة.

الجزء الثاني حول ، إذا جاز التعبير ، النهج الأول للمقذوفات والأفكار والخطط والخطط.

في هذه المقالة سيكون هناك المزيد من المتشددين حول C و K / N interope والكثير من وحدات الماكرو والألم واليأس و "أشعة الخير". بالطبع سيكون هناك فصل مع قصة عن الإنجازات (لن تمدح نفسك ... وكمكافأة ، قصة عن ملحمة fakap.

إخلاء المسؤولية: يتم مراعاة كل ما يلي في سياق كتابة مكتبة لـ PHP.

الفصل الأول Interope ساذجة


يتم وصف كيفية استخدام وظائف K / N في C في الجزء الأول من الدورة. وفقًا لذلك ، سأخبرك هنا بكيفية استخدام وظائف C في K / N.

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

باختصار ، تحتاج إلى إنشاء ملف خاص بامتداد .def وتحديد ملفات الرأس الضرورية فيه.

headers = php.h 

ثم قم بإطعامه إلى برنامج يسمى cinterop .

 # cinterop -def php.def -o php 

عند الإخراج ، ستحصل على مكتبة libphp.klib التي تحتوي على llvm bitcode والمعلومات الوصفية المختلفة.

ثم يمكنك استخدام الوظائف ووحدات الماكرو الموضحة في ملف الرأس ( #define ) بأمان ، دون نسيان توصيل المكتبة في مرحلة التجميع.

 # kotlinc -opt -produce static ${SOURCES} -l libphp.klib -o myLib 

ولكن هناك فارق بسيط. ولا واحد.

في النموذج كما هو موضح أعلاه ، لن يتم تجميع المكتبة


لماذا؟ ولكن لأن الأسطر التالية موجودة في php.h:

 #include "php_version.h" #include "zend.h" #include "zend_sort.h" #include "php_compat.h" #include "zend_API.h" 

هنا تجدر الإشارة إلى أن llvm لا تزال تعمل في تجميع المكتبة ، ولديها مفتاح -I ، و cinterop لديه مفتاح -copt . حسنًا ، فهمت النقطة. ونتيجة لذلك ، يكفي مثل هذا الأمر لتجميع php.h.

 # cinterop -def my.def -o myLib -I${PHP_LIB_ROOT} -copt -I${PHP_LIB_ROOT} \ -copt -I${PHP_LIB_ROOT}/main \ -copt -I${PHP_LIB_ROOT}/Zend \ -copt -I${PHP_LIB_ROOT}/TSRM 

وحدات الماكرو. أنا أحبك وأكرهك! لا ، أنا أكره ذلك فقط.


كل ما تريد معرفته عن #define من حيث C> K / N interope هو
يتم تمثيل كل ماكرو C الذي يمتد إلى ثابت كخاصية Kotlin. وحدات الماكرو الأخرى غير معتمدة.

ثم نتذكر أن ملحق PHP هو ماكرو على ماكرو ويدفع الماكرو وحاول ألا تبكي.

ولكن ليس كل شيء سيئ للغاية. للتغلب على هذه الحالة ، قدم مطورو K / N لفة من الشريط الكهربائي الأزرق لإرفاق ملف def تعريفات مخصصة . يبدو هذا (على سبيل المثال ، خذ الماكرو Z_TYPE_P )

 headers = php.h --- static inline zend_uchar __zp_get_arg_type(zval *z_value) { return Z_TYPE_P(z_value); } 

الآن في كود K / N سيكون من الممكن استخدام وظيفة __zp_get_arg_type

الفصل الثاني إعدادات PHP INI أو ماكرو فرعي.


هذا "شعاع جيد" تجاه شفرة مصدر PHP.

هناك 4 وحدات ماكرو لاستخراج الإعدادات:

 INI_INT(val) INI_FLT(val) INI_STR(val) INI_BOOL(val) 

حيث val عبارة عن سلسلة باسم الإعداد.

الآن دعونا نلقي نظرة على مثال INI_STR لمعرفة كيفية تعريف هذا الماكرو.

 #define INI_STR(name) zend_ini_string_ex((name), sizeof(name)-1, 0, NULL) 

هل لاحظت بالفعل "عيبه القاتل"؟

إذا لم يكن الأمر كذلك ، دعني أخبرك - هذه هي وظيفة sizeof . عند استخدام الماكرو مباشرةً ، يكون كل شيء على ما يرام:

 php_printf("The value is : %s", INI_STR("my.ini")); 

عندما تستخدمه من خلال وظيفة وكيل من ملف .def ، يتحول حامل الخراطيش إلى قرع ، ويعيد sizeof (name) حجم المؤشر. كش ملك Kotlin الأصلي.

في الواقع ، هناك خياران فقط للتحايل.

  1. لا تستخدم وحدات الماكرو ، ولكن الوظائف التي ترتبط بها.
  2. وظائف غلاف الكود الصلب لكل إعداد مطلوب.

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

الفصل الثالث تصحيح؟ ما debag؟


قانون 1 - Interop.


عند نقطة واحدة ، بعد إرفاق الشريط الأزرق بملف def لـ 20 وظيفة وكيل متتالية ، تلقيت خطأً رائعًا.

 Exception in thread "main" java.lang.Error: /tmp/tmp399964332777824085.c:103:38: error: too many arguments to function call, expected 2, have 3 at org.jetbrains.kotlin.native.interop.indexer.UtilsKt.ensureNoCompileErrors(Utils.kt:137) at org.jetbrains.kotlin.native.interop.indexer.IndexerKt.indexDeclarations(Indexer.kt:902) at org.jetbrains.kotlin.native.interop.indexer.IndexerKt.buildNativeIndexImpl(Indexer.kt:892) at org.jetbrains.kotlin.native.interop.indexer.NativeIndexKt.buildNativeIndex(NativeIndex.kt:56) at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.processCLib(main.kt:283) at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.interop(main.kt:38) at org.jetbrains.kotlin.cli.utilities.InteropCompilerKt.invokeInterop(InteropCompiler.kt:100) at org.jetbrains.kotlin.cli.utilities.MainKt.main(main.kt:29) 


التعليق على النصف ، وإعادة الترجمة ، إذا تكرر التعليق على نصف الباقي ، فإننا نجمع ... وبالنظر إلى أن عملية تجميع الرؤوس طويلة بما يكفي ... (نعم ، يبدو أسرع من تسلق اثني عشر ملفات مصدر وبدقة ، مع عدسة مكبرة ، التوفيق).

يذهب "شعاع الخير" الثاني نحو JetBrains.


قانون 2 - وقت التشغيل.


أحصل على خطأ تجزئة وقت التشغيل. حسنًا ، هذا يحدث. أنا أتسلق المصحح. أمم ... ستا؟

 Program received signal SIGSEGV, Segmentation fault. kfun:kotlinx.cinterop.toKString@kotlinx.cinterop.CPointer<kotlinx.cinterop.ByteVarOf<kotlin.Byte>>.()kotlin.String () at /opt/buildAgent/work/4d622a065c544371/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt:402 402 /opt/buildAgent/work/4d622a065c544371/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt: No such file or directory. 


الفصل الرابع سكبت الشاي في الشاي الخاص بك حتى تتمكن من شرب الشاي أثناء شرب الشاي.


هنا من الضروري أن نقول كيف يعمل هذا الهراء الذي أقوم به.

تكتب DSL تصف امتداد PHP المستقبلي ، وتكتب كود K / N مع تنفيذ الوظائف والفئات والأساليب ، ثم تقوم بتشغيل make ، وتحصل على مكتبة جاهزة يمكن توصيلها بـ PHP بأعجوبة.

يمكن تقسيم التجميع إلى 4 مراحل:

  1. إنشاء طبقة بين C و K / N (نفس cinterop )
  2. تمديد كود C توليد
  3. تجميع مكتبة بالمنطق
  4. تجميع المكتبة المستهدفة

الهدف هو إضافة القدرة على إنشاء مثيلات فئة PHP في كود K / N. على سبيل المثال ، بحيث يمكن للفئة تعريف طريقة getInstance() . وأريد أن أجعلها مريحة للاستخدام.

في لغة C ، يتم حل هذه المشكلة مرة أو مرتين.

 zval *obj = malloc(sizeof(zval)); object_init_ex(obj, myClass); 

قد يبدو الأمر بسيطًا - myClass إلى K / N ، ولكن هنا myClass ...

لكن myClass هو متغير عام من نوع zend_class_entry* ، معلن في الكود C للمشروع وباسم غير معروف مقدمًا.

راقب يديك. تحتاج إلى تجميع المكتبة من كود K / N ، حيث ستكون هناك وظيفة تحتاج إلى الوصول إلى myClass ، والتي تم تعريفها في كود C الذي تم إنشاؤه ، ولكن لم يتم تجميعه ، والذي سيتم من خلاله استدعاء هذه الوظيفة.

في نهاية المطاف ، أدى تنفيذ هذه الوظيفة إلى إضافة اثنين من القطع الأثرية الجديدة: .h و. kt في مرحلة إنشاء التعليمات البرمجية ، وتعقيد مرحلة cinterop و phakap الملحمي ، والذي سأتحدث عنه في النهاية.

الفصل الخامس ما في اسمي؟


حكاية لماذا:

 enum class ArgumentType { PHP_STRING, PHP_LONG, PHP_DOUBLE, PHP_NULL, ... } 

أفضل من:

 enum class ArgumentType { STRING, LONG, DOUBLE, NULL, ... } 

نعم ، حتى ليست هناك حاجة لشرح ذلك. هذا ما يتحول إليه ArgumentType.NULL في ملف الرأس لمكتبة Kotlin:

 struct { extension_kt_kref_php_extension_dsl_ArgumentType (*get)(); /* enum entry for NULL. */ } NULL; 

وهذه هي الطريقة التي يتفاعل بها مجلس التعاون الخليجي مع هذا.

 /root/simpleExtension/phpmodule/extension_kt_api.h:113:17: error: expected identifier or '(' before 'void' } NULL; ^ 

الستارة! احترس من الأسماء.

الفصل قبل الأخير. لن تمدح نفسك - لن يمدح أحد.


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

المشروع نفسه ، وآمل ، يمكن عرض وثائق جيدة له على github .

ماذا يمكنني أن أقول عن K / N؟ جيد فقط. الكتابة عليه متعة ، ويمكن أن تعزى الضحكات الصغيرة والخشونة إلى حقيقة أنه لم يخرج حتى من المهد :)

الفصل الأخير. أشعة جيدة ، بدون اقتباسات.


والآن ، على محمل الجد وباحترام عميق ، أود أن أشكر الرجال من JetBrains وسكان قناة Kotlin Native Slack. أنت رائع!

وشكر خاص لنيكولاس إيجوتي .



مكافأة ملحمة فاكاب.


يتم وصف السياق في الفصل الرابع.

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

 # php -dextension=./phpmodule/modules/extension.so -r "var_dump(ExampleClass::getInstance());" *RECURSION* # 

"فيجاس!" - فكرت ، لقد دخلت في شفرة مصدر PHP ووجدت قطعة من هذا القبيل.

 case IS_OBJECT: if (Z_IS_RECURSIVE_P(struc)) { PUTS("*RECURSION*\n"); return; } 

مضيفا التصحيح:

 printf("%u", Z_IS_RECURSIVE_P(struc)) 

أدى إلى:

 undefined symbol: Z_IS_RECURSIVE_P in Unknown on line 0 

"فيجاس!" فكرت مرة أخرى.

في تلك اللحظة ، عندما خمنت أن أنظر إلى php.h (7.1.8) المستخدم بالفعل على مضيف لينكس ، وليس الذي تم سحبه من github من وجبة الغداء الرئيسية (7.3.x) ، مر يوم. يخجل مباشرة.

ولكن ، كما اتضح ، لم يكن بكرة.

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

 RETURN_OBJ( example_symbols()->kotlin.root.php.extension.proxy.objectToZval( example_symbols()->kotlin.root.exampleclass.getInstance(/* */) ) ) 

افتح الماكرو RETURN_OBJ حتى النهاية. الابتعاد عن الشاشات العصبية والحوامل!

 1) RETURN_OBJ(r) 2) { RETVAL_OBJ(r); return; } 3) { ZVAL_OBJ(return_value, r); return; } 4) { do { zval *__z = (return_value); Z_OBJ_P(__z) = (r); Z_TYPE_INFO_P(__z) = IS_OBJECT_EX; } while (0); return; } 5) { do { zval *__z = (return_value); Z_OBJ(*(__z)) = (r); Z_TYPE_INFO(*(__z)) = (IS_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT)); } while (0); return; } 6) { do { zval *__z = (return_value); (*(__z)).value.obj = (r); (*(__z)).u1.type_info = (8 | ((1<<0) << 8)); } while (0); return; } 

هنا شعرت بالخجل للمرة الثانية. أنا ، على عيني الأزرق تمامًا ، دفعت zval* إلى حيث كانت zend_object* تنتظر وقضيت يومين تقريبًا في البحث عن الخطأ.

شكرا للجميع Kotlin! :)

ملاحظة. إذا كانت هناك روح طيبة تقرأ لغتي الإنجليزية الخرقاء وتصحح الوثائق - فلن يكون هناك حد لامتناني.

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


All Articles