PHP擴(kuò)展開發(fā)總結(jié)

使用PHP擴(kuò)展的原因:
①如果應(yīng)用注重效率,使用非常復(fù)雜的算法,推薦使用PHP擴(kuò)展。
②有些系統(tǒng)調(diào)用PHP不能直接訪問(如Linux的fork()函數(shù)創(chuàng)建進(jìn)程),需要編寫成PHP擴(kuò)展。
③應(yīng)用不想暴露關(guān)鍵代碼,可以創(chuàng)建擴(kuò)展使用。
準(zhǔn)備工作
一:了解PHP源碼目錄

網(wǎng)上下載下來PHP 5.4版本源代碼,目錄結(jié)構(gòu)如下:

php-5.4.30
  |____build    --和編譯有關(guān)的目錄,里面包括wk,awk和sh腳本用于編譯處理,其中m4文件是linux下編譯程序自動(dòng)生成的文件,可以使用buildconf命令操作具體的配置文件。
  |____ext      --擴(kuò)展庫代碼,例如Mysql,gd,zlib,xml,iconv 等我們熟悉的擴(kuò)展庫,ext_skel是linux下擴(kuò)展生成腳本,windows下使用ext_skel_win32.php。
  |____main     --主目錄,包含PHP的主要宏定義文件,php.h包含絕大部分PHP宏及PHP API定義。
  |____netware  --網(wǎng)絡(luò)目錄,只有sendmail_nw.h和start.c,分別定義SOCK通信所需要的頭文件和具體實(shí)現(xiàn)。
  |____pear     --擴(kuò)展包目錄,PHP Extension and Application Repository。
  |____sapi     --各種服務(wù)器的接口調(diào)用,如Apache,IIS等。
  |____scripts  --linux下的腳本目錄。
  |____tests    --測試腳本目錄,主要是phpt腳本,由--TEST--,--POST--,--FILE--,--EXPECT--組成,需要初始化可添加--INI--部分。
  |____TSRM     --線程安全資源管理器,Thread Safe Resource Manager保證在單線程和多線程模型下的線程安全和代碼一致性。
  |____win32    --Windows下編譯PHP 有關(guān)的腳本。
  |____Zend     --包含Zend引擎的所有文件,包括PHP的生命周期,內(nèi)存管理,變量定義和賦值以及函數(shù)宏定義等等。
二:自動(dòng)構(gòu)建工具

本篇針對(duì)Linux環(huán)境下創(chuàng)建PHP擴(kuò)展,使用擴(kuò)展自動(dòng)構(gòu)建工具為ext_skel,Windows下使用ext_skel_win32.php,構(gòu)建方式略有不同,其余開發(fā)無差別。
構(gòu)建PHP擴(kuò)展的步驟如下(不唯一):

①cd php_src/ext
②./ext_skel --extname=XXX
    此時(shí)當(dāng)前目錄下會(huì)生成一個(gè)名為XXX的文件夾
③cd XXX/
④vim config.m4
    會(huì)有這段文字:
    dnl If your extension references something external, use with:
    dnl PHP_ARG_WITH(say, for say support,
    dnl Make sure that the comment is aligned:
    dnl [  --with-say             Include say support])
    dnl Otherwise use enable:
    dnl PHP_ARG_ENABLE(say, whether to enable say support,
    dnl Make sure that the comment is aligned:
    dnl [  --enable-say           Enable say support])
其中,dnl 是注釋符號(hào)。上面的代碼說,如果你所編寫的擴(kuò)展如果依賴其它的擴(kuò)展或者lib庫,需要去掉PHP_ARG_WITH相關(guān)代碼的注釋。否則,去掉 PHP_ARG_ENABLE 相關(guān)代碼段的注釋。本篇的擴(kuò)展不依賴其他擴(kuò)展,故修改為:
    dnl If your extension references something external, use with:
    dnl PHP_ARG_WITH(say, for say support,
    dnl Make sure that the comment is aligned:
    dnl [  --with-say             Include say support])
    dnl Otherwise use enable:
    PHP_ARG_ENABLE(say, whether to enable say support,
    Make sure that the comment is aligned:
    [  --enable-XXX           Enable say support])
⑤在XXX.c中具體實(shí)現(xiàn)
⑥編譯安裝
    phpize
    ./configure --with-php-config=php_path/bin/php-config
    make && make install
⑦修改php.ini文件
    增加
    [XXX]
    extension = XXX.so
三:了解PHP生命周期

任何一個(gè)PHP實(shí)例都會(huì)經(jīng)過Module init、Request init、Request shutdown和Module shutdown四個(gè)過程。
1.Module init
在所有請(qǐng)求到達(dá)前發(fā)生,例如啟動(dòng)Apache服務(wù)器,PHP解釋器隨之啟動(dòng),相關(guān)的各個(gè)模塊(Redis、Mysql等)的MINIT方法被調(diào)用。僅被調(diào)用一次。創(chuàng)建XXX擴(kuò)展后,相應(yīng)的XXX.c文件中將自動(dòng)生成該方法:

PHP_MINIT_FUNCTION(XXX) {  
    return SUCCESS;   
}

2.Request init
每個(gè)請(qǐng)求達(dá)到時(shí)都被觸發(fā)。SAPI層將控制權(quán)交由PHP層,PHP初始化本次請(qǐng)求執(zhí)行腳本所需的環(huán)境變量,函數(shù)列表等,調(diào)用所有模塊的RINIT函數(shù)。XXX.c中對(duì)應(yīng)函數(shù)如下:

PHP_RINIT_FUNCTION(XXX){
    return SUCCESS;
}

3.Request shutdown
每個(gè)請(qǐng)求結(jié)束,PHP就會(huì)自動(dòng)清理程序,順序調(diào)用各個(gè)模塊的RSHUTDOWN方法,清除程序運(yùn)行期間的符號(hào)表。典型的RSHUTDOWN方法如:

PHP_RSHUTDOWN_FUNCTION(XXX){
    return SUCCESS;
}

4.Module shutdown
所有請(qǐng)求處理完畢后,SAPI也關(guān)閉了(即服務(wù)器關(guān)閉),PHP調(diào)用各個(gè)模塊的MSHUTDOWN方法釋放內(nèi)存。

PHP_MSHUTDOWN_FUNCTION(XXX){
    return SUCCESS;
}

PHP的生命周期常見如下幾種

①單進(jìn)程SAPI生命周期
②多進(jìn)程SAPI生命周期
③多線程SAPI聲明周期

