通过扩展可以将C语言实现的函数提供给PHP脚本使用,如同大量PHP内置函数一样,这些函数统称为内部函数(internal function),与PHP脚本中定义的用户函数不同,它们无需精力用户函数的编译过程,同时执行时也不像用户函数那样每一个指令都调用一次C语言编写的handler函数,因此,内部函数的执行效率更高。除了性能上的优势,内部函数还可以拥有更高的控制权限,可发挥的作用也更大,能够完成很多用户函数无法实现的功能。

函数通过zend_function来表示,这是一个联合体,用户函数使用zend_function.opoo_array, 内部函数使用zend_function.internal_function,两者具有相同的头部用来记录函数的基本信心。不管是用户函数还是内部函数,其最终都被注册到EG(function_table)中,函数被调用时根据函数名称向这个符号表中查找。从内部函数的注册、使用过程可以看出,其定义实际非常简单,我们只需要定义一个zend_internal_function结构,然后注册到EG(function_table)中即可。

接下来再重新看下内部函数的结构:

// zend_compile.htypedef struct _zend_internal_function {/* Common elements */zend_uchar type;zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */uint32_t fn_flags;zend_string *function_name;zend_class_entry *scope;zend_function *prototype;uint32_t num_args;uint32_t required_num_args;zend_internal_arg_info *arg_info;/* END of common elements */void (*handler)(INTERNAL_FUNCTION_PARAMETERS);struct _zend_module_entry *module;void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_internal_function;

Common elements就是与用户函数相同的头部,用来记录函数的基本信息:函数类型、参数信息、函数名等,handler是此内部函数的具体实现,PHP提供了一个宏用于此handler的定义:

PHP_FUNCTION(function_name)或ZEND_FUNCTION(),展开后:

void *zif_function_name(zend_execute_data *execute_data, zval *return_value)
{
...
}

PHP为函数名加了"zif_"前缀,gdb调试时记得加上这个前缀;另外内部函数定义了两个参数:execute_data、return_value、execute_data不用说了,return_value是函数的返回值,这两个值在扩展中会经常用到。

比如要在扩展中定义两个函数:my_func_1()、my_func_2(),首先是编写函数:

PHP_FUNCTION(my_func_1)
{printf("Hello, I'm my_func_1\n");
}
PHP_FUNCTION(my_func_2)
{printf("hello, I'm my_func_2\n");
}

函数定义完了就需要向PHP注册了,这里并不需要扩展自己注册,PHP提供了一个内部函数注册结构:zend_function_entry,扩展只需要为每个内部函数生成这样一个结构,然后把它们保存到扩展zend_module_entry.functions即可,在加载扩展中会自动向EG(function_table)注册。

typedef struct _zend_function_entry {const char *fname; //函数名称void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //handler实现const struct _zend_internal_arg_info *arg_info; //参数信息uint32_t num_args; //参数数目uint32_t flags;
} zend_function_entry;

zend_function_entry结构可以通过PHP_FE()或ZEND_FE()定义:

const zend_function_entry mytest_functions[] = {PHP_FE(my_func_1, NULL)PHP_FE(my_func_2, NULL)PHP_FE_END  //末尾必须加这个
};

用一张图来简单串一下数据结构的关系:

接下来进入正题我们要写个扩展代替以下的功能:

<?phpfunction hello() {return 'hello world';}

我的开发环境是:

系统: Ubuntu 16.04

PHP: 7.0+

gcc: 4.8.4

PHP已经提供了工具用来创建扩展,并初始化代码: ext_skel

$ cd php-src/ext
# ./ext_skel --extname=hello

第一步修改配置文件 config.m4 (phpize用来准备编译扩展的配置文件)

dnl PHP_ARG_WITH(hello, for hello support,
dnl Make sure that the comment is aligned:
dnl [  --with-hello    Include hello support])dnl Otherwise use enable:PHP_ARG_ENABLE(hello, whether to enable hello support,
Make sure that the comment is aligned:
[  --enable-hello    Enable hello support])

