PHP扩展开发(六)PHP扩展生命周期

全局变量

在单线程和多线程环境中,扩展内部全局变量的创建和使用方式是不同的。

声明扩展全局变量

要声明一个全局变量,首先需要在扩展的头文件中声明:

ZEND_BEGIN_MODULE_GLOBALS(sample4)
    unsigned long counter;
ZEND_END_MODULE_GLOBALS(sample4)

上述代码经过宏替换之后,实际上是声明了一个名为zend_sample4_globals的结构体。

typedef struct _zend_sample4_globals {
    unsigned long counter;
} zend_sample4_globals;

接下来,在扩展的源文件(.c)中,使用ZEND_DELCARE_MODULE_GLOBALS定义一个全局变量。

ZEND_DECLARE_MODULE_GLOBALS(sample4);

这里需要注意的是,在单线程和多线程环境中,该宏展开后的内容是不一样的:

// 在单线程环境中,展开为定义了一个sample4_globals的结构体变量
zend_sample4_globals sample4_globals;

// 在多线程环境中,定义了一个int型的全局变量ID,Zend将会使用该ID检索线程相关的全局变量数据
int sample4_globals_id;

MINIT方法中,为多线程环境中的sample4_globals_id分配一个ID。

#ifdef ZTS // 注意一定要放在ifdef 中,否则单线程编译将报错,没有sample_globals_id
    ts_allocate_id(&sample4_globals_id,
                sizeof(zend_sample4_globals),
                NULL, NULL); // 这里两个NULL,一个是构造回调函数,一个是销毁回调函数
#endif

如果希望对全局变量进行初始化,可以为ts_allocate_id()函数提供最后两个参数指定的函数指针:

static void php_sample4_globals_ctor(
            zend_sample4_globals *sample4_globals TSRMLS_DC)
{
    // Initialize a new zend_sample4_globals struct During thread spin-up
    sample4_globals->counter = 0;
}
static void php_sample4_globals_dtor(
            zend_sample4_globals *sample4_globals TSRMLS_DC)
{
    // Any resources allocated during initialization May be freed here
}

修改后的初始化代码:

PHP_MINIT_FUNCTION(sample4)
{
    REGISTER_STRING_CONSTANT("SAMPLE4_VERSION",
            PHP_SAMPLE4_EXTVER, CONST_CS | CONST_PERSISTENT);
#ifdef ZTS // 线程安全,指定回调函数指针
    ts_allocate_id(&sample4_globals_id,
                sizeof(zend_sample4_globals),
                (ts_allocate_ctor)php_sample4_globals_ctor,
                (ts_allocate_dtor)php_sample4_globals_dtor);
#else // 非线程安全,也要对其进行初始化
    php_sample4_globals_ctor(&sample4_globals TSRMLS_CC);
#endif
    return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(sample4)
{
#ifndef ZTS
    php_sample4_globals_dtor(&sample4_globals TSRMLS_CC);
#endif
    return SUCCESS;
}

注意: 对全局变量初始化时,不要忘记非线程安全的全局变量也是要初始化的。

访问扩展全局变量

全局变量定义之后,在访问时,也要考虑到线程安全问题,因为在线程安全和非线程安全环境中全局变量的定义方式不同,
因此,访问方式也需要对ZTS进行判断,使用不同方式访问,为了提高代码的可读性和方便起见,
通常情况下会定义一个宏来对全局变量进行访问。

在头文件中加入以下代码:

#ifdef ZTS // 如果定义了ZTS,需要引入TSRM.h头文件
#include "TSRM.h"
// 多线程环境中,使用TSRMG宏,根据全局变量ID标识符,查找全局变量
#define SAMPLE4_G(v)    TSRMG(sample4_globals_id,
                         zend_sample4_globals*, v)
#else
// 单线程环境中,直接访问全局变量
#define SAMPLE4_G(v)    (sample4_globals.v)
#endif

Zend定义的全局变量访问宏

EG() 执行器全局变量,该变量在引擎内部主要用来跟踪当前请求的状态,常用的符号表、函数、类、
常量和资源表可以通过该宏查找。

CG() 核心全局变量,该宏主要是Zend引擎在脚本编译以及内核部分执行使用,在扩展开发中很少会用到。

PG() PHP全局变量,可用于访问php.ini中大部分核心指令。例如PG(register_globals)
PG(safe_mode)PG(memory_limit)

FG() 文件全局变量。大部分与文件I/O和流相关的全局变量都使用该结构查询,该宏为标准扩展提供。

注册常量

在PHP中,我们通常会使用define()定义一些常量,但是在扩展中,我们如何定义常量,让PHP能够访问呢?
在扩展开发中,通常使用REGISTER_*_CONSTANT()系列宏定义常量。

在PHP扩展中定义常量的时候,一般会在MINITRINIT函数中注册常量。如果希望常量在所有的脚本中
都被初始化为同样的值的话,需要在MINIT函数中注册,如果是请求相关的常量,则在RINIT函数中注册。

REGISTER_LONG_CONSTANT(char *name, long lval, int flags)
REGISTER_DOUBLE_CONSTANT(char *name, double dval, int flags)
REGISTER_STRING_CONSTANT(char *name, char *value, int flags)
REGISTER_STRINGL_CONSTANT(char *name,
                     char *value, int value_len, int flags)

上述函数并非真实的函数,而是ZEND定义的宏。在使用上述这些宏的时候需要注意的是,对于name参数,
必须提供直接字面值,而不能使用变量。如果需要常量名称从变量中赋值的话,需要使用以下函数代替:

ZEND_API void zend_register_long_constant(const char *name, uint name_len, long lval, int flags, int module_number TSRMLS_DC);