這與PHP的運(yùn)行模式有很大關(guān)系,常見的運(yùn)行模式有CLI、CGI、FastCGI和mod_php。

①CLI模式——單進(jìn)程SAPI生命周期

所謂CLI模式,即在終端界面通過php+文件名的方式執(zhí)行PHP文件


單進(jìn)程SAPI生命周期

輸入命令后,依次調(diào)用MINIT,RINIT,RSHUTDOWN,MSHUTDOWN即完成生命周期,一次只處理一個(gè)請(qǐng)求。

②CGI模式——單進(jìn)程SAPI生命周期

和CLI模式一樣,請(qǐng)求到達(dá)時(shí),為每個(gè)請(qǐng)求fork一個(gè)進(jìn)程,一個(gè)進(jìn)程只對(duì)一個(gè)請(qǐng)求做出響應(yīng),請(qǐng)求結(jié)束后,進(jìn)程也就結(jié)束了。
與CLI模式不同的是,CGI可以看作是規(guī)定了Web Server與PHP的交流規(guī)則,相當(dāng)于執(zhí)行response = exec("php -f xxx.php -url=xxx -cookie=xxx -xxx=xxx")。

③FastCGI模式——多進(jìn)程SAPI生命周期

CGI模式存在明顯缺點(diǎn),每個(gè)進(jìn)程處理一個(gè)請(qǐng)求及結(jié)束,新請(qǐng)求過來需要重新加載php.ini,調(diào)用MINIT等函數(shù)。FastCGI相當(dāng)于可以執(zhí)行多個(gè)請(qǐng)求的CGI,處理完一個(gè)請(qǐng)求后進(jìn)程不結(jié)束,等待下一個(gè)請(qǐng)求到來。
服務(wù)啟動(dòng)時(shí),F(xiàn)astCGI先啟動(dòng)多個(gè)子進(jìn)程等待處理請(qǐng)求,避免了CGI模式請(qǐng)求到來時(shí)fork()進(jìn)程(即fork-and-execute),提高效率。


多進(jìn)程SAPI生命周期
④mod_php模式——多進(jìn)程SAPI生命周期

該模式將PHP嵌入到Apache中,相當(dāng)于給Apache增加了解析PHP的功能。PHP隨服務(wù)器的啟動(dòng)而啟動(dòng),兩者之間存在從屬關(guān)系。
證明:
CGI模式下,修改php.ini無需重啟服務(wù)器,每個(gè)請(qǐng)求結(jié)束后,進(jìn)程自動(dòng)結(jié)束,新請(qǐng)求到來時(shí)會(huì)重新讀取php.ini文件創(chuàng)建新進(jìn)程。
mod_php下,進(jìn)程是啟動(dòng)即創(chuàng)建,只有結(jié)束現(xiàn)有進(jìn)程,重新啟動(dòng)服務(wù)器讀取PHP配置創(chuàng)建新進(jìn)程,修改才有效。

多線程SAPI模式

多線程模式和多進(jìn)程模式的某個(gè)進(jìn)程類似,在整個(gè)生命周期中會(huì)并行重復(fù)著請(qǐng)求開始,請(qǐng)求結(jié)束的環(huán)節(jié)。
只有一個(gè)服務(wù)器進(jìn)程運(yùn)行,但同時(shí)運(yùn)行多個(gè)線程,優(yōu)點(diǎn)是節(jié)省資源開銷,MINIT和MSHUTDOWN只需在Web Server啟動(dòng)和結(jié)束時(shí)執(zhí)行一次。由于線程的特質(zhì),使得各個(gè)請(qǐng)求之間共享數(shù)據(jù)成為可能。


多線程SAPI模式
四:PHP內(nèi)核中的變量

PHP變量的弱類型實(shí)現(xiàn)在之前的文章中講到,可以參讀:http://www.itdecent.cn/p/ef0c91be06a0 PHP的實(shí)現(xiàn)方式即PHP變量在內(nèi)核中的存儲(chǔ)。
PHP提供了一系列內(nèi)核變量的訪問宏。推薦使用它們?cè)O(shè)置和訪問PHP的變量類型和值。
變量類型:

Z_TYPE(zval)  可以獲取和設(shè)置變量類型
Z_TYPE(zval)函數(shù)返回變量的類型,PHP變量類型有:
IS_NULL(空類型),IS_LONG(整型),IS_DOUBLE(浮點(diǎn)型),IS_STRING(字符串),
IS_ARRAY(數(shù)組類型),IS_OBJECT(對(duì)象類型),IS_BOOL(布爾類型),IS_RESOURCE(資源類型)

可以通過
Z_TYPE(zval) = IS_STRING的方式直接設(shè)置變量類型

變量值對(duì)應(yīng)的訪問宏:

整數(shù)類型  Z_LVAL(zval)  對(duì)應(yīng)zval的實(shí)體;Z_VAL_P(&zval)  對(duì)應(yīng)結(jié)構(gòu)體的指針;Z_VAL_PP(&&zval)  對(duì)應(yīng)結(jié)構(gòu)體的二級(jí)指針
浮點(diǎn)數(shù)類型 Z_DVAL(zval)    Z_DVAL_P(&zval)    Z_DVAL_PP(&&zval)
布爾類型 Z_BVAL(zval)    Z_BVAL_P(&zval)    Z_BVAL_PP(&&zval)
字符串類型 
    獲取值:Z_STRVAL(zval)    Z_STRVAL_P(&zval)    Z_STRVAL_PP(&&zval)
    獲取長度:Z_STRLEN(zval)    Z_STRLEN_P(&zval)    Z_STRLEN_PP(&&zval)
數(shù)組類型 Z_ARRVAL(zval)    Z_ARRVAL_P(&zval)    Z_ARRVAL_PP(&&zval)
資源類型 Z_RESVAL(zval)    Z_RESVAL_P(&zval)    Z_RESVAL_PP(&&zval)
五:了解Zend API
1.Zend引擎

Zend引擎就是腳本語言引擎(解釋器+虛擬機(jī)),負(fù)責(zé)解析、翻譯和執(zhí)行PHP腳本。其工作流程大致如下:

①Zend Engine Compiler編譯PHP腳本為Opcode
②Opcode由Zend Engine Executor解析執(zhí)行,期間Zend Engine Executor負(fù)責(zé)調(diào)用使用到的PHP extension
2.Zend內(nèi)存管理

使用C語言開發(fā)PHP擴(kuò)展,需要注意內(nèi)存管理。忘記釋放內(nèi)存將造成內(nèi)存泄漏,釋放多次則產(chǎn)生系統(tǒng)錯(cuò)誤。Zend引擎提供了一些內(nèi)存管理的接口,使用這些接口申請(qǐng)內(nèi)存交由Zend管理。
常見接口:

