Wie man die Erweiterung in PHP7 schwieriger macht als "Hallo Welt" und nicht rotäugig wird. Teil 1

Warum?


Ich schreibe diesen Artikel, damit der Leser den Weg, den ich insgesamt mindestens ein Jahr eingeschlagen habe, in ein paar Stunden gehen kann. Wie meine persönlichen Erfahrungen gezeigt haben, ist das einfache Programmieren in C etwas einfacher als eine ernsthafte Erweiterung für PHP. Hier werde ich Ihnen so viel wie möglich darüber erzählen, wie Sie eine Erweiterung am Beispiel der libtrie- Bibliothek erstellen , die den Präfixbaum implementiert, besser bekannt als trie. Ich werde die beschriebenen Aktionen auf einem frisch installierten Lubuntu 18.04-System schreiben und gleichzeitig ausführen.

Fangen wir an.

Softwareinstallation


Php


  1. Zuerst haben wir das php7.2-dev-Paket eingefügt, darin das phpize-Skript, das zum Erstellen der Erweiterung benötigt wird. Zusätzlich benötigen wir eine funktionierende Version von PHP, auf der wir unsere Erweiterung überprüfen werden. Durch die Installation dieses Pakets werden eine Reihe von abhängigen Paketen aufgerufen. Wir stellen alles bereit, was angeboten wird.

    sudo apt install php7.2-dev 

  2. Wir gehen auf die php.net-Website, gehen zum Download-Bereich und ziehen einen Link zum Archiv mit der neuesten stabilen Version von php, jetzt ist es Version 7.2.11.
    Laden Sie das PHP-Quellarchiv herunter:

     cd /tmp && wget http://it2.php.net/get/php-7.2.11.tar.gz/from/this/mirror -O php7.tar.gz 

  3. Packen Sie nun das Archiv für uns aus:

     sudo tar -xvf php7.tar.gz -C /usr/local/src 


Code-Editoren


Ich benutze normalerweise 2 Code-Editoren. Einfach und schnell, ziemlich nerdig, aber sehr fortschrittlich von JetBrains. Geany Installation von der Standard-Rübe Ubuntu.

 sudo apt install geany 

Laden Sie Clion von der offiziellen JetBrains-Website herunter:

 cd ~/Downloads && wget https://download.jetbrains.com/cpp/CLion-2018.2.5.tar.gz -O clion.tar.gz 

 sudo tar -xvf clion.tar.gz -C /usr/share 

Lassen Sie uns einen Link erstellen, damit Clion bequem von der Konsole aus ausgeführt werden kann.

 sudo ln -s /usr/share/clion-2018.2.5/bin/clion.sh /usr/bin/clion 

Nach dem ersten Start erstellt clion selbst Verknüpfungen aus dem LXpanel-Shell-Menü, die Sie jedoch zum ersten Mal manuell ausführen müssen.

 # clion 

Erweiterung erstellen


Hier haben wir mindestens 3 Möglichkeiten:

  1. Nehmen Sie die rohe Standard-CD aus den von uns heruntergeladenen PHP-Quellen.
  2. Ich habe die Standard-CD mit einem speziellen ext_skel-Skript ein wenig gesehen
  3. Holen Sie sich hier eine gute minimalistische CD.

