在单线程和多线程环境中,扩展内部全局变量的创建和使用方式是不同的。
要声明一个全局变量,首先需要在扩展的头文件中声明:
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
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扩展中定义常量的时候,一般会在MINIT
和RINIT
函数中注册常量。如果希望常量在所有的脚本中
都被初始化为同样的值的话,需要在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
头文件中有定义。下面对使用到的参数进行简要说明:
MINIT
和RINIT
函数提供,只需要直接使用即可,注意的是,该参数需要传递线程安全标识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()
函数或者是执行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();
}
~