How to make an extension for PHP7 harder than “hello, world”, and not become a red-eyed. Part 1

 
3r3-31. 3r33399. WHAT FOR? 3r33400. I am writing this article so that the reader, who took a total of not less than a year to complete the journey, could walk a couple of hours. As my personal experience has shown, simply programming in C is somewhat easier than making a serious PHP extension work. Here I will tell you in as much detail as possible how to make an extension using the example of the library. libtrie that implements a prefix tree, better known as trie. I will write and perform the described actions in parallel on a freshly installed Lubuntu ??? system. Let's start. from here . 3r33333.  
The third option I like the most, but I will use the second, in case of failure to minimize the number of places where I could be mistaken. Although picking a developer disc is still a pleasure :-)
 
Go to the directory with standard php extensions. 3r33434. 3r33417. cd /usr/local/src/php-???/ext
3r33426. 3r33427. In addition to the name of the script, you can specify some extension parameters through the proto file. All this can not be done. I will do everything with my hands, but how proto works will show. We make a trie, so we will call our extension libtrie. To work in the /usr /local /src directory, you need administrator privileges, so as not to endlessly write sudo, I will enable bash with elevated privileges. 3r33434. 3r33417. sudo bash
3r33426. 3r33427. 3r33333.  
Here I will set the parameters of only 1 function that the extension will implement. This is just a demonstration function to show how this is done. We will make a complete analog of the standard function
3r33424. array array_fill (int $ start_index, int $ num, mixed $ value)
3r33426. 3r33427. The syntax in the proto file is very simple, you just need to specify the name of the function. I will write a little more to look more informative. 3r33434. 3r33417. echo my_array_fill (int start_index, int num, mixed value) libtrie.proto
3r33426. 3r33427. 3r33333.  
Now run the ext_skel script, giving it the name of the extension and the proto file we created. 3r33434. 3r33417. ./ext_skel --extname = libtrie --proto =. /libtrie.proto
3r33426. 3r33427. 3r33333.  
We have created a directory with our extension. Let's go into it. 3r33434. 3r33417. cd libtrie
3r33426. 3r33427. 3r33333.  
3r33232. File structure and build principle
3r33333. 3r33347. File structure 3r33030.
3r33434. 3r33417. config.m4 - the initial configuration of the expansion is stored here on the basis of which a special program phpize prepares a script ./configure, which creates a configuration for building a makefile extension.
CREDITS is an empty file where they write who the author is, whom he thanks
libtrie.c - here is the main code of our extension
php_libtrie.h - here is the header file for the
extension. config.w32 - here is the initial configuration for building an extension for windows
EXPERIMENTAL is an empty file. I did not understand what is written in it.
libtrie.php - generated php file for basic validation of the extension.
tests - tests expansion
3r33426. 3r33427.
To successfully build the extension, you need only 3 files. In the minimalist extension bar, which I mentioned above, there are only 3 files. 3r33434. 3r33417. config.m4
php_libtrie.h
libtrie.c
3r33426. 3r33427. I don’t like the standard naming convention in php, I like the header files and files with the body of the program to be called the same. Therefore, rename libtrie.c in php_libtrie.c 3r33434. 3r33417. mv libtrie.c php_libtrie.c
3r33426. 3r33427. 3r33232. Editing config.m4 The default config.m4 file is literally stuffed with content, the abundance of which is confusing and confusing. As I said, this file is needed to form the configure script. Read more about this written here . 3r33434. 3r33417. geany config.m4 &
3r33426. 3r33427. We leave only this:
  3r33417. PHP_ARG_ENABLE (libtrie, whether to enable libtrie support,
W2w2w20.)
if test "$ PHP_LIBTRIE"! = "no"; then
# if you need to include any additional header files
# PHP_ADD_INCLUDE ()
# key line
PHP_NEW_EXTENSION (libtrie, php_libtrie.c, $ ext_shared)
# PHP_NEW_EXTENSION (libtrie, php_libtrie.c, $ ext_shared ,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE = 1)
fi
3r33426. 3r33427. 3r3187. The first macro creates the ability to enable and disable our extension when the configure script being created is run. The second block is the most important, it determines which files will be compiled as part of our extension, whether the extension will be dynamically connected via the .so file, or the extension will be static and integrated with build php. Our will be dynamic. Save the file. Copy the file to the user directory so as not to work in root mode. 3r33434. 3r33417. # exit from rut bash
exit
3r33426. 3r33427. We copy:
  3r33417. cp /usr/local/src/php-???/ext/libtrie ~ /Documents /-r
