Ekstensi PHP dan Kotlin Asli. Bagian ketiga, mungkin final

Pada bagian pertama , hal-hal yang cukup mendasar diceritakan tentang pengaturan alat dan konsep umum.

Bagian kedua adalah tentang, jadi, pendekatan pertama untuk proyektil, ide, rencana, rencana.

Dalam artikel ini akan ada sedikit lebih hardcore tentang C dan K / N interope, banyak makro, rasa sakit, keputusasaan dan "sinar kebaikan". Tentu saja akan ada bab dengan cerita tentang prestasi (Anda tidak akan memuji diri sendiri ... dan sebagai bonus, cerita tentang fakap epik.

Penafian: semua hal berikut dipertimbangkan dalam konteks penulisan perpustakaan untuk PHP.

Bab Satu Naif Interope


Cara menggunakan fungsi K / N di C dijelaskan di bagian pertama siklus. Oleh karena itu, di sini saya akan memberi tahu Anda cara menggunakan fungsi C di K / N.

Dokumentasi resmi agak pelit dan ringkas, namun untuk proyek-proyek sederhana, itu sudah cukup.

Singkatnya, Anda perlu membuat file khusus dengan ekstensi .def dan menentukan file header yang diperlukan di dalamnya.

headers = php.h 

Kemudian beri makan ke program yang disebut cinterop .

 # cinterop -def php.def -o php 

Pada output, Anda akan mendapatkan pustaka libphp.klib yang berisi bitcoin llvm dan berbagai informasi meta.

Kemudian Anda dapat dengan aman menggunakan fungsi dan makro yang dijelaskan dalam file header ( #define ), tanpa lupa menghubungkan perpustakaan pada tahap kompilasi.

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

Namun ada nuansa. Dan bukan satu.

Dalam bentuk seperti yang dijelaskan di atas, perpustakaan tidak akan dirakit


Mengapa Tetapi karena baris-baris berikut ada di php.h:

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

Di sini harus dicatat bahwa llvm masih terlibat dalam kompilasi perpustakaan, dan ia memiliki saklar -I , dan cinterop memiliki saklar -copt . Nah, Anda mengerti intinya. Akibatnya, perintah seperti itu cukup untuk mengkompilasi 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 

Makro. Aku cinta dan membencimu! Tidak, saya hanya membencinya.


Yang perlu Anda ketahui tentang #define dalam hal C> K / N interope adalah
Setiap makro C yang mengembang ke konstanta diwakili sebagai properti Kotlin. Makro lain tidak didukung.

Dan kemudian kita ingat bahwa ekstensi PHP adalah makro pada makro dan mendorong makro dan berusaha untuk tidak menangis.

Tapi tidak semuanya buruk. Untuk mengatasi situasi ini, pengembang K / N menyediakan gulungan pita listrik biru untuk melampirkan file def deklarasi kustom . Sepertinya ini (misalnya, ambil makro Z_TYPE_P )

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

Sekarang dalam kode K / N dimungkinkan untuk menggunakan fungsi __zp_get_arg_type

Bab Dua PHP INI-pengaturan atau sub-sub makro.


Ini adalah "sinar yang baik" menuju kode sumber PHP.

Ada 4 makro untuk mengekstraksi pengaturan:

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

Di mana val adalah string dengan nama pengaturan.

Sekarang mari kita lihat contoh INI_STR untuk melihat bagaimana makro ini didefinisikan.

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

Sudah memperhatikan "cacat fatal" -nya?

Jika tidak, izinkan saya memberi tahu Anda - ini adalah fungsi sizeof . Ketika Anda menggunakan makro secara langsung, maka semuanya baik-baik saja:

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

Ketika Anda menggunakannya melalui fungsi proxy dari file .def , carriage berubah menjadi labu, dan sizeof (nama) mengembalikan ukuran pointer. Sekakmat Kotlin Asli.

Bahkan, hanya ada dua opsi untuk mengelak.

  1. Gunakan bukan makro, tetapi fungsi yang melampirkannya.
  2. Fungsi pembungkus hardcode untuk setiap pengaturan yang diperlukan.

Opsi pertama lebih baik untuk semua orang daripada yang kedua, kecuali untuk satu poin - tidak ada yang akan menjamin bahwa deklarasi makro tidak akan berubah. Karena itu, untuk proyek saya, saya, dengan perasaan tidak puas yang mendalam, memilih opsi kedua.

Bab Tiga Debug? Debag apa?


Babak 1 - Interop.


Pada satu titik, setelah melampirkan pita biru ke file def 20 fungsi proxy berturut-turut, saya menerima kesalahan yang luar biasa.

 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) 


Mengomentari setengah, mengkompilasi ulang, jika komentar pada setengah sisanya diulang, kami kumpulkan ... Dan mengingat bahwa proses kompilasi header cukup lama ... (ya, sepertinya lebih cepat daripada memanjat selusin file sumber dan dengan cermat, dengan kaca pembesar, berdamai).

"Sinar kebaikan" kedua menuju JetBrains.


Babak 2 - runtime.


Saya mendapatkan kesalahan segmentasi runtime. Baiklah ok, itu terjadi. Saya naik ke debugger. Ummm ... STA?

 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. 


Bab Empat Saya menuangkan teh ke teh Anda sehingga Anda bisa minum teh saat Anda minum teh.


Di sini perlu untuk memberitahu bagaimana omong kosong yang saya buat bekerja.

Anda menulis DSL yang menggambarkan ekstensi PHP di masa depan, menulis kode K / N dengan implementasi fungsi, kelas dan metode, kemudian jalankan make dan, secara ajaib, dapatkan perpustakaan yang sudah jadi yang dapat dihubungkan ke PHP.

Majelis dapat dibagi menjadi 4 tahap:

  1. Membuat layer antara C dan K / N ( cinterop sama)
  2. Ekstensi pembuatan kode C
  3. Kompilasi pustaka dengan logika
  4. Mengkompilasi perpustakaan target

Tujuannya adalah untuk menambahkan kemampuan untuk membuat instance dari kelas PHP dalam kode K / N. Misalnya, agar kelas dapat mendefinisikan metode getInstance() . Dan saya ingin membuatnya agar nyaman digunakan.

Dalam C, masalah ini diselesaikan satu atau dua kali.

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

Tampaknya sederhana - bawa dan transfer ke K / N, tapi di sini adalah myClass ...

Tetapi myClass adalah variabel global dari tipe zend_class_entry* , dideklarasikan dalam kode C proyek dan dengan nama yang tidak diketahui sebelumnya.

Awasi tangan Anda. Anda perlu mengkompilasi pustaka dari kode K / N, di mana akan ada fungsi yang perlu memiliki akses ke myClass , yang didefinisikan dalam kode-C yang dibuat, tetapi tidak dikompilasi, dari mana fungsi ini kemudian akan dipanggil.

Pada akhirnya, implementasi fungsi ini mengarah pada penambahan dua artefak baru: .h dan .kt pada tahap pembuatan kode, komplikasi tahap cinterop dan epik phakap, yang akan saya bicarakan di bagian paling akhir.

Bab Lima Apa nama saya?


Kisah Mengapa:

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

lebih baik dari:

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

Ya, bahkan tidak perlu menjelaskannya. Inilah yang berubah menjadi ArgumentType.NULL dalam file header dari perpustakaan Kotlin:

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

Dan ini adalah bagaimana gcc bereaksi terhadap ini.

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

Tirai! Hati-hati dengan nama.

Bab kedua dari belakang. Anda tidak akan memuji diri sendiri - tidak ada yang akan memuji.


Pada umumnya, saya telah mencapai tujuan saya. Tenggelam dalam subjek, "kerangka kerja" untuk menulis ekstensi PHP di Kotlin Native, secara umum, sudah siap. Tetap menambahkan beberapa, bukan yang paling penting, fungsionalitas dan semir.

Proyek itu sendiri dan, saya harap, dokumentasi yang baik untuk itu, dapat dilihat di github .

Apa yang bisa saya katakan tentang K / N? Hanya baik Menulis tentang itu adalah kesenangan, dan beting kecil dan kekasaran dapat dikaitkan dengan fakta bahwa ia bahkan belum keluar dari buaian :)

Bab terakhir. Sinar yang baik, tanpa tanda kutip.


Dan sekarang, benar-benar serius dan dengan rasa hormat yang dalam, saya ingin mengucapkan terima kasih kepada orang-orang dari JetBrains dan penduduk saluran malas Kotlin Asli. Kamu super!

Dan terima kasih khusus kepada Nicholas Igotti .



Bonus Epic Fakap.


Konteksnya dijelaskan dalam bab keempat.

Sebenarnya, ketika semuanya ditambahkan ke keadaan di mana ia dikompilasi tanpa kesalahan, muncul masalah - selama pengujian, PHP dibuka untuk saya dari sisi yang sama sekali tidak dikenal.

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

"Figas!" - Saya pikir, saya masuk ke kode sumber PHP dan menemukan bagian seperti itu.

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

Menambahkan Debugging:

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

menyebabkan:

 undefined symbol: Z_IS_RECURSIVE_P in Unknown on line 0 

"Figas!" Saya berpikir lagi.

Pada saat itu, ketika saya menebak melihat php.h (7.1.8) sebenarnya digunakan pada host linux, dan bukan yang ditarik dari github dari master brunch (7.3.x), satu hari berlalu. Malu malu.

Tapi, ternyata, itu bukan gelendong.

Kode cek rekursi yang benar, pada semua tahap kehidupan objek yang saya kendalikan, melaporkan bahwa semuanya harus bekerja. Dan ini berarti Anda harus hati-hati melihat tempat-tempat yang tidak saya kendalikan. Tepat ada satu hal seperti itu - di mana objek saya dikembalikan ke fungsi var_dump

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

Buka RETURN_OBJ makro sampai akhir. Ambil dari monitor gugup dan hamil!

 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; } 

Di sini saya merasa malu untuk kedua kalinya. Saya, sepenuhnya pada mata biru saya, mendorong zval* ke tempat zend_object* menunggu dan menghabiskan hampir dua hari mencari kesalahan.

Terima kasih untuk semuanya Kotlin! :)

PS. Jika ada orang yang baik hati yang membaca bahasa Inggris saya yang canggung dan mengoreksi dokumentasinya - tidak akan ada batasan untuk rasa terima kasih saya.

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


All Articles