window和linux下的php拓展开发

超低价nisp报名(二级比官网少一千多),可加本人v:zjhululu

1. php拓展相关介绍

php拓展为PHP提供一些扩展的功能

常见的php拓展有redis,mysql,xdebug等等

可以通过phpinfo()来查看php安装的拓展

或者利用php -m来查看安装的插件

php拓展文件一般都放在*\php\php7.3.4nts\ext\*文件夹中

2. php程序拓展安装举例

进入拓展下载网址PECL :: The PHP Extension Community Library,搜索需要的拓展(这里以taint为例),选择与自己php版本匹配的文件下载

解压,复制php_taint.dll文件到*\php\php7.3.4nts\ext\*文件夹中,并在php.ini文件中添加extension=php_taint

重启网站(apache),如果phpinfo中显示有taint的拓展,则表示成功

3. php拓展结构

3.1. PHP拓展与Zend拓展的区别

php拓展:在/php-src/Zend/zend_modules.h头文件中定义了_zend_module_entry结构体,这个结构体就是PHP扩展的结构体,称为module,除了一些基本信息外,主要提供了以上个钩子函数。其变量定义一般为:struct {module_name}_module_entry

Zend拓展:在/php-src/Zend/zend_extensions.h头文件中定义了_zend_extension结构体,这个结构体就是Zend扩展的结构体,称为extension。相比PHP扩展,主要提供了更底层的钩子函数。其变量定义一般为:struct {extension_name}_extension

3.2. php拓展代码结构

PHP中扩展通过zend_module_entry这个结构来表示。

此结构定义了扩展的全部信息:扩展名、扩展版本、扩展提供的函数列表以及PHP四个执行阶段的hook函数等,每一个扩展都需要定义一个此结构的变量,而且这个变量的名称格式必须是:{module_name}_module_entry,内核正是通过这个结构获取到扩展提供的功能的。

struct _zend_module_entry {
    unsigned short size;                                /*
    unsigned int zend_api;                               * STANDARD_MODULE_HEADER
    unsigned char zend_debug;                            *
    unsigned char zts;                                   */

    const struct _zend_ini_entry *ini_entry;            /* 没用过 */
    const struct _zend_module_dep *deps;                /* 模块依赖 */
    const char *name;                                   /* 模块名称 */

		
    // 扩展的函数指针, 用于获取扩展的函数列表
    const struct _zend_function_entry *functions;
		
    // 下面为四个钩子函数
    // MINT钩子函数,模块初始化时被调用
    int (*module_startup_func)(INIT_FUNC_ARGS);
    
    // RINT钩子函数,每一次请求时被调用
    int (*request_startup_func)(INIT_FUNC_ARGS);
    
    // RSHUTDOWN钩子函数,每一次请求结束后被调用
    int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	
    // 调用phpinfo()时打印扩展信息
    void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);   

		// MSHUTDOWN钩子函数,模块结束化时被调用
    int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);


    const char *version;                                /* 模块版本 */

    size_t globals_size;                                /*
#ifdef ZTS                                               *
    ts_rsrc_id* globals_id_ptr;                          *
#else                                                    * Globals management
    void* globals_ptr;                                   *
#endif                                                   *
    void (*globals_ctor)(void *global);                  *
    void (*globals_dtor)(void *global);                  */

    int (*post_deactivate_func)(void);                   /* 很少使用的生命周期钩子 */
    int module_started;                                  /* 是否已启动模块(内部使用) */
    unsigned char type;                                  /* 模块类型(内部使用) */
    void *handle;                                        /* dlopen() 返回句柄 */
    int module_number;                                   /* 模块号 */
    const char *build_id;                                /* 构建编号, STANDARD_MODULE_PROPERTIES_EX 的一部分*/
};

3.3. php文件目录结构

(不能用phpstudy等自带的php程序,必须去下载php源码)

