
En la
primera parte , se cuentan cosas bastante básicas sobre la configuración de herramientas y conceptos generales.
La segunda parte trata , por así decirlo, del primer acercamiento al proyectil, ideas, planes, planes.
En este artículo habrá un poco más de hardcore sobre C y K / N interope, muchas macros, dolor, desesperanza y "rayos de bondad". Por supuesto, habrá un capítulo con una historia sobre logros (no te alabarás a ti mismo ... y, como beneficio adicional, una historia sobre un fakap épico.
Descargo de responsabilidad: todo lo siguiente se considera en el contexto de escribir una biblioteca para PHP.
Capítulo uno Interope ingenuo
Cómo usar las funciones K / N en C se describe en la primera parte del ciclo. En consecuencia, aquí describiré cómo usar las funciones C en K / N.
La documentación oficial es bastante tacaña y concisa, sin embargo, para proyectos simples, es suficiente.
En resumen, debe crear un archivo especial con la extensión
.def y especificar los archivos de encabezado necesarios.
headers = php.h
Luego, aliméntelo a un programa llamado
cinterop .
# cinterop -def php.def -o php
En la salida, obtendrá la biblioteca
libphp.klib que contiene el código de bits llvm y varias metainformaciones.
Luego, puede usar de manera segura las funciones y macros descritas en el archivo de encabezado (
#define
), sin olvidar conectar la biblioteca en la etapa de compilación.
# kotlinc -opt -produce static ${SOURCES} -l libphp.klib -o myLib
Pero hay un matiz. Y no uno.
En la forma descrita anteriormente, la biblioteca no se ensamblará
Por qué Pero porque las siguientes líneas están presentes en php.h:
#include "php_version.h" #include "zend.h" #include "zend_sort.h" #include "php_compat.h" #include "zend_API.h"
Aquí debe tenerse en cuenta que llvm todavía está involucrado en la compilación de la biblioteca, y tiene el modificador
-I , y cinterop tiene el
modificador -copt . Bueno, entiendes el punto. Como resultado, dicho comando es suficiente para compilar
php.h. # cinterop -def my.def -o myLib -I${PHP_LIB_ROOT} -copt -I${PHP_LIB_ROOT} \ -copt -I${PHP_LIB_ROOT}/main \ -copt -I${PHP_LIB_ROOT}/Zend \ -copt -I${PHP_LIB_ROOT}/TSRM
Macros Te amo y te odio No, solo lo odio.
Todo lo que necesita saber sobre
#define
en términos de interopero C> K / N es
Cada macro de C que se expande a una constante se representa como propiedad de Kotlin. Otras macros no son compatibles.
Y luego recordamos que la extensión PHP es una macro en una macro y maneja una macro e intenta no llorar.
Pero no todo es tan malo. Para solucionar esta situación, los desarrolladores de K / N proporcionaron un rollo de cinta aislante azul para adjuntar al archivo def de
declaraciones personalizadas . Se ve así (por ejemplo, tome la macro
Z_TYPE_P
)
headers = php.h --- static inline zend_uchar __zp_get_arg_type(zval *z_value) { return Z_TYPE_P(z_value); }
Ahora en el código K / N será posible usar la función
__zp_get_arg_type
CAPITULO DOS PHP INI-settings o una macro sub-sub.
Este es un "rayo de bien" hacia el código fuente de PHP.
Hay 4 macros para extraer configuraciones:
INI_INT(val) INI_FLT(val) INI_STR(val) INI_BOOL(val)
Donde
val
es una cadena con el nombre de la configuración.
Ahora veamos el ejemplo de
INI_STR
para ver cómo se define esta macro.
#define INI_STR(name) zend_ini_string_ex((name), sizeof(name)-1, 0, NULL)
¿Ya has notado su "defecto fatal"?
Si no, déjame decirte: esta es la función
sizeof
. Cuando usa la macro directamente, entonces todo está bien:
php_printf("The value is : %s", INI_STR("my.ini"));
Cuando lo usa a través de una función proxy de un archivo
.def , el carro se convierte en una calabaza, y
sizeof (name) devuelve el tamaño del puntero. Jaque mate nativo de Kotlin.
De hecho, solo hay dos opciones para evadir.
- No use macros, sino funciones a las que están adjuntas.
- Funciones de envoltura de código duro para cada configuración requerida.
La primera opción es mejor para todos que la segunda, excepto por un punto: nadie garantizará que la macrodeclaración no cambie. Por lo tanto, para mi proyecto, yo, con un sentimiento de profunda insatisfacción, elegí la segunda opción.
Capítulo tres Depuración? ¿Qué debate?
Acto 1 - Interoperabilidad.
En un momento, después de adjuntar la cinta azul al archivo def de 20 funciones proxy consecutivas, recibí un error maravilloso.
Exception in thread "main" java.lang.Error: /tmp/tmp399964332777824085.c:103:38: error: too many arguments to function call, expected 2, have 3 at org.jetbrains.kotlin.native.interop.indexer.UtilsKt.ensureNoCompileErrors(Utils.kt:137) at org.jetbrains.kotlin.native.interop.indexer.IndexerKt.indexDeclarations(Indexer.kt:902) at org.jetbrains.kotlin.native.interop.indexer.IndexerKt.buildNativeIndexImpl(Indexer.kt:892) at org.jetbrains.kotlin.native.interop.indexer.NativeIndexKt.buildNativeIndex(NativeIndex.kt:56) at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.processCLib(main.kt:283) at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.interop(main.kt:38) at org.jetbrains.kotlin.cli.utilities.InteropCompilerKt.invokeInterop(InteropCompiler.kt:100) at org.jetbrains.kotlin.cli.utilities.MainKt.main(main.kt:29)

Comente sobre la mitad, vuelva a compilar, si se repite el comentario sobre la mitad del resto, recopilamos ... Y dado que el proceso de compilación de los encabezados es lo suficientemente largo ... (sí, parecía más rápido que subir una docena de archivos de origen y meticulosamente, con una lupa, reconciliar).
El segundo "rayo del bien" va hacia JetBrains.
Acto 2 - tiempo de ejecución.
Me sale un
error de segmentación en tiempo de ejecución. Pues bien, sucede. Estoy subiendo al depurador. Ummm ... STA?
Program received signal SIGSEGV, Segmentation fault. kfun:kotlinx.cinterop.toKString@kotlinx.cinterop.CPointer<kotlinx.cinterop.ByteVarOf<kotlin.Byte>>.()kotlin.String () at /opt/buildAgent/work/4d622a065c544371/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt:402 402 /opt/buildAgent/work/4d622a065c544371/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt: No such file or directory.
Capítulo cuatro Vertí té en tu té para que puedas beber té mientras bebes té.
Aquí es necesario decir cómo funciona esa basura que hago.
Escribe un DSL que describe la futura extensión de PHP, escribe código K / N con la implementación de funciones, clases y métodos, luego ejecuta
make
y, milagrosamente, obtiene una biblioteca preparada que se puede conectar a PHP.
El montaje se puede dividir en 4 etapas:
- Crear una capa entre C y K / N (el mismo
cinterop
) - Generación de código de extensión C
- Compilar una biblioteca con lógica
- Compilar la biblioteca de destino
El objetivo es agregar la capacidad de crear instancias de una clase PHP en código K / N. Por ejemplo, para que la clase pueda definir el método
getInstance()
. Y quiero hacerlo para que sea conveniente de usar.
En C, este problema se resuelve una o dos veces.
zval *obj = malloc(sizeof(zval)); object_init_ex(obj, myClass);
Parecería simple:
myClass
y transfiérelo a K / N, pero aquí está
myClass
...
Pero
myClass
es una variable global del tipo
zend_class_entry*
, declarada en el código C del proyecto y con un nombre desconocido de antemano.
Cuídate las manos.
myClass
compilar la biblioteca a partir del código K / N, en el que habrá una función que necesita tener acceso a
myClass
, que se define en el código C generado, pero no compilado, desde el cual se llamará a esta función.
Finalmente, la implementación de esta funcionalidad llevó a la adición de dos nuevos artefactos:
.h y
.kt en la etapa de generación de código, la complicación de la etapa cinterop y el épico phakap, del que hablaré al final.
Capítulo cinco ¿Qué hay en mi nombre?
El cuento de por qué:
enum class ArgumentType { PHP_STRING, PHP_LONG, PHP_DOUBLE, PHP_NULL, ... }
mejor que:
enum class ArgumentType { STRING, LONG, DOUBLE, NULL, ... }
Sí, ni siquiera hay necesidad de explicarlo. Esto es en lo que
ArgumentType.NULL
convierte en el archivo de encabezado de la biblioteca Kotlin:
struct { extension_kt_kref_php_extension_dsl_ArgumentType (*get)(); } NULL;
Y así es como gcc reacciona a esto.
/root/simpleExtension/phpmodule/extension_kt_api.h:113:17: error: expected identifier or '(' before 'void' } NULL; ^
La cortina! Cuidado con los nombres.
El penúltimo capítulo. No te alabarás a ti mismo, nadie lo alabará.
En general, he logrado mis objetivos. Inmerso en el tema, el "marco" para escribir extensiones PHP en Kotlin Native, en general, está listo. Queda por agregar algo, no la más crítica, funcionalidad y pulido.
El proyecto en sí y, espero, buena documentación para él, se puede ver
en el github .
¿Qué puedo decir sobre K / N? Solo bueno Escribir en él es un placer, y pequeños bancos de arena y asperezas pueden atribuirse al hecho de que ni siquiera ha salido de la cuna :)
El ultimo capitulo. Rayos del bien, sin comillas.
Y ahora, absolutamente en serio y con profundo respeto, quiero agradecer a los muchachos de JetBrains y a los residentes del canal suelto Kotlin Native. Eres super!
Y un agradecimiento especial a
Nicholas Igotti .
Bono Epic Fakap.
El contexto se describe en el cuarto capítulo.
En realidad, cuando todo se agregó a un estado en el que se compiló sin errores, surgió un problema: durante las pruebas, PHP se abrió para mí desde un lado completamente desconocido.
# php -dextension=./phpmodule/modules/extension.so -r "var_dump(ExampleClass::getInstance());" *RECURSION* #
"¡Figas!" - Pensé, me metí en el código fuente de PHP y encontré esa pieza.
case IS_OBJECT: if (Z_IS_RECURSIVE_P(struc)) { PUTS("*RECURSION*\n"); return; }
Agregar depuración:
printf("%u", Z_IS_RECURSIVE_P(struc))
conducido a:
undefined symbol: Z_IS_RECURSIVE_P in Unknown on line 0
"¡Figas!" Pensé de nuevo.
En ese momento, cuando supuse mirar el php.h (7.1.8) realmente utilizado en el host de Linux, y no el que se extrajo del github del brunch maestro (7.3.x), pasó un día. Directamente avergonzado.
Pero, como resultó, no era una bobina.
El código de verificación de recursión correcto, en todas las etapas de la vida del objeto que controlé, informó que todo debería funcionar. Y esto significa que debes mirar cuidadosamente esos lugares que no controlo. Hubo exactamente una de esas cosas, en la que mi objeto se devuelve a la función
var_dump
RETURN_OBJ( example_symbols()->kotlin.root.php.extension.proxy.objectToZval( example_symbols()->kotlin.root.exampleclass.getInstance() ) )
Abra la macro RETURN_OBJ hasta el final. ¡Quita de los monitores nerviosa y embarazada!
1) RETURN_OBJ(r) 2) { RETVAL_OBJ(r); return; } 3) { ZVAL_OBJ(return_value, r); return; } 4) { do { zval *__z = (return_value); Z_OBJ_P(__z) = (r); Z_TYPE_INFO_P(__z) = IS_OBJECT_EX; } while (0); return; } 5) { do { zval *__z = (return_value); Z_OBJ(*(__z)) = (r); Z_TYPE_INFO(*(__z)) = (IS_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT)); } while (0); return; } 6) { do { zval *__z = (return_value); (*(__z)).value.obj = (r); (*(__z)).u1.type_info = (8 | ((1<<0) << 8)); } while (0); return; }
Aquí entonces me sentí avergonzado por segunda vez. Yo, completamente en mi ojo azul,
zval*
a donde
zend_object*
esperando y pasé casi dos días buscando el error.
¡Gracias a todos Kotlin! :)
PS. Si hay un alma amable que lee mi torpe inglés y corrige la documentación, no habrá límite para mi gratitud.