3r33426. 3r33427. 3r-33199. The demo function is 3-333200. Let me remind you that we will do the full analogue of array_fill (). I will work through clion, but this guide can be done in any editor. Clion is good in that it allows you to automatically do a basic syntax check, and also supports quick transition to files or functions via ctrl + click. In order for such transitions to work in our extension, you will need to configure the CMakeLists.txt file. In order for clion to work properly, you will need to install the cmake build system, with which a bunch of dependent packages will be installed. Install all this with the command:
  3r33417. sudo apt install cmake
3r33426. 3r33427. 3r33232. Configure cmake Open our catalog with the extension in clion. We create a CMakeLists.txt file with the following contents through the context menu by clicking on the name of the root directory at the top of the screen:
  3r33417. cmake_minimum_required (VERSION ???)
project (php-ext-libtrie C)
set (CMAKE_C_STANDARD 11)
# set the phproot variable to more conveniently prescribe paths to php
files. set (phproot /usr/local/src/php-???/)
# here are the directories that need to be included in the project
# we do this to make clion understand the internal functions and macros of php
itself. include_directories ($ {phproot})
include_directories ($ {phproot} TSRM /)
include_directories ($ {phproot} main /)
include_directories ($ {phproot} Zend /)
# without this line clion will not be able to read the file and will not index anything 3r3441. add_executable (php-ext-libtrie php_libtrie.c)
3r33426. 3r33427. How to make an extension for PHP7 harder than “hello, world”, and not become a red-eyed. Part 1 Open our file with the body of our extension php_libtrie.c We remove all comments so that they do not confuse us. 3r33232. Clion checks whether all the functions and macros used in the code have been declared and throws out an error if this is not the case. Obviously, PHP developers do not use clion, but they probably would have done something with it. To prevent these errors from appearing in our extension, we will include the missing header files to us. To arrange everything, I do this: 3r33333.  
all include with headings from php_libtrie.c File transfer in php_libtrie.h , in the first file there is only 1 record:
3r33351. #include "php_libtrie.h"
3r33426. 3r33427. there will be all the other necessary inclusions.
3r33434. 3r33351. #ifndef PHP_LIBTRIE_H
#define PHP_LIBTRIE_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
//here for the macro va_start ()
#include
//here are the standard numeric types
//desired constants
#if defined (__ GNUC__) &&___GNUC__> = 4
# define ZEND_API __attribute__ ((visibility ("default")))
# define ZEND_DLEXPORT __attribute__ ((visibility ("default")))
#else
# define ZEND_API
# define ZEND_DLEXPORT
#endif
# define SIZEOF_SIZE_T 8 //needed for the macro ZVAL_COPY_VALUE ()
#ifndef ZEND_DEBUG
#define ZEND_DEBUG 0
#endif
//here is the declaration of what is used in our extension
#include "php.h"
#include "php_ini.h"
#include "zend.h"
#include "zend_types.h" //ZVAL_COPY_VALUE
#include "ext /standard /info.h"
#include "zend_API.h"
#include "zend_modules.h"
#include "zend_string.h"
#include "spprintf.h"
extern zend_module_entry libtrie_module_entry;
3r33426. 3r33427.
If everything is done correctly, the clion checker will show a yellow or green square in the upper right corner, which means that there are no critical errors. For the normal operation of the expansion, you need 2 things:
 
It is necessary to initialize the special structure zend_module_entry, which contains the following:
3r33351. zend_module_entry libtrie_module_entry = {
STANDARD_MODULE_HEADER, //standard header
"libtrie", //extension name
libtrie_functions, //name of the array with extension functions
PHP_MINIT (libtrie), //function that is started when the
extension is enabled. PHP_MSHUTDOWN (libtrie), //function when shutting down
PHP_RINIT (libtrie), /* Replace with NULL if request start * /
PHP_RSHUTDOWN (libtrie), /* Replace with NULL if there is no end //
PHP_MINFO (libtrie), //apparently what php will show in phpinfo ()
PHP_LIBTRIE_VERSION, //extension version, is installed in the header file 3r3441. STANDARD_MODULE_PROPERTIES //I do not know what it is
};
3r33426. 3r33427. 3r33333.  
Initialize the same array that contains all the functions of our extension. Here, through a special macro wrapper PHP_FE (), the names of all functions in our extension are specified. In general, macros are very actively used in PHP, there are a lot of such macros that simply refer to other macros, and those in turn are further. It is necessary to get used to this. You can figure out if you go through the macros via ctrl + click. Here just clion is irreplaceable. Remember the proto file? We set there 1 function my_array_fill (). So now we have 3 elements here:
3r33351. const zend_function_entry libtrie_functions[]= {
PHP_FE (confirm_libtrie_compiled, NULL) /* For testing, remove later. * /
PHP_FE (my_array_fill, NULL)
PHP_FE_END /* Must be the last line in libtrie_functions[]* /
};
3r33426. 3r33427. The first line is a test function that will indicate that the extension is working, the second is our function, the third is a special macro that should be the last in this array. 3r33333.  
Find our function:
3r33351. PHP_FUNCTION (my_array_fill)
3r33426. 3r33427. As you can see, it is also initializing.tsya through the macro. The thing is that all php functions do not return anything (to be exact, they return void) inside C, and their arguments cannot be changed. Somewhere it is even convenient. If you look in the file structure (part of the window on the left), all the functions of the file are listed here, but in the form in which they will be after precompiling the macros. The screenshot shows that our function my_array_fill will actually be zif_my_array_fill. 3r33333. Arguments from the depths of php to our C function we get a macro. More information about this macro can be found in the file:
3r33417. /usr/local/src/php-???/README.PARAMETER_PARSING_API
3r33426. 3r33427. Below is the code of our analog function with detailed explanations. 3r33333. 3r33347. Code [/b]
3r33434. 3r33351. PHP_FUNCTION (my_array_fill)
{
//First, declare all the variables that we need here
//2 arguments are passed to any function:
//pointers zend_execute_data * execute_data, zval * return_value
//through the first pointer, the function receives the arguments, and through the second it returns the data
//zend_long is int64_t on x64 systems and int32_t on x86 systems 3r3441.
//the number is passed to the function with the type zend_long
zend_long start_index; //1 argument number,
zend_long num; //2 is also the number
zval * value; //since we have a mixed type, we take zval, which can store any type
//get the arguments to the declared variables, then immediately passes a check on the number and type of arguments
if (zend_parse_parameters (ZEND_NUM_ARGS (), "llz",
& start_index, & num, & value) == FAILURE) {
/* our functions do not output anything
* therefore all RETURN_ macros are simply written in
* return_value result and interrupt the function * /
RETURN_FALSE;
}
//check the second argument, where the number of output elements of the
array is specified. if (num <= 0) {
php_error_docref (NULL TSRMLS_CC, E_WARNING, "argument 2 must be> 0"); //another macro for outputting error 3r3443. RETURN_FALSE; 3r3441.}
3r3441 .your connection: 3r3443. in it and initialize the array
//this macro takes as input a pointer to the zval in which to make an array, and the number of elements 3r3441. //cast the type from zend_long to unsigned int32. 3r3441. //The size of the keys of the array is the first value Ie if the first is ? but only 3 is needed, then the array will be of 4 elements
array_init_size (return_value, (uint32_t) (start_index + num)); 3r3441. 3r3441. //add through the cycle, starting from the beginning for the last
for (zend_long i = start_index, last = start_index + num; i < last; ++i) {
//copy the pointer of our zval from the input to each element of the array 3r3441. add_index_zval (return_value, i, value); 3r3r3a1.a. the function returns nothing, and the array is already written in return_value
return;
}
3r33426. 3r33427.
3r33399. Build and test extensions
First, run phpize, which will make us the configure file. 3r33434. 3r33417. phpize
3r33426. 3r33427. Now run ./configure, which will make the makefile. 3r33434. 3r33417. ./configure
3r33426. 3r33427. Finally, run make, which will build us our extension. 3r33434. 3r33417. make
3r33426. 3r33427. Check out what we did. 3r33434. 3r33417. # This will make our php connect our compiled extension from the
directory. # modules. The -a switch will make php work as a command line in
php -d extension = modules /libtrie.so -a
3r33426. 3r33427. Enter in the console php:
3r33424. print_r (my_array_fill (5? ? "hello, baby!"));
3r33426. 3r33427. Enjoying the result. 3r33434. Someone will ask, but where is the trie? About the functions that implement the work trie, I will write in the second part. 3r33434. Stay tuned. 3r33430.
3r3r434. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e.parentNode.insertBefore (r, e)}; "[object Opera]" == e.opera? a.addEventListener? a.addEventListener ("DOMContentLoaded", d,! 1): e.attachEvent ("onload", d ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () (); 3r33434.
+ 0 -

Add comment