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

通过扩展可以将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.h

typedef 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  //末尾必须加这个
};

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

扫描二维码关注公众号,回复: 12902143 查看本文章

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

<?php
    function 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_NO
    unsigned char zend_debug;  //是否开启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; //扩展提供的内部函数列表
    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          |
  | [email protected] 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.m4
   so that your module can be compiled into PHP, it exists only for testing
   purposes. */
 
/* 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 and
   unfold functions in source code. See the corresponding marks just before
   function definition, where the functions purpose is also documented. Please
   follow 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 lines
	REGISTER_INI_ENTRIES();
	*/
	return SUCCESS;
}
/* }}} */
 
/* {
   
   {
   
   { PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(hello)
{
	/* uncomment this line if you have INI entries
	UNREGISTER_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();
#endif
    return 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.ini
	DISPLAY_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_H
 
extern 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
 */

猜你喜欢

转载自blog.csdn.net/zhangge3663/article/details/115130828