资讯专栏INFORMATION COLUMN

Just for fun——PHP7扩展编写中的宏

qieangel2013 / 2839人阅读

摘要:扩展开发者可以通过实现自定义的功能,通过扩展嵌入到中。就是这个扩展注册的函数数组。

PHP内核架构

SAPI是PHP的最上层,它是PHP的应用接口层,对于源码目录为sapi

main是PHP的主要代码,主要是输入/输出,Web通信,以及PHP框架的初始化操作,对于源码目录为main

ZendVM是PHP解释器的主要实现,即ZendVM,对于源码目录为Zend

截一张php-src的图,目录都有对应

PHP的生命周期


PHP根据不同SAPI的实现,各阶段的执行情况有些差异。譬如cli模式的话,完整地经历了这些阶段,而Fastcgi模式下则在启动时执行一次模块初始化,然后各个请求只经历请求初始化,执行请求脚本,请求关闭这几个阶段。

PHP扩展

开发者可以通过C/C++实现自定义的功能,通过扩展嵌入到PHP中。
编写扩展的步骤:

通过ext目录下ext_skel脚本生成扩展的基本框架./ext_skel --extname=module (module is the name of your extension)

修改config.m4配置:设置编译配置参数、设置扩展源文件

编写扩展源代码

生成configure:写完后先phpize(在php的bin目录下)运行一下

编译&安装: ./configure、 make、make install,然后改一下php.ini文件,添加一下.so文件

举例

操作系统:CentOS Linux release 7.3.1611
PHP版本:PHP 7.1.11

生成骨架

</>复制代码

  1. ./ext_skel --extname=my_test --no-help

--no-help是略去注释代码(干净点)
生成目录my_test:

查看C文件
my_test.c

</>复制代码

  1. /* $Salamander$ */
  2. #ifdef HAVE_CONFIG_H
  3. #include "config.h"
  4. #endif
  5. #include "php.h"
  6. #include "php_ini.h"
  7. #include "ext/standard/info.h"
  8. #include "php_my_test.h"
  9. static int le_my_test;
  10. PHP_MINIT_FUNCTION(my_test)
  11. {
  12. return SUCCESS;
  13. }
  14. PHP_MSHUTDOWN_FUNCTION(my_test)
  15. {
  16. return SUCCESS;
  17. }
  18. PHP_RINIT_FUNCTION(my_test)
  19. {
  20. #if defined(COMPILE_DL_MY_TEST) && defined(ZTS)
  21. ZEND_TSRMLS_CACHE_UPDATE();
  22. #endif
  23. return SUCCESS;
  24. }
  25. PHP_RSHUTDOWN_FUNCTION(my_test)
  26. {
  27. return SUCCESS;
  28. }
  29. PHP_MINFO_FUNCTION(my_test)
  30. {
  31. php_info_print_table_start();
  32. php_info_print_table_header(2, "my_test support", "enabled");
  33. php_info_print_table_end();
  34. }
  35. const zend_function_entry my_test_functions[] = {
  36. PHP_FE_END
  37. };
  38. zend_module_entry my_test_module_entry = {
  39. STANDARD_MODULE_HEADER,
  40. "my_test",
  41. my_test_functions,
  42. PHP_MINIT(my_test),
  43. PHP_MSHUTDOWN(my_test),
  44. PHP_RINIT(my_test),
  45. PHP_RSHUTDOWN(my_test),
  46. PHP_MINFO(my_test),
  47. PHP_MY_TEST_VERSION,
  48. STANDARD_MODULE_PROPERTIES
  49. };
  50. #ifdef COMPILE_DL_MY_TEST
  51. #ifdef ZTS
  52. ZEND_TSRMLS_CACHE_DEFINE()
  53. #endif
  54. ZEND_GET_MODULE(my_test)
  55. #endif

可以注意到这里有一些

PHP_MINIT_FUNCTION

PHP_MSHUTDOWN_FUNCTION

PHP_RINIT_FUNCTION

PHP_RSHUTDOWN_FUNCTION

PHP_MINFO_FUNCTION

这些是PHP提供的钩子函数,PHP执行到不同的阶段时回调各个扩展定义的钩子函数,定义完成后,最后设置一下zend_module_entry对应的函数指针即可。
回顾之前的PHP的生命周期,也就是说(=>指对应某个阶段):
PHP_MINIT_FUNCTION => 模块初始化阶段(M就是module的含义,init就是initial)
PHP_MSHUTDOWN_FUNCTION => 模块关闭阶段(M就是module的含义,后面就是shutdown)
PHP_RINIT_FUNCTION => 请求初始化(R就是request的含义,init就是initial)
PHP_RSHUTDOWN_FUNCTION => 请求关闭阶段(R就是request的含义,后面就是shutdown)
PHP_MINFO_FUNCTION 指获取模块信息
最后,设置zend_module_entry这个结构体

</>复制代码

  1. zend_module_entry my_test_module_entry = {
  2. STANDARD_MODULE_HEADER,
  3. "my_test",
  4. my_test_functions,
  5. PHP_MINIT(my_test),
  6. PHP_MSHUTDOWN(my_test),
  7. PHP_RINIT(my_test),
  8. PHP_RSHUTDOWN(my_test),
  9. PHP_MINFO(my_test),
  10. PHP_MY_TEST_VERSION,
  11. STANDARD_MODULE_PROPERTIES
  12. };

获取各个钩子函数的指针,有对对应的宏PHP_MINIT,PHP_MSHUTDOWN,PHP_RINIT,PHP_RSHUTDOWN,PHP_MINFO

注册函数

分为两步:

定义函数,可以通过PHP_FUNCTION()或ZEND_FUNCTION()宏来完成函数声明

注册函数,PHP提供了zend_function_entry,扩展只需为每个内部函数生成这样一个结构,然后将所有函数的结构数组提供给zend_module_entry->functions即可

For Example:

</>复制代码

  1. PHP_FUNCTION(my_func)
  2. {
  3. // 具体实现
  4. }

展开后

</>复制代码

  1. void zif_my_func(zend_execute_data *execute_data, zval *return_value)
  2. {
  3. // ...
  4. }

zend_function_entry可以通过宏PHP_FE或ZEND_FE生成(FE即function entry)。

</>复制代码

  1. const zend_function_entry my_test_functions[] = {
  2. PHP_FE(my_func, NULL)
  3. PHP_FE_END
  4. };

my_test_functions就是这个扩展注册的函数数组。
最后,它设置在了zend_module_entry(第三个参数)

</>复制代码

  1. zend_module_entry my_test_module_entry = {
  2. STANDARD_MODULE_HEADER,
  3. "my_test",
  4. my_test_functions,
  5. PHP_MINIT(my_test),
  6. PHP_MSHUTDOWN(my_test),
  7. PHP_RINIT(my_test),
  8. PHP_RSHUTDOWN(my_test),
  9. PHP_MINFO(my_test),
  10. PHP_MY_TEST_VERSION,
  11. STANDARD_MODULE_PROPERTIES
  12. };
函数参数解析

PHP提供了一个方法将zend_execute_data上的参数解析到指定变量上。

</>复制代码

  1. //file: Zend/zend_API.h
  2. ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...)

num_args:参数数量,用ZEND_NUM_ARGS()可以获取

type_spec 为参数解析规则,是一个字符串

最后一个是可变参数,指定要解析到的变量地址

举例:

</>复制代码

  1. PHP_FUNCTION(my_func)
  2. {
  3. zval *arr;
  4. if(zend_parse_parameters(ZEND_NUM_ARGS(), "a", &a) == FAILURE) {
  5. RETURN_FALSE;
  6. }
  7. ...
  8. }