emalloc(size_t size)    申請(qǐng)size大小的內(nèi)存
efree(void *ptr)    釋放ptr指向的內(nèi)存塊
estrdup(char *str)    申請(qǐng)str大小的內(nèi)存,并將str內(nèi)容復(fù)制進(jìn)去
estrndup(char *str, int slen)    同上,但指定長度復(fù)制
ecalloc(size_t numOfElem, size_t sizeOfElem)    復(fù)制numOfElem個(gè)sizeOfElem大小的內(nèi)存塊
erealloc(void *ptr, size_t nsize)    ptr指向內(nèi)存塊的大小擴(kuò)大到nsize

內(nèi)存管理申請(qǐng)的所有內(nèi)存,將在腳本執(zhí)行完畢和處理請(qǐng)求終止時(shí)被釋放。

3.PHP擴(kuò)展架構(gòu)

使用準(zhǔn)備工作(二)中命令生成基本架構(gòu)后,生成的對(duì)應(yīng)目錄中會(huì)有兩個(gè)文件,php_XXX.h和XXX.c,其中php_XXX.h文件用于聲明擴(kuò)展的一些基本信息和實(shí)現(xiàn)的函數(shù),注意,只是聲明。具體的實(shí)現(xiàn)在XXX.c中。
php_XXX.h大致結(jié)構(gòu)如下:

#ifndef PHP_XXX_H
#define PHP_XXX_H

extern zend_module_entry php_list_module_entry;
#define phpext_php_list_ptr &php_list_module_entry

#define PHP_XXX_VERSION "0.1.0"

#ifdef PHP_WIN32
#   define PHP_XXX_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#   define PHP_XXX_API __attribute__ ((visibility("default")))
#else
#   define PHP_XXX_API
#endif
#ifdef ZTS
#include "TSRM.h"
#endif

PHP_MINIT_FUNCTION(XXX);
PHP_MSHUTDOWN_FUNCTION(XXX);
PHP_RINIT_FUNCTION(XXX);
PHP_RSHUTDOWN_FUNCTION(XXX);
PHP_MINFO_FUNCTION(XXX);

PHP_FUNCTION(confirm_XXX_compiled);

#ifdef ZTS
#define PHP_LIST_G(v) TSRMG(php_list_globals_id, zend_php_list_globals *, v)
#else
#define PHP_LIST_G(v) (php_list_globals.v)
#endif
#endif

大致信息有版本號(hào),MINIT、RINIT、RSHUTDOWN、MSHUTDOWN函數(shù)等,如果聲明自定義函數(shù),可以在之后以PHP_FUNCTION(XXX);的方式聲明函數(shù),并在XXX.c中具體實(shí)現(xiàn)。
XXX.c內(nèi)容如下:

---------------------------------------頭文件--------------------------------------
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_XXX.h"

static int le_XXX;
---------------------------------------Zend函數(shù)快--------------------------------------
const zend_function_entry XXX_functions[] = {
    PHP_FE(confirm_php_list_compiled,   NULL)       /* For testing, remove later. */
    PHP_FE_END  /* Must be the last line in php_list_functions[] */
};
---------------------------------------Zend模塊--------------------------------------
zend_module_entry XXX_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    "XXX",
    XXX_functions,
    PHP_MINIT(XXX),
    PHP_MSHUTDOWN(XXX),
    PHP_RINIT(XXX),     /* Replace with NULL if there's nothing to do at request start */
    PHP_RSHUTDOWN(XXX), /* Replace with NULL if there's nothing to do at request end */
    PHP_MINFO(XXX),
#if ZEND_MODULE_API_NO >= 20010901
    PHP_XXX_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};
---------------------------------------實(shí)現(xiàn)get_module函數(shù)--------------------------------------
#ifdef COMPILE_DL_XXX
ZEND_GET_MODULE(XXX)
#endif
---------------------------------------生命周期函數(shù)--------------------------------------
PHP_MINIT_FUNCTION(XXX)
{
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(XXX)
{
    return SUCCESS;
}

PHP_RINIT_FUNCTION(XXX)
{
    return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(XXX)
{
    return SUCCESS;
}
---------------------------------------導(dǎo)出函數(shù)--------------------------------------
PHP_FUNCTION(confirm_XXX_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.", "php_list", arg);
    RETURN_STRINGL(strg, len, 0);
}
---------------------------------------負(fù)責(zé)擴(kuò)展info顯示--------------------------------------
PHP_MINFO_FUNCTION(XXX)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "XXX support", "enabled");
    php_info_print_table_end();
}

①擴(kuò)展頭文件
所有擴(kuò)展務(wù)必包含的頭文件有且只有一個(gè)——php.h,以使用PHP定義的各種宏和API。如果在php_XXX.h中聲明了擴(kuò)展相關(guān)的宏和函數(shù),需要將其引入。
②導(dǎo)出函數(shù)
先說導(dǎo)出函數(shù),方便理解Zend函數(shù)塊。PHP能夠調(diào)用擴(kuò)展中的類和方法都是通過導(dǎo)出函數(shù)實(shí)現(xiàn)。導(dǎo)出函數(shù)就是按照PHP內(nèi)核要求編寫的函數(shù),形式如下:

void zif_extfunction(){  //extfunction即為擴(kuò)展中實(shí)現(xiàn)的函數(shù)
    int ht;    //函數(shù)參數(shù)的個(gè)數(shù)
    zval *return_value;    //保存函數(shù)的返回值
    zval *this_ptr;    //指向函數(shù)所在對(duì)象
    int return_value_used;    //函數(shù)返回值腳本是否使用,0——不使用;1——使用
    zend_executor_globals *executor_globals;    //指向Zend引擎的全局設(shè)置
}

有如上定義,PHP腳本中即可使用擴(kuò)展函數(shù)

<?php
    extfunction();
?>

由于導(dǎo)出函數(shù)格式固定,Zend引擎通過PHP_FUNCTION()宏聲明

PHP_FUNCTION(extfunction);

即可產(chǎn)生之前代碼塊中的導(dǎo)出函數(shù)結(jié)構(gòu)體。
③Zend函數(shù)塊
作用是將編寫的函數(shù)引入Zend引擎,通過zend_function_entry結(jié)構(gòu)體引入。zend_function_entry結(jié)構(gòu)體聲明如下:

typedef struct _zend_function_entry{
    char *fname;    //指定在PHP腳本里定義的函數(shù)名
    void (*handler)(INTERNAL_FUNCTION_PARAMETERS);  //指向?qū)С龊瘮?shù)的句柄
    unsigned char *func_arg_types;  //標(biāo)示一些參數(shù)是否強(qiáng)制按引用方式傳遞,通常設(shè)為NULL
} zend_function_entry;

Zend引擎通過zend_function_entry數(shù)組將導(dǎo)出函數(shù)引入內(nèi)部。方式:

zend_function_entry XXX_functions[] = {
    PHP_FE(confirm_php_list_compiled,   NULL)       /* For testing, remove later. */
    PHP_FE_END  /* Must be the last line in php_list_functions[] */
};

PHP_FE宏會(huì)把zend_function_entry結(jié)構(gòu)補(bǔ)充完整。

PHP_FE(extfunction);  ===>  ("extfunction", zif_extfunction, NULL);

PHP_FE_END是告知Zend引擎Zend函數(shù)塊到此為止,有的版本可以使用{NULL, NULL, NULL}的方式。但推薦使用本文方式以兼容。
④Zend模塊聲明
Zend模塊包含所有需要向Zend引擎提供的擴(kuò)展模塊信息,底層由zend_module_entry結(jié)構(gòu)體大體實(shí)現(xiàn)

typedef struct _zend_module_entry{
    unsigned short size;       ---|
    unsigned int zend_api;        |
    unsigned char zend_debug;     |--> 通常用STANDARD_MODULE_HEADER填充
    unsigned char zts;         ---|
    char *name;    //模塊名
    zend_function_entry *functions;    //函數(shù)聲明
    int (*module_start_func)(INIT_FUNC_ARGS);    //MINIT函數(shù)
    int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);    //MSHUTDOWN函數(shù)
    int (*request_start_func)(INIT_FUNC_ARGS);    //RINIT函數(shù)
    int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);    //RSHUTDOWN函數(shù)
    char *version;    //版本號(hào)
    ...    //其余信息不做討論
} zend_module_entry;

對(duì)比生成代碼中的模塊聲明和①②③中所講,Zend通過模塊聲明將所有信息讀取并加入到引擎中。
⑤get_module函數(shù)
當(dāng)擴(kuò)展被加載時(shí),調(diào)用get_module函數(shù),該函數(shù)返回一個(gè)指向擴(kuò)展模塊聲明的zend_module_entry指針。是PHP內(nèi)核與擴(kuò)展通信的渠道。
get_module函數(shù)被條件宏包圍,故有些情況下不會(huì)執(zhí)行g(shù)et_module方法,當(dāng)擴(kuò)展被編譯為PHP內(nèi)建模塊時(shí)get_module方法不被實(shí)現(xiàn)。
⑥實(shí)現(xiàn)導(dǎo)出函數(shù)
在php_XXX.h中聲明的函數(shù)在XXX.c中具體實(shí)現(xiàn),實(shí)現(xiàn)方式如自動(dòng)生成的confirm_XXX_compiled導(dǎo)出函數(shù)形式一致

PHP_FUNCTION(extfunction){
    ...具體實(shí)現(xiàn)...
}

在函數(shù)中獲取參數(shù)和返回結(jié)果,后續(xù)講解。
⑦模塊信息函數(shù)
PHP通過phpinfo查看PHP及其擴(kuò)展信息,PHP_MINFO_FUNCTION負(fù)責(zé)實(shí)現(xiàn)。生成代碼函數(shù)體是最基本的模塊信息,可自行設(shè)置顯示內(nèi)容。

4.導(dǎo)出函數(shù)實(shí)現(xiàn)須知

這部分主要講解函數(shù)具體實(shí)現(xiàn)過程中對(duì)參數(shù)和變量的處理。

(1)獲取參數(shù)個(gè)數(shù)

通過ZEND_NUM_ARGS宏獲取參數(shù)個(gè)數(shù),這個(gè)宏實(shí)際上是獲取zif_extfunction的ht字段,定義在Zend/zned_API.h下

#define ZEND_NUM_ARGS()  (ht)
(2)取得參數(shù)實(shí)體

Zend引擎提供獲取參數(shù)實(shí)體的API,聲明如下:

int zend_parse_parameters(int num_args TSRMLS_CC, char *type_spec)

num_args:傳入的參數(shù)個(gè)數(shù)
type_spec:參數(shù)的類型,每種類型對(duì)應(yīng)一個(gè)字符,當(dāng)num_args>1時(shí),接收參數(shù)通過字符串依次指明類型接收。
該函數(shù)成功將返回SUCCESS,失敗返回FAILURE。
可以接受的參數(shù)類型如下:

