Erweiterung PHP und Kotlin Native. Teil Zwei, bewusst

Zusammenfassung des ersten Teils :


  1. Installation und Konfiguration von Werkzeugen.
  2. Schreiben Sie die Funktion helloWorld() in Kotlin Native und kompilieren Sie sie in eine gemeinsam genutzte Bibliothek.
  3. Greifen Sie über den Code der PHP-Erweiterung C auf diese Funktion zu.


In diesem Artikel werde ich über das Erstellen von Tools zum Schreiben einer PHP-Erweiterung sprechen, ohne C berühren zu müssen, ausschließlich auf K / N.

Wen kümmert es - willkommen bei Katze.
Wer liest, ist nicht interessiert, sondern will es nur sehen - willkommen bei Github

Ganz am Anfang möchte ich Nikolai Igotti ein großes Dankeschön für die schnellen und qualitativ hochwertigen Antworten auf meine manchmal albernen und naiven Fragen im Kotlin Native Slack Channel sagen.

Machen Sie sofort einen Vorbehalt, dass ich nicht vorgebe, ein vollwertiges Framework zu erstellen (möglicherweise später), daher werden wir die Funktionalität auf folgende Weise einschränken:

  1. Erstellen von Funktionen, die aus PHP-Code aufgerufen werden können.
  2. Definition von Konstanten.
  3. Wir arbeiten nur mit einfachen PHP-Typen: string , boolean , int , float (und null ). Keine Arrays, Objekte, Ressourcen, Übertragungen durch Referenz usw. - Ich werde Ihnen unten erklären, warum.

Die Besonderheiten bei der Entwicklung von PHP-Erweiterungen bestehen darin, dass fast der gesamte Dienstprogrammcode und die Kommunikation mit der zend engine in Makros geschrieben sind. Einerseits erleichtert es das Schreiben von Erweiterungen in C erheblich, und andererseits macht es es sehr schwierig, dasselbe in allen anderen Programmiersprachen zu tun.

Bei einer solchen Einführung war die naheliegendste Lösung die Verwendung eines Coderinariums. Angesichts der Tatsache, dass Kotlin sehr breite Möglichkeiten zum Erstellen von DSLs bietet, kann die Beschreibung der Erweiterungsstruktur einfach und intuitiv gestaltet werden.

Um die Erweiterungsbibliothek auf klassische Weise zu erstellen (phpize, configure, make), werden mindestens zwei Artefakte benötigt - der Erweiterungscode in C und die Datei config.m4 .

Das Nutzungsszenario sieht folgendermaßen aus:

  1. Mit DSL beschreiben wir die Erweiterung.
  2. Wir schreiben die Implementierung von Funktionen auf K / N.
  3. Entsprechend der Beschreibung generieren wir extension.c und config.m4 . Der Code in extencion.c behandelt das banale Proxying von Funktionsaufrufen.
  4. Entsprechend der Beschreibung generieren wir constants.kt , wodurch wir die angegebenen Konstanten in unseren Funktionen auf K / N verwenden können.
  5. Wir kompilieren K / N-Code in eine statische Bibliothek.
  6. Alles zusammenfügen und in eine Erweiterungsbibliothek kompilieren.

Lass uns gehen!


Um unseren Plan umzusetzen, brauchen wir so etwas wie diese Struktur:

 (, ) 1 2 ... 1(,  ) 1 2 ... 1 ... 

Ich denke, dass es für niemanden, der mit Kotlin zusammenarbeitet, schwierig sein würde, das entsprechende DSL zu schreiben. Im Übrigen gibt es eine große Anzahl von Fachartikeln, in denen dieses Thema viel ausführlicher behandelt wird, als wenn ich dies als Teil dieses Artikels versuche.

Der nächste Schritt besteht darin, dieses DSL in die erforderlichen Artefakte umzuwandeln. Dazu schreiben wir einen Generator auf dieselbe K / N, kompilieren eine ausführbare Datei daraus und unser DSL und führen sie aus - voila! Die Lösung ist nicht die eleganteste, aber mir ist noch nichts Einfacheres und Zuverlässigeres eingefallen.

Nun, dann ist alles einfach - wir kompilieren die Bibliothek mit Funktionen und sammeln die Erweiterung regelmäßig, einschließlich dort.

Zur Vereinfachung der Verwendung ist die gesamte Magie beim Kompilieren in einem Shell-Skript versteckt.

Was ist daraus geworden?


Ein Beispiel für die Beschreibung und den generierten Code für die in diesem DSL beschriebene einfache Erweiterung ( zum besseren Verständnis werden alle Argumente in einer benannten Form angegeben ).