如果有多个变量type_spec可以变为"la",l表示整型,a表示数组(另外还有b:布尔型,s:字符串型,o:对象)
,后面则改为&a, &b

函数返回值

可以设置return_value,但PHP提供了设置了设置返回值的宏

</>复制代码

  1. #define RETURN_BOOL(b) { RETVAL_BOOL(b); return; }
  2. #define RETURN_NULL() { RETVAL_NULL(); return;}
  3. #define RETURN_LONG(l) { RETVAL_LONG(l); return; }
  4. #define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; }
  5. #define RETURN_STR(s) { RETVAL_STR(s); return; }
  6. #define RETURN_INTERNED_STR(s) { RETVAL_INTERNED_STR(s); return; }
  7. #define RETURN_NEW_STR(s) { RETVAL_NEW_STR(s); return; }
  8. #define RETURN_STR_COPY(s) { RETVAL_STR_COPY(s); return; }
  9. #define RETURN_STRING(s) { RETVAL_STRING(s); return; }
  10. #define RETURN_STRINGL(s, l) { RETVAL_STRINGL(s, l); return; }
  11. #define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; }
  12. #define RETURN_RES(r) { RETVAL_RES(r); return; }
  13. #define RETURN_ARR(r) { RETVAL_ARR(r); return; }
  14. #define RETURN_OBJ(r) { RETVAL_OBJ(r); return; }
  15. #define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; }
  16. #define RETURN_FALSE { RETVAL_FALSE; return; }
  17. #define RETURN_TRUE { RETVAL_TRUE; return; }
写个小例子

写一个两个整型变量相加的函数

</>复制代码

  1. /* $Salamander$ */
  2. #ifdef HAVE_CONFIG_H
  3. #include "config.h"
  4. #endif
  5. #include "php.h"
  6. #include "php_ini.h"
  7. #include "ext/standard/info.h"
  8. #include "php_my_test.h"
  9. static int le_my_test;
  10. PHP_FUNCTION(my_add)
  11. {
  12. int argc = ZEND_NUM_ARGS();
  13. zend_long a;
  14. zend_long b;
  15. if (zend_parse_parameters(argc, "ll", &a, &b) == FAILURE)
  16. RETURN_FALSE;
  17. RETURN_LONG(a + b);
  18. }
  19. PHP_MINIT_FUNCTION(my_test)
  20. {
  21. return SUCCESS;
  22. }
  23. PHP_MSHUTDOWN_FUNCTION(my_test)
  24. {
  25. return SUCCESS;
  26. }
  27. PHP_RINIT_FUNCTION(my_test)
  28. {
  29. #if defined(COMPILE_DL_MY_TEST) && defined(ZTS)
  30. ZEND_TSRMLS_CACHE_UPDATE();
  31. #endif
  32. return SUCCESS;
  33. }
  34. PHP_RSHUTDOWN_FUNCTION(my_test)
  35. {
  36. return SUCCESS;
  37. }
  38. PHP_MINFO_FUNCTION(my_test)
  39. {
  40. php_info_print_table_start();
  41. php_info_print_table_header(2, "my_test support", "enabled");
  42. php_info_print_table_end();
  43. }
  44. const zend_function_entry my_test_functions[] = {
  45. PHP_FE(my_add, NULL)
  46. PHP_FE_END
  47. };
  48. zend_module_entry my_test_module_entry = {
  49. STANDARD_MODULE_HEADER,
  50. "my_test",
  51. my_test_functions,
  52. PHP_MINIT(my_test),
  53. PHP_MSHUTDOWN(my_test),
  54. PHP_RINIT(my_test),
  55. PHP_RSHUTDOWN(my_test),
  56. PHP_MINFO(my_test),
  57. PHP_MY_TEST_VERSION,
  58. STANDARD_MODULE_PROPERTIES
  59. };
  60. #ifdef COMPILE_DL_MY_TEST
  61. #ifdef ZTS
  62. ZEND_TSRMLS_CACHE_DEFINE()
  63. #endif
  64. ZEND_GET_MODULE(my_test)
  65. #endif