普通:
l : 長整型
d : 雙精度浮點(diǎn)類型
s : 字符串類型及其長度(需要兩個(gè)變量保存?。。。?b : 布爾類型
r : 資源類型,保存在zva *l中
a : 數(shù)組,保存在zval *中
o : 對(duì)象(任何類型),保存在zval *中
O : 對(duì)象(class entry指定類型),保存在zval *中
z : zval *
特殊:
| : 當(dāng)前及之后的參數(shù)為可選參數(shù),有傳即獲取,否則設(shè)為默認(rèn)值
/ : 當(dāng)前及之后的參數(shù)將以SEPARATE_IF_NOT_REF的方式進(jìn)行拷貝,除非是引用
! : 當(dāng)前及之后的參數(shù)允許為NULL,僅用在r,a,o,O,z類型時(shí)

例:獲取一個(gè)字符串和一個(gè)布爾型參數(shù)

char *str;
int strlen;
zend_bool b;
if(zend_parse_paramsters(ZEND_NUM_ARGS() TSRMLS_CC, "sb", &str, &strlen, &b) == FAILURE){  //字符串需要接收內(nèi)容和長度
    return ;
}

例:獲取一個(gè)數(shù)組和一個(gè)可選的長整型參數(shù)

zval *arr;
long l;
if(zend_parse_parameter(ZEND_NUM_ARGS() TSRMLS_CC, "a|l", &arr, &l) == FAILURE){
    return ;
}

例:獲取一個(gè)對(duì)象或NULL

zval *obj;
if(zend_parse_parameter(ZEND_NUM_ARGS() TSRMLS_CC, "o!", &obj) == FAILURE){
    return ;
}
(3)獲取可變參數(shù)

開發(fā)過程中會(huì)遇到方法可接受可變參數(shù)的情況,不指定類型的情況下接收參數(shù)的方式:

int num_arg = ZEND_NUM_ARGS();
zval **parameters[num_args];
if(zend_get_parameters_array_ex(num_arg, parameters) == FAILURE){
    return ;
}
(4)參數(shù)類型轉(zhuǎn)換

(3)中PHP可以接受任意類型的參數(shù),可能會(huì)導(dǎo)致在具體實(shí)現(xiàn)過程中出問題,因此Zend提供了一系列參數(shù)類型轉(zhuǎn)換的API。


參數(shù)類型轉(zhuǎn)換API
(5)處理通過引用傳遞的參數(shù)

"z"代表的zval類型的傳參即為引用傳遞。PHP規(guī)定修改非引用傳遞的參數(shù)值不會(huì)影響原來變量的值,但PHP內(nèi)核采用引用傳遞方式傳參。PHP內(nèi)核使用"zval分離"的方式避免這一問題。"zval分離"即寫時(shí)復(fù)制,修改非引用類型的參數(shù)時(shí),先復(fù)制一份新值,然后將引用指向新值,修改參數(shù)時(shí)不會(huì)影響原值。
判斷參數(shù)是否為引用,通過PZVAL_IS_REF(zval *),其定義為:

#define PZVAL_IS_REF(z) ((z)->is_ref)

使用宏SEPARATE_ZVAL(zval **)實(shí)現(xiàn)zval分離。

(6)擴(kuò)展中創(chuàng)建變量

PHP擴(kuò)展中創(chuàng)建變量需要以下三步:

①創(chuàng)建一個(gè)zval容器
②對(duì)zval容器進(jìn)行填充
③引入到Zend引擎內(nèi)部符號(hào)表中

//創(chuàng)建zval容器
zval *new_var;
//初始化和填充
MAKE_STD_ZVAL(new_var);
//引入符號(hào)表
ZEND_SET_SYMBOL(EG(active_symbol_table), "new_var", new_var);

MAKE_STD_ZVAL()宏作用是通過ALLOC_ZVAL()申請(qǐng)一個(gè)zval空間,之后通過INIT_ZVAL()進(jìn)行初始化。

#define MAKE_STD_ZVAL(zv) \
  ALLOC_ZVAL(zv); \
  INIT_ZVAL(zv);

INIT_ZVAL()宏定義如下:
#DEFINE INIT_ZVAL(z) \
  (z) -> refcount = 1; \
  (z) -> is_ref = 0;

MAKE_STD_ZVAL()只是為變量分配了內(nèi)存,設(shè)置了refcount和is_ref兩個(gè)屬性。
ZEND_SET_SYMBOL()宏將變量引入到符號(hào)表中,引入時(shí)先檢查變量是否已經(jīng)存在于表中,如果已經(jīng)存在,銷毀原有的zval并替換。
如果創(chuàng)建的是全局變量,前兩步不變,只對(duì)引入操作做調(diào)整。局部變量引入active_symbol_table中,全局變量引入symbol_table中,通過

ZEND_SET_SYMBOL(&EG(symbol_table), "new_var", new_var);

注意:active_symbol_table是個(gè)指針,symbol_table不是指針,需要增加&取地址。
···
擴(kuò)展中:
PHP_FUNCTION(extfunction){
zval *new_var;
ZEND_STD_ZVAL(new_var);
ZVAL_LONG(new_var, 10);
ZEND_SET_SYMBOL(&EG(symbol_table), "new_var", new_var);
}

PHP腳本中:
<?php
extfunction();
var_dump($new_var);
?>
結(jié)果輸出:10
···

(7)變量賦值

①長整型(整型)賦值
PHP中所有整型都是保存在zval的value字段中,整數(shù)保存在value聯(lián)合體的lval字段中,type為IS_LONG,賦值通過宏操作進(jìn)行:

ZVAL_LONG(zval, 10);

②雙精度浮點(diǎn)數(shù)類型賦值
浮點(diǎn)數(shù)保存在value的dval中,type對(duì)應(yīng)IS_DOUBLE,通過宏操作

ZVAL_DOUBLE(zval, 3.14);

③字符串類型
value聯(lián)合體的str結(jié)構(gòu)體保存字符串值,val保存字符串,len保存長度,type為IS_STRING。

char *str = "hello world";
ZVAL_STRING(zval, str, 1);  //結(jié)尾參數(shù)表示字符串是否需要被復(fù)制。

④布爾類型
值存放在value.lval中,TRUE——1;FALSE——0,type對(duì)應(yīng)IS_BOOL。

賦值為真:ZVAL_BOOL(zval, 1);
賦值為假:ZVAL_BOOL(zval, 0);

⑤數(shù)組類型變量
PHP數(shù)組基于HashTable實(shí)現(xiàn),變量賦值為數(shù)組類型時(shí)先要?jiǎng)?chuàng)建一個(gè)HashTable,保存在value的ht字段中。Zend提供array_init()實(shí)現(xiàn)賦值。

array_init(zval);

同時(shí)Zend提供了一套完整的關(guān)聯(lián)數(shù)組、索引數(shù)組API用于添加元素,這里不一一列舉。
⑥對(duì)象類型變量
對(duì)象和數(shù)組類似,PHP中對(duì)象可以轉(zhuǎn)換成數(shù)組,但數(shù)組無法轉(zhuǎn)換成對(duì)象,會(huì)丟失方法。Zend通過object_init()函數(shù)初始化一個(gè)對(duì)象。

if(object_init(zval) != SUCCESS){
    RETURN_NULL();
}

Zend也提供了對(duì)象設(shè)置屬性所需的API,和數(shù)組設(shè)置元素類似,用到時(shí)候找即可。
⑦資源類型
嚴(yán)格而言,資源不是數(shù)據(jù)類型,而是一個(gè)可以維護(hù)任何數(shù)據(jù)類型的抽象,類似C語言的指針。所有資源都保存在一個(gè)Zend內(nèi)部的資源列表中,每份資源都有一個(gè)指向?qū)嶋H數(shù)據(jù)的指針。
為了及時(shí)回收無用的資源,Zend引擎會(huì)自動(dòng)回收引用數(shù)為0的資源的析構(gòu)函數(shù),析構(gòu)函數(shù)需要在擴(kuò)展中自己定義。
Zend使用統(tǒng)一的zend_register_list_destructors_ex()為資源注冊(cè)析構(gòu)函數(shù),該函數(shù)返回一個(gè)句柄,將資源與析構(gòu)函數(shù)相關(guān)聯(lián)。定義如下:

ZEND_ZPI int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, 
  rsrc_dtor_func_t  pld, char *type_name, int module_number);
