首页 > 代码库 > 菜鸟学php扩展 之 自动生成的扩展框架详解(二)

菜鸟学php扩展 之 自动生成的扩展框架详解(二)

前言

上一文:菜鸟学php扩展 之 hello world(一) ,不问所以然的,强行与php扩展say hello了。对于ext_skel自动生成的框架,将在本文进行详解,当作备忘录。

正文

ext_skel的用法

./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]]
           [--skel=dir] [--full-xml] [--no-help]

  --extname=module   module is the name of your extension(模块名,会在当前目录创建一个该名称子目录)
  --proto=file       file contains prototypes of functions to create(函数原型定义文件)
  --stubs=file       generate only function stubs in file
  --xml              generate xml documentation to be added to phpdoc-cvs
  --skel=dir         path to the skeleton directory(设置骨架生成的目录,不设置该项则默认在ext/extname下)
  --full-xml         generate xml documentation for a self-contained extension
                     (not yet implemented)
  --no-help          don‘t try to be nice and create comments in the code
                     and helper functions to test if the module compiled (生成的代码中不显示各种帮助注释)

php与扩展相关的流程

1.PHP程序的启动与终止在概念上是分别存在两个的。

一个是php模块被加载的时候,模块启动函数即被引擎调用(PHP_MINIT_FUNCTION)。这使得引擎做一些例如资源类型,注册INI变量等的一次初始化,并且这些数据是常驻内存的,与之对应一个终止(PHP_MSHUTDOWN_FUNCTION)

另一个是PHP请求开始的时候,请求前的启动函数就别调用(PHP_RINIT_FUNCTION),与之对应一个请求结束后的终止(PHP_RSHUTDOWN_FUNCTION)

2.伴随着PHP的启动,便会开始把自身所有已加载的扩展的MINIT方法(全称Module Initialization,是由每个模块自己定义的函数。)(PHP_MINIT_FUNCTION),都执行一遍,在这个时间里,扩展可以定义一些自己的常量、类、资源等所有会被用户端的PHP脚本用到的东西。 这里定义的东东都会常驻内存,可以被所有请求使用,直到关掉PHP模块。

3.一个请求到来时候,PHP会迅速的开辟一个新的环境,并重新扫描自己的各个扩展, 遍历执行它们各自的RINIT方法(全称Request Initialization)(PHP_RINIT_FUNCTION), 这时候一个扩展可能会初始化在本次请求中会使用到的变量等, 还会初始化等会儿用户端(即PHP脚本)中的变量等等。

4.当请求经过业务代码,执行到最后的时候,PHP会启动回收程序,会执行所有已加载的扩展的RSHUTDOWN(全称Request Shutdown)(PHP_RSHUTDOWN_FUNCTION)方法,利用内核中的变量表之类的做一些事情,一旦执行结束,便会释放掉这次请求使用过的所有东西, 包括变量表的所有变量、所有在这次请求中申请的内存 等等

5.请求处理结束后,该关闭的也关了,PHP便进入MSHUTDOWN(全称Module Shutdown)(PHP_MSHUTDOWN_FUNCTION)阶段,此时PHP会向所有扩展发出最后通牒,如果哪个扩展还有未了的心愿,就放在自己MSHUTDOWN方法里,这可是最后的机会了,一旦PHP把扩展的MSHUTDOWN执行完,便会进入自毁程序。(清除擅自申请的内存的最后机会,否则就内存泄漏了)

汇总,我理解的流程:
PHP_MINIT_FUNCTION(一个进程执行一次)
|
执行很多个PHP_RINIT_FUNCTION
|
执行很多个PHP_RSHUTDOWN_FUNCTION
|
PHP_MSHUTDOWN_FUNCTION(一个进程执行一次)

附上多线程和多进程的图
技术分享

技术分享

config.m4

dnl代表备注掉此行,和php中//一样。为什么是dnl就不研究了,知道是备注就好。

dnl $Id$
dnl config.m4 for extension helloworld

dnl Comments in this file start with the string ‘dnl‘.
dnl Remove where necessary. This file will not work
dnl without editing.

dnl If your extension references something external, use with:

##指定PHP模块的工作方式,动态编译选项,如果想通过.so的方式接入扩展,请去掉前面的dnl注释
PHP_ARG_WITH(helloworld, for helloworld support,
Make sure that the comment is aligned:
[  --with-helloworld             Include helloworld support])

