# 12.4 PHP扩展中的全局变量
这一章,我们将学会如何在PHP扩展中使用全局变量。
在扩展中定义全局变量
首先,我们需要在扩展的头文件中(默认是php_*.h)中定义所有的全局变量。举个例子,比如我们要定义一个无符号的long类型的全局变量,我们可以这样定义:
````c
ZEND_BEGIN_MODULE_GLOBALS(sample4)
unsigned long counter;
ZEND_END_MODULE_GLOBALS(sample4)
````
用ZEND_BEGIN_MODULE_GLOBALS和ZEND_END_MODULE_GLOBALS宏将定义的全局变量包起来。将上例中的宏展开后,是下面这个样子:
````c
typedef struct _zend_sample4_globals {
unsigned long counter;
} zend_sample4_globals;
````
如果你还有其他的全局变量需要定义,只需加在两个宏之间就可以了。接下来我们该在simple4.c中声明我们在头文件中定义的这些全局变量了:
````c
ZEND_DECLARE_MODULE_GLOBALS(sample4);
````
这个宏的内部实现取决于是否启用了线程安全,在非线程安全的环境下,如:Apache1,Apache2-prefork, CGI,CLI...会使用zend_sample4_globals结构来定义
全局变量:
````c
zend_sample4_globals sample4_globals;
````
我们可以直接通过sample4_globals.counter来获取计数器的值。在线程安全的版本中,另一种方法是声明一个整数:
````c
int sample4_globals_id;
````
填充这个ID就等于定义了扩展中的全局变量。根据其定义的信息,将为每个新线程的独立存储空间分配内存块。我们可以在MINIT中这样定义:
````c
#ifdef ZTS
ts_allocate_id(
&sample4_globals_id,
sizeof(zend_sample4_globals),
NULL, NULL);
#endif
````
有一点需要注意这种方法需要包裹在#ifdef中,以防止它在没有启动Zend Thread Safety(ZTS)时执行。因为sample4_globals_id只能在多线程环境中使用。非线程
的版本用我们在前面提到的sample4_globals来声明全局变量。
线程中的初始化和关闭
在非线程的环境中,会将一个zend_sample4_globals结构的副本保存在指定进程中。你可以指定他的默认值,或者在MINIT或者RINIT中分配资源来初始化它。要记得
在对应的MSHUTDOWN或者RSHUTDOWN中及时释放这些资源。
然而在线程版本中,一个新的结构会在一个新线程spun的时候被分配。为了知道怎样初始化和关闭扩展中的全局变量,需要向ZE引擎提供回调函数。在前面我们在调用
ts_allocate_id()的时候是是以NULL来作为这个值的,接下来我们添加2个一会需要在MINIT调用的方法:
````c
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 */
}
````
我们在启用和关闭的时候调用它们:
````c
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的环境中,使用它们很简单,我们还来看前面定义的那个计数器的递增功能如何实现:
````c
PHP_FUNCTION(sample4_counter) {
RETURN_LONG(++sample4_globals.counter);
}
````
是不是看起来很简单,但是,在线程版本中将无法正常工作。那么我们来看看怎么在线程环境中完成这个功能吧:
````c
PHP_FUNCTION(sample4_counter)
{
#ifdef ZTS
RETURN_LONG(++TSRMG(sample4_globals_id, \
zend_sample4_globals*, counter));
#else
/* non-ZTS */
RETURN_LONG(++sample4_globals.counter);
#endif
}
````
看起来很丑对吗?想象一下,在你的整个代码库中,这些IFDEF指令在每一个线程安全的全局访问时穿插。它会看起来比Perl更糟糕!这就是为什么所有
的核心扩展,都有使用一个额外的宏观层抽象这种情况。我们可以在php_sample4.h中找到下面这段代码:
````c
#ifdef ZTS
#include "TSRM.h"
#define SAMPLE4_G(v) TSRMG(sample4_globals_id, zend_sample4_globals*, v)
#else
#define SAMPLE4_G(v) (sample4_globals.v)
#endif
````
使用它们会让你的方法看起来更简洁:
````c
PHP_FUNCTION(sample4_counter) {
RETURN_LONG(++SAMPLE4_G(counter));
}
````
看到*G()这样的宏是不是有种似曾相识的感觉?也许以前你看到过EG()、CG()等宏,了解他们会让你对PHP的了解更深一步:
Accessor Macro | Associated Data
------------- | -------------
EG() | 这个宏可以用来访问符号表,函数,资源信息和常量。
CG() | 用来访问核心全局变量。
PG() | PHP全局变量。我们知道php.ini会映射一个或者多个PHP全局结构。举几个使用这个宏的例子:PG(register_globals), PG(safe_mode), PG(memory_limit)
FG() | 文件全局变量。大多数文件I/O或相关的全局变量的数据流都塞进标准扩展出口结构。
## links
* [目录](<preface.md>)
* 12.3 [常量](<12.3.md>)
* 12.5 [PHP语言中的超级全局变量](<12.5.md>)
- about
- 开始阅读
- 目录
- 1 PHP的生命周期
- 1.让我们从SAPI开始
- 2.PHP的启动与终止
- 3.PHP的生命周期
- 4.线程安全
- 5.小结
- 2 PHP变量在内核中的实现
- 1. 变量的类型
- 2. 变量的值
- 3. 创建PHP变量
- 4. 变量的存储方式
- 5. 变量的检索
- 6. 类型转换
- 7. 小结
- 3 内存管理
- 1. 内存管理
- 2. 引用计数
- 3. 总结
- 4 动手编译PHP
- 1. 编译前的准备
- 2. PHP编译前的config配置
- 3. Unix/Linux平台下的编译
- 4. 在Win32平台上编译PHP
- 5. 小结
- 5 Your First Extension
- 1. 一个扩展的基本结构
- 2. 编译我们的扩展
- 3. 静态编译
- 4. 编写函数
- 5. 小结
- 6 函数返回值
- 1. 一个特殊的参数:return_value
- 2. 引用与函数的执行结果
- 3. 小结
- 7 函数的参数
- 1. zend_parse_parameters
- 2. Arg Info 与类型绑定
- 3. 小结
- 8 使用HashTable与{数组}
- 1. 数组(C中的)与链表
- 2. 操作HashTable的API
- 3. 在内核中操作PHP语言中数组
- 4. 小结
- 9 PHP中的资源类型
- 1. 复合类型的数据——{资源}
- 2. Persistent Resources
- 3. {资源}自有的引用计数
- 4. 小结
- 10 PHP中的面向对象(一)
- 1. zend_class_entry
- 2. 定义一个类
- 3. 定义一个接口
- 4. 类的继承与接口的实现
- 5. 小结
- 11 PHP中的面向对象(二)
- 1. 生成对象的实例与调用方法
- 2. 读写对象的属性
- 3. 小结
- 12 启动与终止的那点事
- 2. 小结
- 1. 关于生命周期
- 2. MINFO与phpinfo
- 3. 常量
- 4. PHP扩展中的全局变量
- 5. PHP语言中的超级全局变量
- 6. 小结
- 13 INI设置
- 1. 声明和访问ini设置
- 2. 小结
- 2. 小结
- 14 流式访问
- 1. 概览
- 2. 打开流
- 3. 访问流
- 4. 静态资源操作
- 5. 小结
- 15 流的实现
- 1. php流的表象之下
- 2. 包装器操作
- 3. 实现一个包装器
- 4. 操纵
- 5. 检查
- 6. 小结
- 16 有趣的流
- 1. 上下文
- 2. 过滤器
- 3. 小结
- 17 配置和链接
- 1. autoconf
- 2. 库的查找
- 3. 强制模块依赖
- 4. Windows方言
- 5. 小结
- 18 扩展生成
- 1. ext_skel
- 2. PECL_Gen
- 3. 小结
- 19 设置宿主环境
- 1. 嵌入式SAPI
- 2. 构建并编译一个宿主应用
- 3. 通过嵌入包装重新创建cli
- 4. 老技术新用
- 5. 小结
- 20 高级嵌入式
- 1. 回调到php中
- 2. 错误处理
- 3. 初始化php
- 4. 覆写INI_SYSTEM和INI_PERDIR选项
- 5. 捕获输出
- 6. 同时扩展和嵌入
- 7. 小结
- 约定