參數(shù)描述:
ld : 普通資源的析構(gòu)函數(shù)
pld : 持久化資源的析構(gòu)函數(shù)
type_name : 為資源類型起的名字,如:fopen()創(chuàng)建的資源名稱為stream
module_number : PHP_MINIT_FUNCTION函數(shù)會(huì)定義,可忽略
兩種析構(gòu)函數(shù)至少提供一個(gè),為空可用NULL指定。

資源的析構(gòu)函數(shù)必須如下定義:(resource_destruction_handler)函數(shù)名隨意。

void resource_destruction_handler(zend_rsrc_entry *rsrc TSRMLS_DC){
    -----------具體實(shí)現(xiàn)代碼------------
}

其中,rsrc是指向zend_rsrc_entry的指針,結(jié)構(gòu)體結(jié)構(gòu)為:

typedef struct _zend_rsrc_entry{
    void *ptr;  //資源的實(shí)際地址,析構(gòu)時(shí)釋放
    int type; 
    int refcount;
} zend_rsrc_entry ; 

通過zend_register_list_destructors_ex()函數(shù)返回的資源句柄,通過一個(gè)全局變量保存,ext_skel生成的擴(kuò)展架構(gòu)中,自動(dòng)生成了一個(gè)'le_'為前綴的int型變量,zend_register_list_destructors_ex()在MINIT函數(shù)中使用并完成注冊(cè)。如實(shí)現(xiàn)鏈表的析構(gòu):

---------------------------------phplist擴(kuò)展-----------------------------------
static le_phplist;  //架構(gòu)自動(dòng)生成,保存資源句柄
//定義鏈表節(jié)點(diǎn)
struct ListNode{
    struct ListNode *next;
    void *data;
}
//析構(gòu)函數(shù)具體實(shí)現(xiàn)
void phplist_destruction_handler(zend_rsrc_entry *rsrc TSRMLS_DC){
    ListNode *pre, *next;
    pre = (ListNode *)rsrc->ptr;  
    while(pre){
        next = pre -> next;
        free(pre);
        pre = next;
    }
}
//MINIT中注冊(cè)析構(gòu)函數(shù)
PHP_MINIT_FUNCTION(phplist){
    //完成注冊(cè)
    le_phplist = zend_register_list_destructors_ex(phplist_destruction_handler,
      NULL, "php_list", module_number);
    return SUCCESS;
}

注冊(cè)完析構(gòu)函數(shù),需要把資源和句柄關(guān)聯(lián)起來,Zend提供zend_register_resource()函數(shù)或者ZEND_REGISTER_RESOURCE()宏完成這一操作。

int zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type);
參數(shù)解釋:
rsrc_result : 存儲(chǔ)zend_register_resource返回的結(jié)果
rsrc_pointer : 指向保存的資源
rsrc_type : 資源類型

該函數(shù)返回int型結(jié)果,該結(jié)果為資源的id。函數(shù)定義源碼:

int zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type){
    int rsrc_id;
    rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type);  //該函數(shù)將資源加入資源列表,并返回資源在列表中的位置(即id)
    if(rsrc_result){
        rsrc_result -> value.lval = rsrc_id;
        rsrc_result -> type = IS_RESOURCE;
    }
    return rsrc_id;
}

用戶根據(jù)資源的id沖資源列表中獲取資源,Zend定義了ZEND_FETCH_RESOURCE()宏獲取指定的資源。

ZEND_FETCH_RESOURCE(rsrc, rsrc_type, rsrc_id, default_rsrc_id, resource_type_name, resource_type);
其中
rsrc : 保存返回的資源
rsrc_type : 表明想要的資源類型,如 ListNode *等
rsrc_id : 用戶通過PHP腳本傳來的資源id
default_rsrc_id : 沒有獲取到資源時(shí)的標(biāo)識(shí)符,一般用-1指定
resource_type_name : 請(qǐng)求資源類的名稱,用于找不到時(shí)拋出錯(cuò)誤信息使用
resource_type : 注冊(cè)析構(gòu)函數(shù)時(shí)的句柄,即le_phplist

例如獲取用戶指定的list

zval *lrc;
ListNode *list;
//獲取用戶參數(shù)
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &lrc) == FAILURE){
    RETURN_FALSE;
}
//獲取對(duì)應(yīng)資源
ZEND_FETCH_RESOURCE(list, ListNode *, &lrc, -1, "php list", le_phplist);
此時(shí)list即為所要獲取的資源。

資源用完需要析構(gòu),當(dāng)引用數(shù)為0時(shí),Zend對(duì)資源進(jìn)行回收,很多擴(kuò)展對(duì)資源有相應(yīng)的析構(gòu)函數(shù),比如mysql_connect()的mysql_close(),fopen()對(duì)應(yīng)fclose()。PHP的unset()也可以直接釋放一個(gè)資源。
如果想顯示的定義函數(shù)釋放資源,在自定義函數(shù)中調(diào)用zend_list_delete()函數(shù)即可

ZEND_API int zend_list_delete(int id TSRMLS_DC);

該函數(shù)的作用是根據(jù)id將資源的引用數(shù)-1,然后判斷引用數(shù)是否大于0,是則觸發(fā)析構(gòu)函數(shù)清除資源。

(8)錯(cuò)誤輸出API

Zend推薦使用zend_error()函數(shù)輸出錯(cuò)誤信息,該函數(shù)定義如下:

ZEND_API void zend_error(int type, char *format, ...)
參數(shù):
type : PHP的6鐘錯(cuò)誤信息類型
    ①E_ERROR:拋出一個(gè)錯(cuò)誤,腳本將停止運(yùn)行
    ②E_WARNING : 拋出警告,腳本繼續(xù)執(zhí)行
    ③E_NOTICE : 拋出通知,腳本繼續(xù)執(zhí)行,一般情況下php.ini設(shè)置不顯示
    ④E_CORE_ERROR : 拋出PHP內(nèi)核錯(cuò)誤
    ⑤E_COMPILE_ERROR : 拋出編譯器內(nèi)部錯(cuò)誤
    ⑥E_COMPILE_WARNING : 拋出編譯器警告
    注意:后三種錯(cuò)誤不應(yīng)由自定義擴(kuò)展模塊拋出?。。?format : 錯(cuò)誤輸出格式
(9)運(yùn)行時(shí)信息函數(shù)

執(zhí)行PHP腳本出錯(cuò)時(shí),經(jīng)常會(huì)有相關(guān)的運(yùn)行信息,指出哪個(gè)文件,哪個(gè)函數(shù),具體哪行有執(zhí)行錯(cuò)誤,Zend引擎有相關(guān)的實(shí)現(xiàn)接口。