config.m4中取消以下注释(删除dnl即可)

</>复制代码

  1. dnl PHP_ARG_ENABLE(my_test, whether to enable my_test support,
  2. dnl Make sure that the comment is aligned:
  3. dnl [ --enable-my_test Enable my_test support])

然后在my_test目录下执行

</>复制代码

  1. phpize
  2. ./configure --with-php-config=/usr/local/php7.1/bin/php-config

php-config这个脚本是获取PHP安装信息的(PHP安装路径,PHP版本,PHP源码的头文件目录,LDFLAGS,依赖的外部库,PHP编译参数),它在php的安装路径的bin目录下,如果你不指定--with-php-config的话,将到默认的PHP的安装路径下搜索(安装了多个PHP版本时最好指定一下,可能会编译不通过
然后

</>复制代码

  1. make && make install

得到

</>复制代码

  1. Installing shared extensions: /usr/local/php7.1/lib/php/extensions/no-debug-zts-20160303/

修改php.ini文件,加入.so

</>复制代码

  1. date.timezone = "Asia/Shanghai"
  2. display_errors = On
  3. error_reporting = E_ALL
  4. short_open_tag=Off
  5. upload_max_filesize = 50M
  6. post_max_size = 50M
  7. memory_limit=512M
  8. extension=my_test.so
测试加载

</>复制代码

  1. php -m

测试函数

</>复制代码

  1. php -r "echo my_add(1, 3);"


函数调用成功。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28162.html

相关文章

  • Just for fun——PHP GUI做一个贪吃蛇

    摘要:图形用户界面扩展今天阅读手册的时候看到有这个图形库的扩展,所以咱赶新鲜用一用安装扩展官方手册的安装步骤是的要求文档写着到上下载的,我用的是位,所以用了这个下载下来的文件目录结构接下来放入到的目录中,然后加入和放入目录中,也就 图形用户界面(GUI) 扩展 今天阅读PHP手册的时候,看到PHP7有ui这个图形库的扩展,所以咱赶新鲜用一用 安装扩展 官方手册的安装步骤是linux的: Re...

    kk_miles 评论0 收藏0
  • Just for fun——windows上bat管理你的Nginx+PHP+MySQL

    摘要:百度云盘下载地址文件目录结构为开启的脚本为开启的脚本为开启服务的脚本为开启上述所有进程的脚本为关闭所有进程的脚本如果你更喜欢程序,可以使用简单使用手册把,,压缩包解压,并命名为,,标识一下新版本而已,以下配置文件用修改即可目录中把或者 百度云盘下载地址 文件目录结构 showImg(https://segmentfault.com/img/bVVFMU?w=757&h=380); ...

    DangoSky 评论0 收藏0
  • Just for fun——windows上bat管理你的Nginx+PHP+MySQL

    摘要:百度云盘下载地址文件目录结构为开启的脚本为开启的脚本为开启服务的脚本为开启上述所有进程的脚本为关闭所有进程的脚本如果你更喜欢程序,可以使用简单使用手册把,,压缩包解压,并命名为,,标识一下新版本而已,以下配置文件用修改即可目录中把或者 百度云盘下载地址 文件目录结构 showImg(https://segmentfault.com/img/bVVFMU?w=757&h=380); ...

    wenshi11019 评论0 收藏0
  • Just for fun——windows上的php-fpm

    摘要:,配是通过一个类似的协议,升级版的的。在上有帮你管理进程,在似乎没有,这是有点令人悲伤的。检验一下然后开启然后配置中里文件在盘建立一个的文件夹,放入,开启测试写入访问应用我的项目就用了这个东西,,欢迎 fastcgi As we all know,nginx配php是通过fastcgi(一个类似http的协议,升级版的cgi)的。在linux上有php-fpm帮你管理进程,在windo...

    kel 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<