php的拓展一般在/php-版本号/ext/*文件夹下,编译后一般为dll(windows)或os(linux)文件,拓展源码主要以以下文件组成:

  • text : 单元测试目录
  • config.m4 :扩展的编译配置文件(Unix系统)
  • config.w32 :扩展的编译配置文件(Windows系统)
  • php_{$module}.h :扩展的头文件
  • {$module}.c :扩展源文件
  • {$module}.php :扩展的测试文件

(1)在编译扩展成功后,会在扩展目录下生成一个run-test.php脚本文件,这个脚本会自动执行tests目录下的所有单元测试。
(2)此外在扩展目录下还会自动生成一个{$module}.php扩展的测试文件,可以方便的测试扩展是否可以正常加载和使用。

(3)扩展下载后只有源码,需要进行编译生成.so扩展文件后才可以被PHP使用,config.m4config.w32这两个文件就是在后续执行phpize阶段会使用到的编译配置文件。

在m4文件中,最前面的dnl宏会删除本行多余的字符,可以简单的理解为注释,如果需要编译其中一个扩展,则需要把这个拓展最前面的dnl宏都去掉。

(4)一般名为php_{$module}.h的是扩展头文件,一般用于定义需要的常量和函数等

(5)般名称为{$module}.c的是扩展源文件,主要由以下部分组成

  • zend_module_entry :定义扩展的结构体
  • PHP_FUNCTION :定义扩展的函数
  • Hook_FUNCTION :如 PHP_MINIT_FUNCTION 等

4. php拓展的生命周期

PHP拓展的调用顺序与钩子函数的调用,前面我们已经得知,php拓展主要有四个钩子函数:

  • MINT :模块初始化时被调用
  • MSHUTDOWN :模块结束化时被调用
  • RINT :每一次请求时被调用
  • RSHUTDOWN :每一次请求结束后被调用

详细步骤为:

  • 如果运行的是CLI或者FPM,它将运行C main();如果作为模块运行到网络服务器,像使用apxs2 SAPI(Apache2),则PHP在Apache启动后不久启动,并开始运行其模块的启动序列, PHP就是其中之一。在内部称启动为模块启动步骤。我们也将其缩写为MINIT步骤。
  • 一旦启动,PHP将等待处理一个或几个请求。当我们谈论PHP CLI时,将只有一个请求:当前脚本要运行。但是,当我们谈论Web环境时一应该是PHP-FPM或Web服务器模块一PHP可以一个接一个地做处理多个请求。这完全依赖于你如何配置你的Wb服务器:你可以告诉它处理无限数量的请求,或在关闭并回收该过程之前处理特定数量的请求。每次一个新的请求在线程中要处理时,PHP就会运行请求启动步骤。我们称之为RINIT
  • 请求得到处理,(可能)生成了一些内容,OK。是时候关闭请求,并准备好处理另一个请求。关闭请求调用请求关闭步骤。我们称之为RSHUTDOWN
  • 当处理完X个请求(一个,几十个,数千个等),PHP最后会自行关闭,然后结束。关闭PHP进程称为模块关闭步骤。缩写为MSHUTDOWN
  • 另外,还有四个较为详细的钩子:
    • Post请求终止:PRSHUTDOWN()
    • 全局初始化:GINIT()
    • 全局终止:GSHUTDOWN()
    • 信息收集:MINFO()
  • 则php拓展的生命周期可表示为:

5. php拓展开发

5.1. windows

5.1.1. php下载

windows.php.net - /downloads/releases/archives/

我下载的这两个:(7.0.9)(试了好多好多,就这个感觉可以,呜呜呜)

5.1.2. 下载Cygwin

(下载地址:Cygwin

5.1.3. 简单配置

可以将*\php-7.0.9-nts-Win32-VC11-x86添加到环境变量中

打开php源码中的*\php-7.0.9-src\ext\ext_skel_win32.php进行编辑

$cygwin_path = 'c:\cygwin\bin';中的位置改为自己cygwin的位置

下载vc++(根据自己php版本不同下载相对应的版本,我7.0.9下载的vc++14(2015))

5.1.4. 生成拓展

进入*\php-7.0.9-src\ext\文件夹内,运行php ext_skel_win32.php --extname=test,这里test代表扩展名,即可生成test拓展

主要结构:

  • text : 单元测试目录
  • config.m4 :扩展的编译配置文件(Unix系统)
  • config.w32 :扩展的编译配置文件(Windows系统)
  • php_{$module}.h :扩展的头文件
  • {$module}.c :扩展源文件
  • {$module}.php :扩展的测试文件
5.1.5. visual studio打开,配置并生成文件
(文件更换位置了,图片中路径可能不太一样)

打开VS选择“文件”--“新建”--“从现有代码创建目录”、

选择C++

选择php扩展文件夹路径D:\phpstudy_pro\Extensions\php\php-7.0.32-src\ext\test,并且给项目命名 test

选择“使用 visual studio”,项目类型选择“动态链接库(DLL)项目”,后面一直默认下一步一直到完成。

接下来,把项目解决方案配置改为Release

右键项目,属性,添加下列内容:

内容一:

C/C++,常规,在附加包含目录后面添加下面内容(以自己的为准)

D:\phpstudy_pro\Extensions\php\php-7.0.9-src;D:\phpstudy_pro\Extensions\php\php-7.0.9-src\main;D:\phpstudy_pro\Extensions\php\php-7.0.9-src\TSRM;D:\phpstudy_pro\Extensions\php\php-7.0.9-src\Zend

内容二:

C/C++,预处理器,预处理器定义,编辑,加入以下变量:

ZEND_DEBUG=0;PHP_EXTENSION;PHP_WIN32;ZEND_WIN32;HAVE_TEST=1;COMPILE_DL_TEST;ZTS;

(这里红色部分,要改成你的扩展名称,不改成你的扩展名,php会不识别)

(最后一个变量ZTS加上是开启线程安全(TS版本),不加是关闭线程安全(NTS版本),因为我下载的编译后php是NTS,所以我没有加)

内容三:

连接器,输入,附加依赖项,编辑,将php7.lib的路径放进去(这个文件在php编译后的程序文件夹里,根目录的dev文件夹里)D:\phpstudy_pro\Extensions\php\php-7.0.9-nts-Win32-VC14-x86\dev\php7.lib

右键项目,生成

遇到问题:

问题一:有很多源文件无法打开

解决方法:随便找一个源文件搜索在自己电脑上的位置,像我的assert.h文件的目录为:

C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\ucrt

然后,右键项目,属性,vc++目录,在包含目录中添加以下内容(以自己目录为准):

C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\shared;C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\winrt

如果还有其他源文件无法打开也同理

右键项目,重新生成

问题二:找不到Windows SDK版本B.1。请安装所需的版本的Windows SDK或者在项目属性页中或通过右键单击解决方案并选择”重定解决方案目标”来更改SDK版本。

解决方法:上面“项目”,“重定解决方案目标”,点击确定即可

右键项目,重新生成

问题三:无法打开包括文件:"main/config.w32.h":No such file or directory

D:\phpstudy_pro\Extensions\php\php-7.0.9-src\win32\build\文件夹里找到“config.w32.h.in”,将这个文件复制到D:\phpstudy_pro\Extensions\php\php-7.0.9-src\main\文件夹里,去掉后面的“.in”

同时,为了让扩展能和php运行环境匹配,要根据自己的php运行环境的编译版本(php7.0.9是VC14编译的),在D:\phpstudy_pro\Extensions\php\php-7.0.9-src\main\文件夹里的config.w32.h文件里加上:#define PHP_COMPILER_ID "VC14"

右键项目,重新生成

问题四: LNK1120: 5 个无法解析的外部命令

就是上面最后一条忘配置了

右键项目,属性,连接器,输入,附加依赖项,编辑,将php7.lib的路径放进去(这个文件在php编译后的程序文件夹里,根目录的dev文件夹里)D:\phpstudy_pro\Extensions\php\php-7.0.9-nts-Win32-VC14-x86\dev\php7.lib

右键项目,重新生成


最后可以看到,生成成功

5.1.6. 重要文件介绍

在test_1.c文件中(我换了一个项目test_1,所以不是test了,实际以自己的项目为准)

主要有两个部分需要特别注意(检索confirm_test_1_compiled):

第一部分:这部分主要是定义一个函数,这里函数名为confirm_test_1_compiled(可以更改或在下面重新定义一个函数)

PHP_FUNCTION(confirm_test_1_compiled)
{
	char *arg = NULL;
	size_t arg_len, len;
	zend_string *strg;
	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
		return;
	}
	strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "test_1", arg);
	RETURN_STR(strg);
}

第二部分:这部分主要用于注册函数,上面定义的函数,必须在这里定义一下才能使用

const zend_function_entry test_1_functions[] = {
	PHP_FE(confirm_test_1_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE_END	/* Must be the last line in test_1_functions[] */
};