查看當(dāng)前執(zhí)行的函數(shù)名
get_active_function_name(TSRMLS_C);
查看當(dāng)前執(zhí)行的文件名
zend_get_executed_filename(TSRMLS_C);
查看所在行
zend_get_executed_lineno(TSRMLS_C);

三個(gè)函數(shù)都需要以TSRMLS_C為參數(shù),作為訪問執(zhí)行器(Executor)全局變量。TSRM_C是TSRM存儲(chǔ)器,與線程安全相關(guān),之后專門寫篇博客講講。

(10)擴(kuò)展調(diào)用腳本中用戶自定義函數(shù)

這種情況比較少,但Zend功能全面,支持這類操作。
在擴(kuò)展中使用用戶自定義函數(shù),通過call_user_function_ex()函數(shù)實(shí)現(xiàn),函數(shù)原型:

int call_user_function_ex(HashTable *function_table,   //要訪問的函數(shù)表指針
    zval **object_pp,   //調(diào)用方法的對(duì)象,沒有設(shè)為NULL
    zval *function_name,   //函數(shù)名
    zval **retval_ptr_ptr,  //保存返回值的指針
    zend_uint param_count,  //參數(shù)個(gè)數(shù)
    zval **params[],  //參數(shù)
    int no_separation,  //是否禁止zcal分離操作
    HashTable symbol_table  //符號(hào)表,一般設(shè)為NULL
    TSRMLS_DC  
);  

其中no_separation為1會(huì)禁止zval分離,節(jié)省內(nèi)存,但任何參數(shù)分離將導(dǎo)致操作失敗,通常設(shè)為0。
腳本中定義用戶函數(shù)

function userfunc(){
    return "call user function success";
}

-------------------------調(diào)用擴(kuò)展方法----------------------------
$ret = call_user_function_in_ext();
var_dump($ret);

擴(kuò)展中需要實(shí)現(xiàn)call_user_function_in_ext()函數(shù)

PHP_FUNCTION(call_user_function_in_ext){
    zval **funcName;
    zval *retval;
  
    if(ZEND_NUM_ARGS() != 1 || 
        zend_get_parameters_ex(1, &function_name) == FAILURE){
        zend_error(E_ERROR, "function %s call in extension fail", (*function_name)->value->str->val);
    }

    if((*function_name)->type != IS_STRING){
        zend_error(E_ERROR, "function name must be string");
    }

    if(call_user_function_ex(CG(function_table), NULL, *function_name, &retval, 0, NULL, 0, NULL TSRMLS_DC) != SUCCESS){
        zend_error(E_ERROR, "function call fail");
    }

    zval *ret_val = *retval;
    zval_copy_ctor(ret_val);
    zval_ptr_dtor(&retval);
}

此外Zend還有提供顯示phpinfo的函數(shù),比較簡單,不做講解。

創(chuàng)建擴(kuò)展

創(chuàng)建一個(gè)鏈表操作的擴(kuò)展,擴(kuò)展名為phplist,生成架構(gòu)先

cd php_src/ext
./ext_skel --extname=phplist

此時(shí)ext目錄下生成phplist/,本例不依賴其他擴(kuò)展或lib庫,按準(zhǔn)備工作(二)修改config.m4文件。
之后實(shí)現(xiàn)擴(kuò)展的函數(shù)。在php_phplist.h中聲明,具體實(shí)現(xiàn)在phplist.c中。
php_phplist.h如下:

#ifndef PHP_PHPLIST_H
#define PHP_PHPLIST_H

extern zend_module_entry phplist_module_entry;
#define phpext_phplist_ptr &phplist_module_entry

#define PHP_PHPLIST_VERSION "0.1.0" /* Replace with version number for your extension */

#ifdef PHP_WIN32
#   define PHP_PHPLIST_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#   define PHP_PHPLIST_API __attribute__ ((visibility("default")))
#else
#   define PHP_PHPLIST_API
#endif

#ifdef ZTS
#include "TSRM.h"
#endif

PHP_MINIT_FUNCTION(phplist);
PHP_MSHUTDOWN_FUNCTION(phplist);
PHP_RINIT_FUNCTION(phplist);
PHP_RSHUTDOWN_FUNCTION(phplist);
PHP_MINFO_FUNCTION(phplist);

PHP_FUNCTION(confirm_phplist_compiled); /* For testing, remove later. */

/*聲明擴(kuò)展函數(shù)*/
PHP_FUNCTION(list_create);  //創(chuàng)建鏈表
PHP_FUNCTION(list_add_head);    //添加到鏈表頭
PHP_FUNCTION(list_add_tail);    //添加到鏈表尾
PHP_FUNCTION(list_get_index);   //獲取節(jié)點(diǎn)
PHP_FUNCTION(list_get_length);  //獲取鏈表長度
PHP_FUNCTION(list_remove_index);    //移除節(jié)點(diǎn)

#ifdef ZTS
#define PHPLIST_G(v) TSRMG(phplist_globals_id, zend_phplist_globals *, v)
#else
#define PHPLIST_G(v) (phplist_globals.v)
#endif

#endif  /* PHP_PHPLIST_H */

在phplist.c中具體實(shí)現(xiàn)

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

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_phplist.h"

static int le_phplist;
static int isFree = 0;

const zend_function_entry phplist_functions[] = {
    PHP_FE(confirm_phplist_compiled,    NULL)       /* For testing, remove later. */
    PHP_FE(list_create, NULL)
    PHP_FE(list_add_head, NULL) 
    PHP_FE(list_add_tail, NULL) 
    PHP_FE(list_get_index, NULL)    
    PHP_FE(list_get_length, NULL)   
    PHP_FE(list_remove_index, NULL)
    PHP_FE(list_destroy, NULL)
    PHP_FE(list_get_head, NULL)
    PHP_FE_END  /* Must be the last line in phplist_functions[] */
};

zend_module_entry phplist_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    "phplist",
    phplist_functions,
    PHP_MINIT(phplist),
    PHP_MSHUTDOWN(phplist),
    PHP_RINIT(phplist),     /* Replace with NULL if there's nothing to do at request start */
    PHP_RSHUTDOWN(phplist), /* Replace with NULL if there's nothing to do at request end */
    PHP_MINFO(phplist),
#if ZEND_MODULE_API_NO >= 20010901
    PHP_PHPLIST_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_PHPLIST
ZEND_GET_MODULE(phplist)
#endif

