PHP extension and Kotlin Native. Part Two, Conscious

    Summary of the first part :


    1. Installation and configuration tools.
    2. Writing a function helloWorld()on Kotlin Native and compiling it in a shared library.
    3. Access to this function from the C-code extension of PHP.


    In this article I will talk about creating tools for writing a PHP extension without having to touch C, exclusively in K / N.

    Who cares - welcome under cat.
    Who is not interested to read, but just want to see - welcome to github

    At the very beginning I want to say a big thank you to Nikolai Igotti for the prompt and high-quality answers to my, sometimes stupid and naive, questions in the Kotlin Native channel.

    Immediately make a reservation that I do not pretend to create a full-fledged framework (maybe later), therefore we will limit the functionality in this way:

    1. Creating functions that can be called from PHP code.
    2. Definition of constants.
    3. We operate only simple types of the PHP: string, boolean, int, float(and null). No arrays, objects, resources, passes by reference, etc. - I will tell you why below.

    The specificity of the development of PHP extensions is that almost all the service code and communication with zend engineis written in macros. On the one hand, this makes it much easier to write extensions in C, and on the other hand, it makes it hard to do the same in all other programming languages.

    With such input, the most obvious solution was to use kodoherenaria. And, given that Kotlin provides very broad possibilities for creating DSL, the process of describing the extension structure can be made simple and intuitive.

    In order to build the extension library in the classical way (phpize, configure, make), you need at least two artifacts - the C extension code and the file config.m4.

    The usage scenario will be as follows:

    1. With DSL we describe the extension.
    2. We write the implementation of functions on K / N.
    3. Under the description we generate extension.cand config.m4. The code in extencion.cwill be engaged in banal calling of functions.
    4. According to the description we generate it constants.kt, which will allow us to use the given constants in our functions on K / N.
    5. Compile the K / N code into a static library.
    6. We collect all this in one pile and compile into the extension library.

    Go!


    To implement our plans, we need to get something like this structure:

    Расширение(имя, версия)
        Константа1
        Константа2
        ...
        Функция1(имя, возвращаемый тип)
            аргумент1
            аргумент2
            ...
            опциональныйАргумент1
            ...

    I think that for anyone working with Kotlin, it will not be difficult to write the corresponding DSL. For the rest, there are a large number of specialized articles where this topic is revealed in much more detail than if I try to do this in the framework of this article.

    The next step is to turn this DSL into necessary artifacts. To do this, we write a generator on the same K / N, compile the executable file from it and our DSL and run it - voila! The decision is not the most elegant, but nothing more simple and reliable until it came to mind.

    Well, then everything is simple - we compile the library with functions and in a regular way we assemble the extension, including it there.

    For ease of use, all the magic with compilations is hidden in a shell script.

    What came of it


    An example of the description and the generated code of a simple extension described on this DSL ( for better understanding, all arguments are given in a named form ).

    konfigure.kt - DSL Extensions

    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 - Implementing Functions

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

    Note the strange algorithm for determining the value for `lang`. This is due to a bug in the current version of K / N, which does not allow an uninitialized variable of type `char *` to be passed as an argument from C - you have to pass an empty string.

    config.m4 - generated file

    PHP_ARG_ENABLE(example, whether to enable example support,[ --enable-example   Enable hello support])
    iftest"$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 - generated file with Kotlin constants

    constval HELLO_EN = "Hello"constval HELLO_ES = "Hola"constval HELLO_RU = "Привет"

    example.c - generated C code file

    #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));
    }

    About why only simple types


    Because they are one to one mapped to Kotlin Native types. At present, the project has implemented, in fact, interop only in one direction, i.e. call K / N functions from C. To handle complex types, such as zend_value, zend_class_entryor zend_fcall_info, you need to import the corresponding structures into the K / N project and write the appropriate wrappers to work with them, and there, too, everything on macros, etc ...

    Jar with tar. Spoon attached.


    1. Kotlin Native Documentation. It seems to be there, but ... So far, the most reliable means of studying is reading the source code.
    2. The size of the resulting expansion is not that small. For the above example, the library is approximately 500KB.
    3. You can not even hope that the extensions written in K / N, fall into the library of PHP extensions. The product is obtained, so to speak, only for internal use.

    What's next


    Implement everything that is described in the section “About why only simple types”.

    Once again the link to the repository .

    Thank you for your attention, wish me luck :)

    Also popular now: