- PVSM.RU - https://www.pvsm.ru -

helloWorld() на Kotlin Native и компиляция ее в shared library.В этой статье я буду рассказывать про создание инструментария для написания расширения PHP без необходимости трогать Си, исключительно на K/N.
Кому интересно — добро пожаловать под кат.
Кому читать не интересно, а просто хочется посмотреть — добро пожаловать на github [2]
В самом начале хочу сказать большое спасибо Николаю Иготти за оперативные и качественные ответы на мои, порой глупые и наивные, вопросы в слак-канале Kotlin Native.
Сразу оговорюсь, что не претендую на создания полноценного фреймворка (может быть потом), потому ограничим функциональность таким образом:
string, boolean, int, float (и null). Никаких массивов, объектом, ресурсов, передач по ссылке и т.д. — ниже расскажу почему.
Специфика разработки расширений PHP состоит в том, что практически весь служебный код и общение с zend engine пишется на макросах. С одной стороны — это сильно облегчает написания расширений на Си, а с другой — сильно мешает делать то же самое на всех остальных языках программирования.
При таких вводных самым очевидным решением было использовать кодогеренарию. И, учитывая, что Kotlin предоставляет очень широкие возможности по созданию DSL, процесс описания структуры расширения можно сделать простым и наглядным.
Для того, чтобы собрать библиотеку расширения классическим образом (phpize, configure, make), необходимы как минимум два артефакта — код расширения на Си и файл config.m4.
Сценарий использования будет таким:
extension.c и config.m4. Код в extencion.c будет заниматься банальным проксированием вызова функций.constants.kt, что позволит использовать заданные константы в наших функциях на K/N.Для реализации задуманного нам понадобится получить примерно вот такую структуру:
Расширение(имя, версия)
Константа1
Константа2
...
Функция1(имя, возвращаемый тип)
аргумент1
аргумент2
...
опциональныйАргумент1
...
Думаю, что ни для кого, работавшего с Kotlin, не составит труда написать соответствующий DSL. Для остальных же есть большое количество специализированных статей, где эта тема раскрывается гораздо подробнее, нежели если я попытаюсь сделать это в рамках данной статьи.
Следующим шагом нам надо превратить этот DSL в необходимые артефакты. Для этого напишем генератор на том же K/N, скомпилируем из него и нашего DSL исполняемый файл и запустим — вуаля! Решение не самое изящное, но ничего более простого и надежного пока в голову не пришло.
Ну а дальше все просто — компилируем библиотеку с функциями и штатным образом собираем расширение, включив туда оную.
Для простоты использования, вся магия с компиляциями спрятана в shell-скрипт.
Пример описания и сгенерированный код простого расширения, описанного на этом DSL (для лучшего понимания все аргументы заданы в именованном виде).
konfigure.kt — DSL расширения
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 — Реализация функций
fun hello(name: String, lang: String?) = "${if (lang ?: "" == "") HELLO_EN else lang} $name!!!n"
Обратите внимание на странный алгоритм определения значения для `lang`. Это связано с багом в текущей версии K/N, не позволяющем передать в качестве аргумента из Си неинициализированную переменную типа `char *` — приходится передавать пустую строку.
config.m4 — сгенерированный файл
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 — сгенерированный файл с константами Kotlin
const val HELLO_EN = "Hello"
const val HELLO_ES = "Hola"
const val HELLO_RU = "Привет"
example.c — сгенерированный файл с кодом на Си
#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] = '';
size_t name_len=0;
char *lang = malloc(1);
lang[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));
}
Потому, что они один к одному отображаются в типы Kotlin Native. На сегодняшний момент в проекте реализован, по сути, интероп только в одну сторону, т.е. вызов функций K/N из Си. Для обработки сложных типов, таких как zend_value, zend_class_entry или zend_fcall_info, необходимо импортировать соответствующие структуры в проект K/N и писать соответствующие обертки для работы с ними, а там тоже все на макросах и т.д…
Реализовать все то, что описано в разделе «Про то, почему только простые типы».
Еще раз ссылка на репозиторий [2].
Спасибо за внимание, пожелайте мне удачи :)
Автор: rjhdby
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/285617
Ссылки в тексте:
[1] первой части: https://habr.com/company/alfa/blog/415471/
[2] github: https://github.com/rjhdby/kotlin-native-php-extension
[3] Источник: https://habr.com/post/416719/?utm_campaign=416719
Нажмите здесь для печати.