如何使PHP7中的扩展比“ hello,world”更困难,并且不致于眼花red乱。 第一部分

怎么了


我写这篇文章是为了使我总共花了至少一年的时间,读者可以走几个小时。 正如我的亲身经验所表明的那样,仅使用C语言进行编程要比对PHP工作进行认真的扩展要容易一些。 在这里,我将使用实现前缀树的libtrie库示例(最好称为trie)来尽可能多地告诉您如何进行扩展。 我将在新安装的Lubuntu 18.04系统上编写并同时执行所描述的操作。

让我们开始吧。

软件安装


p


  1. 首先,我们放入php7.2-dev软件包,其中包含构建扩展所需的phpize脚本。 另外,我们将需要一个工作版本的php,在该版本上我们将检查我们的扩展名。 安装此软件包将拉出许多相关的软件包,我们将提供的所有内容放入其中。

    sudo apt install php7.2-dev 

  2. 我们转到php.net网站,转到“下载”部分,并拉出指向带有最新稳定版本php(现在为7.2.11)的档案的链接。
    下载php源档案:

     cd /tmp && wget http://it2.php.net/get/php-7.2.11.tar.gz/from/this/mirror -O php7.tar.gz 

  3. 现在将存档解压缩到我们自己:

     sudo tar -xvf php7.tar.gz -C /usr/local/src 


代码编辑器


我通常使用2个代码编辑器。 简单快速的geany和漂亮的书呆子,但JetBrains提供了非常先进的提示。 Geany可从标准芜菁Ubuntu安装。

 sudo apt install geany 

从JetBrains官方网站下载Clion:

 cd ~/Downloads && wget https://download.jetbrains.com/cpp/CLion-2018.2.5.tar.gz -O clion.tar.gz 

 sudo tar -xvf clion.tar.gz -C /usr/share 

我们建立一个链接,以方便从控制台运行clion。

 sudo ln -s /usr/share/clion-2018.2.5/bin/clion.sh /usr/bin/clion 

首次启动后,clion本身将通过LXpanel Shell菜单为其自身创建快捷方式,但这是您首次需要手动运行它。

 # clion 

创建扩展


在这里,我们至少有3个选择:

  1. 从我们下载的php来源中获取原始标准光盘。
  2. 用特殊的ext_skel脚本稍微看了标准光盘
  3. 从这里获取优质的极简主义光盘。

我最喜欢第三个选项,但是在失败的情况下,我将使用第二个选项以最大程度地减少可能被误解的地方。 尽管选择开发人员的空白仍然是一种乐趣:-)

  1. 让我们转到带有标准php扩展名的目录。

     cd /usr/local/src/php-7.2.11/ext 

    除了脚本的名称之外,您还可以通过原始文件指定一些扩展参数。 所有这一切都不可能完成。 我将手工做所有事情,但我将展示原型的工作原理。 我们做特里,所以让我们命名扩展libtrie。 要在/ usr / local / src目录中工作,您需要管理员权限,以免最终写sudo,我将包括具有提升权限的bash。

     sudo bash 

  2. 在这里,我将仅设置所创建扩展将实现的1个函数的参数。 这只是一个演示功能,以演示如何完成此操作。

    我们将完全模拟标准功能

     array array_fill ( int $start_index , int $num , mixed $value ) 

    proto文件中的语法非常简单,只需要指定函数的名称即可。 我会写一些更多的东西,使它看起来更有用。

     echo my_array_fill \( int start_index , int num , mixed value \) >> libtrie.proto 

  3. 现在运行ext_skel脚本,为其指定扩展名和我们创建的原型文件。

     ./ext_skel --extname=libtrie --proto=./libtrie.proto 

  4. 我们已经创建了带有扩展名的目录。 让我们开始吧。

     cd libtrie 


文件结构与汇编原理


档案结构
 config.m4 -           phpize   ./configure,       makefile. CREDITS -  ,     ,    libtrie.c -      php_libtrie.h -     config.w32 -        windows EXPERIMENTAL -  .    ,    . libtrie.php -  php      . tests -   


要成功构建扩展,只需要3个文件。 在上面提到的扩展的极简光盘中,只有3个文件。

 config.m4 php_libtrie.h libtrie.c 

我不喜欢php中接受的标准命名,我喜欢标头文件和带有程序主体的文件命名相同。 因此重命名
libtrie.c

php_libtrie.c

 mv libtrie.c php_libtrie.c 

编辑config.m4


默认的config.m4文件实际上挤满了内容,其中的大量内容令人困惑。 如我所说,此文件是形成配置脚本所必需的。 在此处阅读有关此内容的更多信息。

 geany config.m4 & 

我们只剩下这个:

 PHP_ARG_ENABLE(libtrie, whether to enable libtrie support, [ --enable-libtrie Enable libtrie support]) if test "$PHP_LIBTRIE" != "no"; then #    -    # PHP_ADD_INCLUDE() #   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 



第一个宏使您能够在运行生成的配置脚本时启用和禁用我们的扩展。

第二个块是最重要的,它确定哪些文件将作为扩展的一部分进行编译,扩展是通过.so文件动态连接还是扩展是静态的,以及是否将在php构建过程中进行集成。 我们的将充满活力。
保存文件。

我们将文件复制到用户目录,以使其在root模式下不起作用。

 #    exit 

复制:

 cp /usr/local/src/php-7.2.11/ext/libtrie ~/Documents/ -r 

演示功能


让我提醒您,我们将完全模拟array_fill()。 我将通过clion进行研究,但是本指南可以在任何编辑器中完成。 Clion很好,因为它允许您自动执行基本语法检查,并且还支持通过ctrl +单击快速浏览文件或函数。 为了使此类转换在我们的扩展程序中起作用,您将必须配置CMakeLists.txt文件

为了使clion正常工作,您将需要安装cmake构建系统,该系统将安装一堆相关软件包。 使用命令安装所有这些:

 sudo apt install cmake 

配置cmake


我们以扩展形式打开我们的目录。 通过单击屏幕顶部的根目录名称,通过上下文菜单创建具有以下内容的CMakeLists.txt文件:

 cmake_minimum_required(VERSION 3.12) project(php-ext-libtrie C) set(CMAKE_C_STANDARD 11) #   phproot,       php set(phproot /usr/local/src/php-7.2.11/) #   ,      #      clion       php include_directories(${phproot}) include_directories(${phproot}TSRM/) include_directories(${phproot}main/) include_directories(${phproot}Zend/) #    clion          add_executable(php-ext-libtrie php_libtrie.c) 



也许有人知道如何缩短此文件的长度,以便clion开始索引项目文件。 我没有找到更短的方法。 如果有人知道,请在评论中写。

演示功能代码


用扩展名php_libtrie.c的主体打开文件,然后
删除所有评论,以免混淆我们。





Clion检查代码中使用的所有函数和宏是否都已声明,如果没有,则抛出错误。 显然,PHP开发人员不使用clion,否则他们可能会对此做一些事情。 为了防止这些错误在扩展程序中发生,我们将向我们提供缺少的头文件。

为了简化一切,我这样做:
我将所有包含头文件的头文件从php_libtrie.c文件传输到php_libtrie.h ,第一个文件中只有一个条目:

 #include "php_libtrie.h" 



php_libtrie.h文件将包含所有其他必要的包含。



我的头文件的内容
 #ifndef PHP_LIBTRIE_H #define PHP_LIBTRIE_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdarg.h> //   va_start() #include <inttypes.h> //    //  #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 //   ZVAL_COPY_VALUE() #ifndef ZEND_DEBUG #define ZEND_DEBUG 0 #endif //  ,      #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; ... 


如果一切操作正确,则离子检测仪的右上角将显示黄色或绿色方块,这意味着没有严重错误。



理论上的一点偏离


为了使扩展正常工作,需要执行以下两项操作:

  1. 您需要初始化特殊结构zend_module_entry,其中包含以下内容:

     zend_module_entry libtrie_module_entry = { STANDARD_MODULE_HEADER, //  "libtrie", //  libtrie_functions, //     PHP_MINIT(libtrie), //,     PHP_MSHUTDOWN(libtrie), //   PHP_RINIT(libtrie), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(libtrie), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(libtrie), // ,    php  phpinfo() PHP_LIBTRIE_VERSION, // ,     STANDARD_MODULE_PROPERTIES //    }; 

  2. 初始化包含我们扩展的所有功能的同一数组。

    在这里,通过特殊的宏包装器PHP_FE(),设置了扩展中所有函数的名称。 通常,宏在PHP中非常活跃地使用,有很多仅引用其他宏的宏,而反之则更远。 您必须习惯它。 您可以通过ctrl +单击来确定是否通过了宏。 在这里,clion是必不可少的。

    还记得原始文件吗? 我们在那里设置了1个函数my_array_fill()。 所以现在我们在这里有3个元素:

     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[] */ }; 

    第一行是一个测试函数,它将显示扩展名有效,第二行是我们的函数,第三行是一个特殊的宏,它应该是此数组中的最后一个。

找到我们的功能:

 PHP_FUNCTION(my_array_fill) 

如您所见,它也是通过宏初始化的。 事实是,所有php函数都不会在C内部返回任何内容(确切地说,返回void),并且不能更改其参数。 在某个地方甚至更方便。

如果您查看文件结构(左侧窗口的一部分),则会在此处列出文件的所有功能,但以在预编译宏后的形式显示它们。 屏幕快照显示我们的函数my_array_fill实际上将是zif_my_array_fill。



从php的参数到我们的C函数,我们使用了一个宏。 可以在文件中找到有关此宏的更多详细信息:

 /usr/local/src/php-7.2.11/README.PARAMETER_PARSING_API 

以下是我们的模拟功能代码,并附有详细说明。

代号
 PHP_FUNCTION(my_array_fill) { //   ,     //    2 : // zend_execute_data *execute_data, zval *return_value //     ,      //zend_long  int64_t  x64   int32_t  x86  //      zend_long zend_long start_index; //1  , zend_long num; //2   zval *value; //   mixed ,   zval,      //    ,          if (zend_parse_parameters(ZEND_NUM_ARGS(), "llz", &start_index, &num, &value) == FAILURE) { /*     *    RETURN_    * return_value     */ RETURN_FALSE; } //   ,   -    if (num <= 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "argument 2 must be > 0"); //     RETURN_FALSE; } //zval *return_value  ,        //       zval,     ,  -  //   zend_long  unsigned int32. //      + -. ..   1,    3,     4  array_init_size(return_value, (uint32_t)(start_index + num)); //  ,   ,   for(zend_long i = start_index, last = start_index + num; i < last; ++i) { //   zval       add_index_zval(return_value, i, value); } //   ,      return_value return; } 




构建和测试扩展


首先,运行phpize,这将使我们配置文件。

 phpize 

现在运行./configure,它将生成makefile。

 ./configure 

最后,运行make,它将收集我们的扩展名。

 make 

让我们检查一下我们做了什么。

 #    php,       # modules.  -a  php      php -d extension=modules/libtrie.so -a 

我们输入php控制台:

 print_r(my_array_fill(50, 2, "hello, baby!")); 

享受结果。



有人问,特里在哪里? 关于实现trie工作的函数,我将在第二部分中编写。

请继续关注。

Source: https://habr.com/ru/post/zh-CN428626/


All Articles