dnl是注释符,表示当前行是注释。说如果此扩展依赖其他扩展,去掉PHP_ARG_WITH段的注释符;

否则去掉PHP_ARG_ENABLE段的注释符。显然我们不依赖其他扩展或lib库,所以去掉PHP_ARG_ENABLE段的注释符:

第二步  在hello.c中添加我们需要的函数,然后加到编译列表里

首先需要创建一个zend_module_entry结构,这个变量必须是全局变量,且变量名必须是: 扩展名称_module_entry,内核通过这个结构得到这个扩展都提供了哪些功能,换句话说,一个扩展可以只包含一个zend_module_entry结构,相当于定义了一个什么功能都没有的扩展。

//zend_modules.h
struct _zend_module_entry {unsigned short size; //sizeof(zend_module_entry)unsigned int zend_api;  //ZEND_MODULE_API_NOunsigned char zend_debug;  //是否开启debugunsigned char zts;  //是否开启线程安全const struct _zend_ini_entry *ini_entry;const struct _zend_module_dep *deps;const char *name; //扩展名称,不能重复const struct _zend_function_entry *functions; //扩展提供的内部函数列表int (*module_startup_func)(INIT_FUNC_ARGS); //扩展初始化回调函数,PHP_MINIT_FUNCTION或ZEND_MINIT_FUNCTION定义的函数int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);  //扩展关闭时回调函数int (*request_startup_func)(INIT_FUNC_ARGS);  //请求开始前回调函数int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);  //请求结束时回调函数void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); //php_info展示的扩展信息处理函数const char *version; //版本...unsigned char type;void *handle;int module_number; //扩展的唯一编号const char *build_id;
}

这个结构包含很多成员,但并不是所有的都需要自己定义,经常用到的主要有下面几个:

name: 扩展名称,不能重复

functions: 扩展定义的内部函数entry

module_startup_func: PHP在模块初始化时回调的hook函数,可以使扩展介入modulestartup阶段

module_shutdown_func: 在模块关闭阶段回调的函数

request_startup_func: 在请求初始化阶段回调的函数

request_shutdown_func: 在请求结束阶段回调的函数

__info_func: __php_info()函数时调用,用于展示一些配置、运行信息

version:扩展

除了上面这些需要手动设置的成员,其他部分可以通过STANDARD_MODULE_HEADER,STANDARD_MODULE_PROPERTIES宏统一设置,扩展提供的内部函数及四个执行阶段的钩子函数是扩展最常用到的部分,几乎所有的扩展都是基于这两部分实现的。有了这个结构还需要提供一个接口来获取这个结构变量,这个接口是统一的,扩展中通过ZEND_GET_MODULE(extension_name)完成这个接口的定义:

//zend_API.h
#define ZEND_GET_MODULE(name) \
BEGIN_EXTERN_C() \
ZEND_DLEXPORT zend_module_entry *get_module(void)
{ return &name##_module_entry; }\
END_EXTERN_C

展开后可以看到,实际就是定义了一个get_module()函数,返回扩展zend_module_entry结构的地址,这就是为什么这个结构的变量名必须是 扩展名称_module_entry这种格式的原因。

有了扩展的zend_module_entry结构以及获取这个结构的接口一个合格的扩展就编写完成了,只是这个扩展目前还什么都干不了:

zend_module_entry hello_module_entry = {STANDARD_MODULE_HEADER,"hello",hello_functions,PHP_MINIT(hello),PHP_MSHUTDOWN(hello),PHP_RINIT(hello),    /* Replace with NULL if there's nothing to do at request start */PHP_RSHUTDOWN(hello),  /* Replace with NULL if there's nothing to do at request end */PHP_MINFO(hello),PHP_HELLO_VERSION,STANDARD_MODULE_PROPERTIES
};

写上我们的实现:

PHP_FUNCTION(hello)
{zend_string *strg;strg = strpprintf(0, "hello world.");RETURN_STR(strg);
}

添加到编译列表里:

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

第三步 编译与安装

$ phpize
$ ./configure --with-php-config=/usr/bin/php-config
$ make & make install

phpize这个脚本主要是操作复杂的autoconf/automake/autoheader/autolocal等系列命令,用于生成configure文件,GNU auto系列的工具众多,这里不做介绍了。

php-config这个脚本为PHP源码中的/script/php-config.in,PHP安装后被移到安装路径的/bin目录下,并重命名为php-config,这个脚本主要是获取PHP的安装信息的,主要有:

PHP安装路径

PHP版本

PHP源码的头文件目录:main、Zend、ext、TSRM中的头文件,编写扩展时会用到这些头文件,这些头文件保存在PHP安装位置/include/php目录下

LDFLAGS: 外部库路径,比如: -L/usr/bib -L/usr/local/lib

依赖的外部库: 告诉编译器要链接哪些文件,-lcrypt -lresolv -lcrypt等等

扩展存放目录: 扩展.so保存位置,安装扩展make install时将安装到此路径下

编译的SAPI: 如cli、fpm、cgi等

PHP编译参数: 执行./configure时带的参数

执行./configure --with-php-config=xxx生成Makefile时作为参数传入即可,它的作用是提供给configure.in获取上面几个配置,生成Makefile

第四步

修改php.ini,开启扩展,若找不到可以用phpinfo()查看使用哪个配置文件。

extension = hello.so

写个脚本: <?php echo hello();不出意外就能看到输出了。

附上完整的hello.c, php_hello.h


/*+----------------------------------------------------------------------+| PHP Version 7                                                        |+----------------------------------------------------------------------+| Copyright (c) 1997-2016 The PHP Group                                |+----------------------------------------------------------------------+| This source file is subject to version 3.01 of the PHP license,      || that is bundled with this package in the file LICENSE, and is        || available through the world-wide-web at the following url:           || http://www.php.net/license/3_01.txt                                  || If you did not receive a copy of the PHP license and are unable to   || obtain it through the world-wide-web, please send a note to          || license@php.net so we can mail you a copy immediately.               |+----------------------------------------------------------------------+| Author:                                                              |+----------------------------------------------------------------------+
*//* $Id$ */#ifdef HAVE_CONFIG_H
#include "config.h"
#endif#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_hello.h"/* If you declare any globals in php_hello.h uncomment this:
ZEND_DECLARE_MODULE_GLOBALS(hello)
*//* True global resources - no need for thread safety here */
static int le_hello;/* {{{ PHP_INI*/
/* Remove comments and fill if you need to have entries in php.ini
PHP_INI_BEGIN()STD_PHP_INI_ENTRY("hello.global_value",      "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_hello_globals, hello_globals)STD_PHP_INI_ENTRY("hello.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_hello_globals, hello_globals)
PHP_INI_END()
*/
/* }}} *//* Remove the following function when you have successfully modified config.m4so that your module can be compiled into PHP, it exists only for testingpurposes. *//* Every user-visible function in PHP should document itself in the source */
/* {{{ proto string confirm_hello_compiled(string arg)Return a string to confirm that the module is compiled in */
PHP_FUNCTION(confirm_hello_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.", "hello", arg);RETURN_STR(strg);
}PHP_FUNCTION(hello)
{zend_string *strg;strg = strpprintf(0, "hello world.");RETURN_STR(strg);
}/* }}} */
/* The previous line is meant for vim and emacs, so it can correctly fold andunfold functions in source code. See the corresponding marks just beforefunction definition, where the functions purpose is also documented. Pleasefollow this convention for the convenience of others editing your code.
*//* {{{ php_hello_init_globals*/
/* Uncomment this function if you have INI entries
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{hello_globals->global_value = 0;hello_globals->global_string = NULL;
}
*/
/* }}} *//* {{{ PHP_MINIT_FUNCTION*/
PHP_MINIT_FUNCTION(hello)
{/* If you have INI entries, uncomment these linesREGISTER_INI_ENTRIES();*/return SUCCESS;
}
/* }}} *//* {{{ PHP_MSHUTDOWN_FUNCTION*/
PHP_MSHUTDOWN_FUNCTION(hello)
{/* uncomment this line if you have INI entriesUNREGISTER_INI_ENTRIES();*/return SUCCESS;
}
/* }}} *//* Remove if there's nothing to do at request start */
/* {{{ PHP_RINIT_FUNCTION*/
PHP_RINIT_FUNCTION(hello)
{
#if defined(COMPILE_DL_HELLO) && defined(ZTS)ZEND_TSRMLS_CACHE_UPDATE();
#endifreturn SUCCESS;
}
/* }}} *//* Remove if there's nothing to do at request end */
/* {{{ PHP_RSHUTDOWN_FUNCTION*/
PHP_RSHUTDOWN_FUNCTION(hello)
{return SUCCESS;
}
/* }}} *//* {{{ PHP_MINFO_FUNCTION*/
PHP_MINFO_FUNCTION(hello)
{php_info_print_table_start();php_info_print_table_header(2, "hello support", "enabled");php_info_print_table_end();/* Remove comments if you have entries in php.iniDISPLAY_INI_ENTRIES();*/
}
/* }}} *//* {{{ hello_functions[]** Every user visible function must have an entry in hello_functions[].*/
const zend_function_entry hello_functions[] = {PHP_FE(confirm_hello_compiled,  NULL)       /* For testing, remove later. */PHP_FE(hello, NULL)PHP_FE_END   /* Must be the last line in hello_functions[] */
};
/* }}} *//* {{{ hello_module_entry*/
zend_module_entry hello_module_entry = {STANDARD_MODULE_HEADER,"hello",hello_functions,PHP_MINIT(hello),PHP_MSHUTDOWN(hello),PHP_RINIT(hello),       /* Replace with NULL if there's nothing to do at request start */PHP_RSHUTDOWN(hello), /* Replace with NULL if there's nothing to do at request end */PHP_MINFO(hello),PHP_HELLO_VERSION,STANDARD_MODULE_PROPERTIES
};
/* }}} */#ifdef COMPILE_DL_HELLO
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(hello)
#endif/** Local variables:* tab-width: 4* c-basic-offset: 4* End:* vim600: noet sw=4 ts=4 fdm=marker* vim<600: noet sw=4 ts=4*/
/* $Id$ */#ifndef PHP_HELLO_H
#define PHP_HELLO_Hextern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry#define PHP_HELLO_VERSION "0.1.0" /* Replace with version number for your extension */#ifdef PHP_WIN32
#   define PHP_HELLO_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#   define PHP_HELLO_API __attribute__ ((visibility("default")))
#else
#   define PHP_HELLO_API
#endif#ifdef ZTS
#include "TSRM.h"
#endif#if defined(ZTS) && defined(COMPILE_DL_HELLO)
ZEND_TSRMLS_CACHE_EXTERN()
#endif#endif    /* PHP_HELLO_H *//** Local variables:* tab-width: 4* c-basic-offset: 4* End:* vim600: noet sw=4 ts=4 fdm=marker* vim<600: noet sw=4 ts=4*/

【php7扩展开发一】注册一个内部函数hello world相关推荐

  1. php获取字符串扩展,PHP7扩展开发之字符串处理

    标签: 本文和大家分享的主要是PHP7扩展开发中字符串的处理相关知识,希望通过本文的分享能帮助大家更好的学习php. 这次,我们来看看字符串在PHP扩展里面如何处理. 示例代码如下: $len = s ...

  2. php7扩展开发教程,Laravel 7 扩展开发教程

    下面由Laravel入门教程栏目给大家介绍Laravel 7 扩展开发教程,希望对需要的朋友有所帮助! 步骤 1. 创建一个新项目 我更喜欢使用 Laravel 安装程序.laravel new la ...

  3. php7扩展开发教程,Linux下PHP7扩展开发入门教程1:扩展开发流程

    本文将会基于PHP7开发一个最简单的扩展,随便取个名learn_ext,编译生成一个learn_ext.so文件,最终调用可以在php中调用learn_ext扩展中的函数来输出一个hello worl ...

  4. PHP扩展开发 - 构建第一个PHP扩展

    2019独角兽企业重金招聘Python工程师标准>>> 首先需要确定系统中安装了gcc编译器,合适版本的bison等 ####构建一个基本的扩展骨架 在PHP扩展开发时,使用ext_ ...

  5. 【php7扩展开发三】ini配置

    php.ini配置 php.ini是PHP主要的配置文件,解析时PHP将在这些地方依次查找该文件:当前工作目录.环境变量PHPRC指定目录.编译时指定的路径,在命令行模式下,php.ini的查找路径可 ...

  6. php 扩展开发,PHP扩展开发,做一个属于自己的PHP扩展吧

    PHP扩展开发,一般来说需要一定的C基础.但是本篇文章介绍另一种扩展开发的方式.使用Zephir开发PHP扩展. 1.基础准备 1.1环境和软件 以CentOS系统为例,执行: yum install ...

  7. 【php7扩展开发六】zval的操作

    生成各类型zval PHP7将变量的引用计数转移到了具体的value上,所以zval更多的是作为统一的传输格式,很多情况下只是临时性使用,比如函数调用时的传参,最终需要的数据是zval携带的zend_ ...

  8. 【php7扩展开发五】函数调用

    实际应用中,扩展可能需要调用用户自定义的函数或者其他扩展定义的内部函数,PHP提供的函数调用API的使用: ZEND_API int call_user_function(HashTable *fun ...

  9. 【php7扩展开发四】函数的参数 ,引用传参 ,返回值

    函数参数解析 之前我们定义的函数没有接收任何参数,那么扩展定义的内部函数如何读取参数呢?用户自定义函数在编译时会为每个参数创建一个zend_arg_info结构,这个结构用来记录参数的名称.是否引用传 ...

最新文章

  1. 酷派android手机怎么截屏,酷派S688怎么截屏截图?
  2. 驱动学习模块最简单示例
  3. Understanding Design And Development Job Titles--reference
  4. java读取 png_如何让java的ImageBuffer正确读取PNG文件?
  5. Java输入两个正整数m和n,求其最大公约数和最小公倍数。
  6. python莫比乌斯环_有哪些完美或接近完美的构造(机械,生物,数学公式,文章,软件等等)?为什么完美?又能有什么领悟?...
  7. 因为不想「被绿」,美国年轻人只想和 iPhone 聊天
  8. 产业链人士:存储芯片平均售价有望在明年一季度停止下滑 随后趋于稳定
  9. 【干货】借助用户画像解决电商业务问题.pdf(附下载链接)
  10. 听说你决定当全职自由漏洞猎人了?过来人想跟你聊聊
  11. 蓝桥杯 Fibonacci数列求余数 C语言版本
  12. 3000字神经网络论文
  13. Vcc(电源)和GND(地)之间接电容的作用
  14. 在Windows x64中加载驱动
  15. 两个ESP8266一个作为服务器一个作为客户端实现互相通讯
  16. Error launching IDEA解决方法
  17. 传递函数化为状态空间表达式
  18. arcgis 删除创建的自定义地理变换文件
  19. 推荐系统学习笔记-FNN
  20. asm cli/sti 指令

热门文章

  1. phpmyadmin能合并行吗_去二手车行当学徒真的能学到技术吗?过来人劝你要谨慎!...
  2. Thrall’s Dream HRBUST - 2048【BFS or 强连通分量】
  3. NOSQL搭建redis群集
  4. laytpl遍历实体列表_Layui数据表格之获取表格中所有的数据方法
  5. 2 自动递增_有石CAD自动下单,1天工作量1小时完成
  6. 关于堆空间溢出的错误解决办法
  7. java打印6个偶数_Java编写一个应用程序,打印所有偶数从2到100
  8. linux 误删除mysql表能恢复吗,Linux中误删除数据文件和归档日志的恢复方法
  9. 自动控制matlab实验,自动控制matlab实验.doc
  10. python 下载文件 登录信息-Python爬虫 登录网页后下载图片,怎么保持登录状态?...