菜鸟学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命令,一切恢复正常。

时间: 2024-10-15 10:28:34

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

自动生成Makefile的全过程详解

一.简介 Linux下的程序开发人员,一定都遇到过Makefile,用make命令来编译自己写的程序确实是很方便.一般情况下,大家都是手工写一个简单Makefile,如果要想写出一个符合自由软件惯例的Makefile就不那么容易了. 在本文中,将介绍如何使用autoconf和automake两个工具来帮助我们自动地生成符合自由软件惯例的Makefile,这样就可以象常见的GNU程序一样,只要使用"./configure","make","make inst

使用Aspose.Cells生成Excel的方法详解(转)

using System; using System.Collections.Generic;  using System.Linq;  using System.Web;  using System.IO;  using System.Data;  using Aspose.Cells;   /// <summary> ///OutFileDao 的摘要说明 /// </summary>  public class OutFileDao  {          public Ou

Protobuf 文件生成工具 Prototool 命令详解

Protobuf 文件生成工具 Prototool 命令详解 简介 Prototool 是 Protobuf 文件的生成工具, 目前支持go, php, java, c#, object c 五种语言包的生成. 详情参考Github: https://github.com/uber/prototool docker 方式使用 prototool 工具 使用方式 // prototool 的使用 docker run --rm -v $(pwd):/work "uber/prototool&quo

Java基础之包装类的自动装箱和拆箱详解

定义 在java中,数据类型可以分为两大类,即基本数据类型和引用数据类型,基本数据类型的数据不是对象,所以对于要将数据类型作为对象来使用的情况,java提供了相对应的包装类.(关于包装类的详细介绍请参看博客Java基础之常用类详解) 本篇博客主要讲述包装类的自动装箱和拆行机制. 所谓装箱,就是把基本数据类型用它们相对应的引用类型包起来,使它们可以具有对象的特质,如我们可以把int类型包装成Integer类型的对象,或者把double包装秤Double,等等. 所谓拆箱,就是和装箱的方向相反,将I

IDEA里如何实现代码自动提示?(图文详解)

不多说,直接上干货! 前言 使用eclipse都习惯使用快捷键ALT+/ 来代码自动提示,后来使用IntelliJ Idea这个快捷键并不管用,十分不便,这里记录如何使更改idea代码自动提示快捷键. 首先,我这里,先复制Default,怎么做? 见 然后,我这里是以Default的复制(Default cpoy)为例 IDEA里的Keymap(Default.Default for GNOME.Default for KDE.Default for XWin.Eclipse.Eclipse(M

unittest框架扩展(自动生成用例)自动化-上

一.思想: 基于数据驱动和代码驱动结合的自动化测试框架. 二.自动化测试框架步骤: 1.获取用例,用例格式:.ymal 2.调用接口 3.校验结果 4.发送测试报告 5.异常处理 6.日志模块 三.基于上一篇文章中,使用unittest模块框架,编写自动化调用接口测试,拷贝生成用例的python文件作为模板,在conf下新建base.txt,只需每次修改文件中类名:和文件名:生成同样的python文件作为用例即可. base.txt如下: import unittest,requests imp

自动生成数学题型(框架Struts2)

1. 加减乘除 1.1 随机生成制定范围的整数 1 /** 2 * 随机产生一个被限定范围的整数 3 * 4 * @param num1 5 * 定义起始范围 num1 6 * @param num2 7 * 定义终止范围 num2 8 * @return 返回一个 num1 到num2 之间的随机整数数值,且num1<num2 9 * 10 */ 11 12 public static int generate(int num1, int num2) { 13 boolean i = num1

Hibernate之:各种主键生成策略与配置详解

1.assigned 主键由外部程序负责生成,在 save() 之前必须指定一个.Hibernate不负责维护主键生成.与Hibernate和底层数据库都无关,可以跨数据库.在存储对象前,必须要使用主键的setter方法给主键赋值,至于这个值怎么生成,完全由自己决定,这种方法应该尽量避免. <id name="id" column="id"> <generator class="assigned" /> </id&g

Hibernate各种主键生成策略与配置详解

1.assigned 主键由外部程序负责生成,在 save() 之前必须指定一个.Hibernate不负责维护主键生成.与Hibernate和底层数据库都无关,可以跨数据库.在存储对象前,必须要使用主键的setter方法给主键赋值,至于这个值怎么生成,完全由自己决定,这种方法应该尽量避免. <id name="id" column="id"> <generator class="assigned" /> </id&g