konfigure.kt - DSL-Erweiterungen

 import php.extension.dsl.* val dsl = extension(name = "example", version = "0.1") { constant(name = "HELLO_EN", value = "Hello") constant(name = "HELLO_ES", value = "Hola") constant(name = "HELLO_RU", value = "") function(name = "hello", returnType = ArgumentType.STRING) { arg(type = ArgumentType.STRING, name = "name") arg(type = ArgumentType.STRING, name = "lang", optional = true) } } fun main(args: Array<String>) = dsl.make() 

example.kt - Funktionen implementieren

 fun hello(name: String, lang: String?) = "${if (lang ?: "" == "") HELLO_EN else lang} $name!!!\n" 

Beachten Sie den seltsamen Algorithmus zum Bestimmen des Werts für "lang". Dies liegt an einem Fehler in der aktuellen Version von K / N, der es nicht erlaubt, eine nicht initialisierte Variable vom Typ "char *" als Argument von C zu übergeben. - Sie müssen eine leere Zeichenfolge übergeben.

config.m4 - generierte Datei

 PHP_ARG_ENABLE(example, whether to enable example support,[ --enable-example Enable hello support]) if test "$PHP_EXAMPLE" != "no"; then PHP_ADD_INCLUDE(.) PHP_ADD_LIBRARY_WITH_PATH(example_kt, ., EXAMPLE_SHARED_LIBADD) PHP_SUBST(EXAMPLE_SHARED_LIBADD) PHP_NEW_EXTENSION(example, example.c, $ext_shared) fi 

example_generated_constants.kt - generierte Datei mit Kotlin-Konstanten

 const val HELLO_EN = "Hello" const val HELLO_ES = "Hola" const val HELLO_RU = "" 

example.c - generierte Datei mit C-Code

 #include "php.h" #include "example_kt_api.h" PHP_FUNCTION(hello); static zend_function_entry example_functions[] = { PHP_FE(hello, NULL) {NULL,NULL,NULL} }; PHP_MINIT_FUNCTION(example); zend_module_entry example_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "example", example_functions, PHP_MINIT(example), NULL, NULL, NULL, NULL, #if ZEND_MODULE_API_NO >= 20010901 "0.1", #endif STANDARD_MODULE_PROPERTIES }; ZEND_GET_MODULE(example) PHP_MINIT_FUNCTION(example) { REGISTER_STRING_CONSTANT("HELLO_EN", "Hello", CONST_CS|CONST_PERSISTENT); REGISTER_STRING_CONSTANT("HELLO_ES", "Hola", CONST_CS|CONST_PERSISTENT); REGISTER_STRING_CONSTANT("HELLO_RU", "", CONST_CS|CONST_PERSISTENT); return SUCCESS; } PHP_FUNCTION(hello){ //-,      char*  K/N char *name = malloc(1); name[0] = '\0'; size_t name_len=0; char *lang = malloc(1); lang[0] = '\0'; size_t lang_len=0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &name, &name_len, &lang, &lang_len) == FAILURE) { return; } RETURN_STRING(example_kt_symbols()->kotlin.root.hello(name, lang)); } 

Über warum nur einfache Typen


Weil sie eins zu eins den einheimischen Kotlin-Typen zugeordnet sind. Bisher implementiert das Projekt tatsächlich nur Interop in eine Richtung, d. H. Aufruf von K / N-Funktionen von C. Um komplexe Typen wie zend_value , zend_class_entry oder zend_fcall_info , müssen Sie die entsprechenden Strukturen in das K / N-Projekt importieren und die entsprechenden Wrapper schreiben, um mit ihnen zu arbeiten. Außerdem gibt es alle Makros usw.

Glas mit Teer. Ein Löffel ist angebracht.


  1. Kotlin Native Dokumentation. Es scheint da zu sein, aber ... Bisher ist das zuverlässigste Mittel zum Lernen das Lesen der Quelle.
  2. Die Größe der resultierenden Erweiterung ist nicht so klein. Für das obige Beispiel wird eine Bibliothek von ungefähr 500 KB erhalten.
  3. Sie müssen nicht einmal hoffen, dass in K / N geschriebene Erweiterungen in der PHP-Erweiterungsbibliothek landen. Das Produkt wird sozusagen nur für den internen Gebrauch erhalten.

Was weiter


Implementieren Sie alles, was im Abschnitt „Warum nur einfache Typen“ beschrieben wird.

Wieder ein Link zum Repository .

Vielen Dank für Ihre Aufmerksamkeit, wünschen Sie mir viel Glück :)

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


All Articles