ZEND_API void zend_register_double_constant(const char *name, uint name_len, double dval, int flags, int module_number TSRMLS_DC);

ZEND_API void zend_register_string_constant(const char *name, uint name_len, char *strval, int flags, int module_number TSRMLS_DC);

ZEND_API void zend_register_stringl_constant(const char *name, uint name_len, char *strval, uint strlen, int flags, int module_number TSRMLS_DC);

具体函数以及宏的定义在PHP源码的zend_constants.h头文件中有定义。下面对使用到的参数进行简要说明:

  • name/name_len 常量名称、名称长度,这里长度不需要-1
  • lval/dval/value/strval 常量值
  • flags 常来标识,多个用“|”分隔
  • module_number 该参数由引擎的MINITRINIT函数提供,只需要直接使用即可,

注意的是,该参数需要传递线程安全标识TSRMLS_CC。

需要特别指出的是flags参数,该参数可以取下面几个值:

  • CONST_CS 表明该常量名称为区分大小写的,如果不提供则为不区分大小写
  • CONST_PERSISTENT 表示该常量是否是持久化存储的,对于在MINIT中注册的常量使用该参数。

注册常量的示例:

PHP_MINIT_FUNCTION(ext_demo_1)
{
    // 注册扩展常量
  REGISTER_STRING_CONSTANT("EXT_DEMO_1_VERSION", "1.0.1", CONST_CS | CONST_PERSISTENT);

  // 直接使用函数
  register_string_constant("SAMPLE4_VERSION",
        sizeof("SAMPLE4_VERSION"),
        "1.0",
        CONST_CS | CONST_PERSISTENT,
        module_number TSRMLS_CC);

    return SUCCESS;
}

除了数组和对象,其它类型都可以注册为常量,但是因为Zend没有提供特殊的宏或者函数,因此需要手动注册。

void php_sample4_register_boolean_constant(char *name, uint len,
    zend_bool bval, int flags, int module_number TSRMLS_DC)
{
    zend_constant c;// 手动创建zend_constant结构体变量

    ZVAL_BOOL(&c.value, bval);
    c.flags = CONST_CS | CONST_PERSISTENT;
    c.name = zend_strndup(name, len - 1);
    c.name_len = len;
    c.module_number = module_number;
    zend_register_constant(&c TSRMLS_CC);// 使用zend_register_constant函数注册常量
}

其中,zend_constant常量的结构如下:

typedef struct _zend_constant {
    zval value;    // 常量值
    int flags;    // 常量标识
    char *name;    // 常量名称
    uint name_len;    // 名称长度
    int module_number;    // module_number
} zend_constant;

为phpinfo提供扩展信息

在加载扩展之后,我们可以在使用phpinfo()函数或者是执行php -i命令显示PHP环境配置信息,
我们自己写的扩展的信息也将在这里面展示出来。

在PHP扩展程序中,通过使用MINFO函数提供扩展的基本信息。

#include "ext/standard/info.h"

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

注意,在zend_module_entry结构体中info_func函数指针部分指定该函数。

zend_module_entry ext_demo_1_module_entry = {
  ...
    PHP_MINFO(ext_demo_1),
  ...
};

MINFO函数中,使用php_info_*()系列函数创建需要显示的信息,需要注意的是,
使用之前检查一下是否已经加载了ext/standard/info.h头文件。

php_info_*系列函数列表:

PHPAPI char *php_info_html_esc(char *string TSRMLS_DC);
PHPAPI void php_info_html_esc_write(char *string, int str_len TSRMLS_DC);
PHPAPI void php_print_info_htmlhead(TSRMLS_D);
PHPAPI void php_print_info(int flag TSRMLS_DC);
PHPAPI void php_print_style(void);
PHPAPI void php_info_print_style(TSRMLS_D);
PHPAPI void php_info_print_table_colspan_header(int num_cols, char *header);
PHPAPI void php_info_print_table_header(int num_cols, ...);
PHPAPI void php_info_print_table_row(int num_cols, ...);
PHPAPI void php_info_print_table_row_ex(int num_cols, const char *, ...);
PHPAPI void php_info_print_table_start(void);
PHPAPI void php_info_print_table_end(void);
PHPAPI void php_info_print_box_start(int bg);
PHPAPI void php_info_print_box_end(void);
PHPAPI void php_info_print_hr(void);
PHPAPI void php_info_print_module(zend_module_entry *module TSRMLS_DC);

MINFO函数中输出扩展的信息时,不仅可以使用上述的api函数,我们还可以使用PHPWRITE()
php_printf()函数,不过需要注意的是,使用这两个函数的时候需要判断当前的SAPI环境,
以在WEB和CLI模式下输出正确的信息。要判断环境,使用全局变量sapi_module结构体的
phpinfo_as_text属性。

PHP_MINFO_FUNCTION(sample4)
{
    php_info_print_table_start();
    php_info_print_table_row(2, "Sample4 Module", "enabled");
    php_info_print_table_row(2, "version", PHP_SAMPLE4_EXTVER);
    if (sapi_module.phpinfo_as_text) {
        /* No HTML for you */
        php_info_print_table_row(2, "By",
            "Example Technologies\nhttp://www.example.com");
    } else {
        /* HTMLified version */
        php_printf("<tr>"
            "<td class=\"v\">By</td>"
            "<td class=\"v\">"
            "<a href=\"http://www.example.com\""
            " alt=\"Example Technologies\">"
            "<img src=\"http://www.example.com/logo.png\" />"

            "</a></td></tr>");
    }
    php_info_print_table_end();
}

~

Scroll Up