PHP extension and Kotlin Native. Part one, naive

Published on June 28, 2018

PHP extension and Kotlin Native. Part one, naive

    This article describes the most naive and simplest approach to creating a PHP extension using Kotlin Native. I pay attention that not on , but with use .

    It is rather a tutorial describing the problems that have arisen when crossing the grass with the hedgehog and the ways to solve them. Revelations will not, but maybe someone will come in handy.

    So, if interested, then welcome under cat.

    The task is to write an extension with one function `hello ($ name)`, which accepts a string and prints `Hello, $ name!`.

    We will decide naively, as the name says.

    1. Let's write a function on Kotlin
    2. Let's compile in shared library
    3. In the classical way (in C) we will write an extension that will redirect the function call to this library.

    It seems simple, but a rake has already lurked in the thick grass:

    1. There are a number of examples of using C-libraries in Kotlin, but I couldn’t find anything adequate about how to use the functions of the Kotlin-library in C (well, I couldn’t search for anything)
    2. The kotlinc compiler on the first run loads dependencies. But bad luck - my Linux virtualka in the corporate cloud. Without even theoretical access to the Internet.
    3. These rakes are most likely from the lack of experience in C, but still I will tell you how I was notably trolled by the linker.

    Everything happened on Red Hat Enterprise Linux Server release 7.5.

    Go!


    First, install ... No, not the Kotlin compiler. First, install the JDK. So like this.
    Then install the Kotlin compiler. Simply download the archive from the githaba and unzip it, for example, to your home directory.

    In an ideal world, when you first start, he downloads all the dependencies himself, but, in the absence of the Internet, we act as follows (secret knowledge is obtained in a weak way from the staff of JetBrains):

    • Create any simplest script on Kotlin so that it is something to slip to the compiler in the next step
    • Run $ KOTLIN_HOME / bin / kotlinc SimpleScript.kt, wait a bit and press CTRL + C - this is to create a folder structure in the home directory
    • We look in the $ KOTLIN_HOME / konan / konan.properties file, in the section with our architecture, and look for the item:

    dependencies.linux_x64 = \
    	    clang-llvm-5.0.0-linux-x86-64 \
    	    target-gcc-toolchain-3-linux-x86-64 \
    	    libffi-3.2.1-2-linux-x86-64

    • Go to a special repository and download all of the above.
    • Putting all this into ~ / .konan / cache (remember, the Linux home directory is a tilde)

    Now when you first start the compiler will use these distributions and will not work on the Internet.

    Consider that the dependencies are not very small in size and, after their installation, my home directory has become heavier at 3.4GB. For those who have a separate volume for the home, it can be critical.

    After that, with the standard package manager, install php and its corresponding php-devel.
    At this preparatory activities are completed.

    Since the task to tell about writing PHP extensions is not worth it, let's get by with the shortest possible code.

    Let's start with Kotlin


    hellokt.kt

    fun kt_print(string:String){
        println("Hello, $string!!!")
    }

    In various manuals and tutorials, either kotlinc or konanc are used as compilers, which is somewhat confusing. So - this is the same thing. Here is proof:


    Compile

    # $KOTLINC_HOME/kotlinc -opt ./hellokt.kt -o hellokt -produce dynamic

    With key -opt libraries receive less. -produce dynamic tells the compiler what to do with the shared library for the current platform.

    After execution, we will have two files: libhellokt.so and hellokt_api.h. Library and header file for it. Please note that prefixes and suffixes are generated automatically and we cannot influence them (probably).

    In our case, this is the header file.

    #ifndef KONAN_HELLOKT_H
    #define KONAN_HELLOKT_H
    #ifdef __cplusplus
    extern "C" {
    #endif
    #ifdef __cplusplus
    typedef bool            hellokt_KBoolean;
    #else
    typedef _Bool           hellokt_KBoolean;
    #endif
    typedef char            hellokt_KByte;
    typedef unsigned short  hellokt_KChar;
    typedef short           hellokt_KShort;
    typedef int             hellokt_KInt;
    typedef long long       hellokt_KLong;
    typedef float           hellokt_KFloat;
    typedef double          hellokt_KDouble;
    typedef void*           hellokt_KNativePtr;
    struct hellokt_KType;
    typedef struct hellokt_KType hellokt_KType;
    typedef struct {
      /* Service functions. */
      void (*DisposeStablePointer)(hellokt_KNativePtr ptr);
      void (*DisposeString)(const char* string);
      hellokt_KBoolean (*IsInstance)(hellokt_KNativePtr ref, const hellokt_KType* type);
      /* User functions. */
      struct {
        struct {
          void (*kt_print)(const char* string);
        } root;
      } kotlin;
    } hellokt_ExportedSymbols;
    extern hellokt_ExportedSymbols* hellokt_symbols(void);
    #ifdef __cplusplus
    }  /* extern "C" */
    #endif
    #endif  /* KONAN_HELLOKT_H */

    Access to our kt_print function will follow this path.

    hellokt_symbols()->kotlin.root.kt_print(char *);

    I will tell about classes and packages below - there are nuances.

    The library is ready, go to C


    config.m4 ( how to create it )

    PHP_ARG_ENABLE(hello, whether to enable hello support,[ --enable-hello   Enable hello support])
    if test "$PHP_HELLO" != "no"; then
        PHP_ADD_LIBRARY_WITH_PATH(hellokt, /path/to/compiled/library, HELLO_SHARED_LIBADD)
        PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
        PHP_SUBST(HELLO_SHARED_LIBADD)
    fi

    The first line is required!
    The three first lines using the PHP_ARG_WITH () and PHP_ARG_ENABLE ().
    ... You
    can choose your extension into php.

    Note that in PHP_ADD_LIBRARY_WITH_PATH the first argument specifies the name of the library. Not libhellokt, but hellokt. Two hours ditched, until found, why ld can not find the library. (here is the promised story about linker bullying).

    Now, actually, the hello.c extension code

    itself

    #include "php.h"
    //Заголовочный файл нашей библиотеки
    #include "hellokt_api.h"    
    #define PHP_MY_EXTENSION_VERSION "1.0"
    #define PHP_MY_EXTENSION_EXTNAME "hello"
    PHP_FUNCTION(hello);
    static zend_function_entry hello_functions[] = {
            PHP_FE(hello, NULL)
    };
    zend_module_entry hello_module_entry = {
    #if ZEND_MODULE_API_NO >= 20010901
            STANDARD_MODULE_HEADER,
    #endif
            PHP_MY_EXTENSION_EXTNAME,
            hello_functions,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
    #if ZEND_MODULE_API_NO >= 20010901
            PHP_MY_EXTENSION_VERSION,
    #endif
            STANDARD_MODULE_PROPERTIES
    };
    ZEND_GET_MODULE(hello)
    PHP_FUNCTION(hello) {
            char * name;
            size_t name_len;
            if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
                RETURN_NULL();
            }
            hellokt_symbols()->kotlin.root.kt_print(name); //Вызов библиотечной функции
            efree(name);
    }

    There are a lot of articles about writing PHP extensions, and there is documentation, so I’ll confine myself to using zend_parse_parameters , it will be relevant .

    Well, then everything is on the way:

    # $PHP_PATH/phpize
    # ./configure  --with-php-config=$PHP_PATH/php-config
    # make
    # $PHP_PATH/php -dextension=./modules/hello.so -r "echo hello('World');"
    Hello, World!!!
    

    Bonus, pro classes and packages


    Suppose we wanted to do it by Fenshui and dazzled such a code.

    package hello.kt;
    public class HelloKt {
      fun kt_print(string: String) {
          println("Hello, $string!!!")
      }
    }

    The usual method call here is not enough - first you have to create a class and pass it with the first argument to the called function.

    hellokt_kref_hello_kt_HelloKt helloKt = { 0 };
    if(!helloKt.pinned){
            helloKt = hellokt_symbols()->kotlin.root.hello.kt.HelloKt.HelloKt();
    }
    hellokt_symbols()->kotlin.root.hello.kt.HelloKt.kt_print(helloKt, name);

    What's next? Rejecting the shared library, minimizing the use of C, and what the heck is not joking, a mini framework is such a thing. Wish me good luck! :)