Ich mag die dritte Option am meisten, aber ich werde die zweite im Falle eines Fehlers verwenden, um die Anzahl der Stellen zu minimieren, an denen ich mich irren könnte. Obwohl es immer noch ein Vergnügen ist, einen Rohling von Entwicklern auszuwählen :-)

  1. Gehen wir zum Verzeichnis mit den Standard-PHP-Erweiterungen.

     cd /usr/local/src/php-7.2.11/ext 

    Zusätzlich zum Namen des Skripts können Sie einige Erweiterungsparameter über die Protodatei angeben. All dies kann nicht getan werden. Ich werde alles von Hand machen, aber ich werde zeigen, wie Proto funktioniert. Wir versuchen es, also nennen wir unsere Erweiterung libtrie. Um im Verzeichnis / usr / local / src arbeiten zu können, benötigen Sie Administratorrechte. Um nicht sudo zu schreiben, werde ich bash mit erhöhten Rechten einschließen.

     sudo bash 

  2. Hier werde ich die Parameter von nur 1 Funktion einstellen, die die erstellte Erweiterung implementieren wird. Dies ist nur eine Demofunktion, um zu zeigen, wie dies gemacht wird.

    Wir werden ein vollständiges Analogon der Standardfunktion erstellen

     array array_fill ( int $start_index , int $num , mixed $value ) 

    Die Syntax in der Protodatei ist sehr einfach, Sie müssen nur den Namen der Funktion angeben. Ich werde etwas mehr schreiben, damit es informativer aussieht.

     echo my_array_fill \( int start_index , int num , mixed value \) >> libtrie.proto 

  3. Führen Sie nun das Skript ext_skel aus und geben Sie ihm den Namen der Erweiterung und die von uns erstellte Protodatei.

     ./ext_skel --extname=libtrie --proto=./libtrie.proto 

  4. Wir haben ein Verzeichnis mit unserer Erweiterung erstellt. Lass uns darauf eingehen.

     cd libtrie 


Dateistruktur und Assemblierungsprinzip


Dateistruktur
 config.m4 -           phpize   ./configure,       makefile. CREDITS -  ,     ,    libtrie.c -      php_libtrie.h -     config.w32 -        windows EXPERIMENTAL -  .    ,    . libtrie.php -  php      . tests -   


Um die Erweiterung erfolgreich zu erstellen, benötigen Sie nur 3 Dateien. Auf der oben erwähnten minimalistischen CD der Erweiterung befinden sich nur 3 Dateien.

 config.m4 php_libtrie.h libtrie.c 

Ich mag die in PHP akzeptierte Standardbenennung nicht, ich mag es, wenn die Header-Dateien und Dateien mit dem Programmkörper den gleichen Namen haben. Deshalb umbenennen
libtrie.c
in
php_libtrie.c

 mv libtrie.c php_libtrie.c 

Bearbeiten von config.m4


Die Standarddatei config.m4 ist buchstäblich mit Inhalten überfüllt, deren Fülle verwirrend und verwirrend ist. Wie gesagt, diese Datei wird benötigt, um ein Konfigurationsskript zu erstellen. Lesen Sie hier mehr darüber.

 geany config.m4 & 

Wir lassen nur das:

 PHP_ARG_ENABLE(libtrie, whether to enable libtrie support, [ --enable-libtrie Enable libtrie support]) if test "$PHP_LIBTRIE" != "no"; then #    -    # PHP_ADD_INCLUDE() #   PHP_NEW_EXTENSION(libtrie, php_libtrie.c, $ext_shared) # PHP_NEW_EXTENSION(libtrie, php_libtrie.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) fi 



Das erste Makro bietet die Möglichkeit, unsere Erweiterung zu aktivieren und zu deaktivieren, wenn das generierte Konfigurationsskript ausgeführt wird.

Der zweite Block ist der wichtigste. Er bestimmt, welche Dateien als Teil unserer Erweiterung kompiliert werden, ob die Erweiterung dynamisch über eine .so-Datei verbunden wird oder ob die Erweiterung statisch ist und während der PHP-Erstellung integriert wird. Unsere werden dynamisch sein.
Speichern Sie die Datei.

Wir kopieren die Datei in das Benutzerverzeichnis, damit sie im Root-Modus nicht funktioniert.

 #    exit 

Kopie:

 cp /usr/local/src/php-7.2.11/ext/libtrie ~/Documents/ -r 

Demofunktion


Ich möchte Sie daran erinnern, dass wir das vollständige Analogon von array_fill () ausführen werden. Ich werde Clion durcharbeiten, aber diese Anleitung kann in jedem Editor erstellt werden. Clion ist gut, weil es Ihnen ermöglicht, grundlegende Syntaxprüfungen automatisch durchzuführen und die schnelle Navigation durch Dateien oder Funktionen per Strg + Klick unterstützt. Damit solche Übergänge in unserer Erweiterung funktionieren, müssen Sie die Datei CMakeLists.txt konfigurieren