//定義鏈表節(jié)點(diǎn)和鏈表頭
typedef struct _ListNode{
    struct _ListNode *prev;
    struct _ListNode *next;
    zval *value;
}ListNode;
typedef struct _ListHead{
    struct _ListNode *head;
    struct _ListNode *tail;
    int size;
}ListHead;

//創(chuàng)建鏈表具體實(shí)現(xiàn)
ListHead * list_create(){

    ListHead *head;
    head = (ListHead *)malloc(sizeof(ListHead));
    if (head){
        head->size = 0;
        head->head = NULL;
        head->tail = NULL;
    }
    return head;
}

//向頭部添加
int list_add_head(ListHead *head, zval *value){

    ListNode *node;
    node = (ListNode *)malloc(sizeof(*node));
    if (!node){
        return 0;
    }
    node->value = value;
    node->prev = NULL;
    node->next = head->head;
    if (head->head){
        head->head->prev = node;
    }
    head->head = node;
    if(!head->tail){
        head->tail = head->head;
    }
    head->size++;
    return 1;
}

//鏈表尾添加
int list_add_tail(ListHead *list, zval *value){

    ListNode *node;
    node = (ListNode *)malloc(sizeof(*node));
    if(!node){
        return 0;
    }
    node->value = value;
    node->next = NULL;
    node->prev = list->tail;
    if (list->tail){
        list->tail->next = node;
    }
    list->tail = node;
    if (!list->head){
        list->head = list->tail;
    }
    list->size++;
    return 1;
}

//獲取指定元素
int list_get_index(ListHead *list, int index, zval **retval){

    ListNode *node;
    if(!list){
        return 0;
    }
    if (index <= 0 || list->size == 0 ||  index > list->size){
        return 0;
    }
    if (index < list->size / 2){
        node = list->head;
        while(node && index > 0){
            node = node->next;
            --index;
        }
    }else{
        node = list->tail;
        while(node && index > 0){
            node = node->prev;
            --index;
        }
    }
    *retval = node->value;
    return 1;
}

//獲取鏈表長度
int list_get_length(ListHead *list){

    if (list){
        return list->size;
    }else{
        return 0;
    }
}

//刪除節(jié)點(diǎn)
int list_remove_index(ListHead *list, int index){

    ListNode *node;
    if(!list){
        return 0;
    }
    if (index <= 0 || list->size == 0 || index > list->size){
        return 0;
    }
    if (index < list->size / 2){
        node = list->head;
        while(node && index > 0){
            node = node->next;
            --index;
        }
    }else{
        node = list->tail;
        while(node && index > 0){
            node = node->prev;
            --index;
        }
    }
    if (!node){
        return 0;
    }
    if (node->prev){
        node->prev->next = node->next;
    }else{
        list->head = node->next;
    }
    if(node->next){
        node->next->prev = node->prev;
    }else{
        list->tail = node->prev;
    }
    list->size--;
    return 1;
}

//釋放鏈表
void list_destroy(ListHead *list){
    
    ListNode *pre, *next;
    pre = list->head;
    while(pre){
        next = pre->next;
        free(pre);
        pre = next;
    }
    free(list);
}

int list_get_head(ListHead *list, zval **retval){

    if (!list || !list->head){
        return 0;
    }
    *retval = list->head->value;
    return 1;
}

//析構(gòu)函數(shù)實(shí)現(xiàn)
void phplist_destructor_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC){
    if (!isFree){
        ListHead *list;
        list = (ListHead *)rsrc->ptr;
        list_destroy(list);
        isFree = 1;
    }
}

PHP_MINIT_FUNCTION(phplist)
{
    le_phplist = zend_register_list_destructors_ex(phplist_destructor_handler, NULL, "phplist", module_number);
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(phplist)
{
    return SUCCESS;
}

PHP_RINIT_FUNCTION(phplist)
{
    return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(phplist)
{
    return SUCCESS;
}

PHP_MINFO_FUNCTION(phplist)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "phplist support", "enabled");
    php_info_print_table_end();
}

PHP_FUNCTION(confirm_phplist_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.", "phplist", arg);
    RETURN_STRINGL(strg, len, 0);
}

PHP_FUNCTION(list_create){

    ListHead *list;
    list = list_create();
    if (!list){
        RETURN_NULL();
    }
    ZEND_REGISTER_RESOURCE(return_value, list, le_phplist);
}

PHP_FUNCTION(list_add_head){

    zval *value;
    zval *lrc;
    ListHead *list;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &lrc, &value) == FAILURE){
        RETURN_FALSE;
    }
    ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);

    int ret = list_add_head(list, value);
    if(ret){
        RETURN_TRUE;
    }
    RETURN_FALSE;
}

PHP_FUNCTION(list_add_tail){

    zval *value;
    zval *lrc;
    ListHead *list;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &lrc, &value) == FAILURE){
        RETURN_FALSE;
    }
    ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);

    int ret = list_add_tail(list, value);
    if(ret){
        RETURN_TRUE;
    }
    RETURN_FALSE;
}

PHP_FUNCTION(list_get_index){

    zval *lrc;
    ListHead *list;
    long index;
    zval *retval;
    if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &lrc, &index) == FAILURE){
        RETURN_FALSE;
    }
    ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
    int ret = list_get_index(list, index, &retval);
    if (ret){
        RETURN_ZVAL(retval, 1, 0);
    }
    RETURN_NULL();
}

PHP_FUNCTION(list_get_length){

    zval *lrc;
    ListHead *list;
    if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &lrc) == FAILURE){
        RETURN_FALSE;
    }
    ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
    RETURN_LONG(list_get_length(list));
}

PHP_FUNCTION(list_remove_index){

    zval *lrc;
    ListHead *list;
    long index;
    if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &lrc, &index) == FAILURE){
        RETURN_FALSE;
    }
    ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
    int ret = list_remove_index(list, index);
    if (ret){
        RETURN_TRUE;
    }
    RETURN_FALSE;
}

PHP_FUNCTION(list_destroy){

    zval *lrc;
    ListHead *list;
    if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &lrc) == FAILURE){
        RETURN_FALSE;
    }
    ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
    list_destroy(list);
}

PHP_FUNCTION(list_get_head){

    zval *lrc;
    zval *retval;
    ListHead *list;
    if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &lrc) == FAILURE){
        RETURN_FALSE;
    }
    ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
    int ret = list_get_head(list, &retval);
    if (ret){
        RETURN_ZVAL(retval, 1, 0);
    }
    RETURN_NULL();
}

之后按照步驟在php.ini中引入擴(kuò)展即可使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容