Это скорее некий туториал с описанием возникших при скрещивании ужа с ежом проблем и путей их решения. Откровений не будет, но возможно кому-то и пригодится.
Итак, если интересно, то добро пожаловать под кат.
Задача — написать расширение с одной функцией `hello($name)`, принимающей строку и печатающей `Hello, $name!`.
Решать будем наивно, как и сказано в названии.
- Напишем функцию на Kotlin
- Скомпилируем в shared library
- Классическим образом (на C) напишем расширение, которое будет переадресовывать вызов функции в эту библиотеку
Вроде просто, но в густой траве уже затаились грабли:
- Есть некоторое количество примеров использования C-библиотек в Kotlin, но вот про то, как использовать функции Kotlin-библиотеки в C мне ничего адекватного найти не удалось (ну может плохо искал, чего уж)
- Компилятор kotlinc при первом запуске загружает зависимости. Но вот незадача — моя линуксовая виртуалка в корпоративном облаке. Без, даже теоретической, возможности достучаться в интернет.
- Эти грабли скорее от отсутствия опыта в С, но все равно расскажу как меня знатно троллил линковщик.
Все происходило на 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, минимизация использования С и, чем черт не шутит, мини фреймворк — такие дела. Пожелайте мне удачи! :)
Комментарии (34)
staticlab
28.06.2018 11:15Если всё равно писать на Котлине, то зачем тогда ПХП? Не проще ли сразу на Котлине весь сервер написать?
rjhdby Автор
28.06.2018 11:18У этих языков, по большому счету, разные области применения. Вас же не смущает, что расширения для PHP, в 99.9% случаев, пишутся на С? А Kotlin чем хуже?
staticlab
28.06.2018 11:20Дело в том, что и ПХП, и Котлин подходят для написания веб-приложений, а вот на Си это делать очень неудобно и сложно. Но расширения на нём писать выгодно из-за производительности и возможности интеграции с внешними библиотеками.
rjhdby Автор
28.06.2018 11:35Собственно вы очень здорово сформулировали ответ на вопрос «зачем я это делаю?» (отчасти)
на Си это делать очень неудобно и сложно
Kotlin Native, так же как и Си, генерирует нативный код и интегрируется с внешними библиотеками, но, при этом, на порядок более удобен и прост. За производительность пока ничего не скажу — надо тестировать.staticlab
28.06.2018 11:51Настолько хорошо, что прокладку между котлином и пхп всё равно приходится писать на си? :)
VolCh
28.06.2018 12:01Прокладка нужна не для интеграции Котлина с внешними библиотеками, а для интеграции PHP с внешними библиотеками, на каком бы языке они не были написаны. Возможно скоро PHP интегрировать с внешними библиотеками можно будет на самом PHP, без использования таких прокладок.
rjhdby Автор
28.06.2018 12:02Тут есть два стремных момента:
1) Расширения PHP, в конечном итоге, должны реализовывать некий «интерфейс», описания которого в открытом доступе я не нашел. То API, которое генерирует Kotlin, прямо вот вообще не дает надежды на полный отказ от Си.
2) Специфика написания расширений PHP такова, что вся оснастка взаимодействия с Zend engine пишется макросами чуть менее, чем на 100%
Ну и в целом, я отношусь к этой активности как к интересной задаче и возможности поизучать Kotlin Native. :)
untilx
28.06.2018 16:06Хм. А зачем для этого использовать Kotlin, когда есть Zephir?
Fesor
29.06.2018 00:43+1Потому что писать на котлине веселей при том что результат примерно одинаков?
rjhdby Автор
29.06.2018 00:55Bingo!
Ну и кроме прочего, всегда импонировал подход «а еще это можно сделать вот так»
untilx
29.06.2018 13:43Синтаксис на любителя, переключение контекста, необходимость лезть в сишный код… Ну, так себе. Зефир лишён этих недостатков.
Fesor
29.06.2018 14:39Синтаксис на любителя
сказал phpник
переключение контекста
в реальности незначительно, если рассматривать зачем вообще такие штуки делают.
необходимость лезть в сишный код
написать кодогенератор и вжух. будет получше чем зефир.
untilx
29.06.2018 15:15сказал phpник
А ещё питонщик, рубист, эликсирщик и ещё кое-кто, в том числе сишник и 6502-ассемблерщик. И это всё не разные люди, но к делу не имеет совершенно никакого отношения.
в реальности незначительно, если рассматривать зачем вообще такие штуки делают.
Ну так-то, если проект на php, то с практической точки зрения, гораздо ценнее возможность писать расширения на языке, максимально приближенном к нему.VolCh
29.06.2018 16:25С практической точки зрения, если на проекте на php дошло до написания расширений, то должна быть гораздо ценнее возможность писать эффективно эффективный код, чем приближенность.
untilx
29.06.2018 16:39Ну, тогда точно не нужен Kotlin, потому что полно более лаконичных языков без дурной наследственности java: хоть те же Rust или Golang.
Fesor
30.06.2018 09:47а можно чуть больше аргументов? вы же понимаете что у вас уже может быть существующая система на Kotlin а вам просто нужно интегрировать что-то в проект. Или много других кейсов где нужен interop между php и kotlin.
Мне нравится rust (а вот на go я бы не стал писать расширения — специфика языка не подразумевает этого) для написания экстеншенов для php но сегодня очень маленький процент бэкэнд проектов реализуется на rust в той мере что бы имело смысл организовывать interop между php и rust.
rjhdby Автор
29.06.2018 15:17Не поймите превратно, но вот решил я, после вашего поста, попробовать zephir.
Первое, что насторожило — первая не альфа версия датируется 2015 годом, а текущая версия 0.10.10. Т.е. сами создатели не готовы заявить о стабильном релизе, за который они ручаются.
Второе. Начал устанавливать по инструкции — ругается «Note: Zephir no longer distributed with internal parser.». Еще раз — официальная дока не точна начиная с раздела «Install» — могу ли я ей после такого доверять в остальном? Не самый простительный косяк.
Ну и по сообщению агенства «Одна Бабка Сказала» вспоминаются не совсем лесные отзывы об итоговой производительности. Вполне осознаю, что это не аргумент — надо самому мерить (попробуйте найти хоть один бенчмарк зефира в сравнении с голым PHP7, ага), но, как в том анекдоте — «осадок остался».untilx
29.06.2018 15:27Т.е. сами создатели не готовы заявить о стабильном релизе, за который они ручаются
Тем не менее, это им не мешает разрабатывать на нём Phalcon Framework, для которого, собственно, и был создан Зефир.
Начал устанавливать по инструкции — ругается «Note: Zephir no longer distributed with internal parser.».
Не ругается, а сообщает о том, где найти парсер зефира для php, если вдруг случится такое, что он нужен. Это совсем не значит, что что-то пошло не так и в документации ошибка.rjhdby Автор
29.06.2018 15:51Тем не менее, это им не мешает разрабатывать на нём Phalcon Framework, для которого, собственно, и был создан Зефир.
О чем тогда вообще можно говорить? Завязываться на технологию, которая пилится под один конкретный проект — это не самая лучшая идея.
Не ругается, а сообщает о том, где найти парсер зефира для php, если вдруг случится такое, что он нужен. Это совсем не значит, что что-то пошло не так и в документации ошибка.
Нет уж простите. Если я иду четко по гайду, с самого первого шага, ничего не пропуская и, на очередном шаге, все ломается (не по моей вине), то это именно вина гайда.untilx
29.06.2018 16:23Нет уж простите. Если я иду четко по гайду, с самого первого шага, ничего не пропуская и, на очередном шаге, все ломается (не по моей вине), то это именно вина гайда.
Специально сейчас обновился по инструкции. Даже этого сообщения не было. Но, как я уже сказал, оно не означает ошибку. Убедиться в этом можно классическим методом: zephir help.rjhdby Автор
29.06.2018 16:38А при обновлении оно и не должно ругаться, зависимости то уже установлены.
Вы посмотрите на это с точки зрения человека, который ничего не знает о технологии и пытается, для начала, написать «hello world», пользуясь официальным гайдом.
Вот, что написано в гайде:
$ zephir build
...
Extension installed!
Add extension=utils.so to your php.ini
Don't forget to restart your web server
Но вместо «Extension installed!» человек видит невнятный стектрейс. Все еще считаете, что ошибки в документации нет?untilx
29.06.2018 21:50Вот это уже другой разговор. Изначально про установку писали, а тут, оказывается, при сборке проекта. Да, есть такая штука. Когда я в прошлый раз с ним работал, такого ещё не было, либо было, но я по привычке просто поставил и забыл. PR в документацию сделаете или я сделаю?
rjhdby Автор
29.06.2018 22:31Уж я не знаю, каким образом вы с этим проектом связаны — видно, что он вам сильно не безразличен, но давайте все же соблюдать некоторые этические нормы. Вы достаточно агрессивно пытаетесь меня убедить, что мне стоит перестать «заниматься фигней» и начать использовать вашего фаворита, вплоть до того, что пытаетесь подписать делать в него PR. Смотрится это… странно.
Я ничего не имею против Zephir, но и особого интереса к его развитию у меня, если честно, нет.
Давайте все же уважать желания друг друга заниматься тем, что нам кажется более интересны и полезным, хорошо?
VolCh
29.06.2018 16:27> Завязываться на технологию, которая пилится под один конкретный проект — это не самая лучшая идея.
Между «изначально создана для» и «пилится под» всё же есть разница. Вы же завязались на язык, созданный для размещения резюме? :)rjhdby Автор
29.06.2018 16:41Мне кажется, что мы попали в какой то порочный круг.
Я: За N лет ни одного стабильного релиза. Последний 0.10.10
Мне: Так они его для себя пишут
Я: Ну ок. Раз для себя, то я пожалуй мимо пройду
Мне: Да нет же! Сейчас-то для всех!
GOTO 1 :D
При этом я не имею ничего против Zephir'а, я просто пытаюсь понять, зачем мне его так активно навязывают в статье про Kotlin Native
pewpew
[offtopic]Шутка про мсье и про трллейбус.[/offtopic]
Удачи! Вы — странный, но быть странным лучше, чем как все.
И всё же… В заголовке сказано, что это часть первая. Но вроде бы всё целостно. Оптимизации — это уже рутина.
rjhdby Автор
В планах на вторую часть предоставить полноценный туториал/инструментарий для написания расширений на Kotlin Native без порождения лишних артефактов и погружения в С. Пока задача видится не самой простой, потому про «пожелайте удачи» и упомянул