Extension PHP et Kotlin Native. Deuxième partie, consciente

Résumé de la première partie :


  1. Installation et configuration d'outils.
  2. Écrire la fonction helloWorld() dans Kotlin Native et la compiler dans une bibliothèque partagée.
  3. Accédez à cette fonction à partir du code d'extension PHP C.


Dans cet article, je parlerai de la création d'outils pour écrire une extension PHP sans avoir à toucher C, exclusivement sur K / N.

Peu importe - bienvenue au chat.
Celui qui lit n'est pas intéressé, mais veut juste le voir - bienvenue sur github

Au tout début, je tiens à remercier Nikolai Igotti pour ses réponses rapides et de haute qualité à mes questions, parfois stupides et naïves, sur le canal mou de Kotlin Native.

Faites immédiatement une réservation que je ne prétends pas créer un cadre à part entière (peut-être plus tard), donc nous limiterons la fonctionnalité de cette façon:

  1. Création de fonctions pouvant être appelées à partir du code PHP.
  2. Définition des constantes.
  3. Nous fonctionnons uniquement avec des types PHP simples: string , boolean , int , float (et null ). Pas de tableaux, d'objets, de ressources, de transferts par référence, etc. - Je vais vous expliquer pourquoi ci-dessous.

La spécificité du développement d'extensions PHP est que presque tout le code utilitaire et la communication avec le zend engine écrits sur des macros. D'une part, cela facilite grandement l'écriture d'extensions en C, et d'autre part, il est très difficile de faire de même dans tous les autres langages de programmation.

Avec une telle introduction, la solution la plus évidente était d'utiliser le codérinarium. Et, étant donné que Kotlin offre des possibilités très larges pour créer des DSL, le processus de description de la structure d'extension peut être rendu simple et intuitif.

Afin de construire la bibliothèque d'extensions de manière classique (phpize, configure, make), au moins deux artefacts sont nécessaires - le code d'extension en C et le fichier config.m4 .

Le scénario d'utilisation sera le suivant:

  1. En utilisant DSL, nous décrivons l'extension.
  2. Nous écrivons l'implémentation des fonctions sur K / N.
  3. Selon la description, nous générons extension.c et config.m4 . Le code dans extencion.c traitera du mandatement banal des appels de fonction.
  4. Selon la description, nous générons constants.kt , ce qui nous permet d'utiliser les constantes données dans nos fonctions sur K / N.
  5. Nous compilons le code K / N dans une bibliothèque statique.
  6. Tout rassembler et le compiler dans une bibliothèque d'extensions.

C'est parti!


Pour mettre en œuvre notre plan, nous devons obtenir quelque chose comme cette structure:

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

Je pense qu'il ne serait pas difficile pour quiconque travaillant avec Kotlin d'écrire la DSL appropriée. Pour le reste, il existe un grand nombre d'articles spécialisés où ce sujet est couvert de manière beaucoup plus détaillée que si j'essayais de le faire dans le cadre de cet article.

L'étape suivante consiste à transformer cette DSL en artefacts nécessaires. Pour ce faire, nous allons écrire un générateur sur le même K / N, compiler un fichier exécutable à partir de celui-ci et de notre DSL et l'exécuter - le tour est joué! La solution n'est pas la plus élégante, mais rien de plus simple et fiable ne m'est encore venu à l'esprit.

Eh bien, alors tout est simple - nous compilons la bibliothèque avec des fonctions et collectons l'extension de manière régulière, y compris là-bas.

Pour la facilité d'utilisation, toute la magie de la compilation est cachée dans un script shell.

Ce qui en est sorti


Un exemple de la description et du code généré pour l'extension simple décrite sur cette DSL ( pour une meilleure compréhension, tous les arguments sont donnés sous une forme nommée ).

konfigure.kt - extensions 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 - Implémentation de fonctions

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

Notez l'étrange algorithme pour déterminer la valeur de `lang`. Cela est dû à un bogue dans la version actuelle de K / N, qui ne permet pas de passer une variable non initialisée de type `char *` comme argument de C. - vous devez passer une chaîne vide.

config.m4 - fichier généré

 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 - fichier généré avec des constantes Kotlin

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

example.c - fichier généré avec le code 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] = '\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)); } 

Pourquoi seulement des types simples


Parce qu'ils sont un à un mappés aux types natifs Kotlin. À ce jour, le projet met en œuvre, en fait, n'interopérer que dans une seule direction, c'est-à-dire appel des fonctions K / N depuis C. Pour traiter des types complexes, tels que zend_value , zend_class_entry ou zend_fcall_info , vous devez importer les structures correspondantes dans le projet K / N et écrire les wrappers appropriés pour travailler avec eux, et il y a aussi toutes les macros, etc.

Pot de goudron. Une cuillère est attachée.


  1. Documentation native de Kotlin. Cela semble être là, mais ... Jusqu'à présent, le moyen le plus fiable pour étudier est de lire la source.
  2. La taille de l'extension résultante n'est pas si petite. Pour l'exemple ci-dessus, une bibliothèque d'environ 500 Ko est obtenue.
  3. Vous n'avez même pas à espérer que les extensions écrites en K / N finiront dans la bibliothèque d'extensions PHP. Le produit est obtenu, pour ainsi dire, uniquement pour un usage interne.

Et ensuite


Implémentez tout ce qui est décrit dans la section «À propos des seuls types simples».

Encore une fois, un lien vers le référentiel .

Merci de votre attention, bonne chance :)

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


All Articles