Damit Clion ordnungsgemäß funktioniert, müssen Sie das cmake-Build-System installieren, mit dem eine Reihe abhängiger Pakete installiert werden. Installieren Sie dies alles mit dem Befehl:

 sudo apt install cmake 

Konfigurieren Sie cmake


Wir öffnen unseren Katalog mit Erweiterung in Clion. Erstellen Sie eine CMakeLists.txt-Datei mit den folgenden Inhalten über das Kontextmenü, indem Sie oben auf dem Bildschirm auf den Namen des Stammverzeichnisses klicken:

 cmake_minimum_required(VERSION 3.12) project(php-ext-libtrie C) set(CMAKE_C_STANDARD 11) #   phproot,       php set(phproot /usr/local/src/php-7.2.11/) #   ,      #      clion       php include_directories(${phproot}) include_directories(${phproot}TSRM/) include_directories(${phproot}main/) include_directories(${phproot}Zend/) #    clion          add_executable(php-ext-libtrie php_libtrie.c) 



Vielleicht weiß jemand, wie man diese Datei kürzer macht, damit Clion mit der Indizierung von Projektdateien beginnt. Ich habe keinen kürzeren Weg gefunden. Wenn jemand weiß, schreiben Sie in die Kommentare.

Demo-Funktionscode


Öffnen Sie unsere Datei mit dem php_libtrie.c unserer Erweiterung php_libtrie.c und
Löschen Sie alle Kommentare, damit sie uns nicht verwirren.





Clion prüft, ob alle im Code verwendeten Funktionen und Makros deklariert wurden, und gibt einen Fehler aus, wenn dies nicht der Fall ist. Offensichtlich verwenden PHP-Entwickler kein Clion, sonst würden sie wahrscheinlich etwas dagegen tun. Um zu verhindern, dass diese Fehler in unserer Erweiterung auftreten, fügen wir uns die fehlenden Header-Dateien hinzu.

Um alles zu rationalisieren, mache ich Folgendes:
Ich übertrage alle Include mit Headern aus der Datei php_libtrie.c nach php_libtrie.h , es bleibt nur 1 Eintrag in der ersten Datei:

 #include "php_libtrie.h" 



Die Datei php_libtrie.h enthält alle anderen erforderlichen Einschlüsse.



Inhalt meiner Header-Datei
 #ifndef PHP_LIBTRIE_H #define PHP_LIBTRIE_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdarg.h> //   va_start() #include <inttypes.h> //    //  #if defined(__GNUC__) && __GNUC__ >= 4 # define ZEND_API __attribute__ ((visibility("default"))) # define ZEND_DLEXPORT __attribute__ ((visibility("default"))) #else # define ZEND_API # define ZEND_DLEXPORT #endif # define SIZEOF_SIZE_T 8 //   ZVAL_COPY_VALUE() #ifndef ZEND_DEBUG #define ZEND_DEBUG 0 #endif //  ,      #include "php.h" #include "php_ini.h" #include "zend.h" #include "zend_types.h" //ZVAL_COPY_VALUE #include "ext/standard/info.h" #include "zend_API.h" #include "zend_modules.h" #include "zend_string.h" #include "spprintf.h" extern zend_module_entry libtrie_module_entry; ... 


Wenn alles richtig gemacht wurde, zeigt der Clion-Tester in der oberen rechten Ecke ein gelbes oder grünes Quadrat an, was bedeutet, dass keine kritischen Fehler vorliegen.



Ein kleiner theoretischer Exkurs


