Welcome 微信登录

首页 / 网页编程 / PHP / PHP的资源数据类型

PHP的资源数据类型2013-09-07资源数据类型

迄今为止, 你都是工作在非常基础的用户空间数据类型上, 字符串, 数值, TRUE/FALSE等值. 即便上一章你已经开始接触数组了, 但也只是收集这些基础数据类型的数组.

复杂的结构体

现实世界中, 你通常需要在更加复杂的数据集合下工作, 通常涉及到晦涩的结构体指针. 一个常见的晦涩的结构体指针示例就是stdio的文件描述符, 即便是在C语言中也只是一个指针.

#include <stdio.h>int main(void){FILE *fd;fd = fopen("/home/jdoe/.plan", "r");fclose(fd);return 0;}
stdio的文件描述符和其他多数文件描述符一致, 都像是一个书签. 你扩展的调用应用仅需要在feof(), fread(), fwrite(), fclose()这样的实现函数调用时传递这个值. 有时, 这个书签必须是用户空间代码可访问的; 因此, 就需要在标准的php变量或者说zval *中有表示它的方法.

这里就需要一种新的数据类型. RESOURCE数据类型在zval *中存储一个简单的整型值, 使用作为已注册资源的索引用来查找. 资源条目包含了资源索引所表示的内部数据类型, 以及存储资源数据的指针等信息.

定义资源类型

为了使注册的资源条目所包含的资源信息更加明确, 需要定义资源的类型. 首先在你的sample.c中已有的函数实现下增加下面的代码片段

static int le_sample_descriptor;PHP_MINIT_FUNCTION(sample){le_sample_descriptor = zend_register_list_destructors_ex(NULL, NULL, PHP_SAMPLE_DESCRIPTOR_RES_NAME,module_number);return SUCCESS;}
接下来, 滚动到你的代码文件末尾, 修改sample_module_entry结构体, 将NULL, /* MINIT */一行替换为下面的内容. 就像你给这个结构中增加函数列表结构时一样, 你需要确认在这一行末尾保留一个逗号.

PHP_MINIT(sample), /* MINIT */  

最后, 你需要在php_sample.h中定义PHP_SAMPLE_DESCRIPTOR_RES_NAME, 将下面的代码放到你的其他常量定义下面:

#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "File Descriptor"  

PHP_MINIT_FUNCTION()代表第1章"PHP生命周期"中介绍的4个特殊的启动和终止操作中的第一个, 关于生命周期, 在第12章"启动, 终止以及之间的几个关键点"和第13章"INI设置"中还将深入讨论.

这里需要知道的非常重要的一点是MINIT函数在你的扩展第一次加载时执行一次, 它会在所有请求到达之前被执行. 这里我们利用这个机会注册了析构函数, 不过它们是NULL值, 不过在通过一个唯一整型ID足以知道一个资源类型时, 你很快就会修改它.

注册资源

现在引擎已经知道了你要存储一些资源数据, 是时候给用户空间的代码一种方式去产生实际的资源了. 要做到这一点, 需要如下重新实现fopen()命令:

PHP_FUNCTION(sample_fopen){FILE *fp;char *filename, *mode;int filename_len, mode_len;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&filename, &filename_len,&mode, &mode_len) == FAILURE) {RETURN_NULL();}if (!filename_len || !mode_len) {php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid filename or mode length");RETURN_FALSE;}fp = fopen(filename, mode);if (!fp) {php_error_docref(NULL TSRMLS_CC, E_WARNING,"Unable to open %s using mode %s",filename, mode);RETURN_FALSE;}ZEND_REGISTER_RESOURCE(return_value, fp,le_sample_descriptor);}
为了让编译器知道什么是FILE *, 你需要包含stdio.h. 这可以放在sample.c中, 但是为了本章后面部分做准备, 我还是要求你放到php_sample.h中.

如果你对前面的章节付出了努力, 最后一行前面的所有内容都应该可以读懂. 这一行代码执行的任务是将fp指针存储到资源的索引中, 将它和MINIT中定义的类型关联起来, 并存储一个可用于查找的key到return_value中.

如果需要存储多于一个指针的值, 或者存储一个直接量, 则必须新分配一段内存用来存储数据, 接着将指向这段内存的指针注册为资源.

译注:

1. 资源数据类型的注册实际上是在list_destructors(Zend/zend_list.c中定义的静态全局变量HashTable)中插入一个新构建的zend_rsrc_list_dtors_entry结构体, 这个结构体描述了这个资源类型的信息.

2. 资源数据的注册(ZEND_REGISTER_RESOURCE)实际上是在EG(regular_list)中使用zend_hash_next_free_element()得到下一个数值下标, 作为资源的ID, 并将传入的资源指针(封装为zend_rsrc_list_entry结构体)存储到EG(regular_list)中这个下标对应的元素中.

3. EG(regular_list)的初始化是在请求初始化阶段完成的, 通过跟踪代码, 可以看到其函数调用流程如下: php_request_startup(main/main.c) --> zend_active(Zend/zend.c) --> init_compiler(Zend/zend_compile.c) --> zend_init_rsrc_list(Zend/zend_list.c). 通过观察zend_init_rsrc_list()函数可以看出EG(regular_list)的析构函数是list_entry_destructor(Zend/zend_list.c). 而list_entry_destructor()的逻辑是从list_destructors(上面第一步所述的静态全局变量)中查找要释放的资源对象类型的信息, 接着按照注册资源类型时所指定的析构器进行析构.

4. 按照上面几点, 可以很容易理清本章前面所述内容. 首先注册一个资源类型, 这个资源类型中包含了诸如所属模块编号, 析构器句柄这样的信息. 接着, 在创建具体的资源对象时, 将资源对象和资源类型做了一个关联.