然后“生成”——》“生成解决方案”

5.1.7. 案例

在上面说的第一部分下面,新定义一个My_test函数,用来打印hello world

PHP_FUNCTION(My_test)
{
	char *arg = NULL;
	size_t arg_len, len;
	zend_string *strg;
	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
		return;
	}
	strg = strpprintf(0, "hello world", "test_1", arg);
	RETURN_STR(strg);
}

在第二部分const zend_function_entry test_1_functions[]里面,添加PHP_FE(My_test,NULL)

const zend_function_entry test_1_functions[] = {
	PHP_FE(confirm_test_1_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE(My_test,	NULL)
	PHP_FE_END	/* Must be the last line in test_1_functions[] */
};

然后“生成”——》“生成解决方案”

D:\phpstudy_pro\Extensions\php\php-7.0.9-src\ext\test_1\Release目录下生成了一个test_1.dll文件(即拓展文件)

将其复制到D:\phpstudy_pro\Extensions\php\php-7.0.9-nts-Win32-VC14-x86\ext文件夹中

如果D:\phpstudy_pro\Extensions\php\php-7.0.9-nts-Win32-VC14-x86文件夹中没有php.ini文件

可以复制一下php.ini-development文件,并将副本文件名改为php.ini

在php.ini中添加拓展extension=test_1.dll

检索; On windows:,更改拓展文件夹位置并取消注释

接下来新建php文件:test.php

里面写入

<?php 
    echo My_test('111');

得到

5.2. linux下(centos)

5.2.1. 下载宝塔面板,并下载相关组件
yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec
5.2.2. 下载php源码

windows.php.net - /downloads/releases/archives/

我这里使用7.0.9

放到www/server/php/*下,这样可以直接在宝塔面板上进行代码的修改

5.2.3. 生成拓展源码
cd www/server/php/php-7.0.9-src/ext/
./ext_skel --extname=test  //这里test代表扩展名,即生成test拓展
5.2.4. 重要文件介绍以及修改

1) config.m4
找到这几行

dnl PHP_ARG_ENABLE(test, whether to enable test support,
dnl Make sure that the comment is aligned:
dnl [  --enable-test           Enable test support])

去掉这几行前面的dnl(注释作用),改为

PHP_ARG_ENABLE(test, whether to enable test support,
Make sure that the comment is aligned:
[  --enable-test           Enable test support])

这样以后编译php时,./configure后面加 --enable-test 就可以加载php模块了

2)在test.c文件中

主要有两个部分需要特别注意(检索confirm_test_compiled):

第一部分:这部分主要是定义一个函数,这里函数名为confirm_test_compiled(可以更改或在下面重新定义一个函数)

PHP_FUNCTION(confirm_test_compiled)
{
	char *arg = NULL;
	size_t arg_len, len;
	zend_string *strg;
	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
		return;
	}
	strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "test_1", arg);
	RETURN_STR(strg);
}

第二部分:这部分主要用于注册函数,上面定义的函数,必须在这里定义一下才能使用

const zend_function_entry test_functions[] = {
	PHP_FE(confirm_test_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE_END	/* Must be the last line in test_functions[] */
};