Damit die Erweiterung ordnungsgemäß funktioniert, sind zwei Dinge erforderlich:

  1. Sie müssen die spezielle Struktur zend_module_entry initialisieren, die Folgendes enthält:

     zend_module_entry libtrie_module_entry = { STANDARD_MODULE_HEADER, //  "libtrie", //  libtrie_functions, //     PHP_MINIT(libtrie), //,     PHP_MSHUTDOWN(libtrie), //   PHP_RINIT(libtrie), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(libtrie), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(libtrie), // ,    php  phpinfo() PHP_LIBTRIE_VERSION, // ,     STANDARD_MODULE_PROPERTIES //    }; 

  2. Initialisieren Sie dasselbe Array, das alle Funktionen unserer Erweiterung enthält.

    Hier werden über einen speziellen Makro-Wrapper PHP_FE () die Namen aller Funktionen in unserer Erweiterung festgelegt. Im Allgemeinen werden Makros in PHP sehr aktiv verwendet, es gibt viele Makros, die sich einfach auf andere Makros beziehen, und diese wiederum sind weiter entfernt. Man muss sich daran gewöhnen. Sie können herausfinden, ob Sie die Makros mit Strg + Klick durchgehen. Hier ist Clion unverzichtbar.

    Erinnerst du dich an die Protodatei? Wir setzen dort 1 Funktion my_array_fill (). Jetzt haben wir hier 3 Elemente:

     const zend_function_entry libtrie_functions[] = { PHP_FE(confirm_libtrie_compiled, NULL) /* For testing, remove later. */ PHP_FE(my_array_fill, NULL) PHP_FE_END /* Must be the last line in libtrie_functions[] */ }; 

    Die erste Zeile ist eine Testfunktion, die zeigt, dass die Erweiterung funktioniert, die zweite ist unsere Funktion, die dritte ist ein spezielles Makro, das das letzte in diesem Array sein sollte.

Finden Sie unsere Funktion:

 PHP_FUNCTION(my_array_fill) 

Wie Sie sehen können, wird es auch über ein Makro initialisiert. Die Sache ist, dass alle PHP-Funktionen innerhalb von C nichts zurückgeben (genauer gesagt, void zurückgeben) und ihre Argumente nicht geändert werden können. Irgendwo ist es sogar bequem.

Wenn Sie sich die Dateistruktur ansehen (Teil des Fensters links), werden hier alle Funktionen der Datei aufgelistet, jedoch in der Form, in der sie nach dem Vorkompilieren der Makros vorliegen. Der Screenshot zeigt, dass unsere Funktion my_array_fill tatsächlich zif_my_array_fill ist.



Argumente aus dem Darm von PHP zu unserer C-Funktion erhalten wir mit einem Makro. Weitere Details zu diesem Makro finden Sie in der Datei:

 /usr/local/src/php-7.2.11/README.PARAMETER_PARSING_API 

Unten finden Sie den Code unserer Analogfunktion mit detaillierten Erklärungen.

Code
 PHP_FUNCTION(my_array_fill) { //   ,     //    2 : // zend_execute_data *execute_data, zval *return_value //     ,      //zend_long  int64_t  x64   int32_t  x86  //      zend_long zend_long start_index; //1  , zend_long num; //2   zval *value; //   mixed ,   zval,      //    ,          if (zend_parse_parameters(ZEND_NUM_ARGS(), "llz", &start_index, &num, &value) == FAILURE) { /*     *    RETURN_    * return_value     */ RETURN_FALSE; } //   ,   -    if (num <= 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "argument 2 must be > 0"); //     RETURN_FALSE; } //zval *return_value  ,        //       zval,     ,  -  //   zend_long  unsigned int32. //      + -. ..   1,    3,     4  array_init_size(return_value, (uint32_t)(start_index + num)); //  ,   ,   for(zend_long i = start_index, last = start_index + num; i < last; ++i) { //   zval       add_index_zval(return_value, i, value); } //   ,      return_value return; } 




Erweiterungen erstellen und testen


Führen Sie zunächst phpize aus, um die Datei zu konfigurieren.

 phpize 

Führen Sie nun ./configure aus, um das Makefile auszuführen.

 ./configure 

Führen Sie abschließend make aus, um unsere Erweiterung zu sammeln.

 make 

Lassen Sie uns überprüfen, was wir getan haben.

 #    php,       # modules.  -a  php      php -d extension=modules/libtrie.so -a 

Wir geben in der PHP-Konsole:

 print_r(my_array_fill(50, 2, "hello, baby!")); 

Genieße das Ergebnis.



Jemand fragt, wo ist Trie hier? Über Funktionen, die die Arbeit von trie implementieren, werde ich im zweiten Teil schreiben.

Bleib dran.

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


All Articles