📜 ⬆️ ⬇️

We write extensions for PHP to C (C)

A modern PHP developer may need this knowledge to expand consciousness, rather than a direct guide to action, but despite the fact that almost everything you need is already built into PHP, and in various PEAR and PECL repositories you can find a package of add-ons for every taste, I think many will interesting, and some useful to know how and what works inside PHP.

And since Zend provided us with such convenient tools, why not use them? For example, to optimize some processes, conceal one's own when in commercial applications and embed a license mechanism, implement multithreading, or for something else ...


There are two options for extensions: build PHP with our code or dynamically loadable modules. Both options differ only in one line - the presence of the macro ZEND_GET_MODULE in the last one. There is no particular difference between them, but in the process of developing and further supporting the application it is much more convenient to work with individual modules.
')

Hello world


Let's look at a slightly modified for simplicity example from the official documentation.
All that our first program does is return “1”, and gives us an idea of
standard extension structure. In essence, this minimal set of macros and APIs is a skeleton that will work in most cases.

 #include "php.h"

 / * declaration of functions to be exported * /
 ZEND_FUNCTION (my_function);

 / * compiled function list so that Zend knows what's in this module * /
 zend_function_entry firstmod_functions [] =
 {
	 ZEND_FE (my_function, NULL)
	 {NULL, NULL, NULL}
 };

 / * compiled module information * /
 zend_module_entry firstmod_module_entry =
 {
	 STANDARD_MODULE_HEADER,
	 "First Module",
	 firstmod_functions,
	 Null
	 Null
	 Null
	 Null
	 Null
	 NO_VERSION_YET,
	 STANDARD_MODULE_PROPERTIES
 };

 ZEND_GET_MODULE (firstmod)

 / * implement the function to make it available to PHP * /
 ZEND_FUNCTION (my_function)
 {
	 RETURN_LONG (1);
 }


Compilation


 cc -fpic -Wall -I / opt / php-5.2.6 / include / php / Zend -I / opt / php-5.2.6 / include / php / main 
 -I / opt / php-5.2.6 / include / php / TSRM -I / opt / php-5.2.6 / include / php -c -o test.o test.c

 cc -shared -rdynamic -Wall -O3 -o test.so test.o

To make it clear where everything comes from, I have shown the full paths to the header files.
But if you are going to work on a large project, then you should pay attention to
Recommended and pre-installed tools, and use the ext_skel utility.
(php-dist / ext / ext_skel --extname = mymod), which will generate a directory with configs for the new
module. Here we will not consider this in detail, there is no global difference, but for understanding
manual assembly is more convenient.

Now copy test.so to 'extension_dir'. extension_dir we must either write in php.ini,
or use the existing one. It all depends on the previous setting of your system.

Immediately after this, our module is ready for work; all that remains is to load it into a PHP script.
Create test.php

 <? php
	
	 dl ("test.so");  // Load our module
	 $ return = my_function (); 
	 print ("We got '$ return'");

 ?>


Getting, processing arguments and returning results.


First, let's write a module that will take three arguments and just output them.
The title remains the same, changing only my_function
 ZEND_FUNCTION (my_function)
 {
     char * str;  int str_len;
     long l;
     zval * array_arg;
     zval ** tmp;
     HashTable * target;
     HashPosition pos;
	
     if (ZEND_NUM_ARGS ()! = 3) WRONG_PARAM_COUNT;	
	
     if (zend_parse_parameters (ZEND_NUM_ARGS () TSRMLS_CC, "zsl", 
         & array_arg, & str, & str_len, & l) == FAILURE) {
         return;
     }
     / *
         z - zval *
         s - string, char *, int - note 
                 that the pointer is followed by an int variable,
                 which is written in length.  [..., & str, & str_len, ...] Do not forget
                 this, if you are going to use string in their understanding.
         l - Long, long
		
         list of other types and codes available in this function
         b - boolean, zend_bool
         d - double, double
         r - resource, zval *
         a - array, zval *
         o - object, zval *
     * /
	
     / * Processing array * /
     target = Z_ARRVAL_P (array_arg);
     if (! target) {
         php_error_docref (NULL TSRMLS_CC, E_WARNING, 
                                     "The argument 'array_arg' should be an array");
         RETURN_FALSE;
     }
	
     zend_hash_internal_pointer_reset_ex (target, & pos);
	
     zend_printf ("Array size:% d <br> \ r \ n", zend_hash_num_elements (target));
	
     while (zend_hash_get_current_data_ex (target, (void **) & tmp, & pos) == SUCCESS) {
         convert_to_string (* tmp);
         zend_printf ("% s <br> \ r \ n", Z_STRVAL_PP (tmp));
         zend_hash_move_forward_ex (target, & pos);
     }
	
     / * It's much easier with ordinary variables * /
     zend_printf ("% s <br> \ r \ n", str);
     zend_printf ("% ld <br> \ r \ n", l);
    
     RETURN_LONG (1);

 }


