PHP-Erweiterung und Kotlin Native. Teil zwei, bewusst

Published on July 10, 2018

PHP-Erweiterung und Kotlin Native. Teil zwei, bewusst

    Zusammenfassung des ersten Teils :


    1. Tools installieren und konfigurieren.
    2. Eine Funktion helloWorld()in Kotlin Native schreiben und in einer gemeinsam genutzten Bibliothek kompilieren.
    3. Greifen Sie auf diese Funktion über die C-Code-Erweiterung von PHP zu.


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

    Wen kümmert's - willkommen unter Katze.
    Wer ist nicht daran interessiert zu lesen, will aber nur sehen - willkommen bei github

    Ganz zu Beginn möchte ich mich bei Nikolai Igotti für die prompten und qualitativ hochwertigen Antworten auf meine, manchmal dummen und naiven Fragen im Kotlin Native Channel bedanken.

    Reservieren Sie sofort, dass ich nicht vorgebe, ein vollwertiges Framework (möglicherweise später) zu erstellen. Daher beschränken wir die Funktionalität auf diese Weise:

    1. Erstellen von Funktionen, die aus PHP-Code aufgerufen werden können.
    2. Definition von Konstanten.
    3. Wir arbeiten nur einfache Typen der PHP: string, boolean, int, float(und null). Keine Arrays, Objekte, Ressourcen, Referenzübergänge usw. - Ich erkläre dir warum.

    Die Besonderheit der Entwicklung von PHP-Erweiterungen ist, dass fast der gesamte Servicecode und die Kommunikation mit zend engineMakros geschrieben werden. Dies macht es zum einen wesentlich einfacher, Erweiterungen in C zu schreiben, zum anderen ist es schwierig, dies in allen anderen Programmiersprachen zu tun.

    Die naheliegendste Lösung bei dieser Eingabe war die Verwendung von Kodoherenaria. Da Kotlin sehr breite Möglichkeiten für die Erstellung von DSL bietet, kann der Prozess der Beschreibung der Erweiterungsstruktur einfach und intuitiv gestaltet werden.

    Um die Erweiterungsbibliothek auf klassische Weise (phpize, configure, make) zu erstellen, benötigen Sie mindestens zwei Artefakte - den C-Erweiterungscode und die Datei config.m4.

    Das Nutzungsszenario sieht wie folgt aus:

    1. Mit DSL beschreiben wir die Erweiterung.
    2. Wir schreiben die Implementierung von Funktionen auf K / N.
    3. Unter der Beschreibung generieren wir extension.cund config.m4. Der Code in extencion.cwird mit dem banalen Aufruf von Funktionen befasst.
    4. Gemäß der Beschreibung erzeugen wir sie constants.kt, wodurch wir die gegebenen Konstanten in unseren Funktionen auf K / N verwenden können.
    5. Kompilieren Sie den K / N-Code in eine statische Bibliothek.
    6. Wir sammeln all dies auf einem Stapel und kompilieren es in der Erweiterungsbibliothek.

    Lass uns gehen!


    Um unsere Pläne umzusetzen, brauchen wir etwas wie diese Struktur:

    Расширение(имя, версия)
        Константа1
        Константа2
        ...
        Функция1(имя, возвращаемый тип)
            аргумент1
            аргумент2
            ...
            опциональныйАргумент1
            ...

    Ich denke, dass es für jeden, der mit Kotlin arbeitet, nicht schwierig sein wird, das entsprechende DSL zu schreiben. Im Übrigen gibt es eine Vielzahl von Fachartikeln, in denen dieses Thema sehr viel detaillierter behandelt wird, als wenn ich dies im Rahmen dieses Artikels versuche.

    Der nächste Schritt besteht darin, aus diesem DSL die notwendigen Artefakte zu machen. Dazu schreiben wir einen Generator auf demselben K / N, kompilieren die ausführbare Datei daraus und unser DSL und führen sie aus - voila! Die Entscheidung ist nicht die eleganteste, aber nichts einfacher und zuverlässiger, bis sie in den Sinn kommt.

    Nun, dann ist alles einfach - wir stellen die Bibliothek mit Funktionen zusammen und bauen die Erweiterung regelmäßig auf, einschließlich der Erweiterung.

    Zur Vereinfachung ist die ganze Magie bei Zusammenstellungen in einem Shell-Skript verborgen.

    Was ist dabei herausgekommen?


    Ein Beispiel für die Beschreibung und den generierten Code einer einfachen Erweiterung, die in diesem DSL beschrieben wird ( 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 - Implementierungsfunktionen

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

    Beachten Sie den merkwürdigen Algorithmus zur Bestimmung des Wertes für 'lang'. Dies liegt an einem Fehler in der aktuellen Version von K / N, der es nicht zulässt, dass eine nicht initialisierte Variable vom Typ "char *" als Argument von C übergeben wird - Sie müssen einen leeren String ü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 C-Code-Datei

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

    Warum nur einfache Typen


    Weil sie eins zu eins auf Kotlin Native-Typen abgebildet sind. Derzeit hat das Projekt tatsächlich nur in eine Richtung interopiert, d. H. Aufruf von K / N-Funktionen von C. So behandeln komplexe Typen, wie zend_value, zend_class_entry, oder zend_fcall_info, müssen Sie die entsprechende Struktur in K / N - Projekt importieren und die entsprechenden Wrapper schreiben mit ihnen zu arbeiten, und auch da, alle Makros, etc ...

    Glas mit Teer Löffel befestigt


    1. Native Dokumentation von Kotlin. Es scheint dort zu sein, aber ... Das zuverlässigste Mittel zum Lernen ist das Lesen des Quellcodes.
    2. Die Größe der resultierenden Erweiterung ist nicht so klein. Für das obige Beispiel ist die Bibliothek ungefähr 500 KB groß.
    3. Sie können nicht einmal hoffen, dass die in K / N geschriebenen Erweiterungen in die Bibliothek der PHP-Erweiterungen fallen. 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.

    Noch einmal der Link zum Repository .

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