首頁  >  文章  >  後端開發  >  php擴展之擴展框架的自動生成

php擴展之擴展框架的自動生成

黄舟
黄舟原創
2017-08-14 09:32:121960瀏覽

前言

上一文:菜鳥學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(一個行程執行一次)

附上多執行緒與多行程的圖
php擴展之擴展框架的自動生成

php擴展之擴展框架的自動生成

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

以上是php擴展之擴展框架的自動生成的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn