Extensão PHP e Kotlin Native. Parte Dois, Consciente

Resumo da primeira parte :


  1. Instalação e configuração de ferramentas.
  2. Escrevendo a função helloWorld() no Kotlin Native e compilando-a em uma biblioteca compartilhada.
  3. Acesse esta função a partir do código C da extensão PHP.


Neste artigo, falarei sobre a criação de ferramentas para escrever uma extensão PHP sem precisar tocar em C, exclusivamente no K / N.

Quem se importa - bem-vindo ao gato.
Quem lê não está interessado, mas apenas quer vê-lo - bem-vindo ao github

Desde o início, quero agradecer a Nikolai Igotti por respostas rápidas e de alta qualidade às minhas perguntas, às vezes tolas e ingênuas, no canal de folga do Kotlin Native.

Faça imediatamente uma reserva que não pretendo criar uma estrutura completa (talvez mais tarde); portanto, limitaremos a funcionalidade desta maneira:

  1. Criando funções que podem ser chamadas a partir do código PHP.
  2. Definição de constantes.
  3. Operamos apenas com tipos simples de PHP: string , boolean , int , float (e null ). Sem matrizes, objetos, recursos, transferências por referência, etc. - Vou lhe dizer por que abaixo.

As especificidades do desenvolvimento de extensões PHP são que quase todo o código do utilitário e a comunicação com o zend engine escritos em macros. Por um lado, facilita muito a escrita de extensões em C e, por outro lado, torna muito difícil fazer o mesmo em todas as outras linguagens de programação.

Com essa introdução, a solução mais óbvia foi usar o coderinarium. E, dado que o Kotlin oferece possibilidades muito amplas para a criação de DSLs, o processo de descrição da estrutura de extensão pode ser simplificado e intuitivo.

Para construir a biblioteca de extensões de maneira clássica (phpize, configure, make), são necessários pelo menos dois artefatos - o código de extensão em C e o arquivo config.m4 .

O cenário de uso será assim:

  1. Usando DSL, descrevemos a extensão.
  2. Escrevemos a implementação de funções em K / N.
  3. De acordo com a descrição, geramos extension.c e config.m4 . O código em extencion.c tratará da proxy banal de chamadas de função.
  4. De acordo com a descrição, geramos constants.kt , o que nos permite usar as constantes dadas em nossas funções em K / N.
  5. Nós compilamos o código K / N em uma biblioteca estática.
  6. Reunindo tudo e compilando-o em uma biblioteca de extensão.

Vamos lá!


Para implementar nosso plano, precisamos obter algo como esta estrutura:

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

Eu acho que não seria difícil para quem trabalha com Kotlin escrever a DSL apropriada. Quanto ao resto, há um grande número de artigos especializados nos quais esse tópico é abordado com muito mais detalhes do que se eu tentasse fazer isso como parte deste artigo.

A próxima etapa é transformar esse DSL nos artefatos necessários. Para fazer isso, escreveremos um gerador no mesmo K / N, compilaremos um arquivo executável a partir dele e de nossa DSL e executá-lo - voila! A solução não é a mais elegante, mas nada mais simples e confiável ainda me ocorreu.

Bem, então tudo é simples - compilamos a biblioteca com funções e coletamos a extensão regularmente, incluindo-a lá.

Para facilitar o uso, toda a mágica da compilação está oculta em um script de shell.

O que veio disso


Um exemplo da descrição e do código gerado para a extensão simples descrita nesta DSL ( para melhor compreensão, todos os argumentos são fornecidos em um formulário nomeado ).

konfigure.kt - extensões 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 - Implementando funções

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

Observe o algoritmo estranho para determinar o valor para `lang`. Isso ocorre devido a um erro na versão atual do K / N, que não permite passar uma variável não inicializada do tipo `char *` como argumento de C. - você precisa passar uma string vazia.

config.m4 - arquivo gerado

 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 - arquivo gerado com constantes Kotlin

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

example.c - arquivo gerado com código 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)); } 

Sobre por que apenas tipos simples


Porque eles são um a um mapeados para os tipos nativos do Kotlin. Até o momento, o projeto implementa, de fato, a interoperabilidade apenas em uma direção, ou seja, chamando funções K / N de C. Para processar tipos complexos, como zend_value , zend_class_entry ou zend_fcall_info , você precisa importar as estruturas correspondentes no projeto K / N e gravar os wrappers apropriados para trabalhar com eles, além de todas as macros, etc.

Jarra com alcatrão. Uma colher está presa.


  1. Documentação nativa Kotlin. Parece estar lá, mas ... Até agora, o meio mais confiável de estudar é ler a fonte.
  2. O tamanho da extensão resultante não é tão pequeno. Para o exemplo acima, é obtida uma biblioteca de aproximadamente 500 KB.
  3. Você nem precisa esperar que as extensões escritas em K / N acabem na biblioteca de extensões PHP. O produto é obtido, por assim dizer, apenas para uso interno.

O que vem a seguir


Implemente tudo o que está descrito na seção "Sobre por que apenas tipos simples".

Mais uma vez, um link para o repositório .

Obrigado pela atenção, me deseje boa sorte :)

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


All Articles