dnl Otherwise use enable:

##指定PHP模块的工作方式,静态编译选项,如果想通过enable的方式来启用,去掉dnl注释
PHP_ARG_ENABLE(helloworld, whether to enable helloworld support,
Make sure that the comment is aligned:
[  --enable-helloworld           Enable helloworld support])

if test "$PHP_HELLOWORLD" != "no"; then
  dnl Write more examples of tests here...

  dnl # --with-helloworld -> check with-path
  dnl SEARCH_PATH="/usr/local /usr"     # you might want to change this
  dnl SEARCH_FOR="/include/helloworld.h"  # you most likely want to change this
  dnl if test -r $PHP_HELLOWORLD/$SEARCH_FOR; then # path given as parameter
  dnl   HELLOWORLD_DIR=$PHP_HELLOWORLD
  dnl else # search default path list
  dnl   AC_MSG_CHECKING([for helloworld files in default path])
  dnl   for i in $SEARCH_PATH ; do
  dnl     if test -r $i/$SEARCH_FOR; then
  dnl       HELLOWORLD_DIR=$i
  dnl       AC_MSG_RESULT(found in $i)
  dnl     fi
  dnl   done
  dnl fi
  dnl 
  dnl if test -z "$HELLOWORLD_DIR"; then
  dnl   AC_MSG_RESULT([not found])
  dnl   AC_MSG_ERROR([Please reinstall the helloworld distribution])
  dnl fi

  dnl # --with-helloworld -> add include path
  dnl PHP_ADD_INCLUDE($HELLOWORLD_DIR/include)
  dnl # --with-helloworld -> check for lib and symbol presence
  dnl LIBNAME=helloworld # you may want to change this
  dnl LIBSYMBOL=helloworld # you most likely want to change this 

  dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
  dnl [
  dnl   PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $HELLOWORLD_DIR/$PHP_LIBDIR, HELLOWORLD_SHARED_LIBADD)
  dnl   AC_DEFINE(HAVE_HELLOWORLDLIB,1,[ ])
  dnl ],[
  dnl   AC_MSG_ERROR([wrong helloworld lib version or lib not found])
  dnl ],[
  dnl   -L$HELLOWORLD_DIR/$PHP_LIBDIR -lm
  dnl ])
  dnl

  ##用于说明这个扩展编译成动态链接库的形式
  dnl PHP_SUBST(HELLOWORLD_SHARED_LIBADD)

  ##用于指定有哪些源文件应该被编译,文件和文件之间用空格隔开
  PHP_NEW_EXTENSION(helloworld, helloworld.c, $ext_shared)
fi

php_helloworld.h

发现网上很多很早以前写的教材,都有在头文件理由申明一下函数。貌似较新的版本就不需要了。因为默认生成的框架在头文件里面也没有看到类似“PHP_FUNCTION(confirm_helloworld_compiled)”的字样。所以此文件不用太管他。(但是头文件申明一下要实现的函数是好习惯)

知道一下helloworld.c下面会用到的版本号在这里定义了

#define PHP_HELLOWORLD_VERSION "0.1.0"

helloworld.c

代码结构

以PHP_XXX的宏很多是在main/php.h里头定义的

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

##包含头文件(引入所需要的宏、API定义等)
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_helloworld.h"

static int le_helloworld;

##PHP核心定义的一个宏,与ZEND_FUNCTION相同,用于定义扩展函数(这个函数是系统默认生成的,用于确认之用)
PHP_FUNCTION(confirm_helloworld_compiled)
{
    char *arg = NULL;
    int arg_len, len;
    char *strg;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
        return;
    }

    len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "helloworld", arg);
    RETURN_STRINGL(strg, len, 0);
}

##定义PHP中可以调用的函数
PHP_FUNCTION(helloworld) {
    php_printf("Hello World!\n");
    RETURN_TRUE;
}

##初始化module时运行
PHP_MINIT_FUNCTION(helloworld)
{
    /* If you have INI entries, uncomment these lines 
    REGISTER_INI_ENTRIES();
    */
    return SUCCESS;
}

##当module被卸载时运行
PHP_MSHUTDOWN_FUNCTION(helloworld)
{
    /* uncomment this line if you have INI entries
    UNREGISTER_INI_ENTRIES();
    */
    return SUCCESS;
}