Here I would like to focus on a few points. Since there are no variable types in PHP
form, which C understands them, and they can contain anything, then you need to determine for yourself
where to prepare the transmitted data.
This can be done in a PHP script, and given to a function, as is done in the example or
check everything in the module, in this case everything is transmitted as “z”. Doing it in two places can
be right, but not very reasonable, but we strive for optimization.

And if the arguments passed for us are processed by zend_parse_parameters, then with arrays
have to do it yourself.
- in the example, all elements of the array are applied
     convert_to_string (* tmp);
     Z_STRVAL_PP (tmp);

in this case it fits - we just need to print it.

We will leave to your discretion the processing and pre-preparation of arguments in PHP, who knows how,
that’s what Zend does, Zend API offers us a wonderful set of macros, it can look like,
for example:

 while (zend_hash_get_current_data_ex (target, (void **) & tmp, & pos) == SUCCESS) {
	 switch (Z_TYPE_PP (tmp)) {
		 case IS_NULL:
			 php_printf ("NULL <br>");
			 break;
		 case IS_BOOL:
			 convert_to_boolean (* tmp);
			 php_printf ("Boolean:% s <br>", Z_LVAL_PP (tmp)? "TRUE": "FALSE");
			 break;
		 case IS_LONG:
			 convert_to_long (* tmp);
			 php_printf ("Long:% ld <br>", Z_LVAL_PP (tmp));
			 break;
		 case IS_DOUBLE:
			 convert_to_double (* tmp);
			 php_printf ("Double:% f <br>", Z_DVAL_PP (tmp));
			 break;
		 case IS_STRING:
			 convert_to_string (* tmp);
			 php_printf ("String:% s, len (% d) <br>", Z_STRVAL_PP (tmp), Z_STRLEN_PP (tmp));
			 break;
		 case IS_RESOURCE:
			 php_printf ("Resource <br>");
			 break;
		 case IS_ARRAY:
			 php_printf ("Array <br>");
			 break;
		 case IS_OBJECT:
			 php_printf ("Object <br>");
			 break;
		 default:
			 php_printf ("Unknown <br>");
	 }
	 zend_hash_move_forward_ex (target, & pos);
 }



We return the result


Now let's talk a little about what and how you can return from our program.
As expected, all types except arrays are returned with little effort from ours.
parties:
     RETURN_BOOL (bool)
     RETURN_NULL ()
     RETURN_LONG (long)
     RETURN_DOUBLE (double)
     RETURN_STRING (string, duplicate)
     RETURN_EMPTY_STRING ()
     RETURN_FALSE
     RETURN_TRUE

(Full list of macros can be found in the official documentation)

For arrays, there is a specific procedure for the actions we need to perform:

array_init (return_value) - initialize the array
then we add elements to it in the following ways.

add_next_index_ * for an index array
or

add_index_ * for index array by key
or

add_assoc_ * for an associative array by key

* this
  bool, long, string, null, zval, double 


Here is a small example of how to return an array whose elements are other
arrays containing in turn the result of a SELECT.

	 array_init (return_value);  / * Initialize the main array * /
	
	 rcnt = PQntuples (result);  / * Number of lines (postgresql api) * /
	 fcnt = PQnfields (result);  / * Number of columns (postgresql api) * /
	 for (i = 0; i <rcnt; i ++) {	
		 MAKE_STD_ZVAL (row);  / * Create a zval container for the future array * /
                 / * Initialize the second array in which the data will be stored * /
		 array_init (row);	
		 for (x = 0; x <fcnt; x ++) 
			 add_index_stringl (row, x, (char *) PQgetvalue (result, i, x), 
                                                  strlen ((char *) PQgetvalue (result, i, x)), 1);

		 / * Add row as an element of the return_value array * /	
		 add_index_zval (return_value, i, row);	
	 }



