Расширение PHP и Kotlin Native. Часть первая, наивная

    В этой статье рассматривается самый наивный и простой подход к созданию расширения PHP с использованием Kotlin Native. Обращаю внимание, что не на, а с использованием.

    Это скорее некий туториал с описанием возникших при скрещивании ужа с ежом проблем и путей их решения. Откровений не будет, но возможно кому-то и пригодится.

    Итак, если интересно, то добро пожаловать под кат.

    Задача — написать расширение с одной функцией `hello($name)`, принимающей строку и печатающей `Hello, $name!`.

    Решать будем наивно, как и сказано в названии.

    1. Напишем функцию на Kotlin
    2. Скомпилируем в shared library
    3. Классическим образом (на C) напишем расширение, которое будет переадресовывать вызов функции в эту библиотеку

    Вроде просто, но в густой траве уже затаились грабли:

    1. Есть некоторое количество примеров использования C-библиотек в Kotlin, но вот про то, как использовать функции Kotlin-библиотеки в C мне ничего адекватного найти не удалось (ну может плохо искал, чего уж)
    2. Компилятор kotlinc при первом запуске загружает зависимости. Но вот незадача — моя линуксовая виртуалка в корпоративном облаке. Без, даже теоретической, возможности достучаться в интернет.
    3. Эти грабли скорее от отсутствия опыта в С, но все равно расскажу как меня знатно троллил линковщик.

    Все происходило на Red Hat Enterprise Linux Server release 7.5.

    Поехали!


    Для начала устанавливаем… Нет, не компилятор Kotlin. Для начала устанавливаем JDK. Ну вот так.
    Потом устанавливаем компилятор Kotlin. Просто скачиваем архив с гитхаба и разархивируем, например, в домашний каталог.

    В идеальном мире, при первом запуске, он сам скачает все зависимости, но, в случае отсутствия интернета, действуем следующим образом (тайное знание добыто в слаке у сотрудников JetBrains):

    • Создаем любой простейший скрипт на Kotlin'е, чтобы было что подсунуть компилятору на следующем шаге
    • Запускаем $KOTLIN_HOME/bin/kotlinc SimpleScript.kt, немного ждем и жмем CTRL+C — это для того, чтобы создалась структура папок в домашнем каталоге
    • Смотрим в файл $KOTLIN_HOME/konan/konan.properties, в секцию с нашей архитектурой, и ищем пункт:

    dependencies.linux_x64 = \
    	    clang-llvm-5.0.0-linux-x86-64 \
    	    target-gcc-toolchain-3-linux-x86-64 \
    	    libffi-3.2.1-2-linux-x86-64

    • Идем в специальный репозиторий и скачиваем все вышеперечисленное.
    • Складываем все это в ~/.konan/cache (напоминаю, что тильдой в Linux обозначается домашний каталог)

    Теперь при первом запуске компилятор воспользуется этими дистрибутивами и в интернет не полезет.

    Учтите, что зависимости очень не маленькие по размеру и, после их установки, мой домашний каталог потяжелел на 3.4ГБ. Для тех, у кого под домашний нарезан отдельный том, может быть критично.

    После, стандартным пакетным менеджером, устанавливаем php и соответствующий ему php-devel.
    На этом подготовительные мероприятия закончены.

    Так как задачи рассказать про написание PHP расширений не стоит, то обойдемся максимально коротким кодом.

    Начнем с Kotlin


    hellokt.kt

    fun kt_print(string:String){
        println("Hello, $string!!!")
    }

    В различных мануалах и туториалах в качестве компиляторов используются либо kotlinc, либо konanc, что несколько запутывает. Так вот — это одно и то же. Вот пруф:


    Компилируем

    # $KOTLINC_HOME/kotlinc -opt ./hellokt.kt -o hellokt -produce dynamic

    С ключем -opt библиотека получается поменьше. -produce dynamic говорит компилятору, что нужно делать shared library для текущей платформы.

    После выполнения у нас появятся два файла: libhellokt.so и hellokt_api.h. Библиотека и заголовочный файл для нее. Обратите внимание, что префиксы и суффиксы генерируются автоматом и повлиять на них мы не можем (наверное).

    В нашем случае получится вот такой заголовочный файл.

    #ifndef KONAN_HELLOKT_H
    #define KONAN_HELLOKT_H
    #ifdef __cplusplus
    extern "C" {
    #endif
    #ifdef __cplusplus
    typedef bool            hellokt_KBoolean;
    #else
    typedef _Bool           hellokt_KBoolean;
    #endif
    typedef char            hellokt_KByte;
    typedef unsigned short  hellokt_KChar;
    typedef short           hellokt_KShort;
    typedef int             hellokt_KInt;
    typedef long long       hellokt_KLong;
    typedef float           hellokt_KFloat;
    typedef double          hellokt_KDouble;
    typedef void*           hellokt_KNativePtr;
    struct hellokt_KType;
    typedef struct hellokt_KType hellokt_KType;
    
    typedef struct {
      /* Service functions. */
      void (*DisposeStablePointer)(hellokt_KNativePtr ptr);
      void (*DisposeString)(const char* string);
      hellokt_KBoolean (*IsInstance)(hellokt_KNativePtr ref, const hellokt_KType* type);
    
      /* User functions. */
      struct {
        struct {
          void (*kt_print)(const char* string);
        } root;
      } kotlin;
    } hellokt_ExportedSymbols;
    extern hellokt_ExportedSymbols* hellokt_symbols(void);
    #ifdef __cplusplus
    }  /* extern "C" */
    #endif
    #endif  /* KONAN_HELLOKT_H */

    Доступ к нашей функции kt_print пойдет по такому пути

    hellokt_symbols()->kotlin.root.kt_print(char *);

    Про классы и пакеты расскажу ниже — там есть нюансы.

    Библиотека готова, переходим к C


    config.m4 (как его создавать)

    PHP_ARG_ENABLE(hello, whether to enable hello support,[ --enable-hello   Enable hello support])
    
    if test "$PHP_HELLO" != "no"; then
        PHP_ADD_LIBRARY_WITH_PATH(hellokt, /path/to/compiled/library, HELLO_SHARED_LIBADD)
        PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
        PHP_SUBST(HELLO_SHARED_LIBADD)
    fi

    Первая строка обязательна!
    The very first thing seen in the example config.m4 above, aside from a couple of comments, are three lines using PHP_ARG_WITH() and PHP_ARG_ENABLE().

    Every extension should provide at least one or the other with the extension name, so that users can choose whether or not to build the extension into PHP.

    Обратите внимание, что в PHP_ADD_LIBRARY_WITH_PATH первым аргументом указывается имя библиотеки. Не libhellokt, а именно hellokt. Два часа угробил, пока нашел, почему ld не может библиотеку найти. (вот он обещанный рассказ про издевательства линкера).

    Теперь, собственно, сам код расширения

    hello.c

    #include "php.h"
    //Заголовочный файл нашей библиотеки
    #include "hellokt_api.h"    
    
    #define PHP_MY_EXTENSION_VERSION "1.0"
    #define PHP_MY_EXTENSION_EXTNAME "hello"
    
    PHP_FUNCTION(hello);
    
    static zend_function_entry hello_functions[] = {
            PHP_FE(hello, NULL)
    };
    
    zend_module_entry hello_module_entry = {
    #if ZEND_MODULE_API_NO >= 20010901
            STANDARD_MODULE_HEADER,
    #endif
            PHP_MY_EXTENSION_EXTNAME,
            hello_functions,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
    #if ZEND_MODULE_API_NO >= 20010901
            PHP_MY_EXTENSION_VERSION,
    #endif
            STANDARD_MODULE_PROPERTIES
    };
    
    ZEND_GET_MODULE(hello)
    
    PHP_FUNCTION(hello) {
            char * name;
            size_t name_len;
    
            if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
                RETURN_NULL();
            }
    
            hellokt_symbols()->kotlin.root.kt_print(name); //Вызов библиотечной функции
    
            efree(name);
    }

    Про написание расширений PHP довольно много статей, да и документация присутствует, так что ограничусь ссылкой на использование zend_parse_parameters — оно будет к месту.

    Ну а дальше все по накатанному пути:

    # $PHP_PATH/phpize
    # ./configure  --with-php-config=$PHP_PATH/php-config
    # make
    # $PHP_PATH/php -dextension=./modules/hello.so -r "echo hello('World');"
    Hello, World!!!
    

    Бонус, про классы и пакеты


    Допустим мы захотели сделать по феншую и слепили вот такой код.

    package hello.kt;
    public class HelloKt {
      fun kt_print(string: String) {
          println("Hello, $string!!!")
      }
    }

    Обычным вызовом метода тут не обойтись — придется сначала создавать класс и передавать его первым аргументом в вызываемую функцию.

    hellokt_kref_hello_kt_HelloKt helloKt = { 0 };
    
    if(!helloKt.pinned){
            helloKt = hellokt_symbols()->kotlin.root.hello.kt.HelloKt.HelloKt();
    }
    
    hellokt_symbols()->kotlin.root.hello.kt.HelloKt.kt_print(helloKt, name);

    Что дальше? Отказ от shared library, минимизация использования С и, чем черт не шутит, мини фреймворк — такие дела. Пожелайте мне удачи! :)

    Альфа-Банк

    195,00

    Компания

    Поделиться публикацией

    Похожие публикации

    Комментарии 38
      0
      [offtopic]Шутка про мсье и про трллейбус.[/offtopic]
      Пожелайте мне удачи! :)

      Удачи! Вы — странный, но быть странным лучше, чем как все.
      И всё же… В заголовке сказано, что это часть первая. Но вроде бы всё целостно. Оптимизации — это уже рутина.
        0
        В планах на вторую часть предоставить полноценный туториал/инструментарий для написания расширений на Kotlin Native без порождения лишних артефактов и погружения в С. Пока задача видится не самой простой, потому про «пожелайте удачи» и упомянул
        0

        Если всё равно писать на Котлине, то зачем тогда ПХП? Не проще ли сразу на Котлине весь сервер написать?

          0
          У этих языков, по большому счету, разные области применения. Вас же не смущает, что расширения для PHP, в 99.9% случаев, пишутся на С? А Kotlin чем хуже?
            0

            Дело в том, что и ПХП, и Котлин подходят для написания веб-приложений, а вот на Си это делать очень неудобно и сложно. Но расширения на нём писать выгодно из-за производительности и возможности интеграции с внешними библиотеками.

              0
              Собственно вы очень здорово сформулировали ответ на вопрос «зачем я это делаю?» (отчасти)
              на Си это делать очень неудобно и сложно


              Kotlin Native, так же как и Си, генерирует нативный код и интегрируется с внешними библиотеками, но, при этом, на порядок более удобен и прост. За производительность пока ничего не скажу — надо тестировать.
                0

                Настолько хорошо, что прокладку между котлином и пхп всё равно приходится писать на си? :)

                  0
                  Прокладка нужна не для интеграции Котлина с внешними библиотеками, а для интеграции PHP с внешними библиотеками, на каком бы языке они не были написаны. Возможно скоро PHP интегрировать с внешними библиотеками можно будет на самом PHP, без использования таких прокладок.
                    0
                    Тут есть два стремных момента:
                    1) Расширения PHP, в конечном итоге, должны реализовывать некий «интерфейс», описания которого в открытом доступе я не нашел. То API, которое генерирует Kotlin, прямо вот вообще не дает надежды на полный отказ от Си.
                    2) Специфика написания расширений PHP такова, что вся оснастка взаимодействия с Zend engine пишется макросами чуть менее, чем на 100%

                    Ну и в целом, я отношусь к этой активности как к интересной задаче и возможности поизучать Kotlin Native. :)
                      0
                      Ну вот вроде в PHP 8.0 будет реализован FFI, позволяющий обращаться к библиотекам, предоставляющим обычный для C интерфейс вызова.
                        0
                        Не факт, что будет. Опять же когда его ждать? По сегодняшним прогнозам это конец 21-го года.
            0
            Хм. А зачем для этого использовать Kotlin, когда есть Zephir?
              0
              Ответ «потому, что мне это интересно» подойдет? ;)
                +1
                Вполне. Мсье знает толк…
                +1
                Потому что писать на котлине веселей при том что результат примерно одинаков?
                  0
                  Bingo!
                  Ну и кроме прочего, всегда импонировал подход «а еще это можно сделать вот так»
                    0
                    Синтаксис на любителя, переключение контекста, необходимость лезть в сишный код… Ну, так себе. Зефир лишён этих недостатков.
                      0
                      Синтаксис на любителя

                      сказал phpник


                      переключение контекста

                      в реальности незначительно, если рассматривать зачем вообще такие штуки делают.


                      необходимость лезть в сишный код

                      написать кодогенератор и вжух. будет получше чем зефир.

                        0
                        сказал phpник

                        А ещё питонщик, рубист, эликсирщик и ещё кое-кто, в том числе сишник и 6502-ассемблерщик. И это всё не разные люди, но к делу не имеет совершенно никакого отношения.

                        в реальности незначительно, если рассматривать зачем вообще такие штуки делают.

                        Ну так-то, если проект на php, то с практической точки зрения, гораздо ценнее возможность писать расширения на языке, максимально приближенном к нему.
                          0
                          С практической точки зрения, если на проекте на php дошло до написания расширений, то должна быть гораздо ценнее возможность писать эффективно эффективный код, чем приближенность.
                            0
                            Ну, тогда точно не нужен Kotlin, потому что полно более лаконичных языков без дурной наследственности java: хоть те же Rust или Golang.
                              0
                              а можно чуть больше аргументов? вы же понимаете что у вас уже может быть существующая система на Kotlin а вам просто нужно интегрировать что-то в проект. Или много других кейсов где нужен interop между php и kotlin.

                              Мне нравится rust (а вот на go я бы не стал писать расширения — специфика языка не подразумевает этого) для написания экстеншенов для php но сегодня очень маленький процент бэкэнд проектов реализуется на rust в той мере что бы имело смысл организовывать interop между php и rust.
                                0
                                Придумать можно много чего. Как с математиками: дать волю, так могут договориться и до комплексного времени. Я не разрабатываю на kotlin и не знаю ни одного проекта написанного на kotlin native, поэтому мне сложно придумать, где бы это было полезно в реальности. Это должен быть какой-то очень специфический случай, вроде дёрганья внутренних функций 1c напрямую через расширение php.
                            0
                            А ещё питонщик, рубист, эликсирщик и ещё кое-кто, в том числе сишник и 6502-ассемблерщик. И это всё не разные люди, но к делу не имеет совершенно никакого отношения.

                            Знаешь много языков — не знаешь ни один
                              0
                              большинство и одного языка не знает толком.
                          0
                          Не поймите превратно, но вот решил я, после вашего поста, попробовать zephir.
                          Первое, что насторожило — первая не альфа версия датируется 2015 годом, а текущая версия 0.10.10. Т.е. сами создатели не готовы заявить о стабильном релизе, за который они ручаются.
                          Второе. Начал устанавливать по инструкции — ругается «Note: Zephir no longer distributed with internal parser.». Еще раз — официальная дока не точна начиная с раздела «Install» — могу ли я ей после такого доверять в остальном? Не самый простительный косяк.

                          Ну и по сообщению агенства «Одна Бабка Сказала» вспоминаются не совсем лесные отзывы об итоговой производительности. Вполне осознаю, что это не аргумент — надо самому мерить (попробуйте найти хоть один бенчмарк зефира в сравнении с голым PHP7, ага), но, как в том анекдоте — «осадок остался».
                            0
                            Т.е. сами создатели не готовы заявить о стабильном релизе, за который они ручаются

                            Тем не менее, это им не мешает разрабатывать на нём Phalcon Framework, для которого, собственно, и был создан Зефир.

                            Начал устанавливать по инструкции — ругается «Note: Zephir no longer distributed with internal parser.».

                            Не ругается, а сообщает о том, где найти парсер зефира для php, если вдруг случится такое, что он нужен. Это совсем не значит, что что-то пошло не так и в документации ошибка.
                              0
                              Тем не менее, это им не мешает разрабатывать на нём Phalcon Framework, для которого, собственно, и был создан Зефир.
                              О чем тогда вообще можно говорить? Завязываться на технологию, которая пилится под один конкретный проект — это не самая лучшая идея.
                              Не ругается, а сообщает о том, где найти парсер зефира для php, если вдруг случится такое, что он нужен. Это совсем не значит, что что-то пошло не так и в документации ошибка.
                              Нет уж простите. Если я иду четко по гайду, с самого первого шага, ничего не пропуская и, на очередном шаге, все ломается (не по моей вине), то это именно вина гайда.
                                0
                                Нет уж простите. Если я иду четко по гайду, с самого первого шага, ничего не пропуская и, на очередном шаге, все ломается (не по моей вине), то это именно вина гайда.

                                Специально сейчас обновился по инструкции. Даже этого сообщения не было. Но, как я уже сказал, оно не означает ошибку. Убедиться в этом можно классическим методом: zephir help.
                                  0
                                  А при обновлении оно и не должно ругаться, зависимости то уже установлены.

                                  Вы посмотрите на это с точки зрения человека, который ничего не знает о технологии и пытается, для начала, написать «hello world», пользуясь официальным гайдом.

                                  Вот, что написано в гайде:
                                  $ zephir build
                                  ...
                                  Extension installed!
                                  Add extension=utils.so to your php.ini
                                  Don't forget to restart your web server


                                  Но вместо «Extension installed!» человек видит невнятный стектрейс. Все еще считаете, что ошибки в документации нет?
                                    0
                                    Вот это уже другой разговор. Изначально про установку писали, а тут, оказывается, при сборке проекта. Да, есть такая штука. Когда я в прошлый раз с ним работал, такого ещё не было, либо было, но я по привычке просто поставил и забыл. PR в документацию сделаете или я сделаю?
                                      0
                                      Уж я не знаю, каким образом вы с этим проектом связаны — видно, что он вам сильно не безразличен, но давайте все же соблюдать некоторые этические нормы. Вы достаточно агрессивно пытаетесь меня убедить, что мне стоит перестать «заниматься фигней» и начать использовать вашего фаворита, вплоть до того, что пытаетесь подписать делать в него PR. Смотрится это… странно.
                                      Я ничего не имею против Zephir, но и особого интереса к его развитию у меня, если честно, нет.
                                      Давайте все же уважать желания друг друга заниматься тем, что нам кажется более интересны и полезным, хорошо?
                                        0
                                        Я ни в чём не убеждаю, но если есть возможность помочь проекту, то почему бы это не сделать? Тем более, что мне не сложно черкнуть пару строк в документацию.
                                  0
                                  > Завязываться на технологию, которая пилится под один конкретный проект — это не самая лучшая идея.

                                  Между «изначально создана для» и «пилится под» всё же есть разница. Вы же завязались на язык, созданный для размещения резюме? :)
                                    +1
                                    Мне кажется, что мы попали в какой то порочный круг.
                                    Я: За N лет ни одного стабильного релиза. Последний 0.10.10
                                    Мне: Так они его для себя пишут
                                    Я: Ну ок. Раз для себя, то я пожалуй мимо пройду
                                    Мне: Да нет же! Сейчас-то для всех!
                                    GOTO 1 :D

                                    При этом я не имею ничего против Zephir'а, я просто пытаюсь понять, зачем мне его так активно навязывают в статье про Kotlin Native
                        0

                        Еще было бы классно сделать автоматическую генерацию расширения для PHP, чтобы не приходилось писать руками эту прокладку. ;)

                          0
                          Да, конечно. Это же главный критерий успешности всех этих упражнений. :)
                          0
                          .

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое