怎么了
我写这篇文章是为了使我总共花了至少一年的时间,读者可以走几个小时。 正如我的亲身经验所表明的那样,仅使用C语言进行编程要比对PHP工作进行认真的扩展要容易一些。 在这里,我将使用实现前缀树的
libtrie库示例(最好称为trie)来尽可能多地告诉您如何进行扩展。 我将在新安装的Lubuntu 18.04系统上编写并同时执行所描述的操作。
让我们开始吧。
软件安装
p
- 首先,我们放入php7.2-dev软件包,其中包含构建扩展所需的phpize脚本。 另外,我们将需要一个工作版本的php,在该版本上我们将检查我们的扩展名。 安装此软件包将拉出许多相关的软件包,我们将提供的所有内容放入其中。
sudo apt install php7.2-dev
- 我们转到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
- 现在将存档解压缩到我们自己:
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菜单为其自身创建快捷方式,但这是您首次需要手动运行它。
创建扩展
在这里,我们至少有3个选择:
- 从我们下载的php来源中获取原始标准光盘。
- 用特殊的ext_skel脚本稍微看了标准光盘
- 从这里获取优质的极简主义光盘。
我最喜欢第三个选项,但是在失败的情况下,我将使用第二个选项以最大程度地减少可能被误解的地方。 尽管选择开发人员的空白仍然是一种乐趣:-)
- 让我们转到带有标准php扩展名的目录。
cd /usr/local/src/php-7.2.11/ext
除了脚本的名称之外,您还可以通过原始文件指定一些扩展参数。 所有这一切都不可能完成。 我将手工做所有事情,但我将展示原型的工作原理。 我们做特里,所以让我们命名扩展libtrie。 要在/ usr / local / src目录中工作,您需要管理员权限,以免最终写sudo,我将包括具有提升权限的bash。
sudo bash
- 在这里,我将仅设置所创建扩展将实现的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
- 现在运行ext_skel脚本,为其指定扩展名和我们创建的原型文件。
./ext_skel --extname=libtrie --proto=./libtrie.proto
- 我们已经创建了带有扩展名的目录。 让我们开始吧。
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

第一个宏使您能够在运行生成的配置脚本时启用和禁用我们的扩展。
第二个块是最重要的,它确定哪些文件将作为扩展的一部分进行编译,扩展是通过.so文件动态连接还是扩展是静态的,以及是否将在php构建过程中进行集成。 我们的将充满活力。
保存文件。
我们将文件复制到用户目录,以使其在root模式下不起作用。
复制:
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)

也许有人知道如何缩短此文件的长度,以便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 #endif #include <stdarg.h> // va_start() #include <inttypes.h> // // #if defined(__GNUC__) && __GNUC__ >= 4 # define ZEND_API __attribute__ ((visibility())) # define ZEND_DLEXPORT __attribute__ ((visibility())) #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 #include #include #include //ZVAL_COPY_VALUE #include #include #include #include #include extern zend_module_entry libtrie_module_entry; ...
如果一切操作正确,则离子检测仪的右上角将显示黄色或绿色方块,这意味着没有严重错误。

理论上的一点偏离
为了使扩展正常工作,需要执行以下两项操作:
- 您需要初始化特殊结构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 // };
- 初始化包含我们扩展的所有功能的同一数组。
在这里,通过特殊的宏包装器PHP_FE(),设置了扩展中所有函数的名称。 通常,宏在PHP中非常活跃地使用,有很多仅引用其他宏的宏,而反之则更远。 您必须习惯它。 您可以通过ctrl +单击来确定是否通过了宏。 在这里,clion是必不可少的。
还记得原始文件吗? 我们在那里设置了1个函数my_array_fill()。 所以现在我们在这里有3个元素:
const zend_function_entry libtrie_functions[] = { PHP_FE(confirm_libtrie_compiled, NULL) PHP_FE(my_array_fill, NULL) PHP_FE_END };
第一行是一个测试函数,它将显示扩展名有效,第二行是我们的函数,第三行是一个特殊的宏,它应该是此数组中的最后一个。
找到我们的功能:
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控制台:
print_r(my_array_fill(50, 2, "hello, baby!"));
享受结果。

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