Multithreading ...?


In the official documentation, I did not find any information regarding the features.
using program threads when writing PHP extensions so that you can do
the conclusion is that they work like any other library included in our code. Not this way
long time ago there was a link to an article about multithreading using PHP itself. In my opinion,
if you have already decided on such a step, it is better to use pthread and modules written in C.
Let's check that it all works. We will write a small tcp server that simultaneously
will open 3 incoming ports. Thus, we will be able to make sure that everything works correctly by simply running netstat -ln or opening 3 connections simultaneously.

The title is left the same (as in the first example), we change only my_func and add the function
thr.

 void * thr (void * ptr)
 {
         struct sockaddr_in cliaddr, servaddr;
         int servsock, sock, on, Port = -1, i = 0;
         socklen_t clilen;
         char p, line [128];
        
         pthread_detach (pthread_self ());

         if ((servsock = socket (AF_INET, SOCK_STREAM, 0)) <0) {
                 zend_printf ("can't create servsocket <br> \ n");
                 return (0);
         }
         if (setsockopt (servsock, SOL_SOCKET, SO_REUSEADDR, (char *) & on, sizeof (on)) <0) {
                 zend_printf ("SO_REUSEADDR error <br> \ n");
                 return (0);
         }
        
         Port = * ((int *) ptr);
         free (ptr);
        
         servaddr.sin_family = AF_INET;
         servaddr.sin_port = htons (Port);
         servaddr.sin_addr.s_addr = htonl (INADDR_ANY);

         if (bind (servsock, (struct sockaddr *) & servaddr, sizeof (servaddr)) <0) {
                 zend_printf ("can't bind socket <br> \ n");
                 return (0);
         }
         listen (servsock, 5);
         clilen = sizeof (cliaddr);	
        
         while (1) {
                 sock = accept (servsock, (struct sockaddr *) & cliaddr, & clilen);
                 if (sock <0) {
                         zend_printf ("[-] accept error <br> \ n");
                         return (0);
                 }
                 zend_printf ("[+]% s: connected <br> \ n", inet_ntoa (cliaddr.sin_addr));
                
                 memset (& line, 0x00, sizeof (line));
                                
                 while (read (sock, (char *) & p, 1)> 0) {/ * Do not do this!  Here it is 
                                                           just for simplicity code.  * /
                         if (i> = 127) {
                                 memset (& line, 0x00, sizeof (line));
                                 i = 0;
                         }
                         if (p == '\ n') {
                                 line [i] = '\ 0';
                                 zend_printf ("port [% d] line:% s <br> \ r \ n", Port, line);
                                 memset (& line, 0x00, sizeof (line));
                                 i = 0;
                         }
                         line [i] = p;
                         i ++;
                 }

         }
        
         return (0);
 }


 ZEND_FUNCTION (my_function)
 {
        
         pthread_t tid;
         int * port_arg;
         int i = 0;
        
         for (i = 1; i <4; i ++) {
                 port_arg = (int *) malloc (sizeof (int));
                 * port_arg = 1024 + i;
                
                 if (pthread_create (& tid, NULL, & thr, (void *) port_arg)! = 0) {
                         zend_printf ("thread creating error <br> \ n");
                         RETURN_LONG (-1);
                 }
                 zend_printf ("Thread [% d] created, port_arg =% d <br> \ r \ n", i, * port_arg);
         }
        
         sleep (30);  / * 
                         After 30 seconds, all our processes will die.
                      * /
         zend_printf ("done <br> \ n");		
        
         RETURN_LONG (1);
 }


We change our test.php a bit
 <? php

   dl ("test.so"); 

   ob_implicit_flush (true);
   ob_end_flush ();

 
   my_function ();
  
   return;      
 ?>


Additional Information


Official documentation
Extensions articles in Sehnde
I also highly recommend that you familiarize yourself with the source code of the extensions included in
PHP distribution, especially with ext / standart folder.

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


All Articles