在上面说的第一部分下面,新定义一个test_echo函数,用来打印hello world

PHP_FUNCTION(test_echo)
{
	zend_printf("hello world");
}

在第二部分const zend_function_entry test_functions[]里面,添加PHP_FE(test_echo,NULL)

const zend_function_entry test_functions[] = {
	PHP_FE(confirm_test_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE(test_echo,	NULL)
	PHP_FE_END	/* Must be the last line in test_functions[] */
};

也可搜索:PHP_MINFO_FUNCTION(test),新增版本、作者等信息

php_info_print_table_row(2, "Version", "1.0");
php_info_print_table_row(2, "Author", "hululu");
5.2.5. 编译拓展

进入test文件夹

cd www/sever/php/php-7.0.9-src/ext/test_1/
phpize
./configure --with-php-config=/www/server/php/70/bin/php-config  //后面是宝塔上面安装的php路径。检查并生成编译配置
make && make install   //编译并安装

在这个目录下的modules/里面,生成了一个test.so文件

同时,这个文件也会直接添加到 /www/server/php/70/lib/php/extensions/no-debug-non-zts-20151012/ 文件夹中(即已经编译后的php拓展文件夹中,类似于Windows中php的ext文件夹)

5.2.6. 验证拓展

先新建test.php文件,写入

<?php
test_echo();

运行php -dextension=test.so test.php 可以验证成功(打印出hello world)

修改 /www/server/php/70/etc/php.ini 文件,在里面添加 extension=test.so,保存(有时候还得重启一下。)

打开phpinfo(),可以看到拓展信息

将test.php文件放入网站目录,也可进行验证

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zjeweler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值