##当一个REQUEST请求初始化时运行
PHP_RINIT_FUNCTION(helloworld)
{
    return SUCCESS;
}

##当一个REQUEST请求结束时运行
PHP_RSHUTDOWN_FUNCTION(helloworld)
{
    return SUCCESS;
}

##声明模块信息函数,即可以在phpinfo看到的信息
PHP_MINFO_FUNCTION(helloworld)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "helloworld support", "enabled");
    php_info_print_table_end();

    /* Remove comments if you have entries in php.ini
    DISPLAY_INI_ENTRIES();
    */
}

##声明(引入)Zend(PHP)函数块
const zend_function_entry helloworld_functions[] = {
    PHP_FE(confirm_helloworld_compiled, NULL)       /* For testing, remove later. */
    ##上一讲中就是在这里添加了自己定义的函数模块
    PHP_FE(helloworld,  NULL)       /*  */
    ##zend引擎认为结束的标记,老版本的是“{NULL,NULL,NULL}”,后面PHP源代码直接定义了个宏PHP_FE_END,这里就直接用这个了。虽然都一个意思但看过去爽多了
    ##如果遇到PHP_FE_END未定义undefine的问题,请见附录1
    PHP_FE_END  /* Must be the last line in helloworld_functions[] */
};

##声明 Zend模块,是不是感觉下面的模块名字很熟悉,对的,就是前文讲到的PHP流程中会用到的,现在懂了为什么要先讲流程了吧~
zend_module_entry helloworld_module_entry = {
    STANDARD_MODULE_HEADER,
    "helloworld",
    helloworld_functions,
    PHP_MINIT(helloworld),
    PHP_MSHUTDOWN(helloworld),
    PHP_RINIT(helloworld),      /* Replace with NULL if there‘s nothing to do at request start */
    PHP_RSHUTDOWN(helloworld),  /* Replace with NULL if there‘s nothing to do at request end */
    PHP_MINFO(helloworld),
    PHP_HELLOWORLD_VERSION,
    STANDARD_MODULE_PROPERTIES
};

##实现get_module()函数
#ifdef COMPILE_DL_HELLOWORLD
ZEND_GET_MODULE(helloworld)
#endif

模块结构

1.包含头文件(引入所需要的宏、API定义等);

模块所必须包含的头文件仅有一个 php.h,它位于 main目录下。这个文件包含了构建模块时所必需的各种宏和API定义。

2.声明导出函数(用于 Zend函数块的声明);

ZEND_FUNCTION 宏声明了一个使用 Zend内部 API来编译的新的C函数。这个 C函数是 void类型,以 INTERNAL_FUNCTION_PARAMETERS(这是另一个宏)为参数,而且函数名字以 zif_为前缀。
PHP_FUNCTION和这个是一样的有在/main/php.h中已有定义宏了

#define PHP_FUNCTION            ZEND_FUNCTION

3.声明 Zend函数块;

现在你已经声明了导出函数,但Zend并不知道如何调用,因此还必须得将其引入 Zend。这些函数的引入是通过一个包含有N个zend_function_entry结构的数组来完成的。数组的每一项都对应于一个外部可见的函数,每一项都包含了某个函数在 PHP中出现的名字以及在 C代码中所定义的名字。

4.声明 Zend模块;

Zend模块的信息被保存在一个名为zend_module_entry的结构,它包含了所有需要向 Zend提供的模块信息。

5.实现get_module()函数;

这个函数只用于动态可加载模块

6.实现导出函数。

实现想要扩展的函数,PHP_FUNCTION(helloworld)

ps:模块部分是学习这篇文章的,本来写好了,后面发现他写的比我好就借鉴了PHP扩展代码结构详解

附录

1.error: ‘PHP_FE_END’ undeclared here (not in a function)错误。

原因:是因为zend引擎版本太老了。
1、切换到php的源码目录,
2、执行下面两行

# sed -i ‘s|PHP_FE_END|{NULL,NULL,NULL}|‘ ./ext/**/*.c
# sed -i ‘s|ZEND_MOD_END|{NULL,NULL,NULL}|‘ ./ext/**/*.c

3.切换到mcrypt目录,如php-5.x.x/ext/mcrypt/。再次执行make命令,一切恢复正常。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    菜鸟学php扩展 之 自动生成的扩展框架详解(二)