首页
Web开发
Windows程序
编程语言
数据库
移动开发
系统相关
微信
其他好文
会员
首页
>
Web开发
> 详细
Php学习之资源类型的使用详解
时间:
2017-08-17 20:30:12
阅读:
205
评论:
0
收藏:
0
[点我收藏+]
标签:
本文和大家分享的主要是php中资源类型的使用相关内容,一起来看看吧,希望对大家
学习php
有所帮助。
资源类型是一种特殊类型,它实际上可以保存任意的C指针,对PHP表现出一个资源对象的模样,例如:PHP里fopen的返回值就是一个resource。
我们可以利用资源类型,保存类型对象的指针,比如:一个FILE*文件描述符,或者仅仅是一个简单的char *字符串,其意义是可以将我们希望传递的C语言内存对象通过zval的形式包装起来,以便C和PHP跨语言传递。
资源类型是一个zval的底层数据类型,叫做zend_resource:
struct _zend_resource {
zend_refcounted_h gc;
int handle; // TODO: may be removed ???
int type;
void *ptr;
};
· gc:zval底层数据类型的第一个字段都是引用计数。
· handle:唯一标识一个资源对象,后面会讲到其来源。
· type:标识资源对象的类型,每个资源对象都属于一个资源类型。
· ptr:任意的C指针,保存我们需要用到的东西。
为了使用资源,我们必须要注册资源类型,之后才能创建资源对象。因此,我在扩展的启动回调函数里完成资源类型的注册:
int extension_startup(int type, int module_number) {
....
// register resource type
myext_string_resource_id = zend_register_list_destructors_ex(myext_string_resource_dtor, NULL, MYEXT_STRING_RESOURCE_DTOR, module_number);
assert(myext_string_resource_id != FAILURE);
通过zend_register_list_destructors_ex函数可以注册一个资源类型,该函数的主要目的是定义资源对象的销毁回调函数,以及资源类型的可读名字,具体看一下定义:
/* true global */static HashTable list_destructors;
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, const char *type_name, int module_number)
{
zend_rsrc_list_dtors_entry *lde;
zval zv;
lde = malloc(sizeof(zend_rsrc_list_dtors_entry));
lde->list_dtor_ex = ld;
lde->plist_dtor_ex = pld;
lde->module_number = module_number;
lde->resource_id = list_destructors.nNextFreeElement;
lde->type_name = type_name;
ZVAL_PTR(&zv, lde);
if (zend_hash_next_index_insert(&list_destructors, &zv) == NULL) {
return FAILURE;
}
return list_destructors.nNextFreeElement-1;
}
该函数原理简单,创建一个类型zend_rsrc_list_dtors_entry的结构体,里面的list_dtor_ex保存了非持久化资源对象的销毁回调函数,plist_dtor_ex是持久化资源对象的销毁函数(我们通常用不到持久化的资源对象)。
最后,将这个资源类型对应的zend_rsrc_list_dtors_entry对象append到哈希表list_destructors中,以便后续销毁资源对象时可以来找到对应的销毁函数,其数组下标就唯一标识了这个资源类型,返回给调用者。
可见,所谓的注册资源类型,就是在一个全局哈希表HashTable list_destructors中保存了该类型资源对象的销毁回调函数。
再回头看看我注册资源类型的代码,其参数的具体实现如下:
// resource idint myext_string_resource_id = 0;
// resource type description#define MYEXT_STRING_RESOURCE_DTOR "myext_string_resource"
// resource destructor callbackvoid myext_string_resource_dtor(zend_resource *res) {
assert(res->type == myext_string_resource_id);
free(res->ptr);
}
我将注册返回的资源类型ID保存在myext_string_resource_id中,资源类型的描述信息是”myext_string_resource”(当你var_dump资源对象时会显示给用户),myext_string_resource_dtor是资源销毁函数,当资源引用计数降低为0时,该函数将被回调以便我们有机会释放zend_resource.ptr关联的内存资源。
这里我的resource类型就是保存一个普通C字符串,所以我在回调函数里free它的内存即可。
在注册了这个资源类型后,我们进入测试环节,我新增了一个测试函数:
void zif_myext_test_resource(zend_execute_data *execute_data, zval *return_value) {
char *string = strdup("i am a string resource");
zend_resource *res = zend_register_resource(string, myext_string_resource_id);
assert(GC_REFCOUNT(res) == 1);
首先在堆上分配了一个C字符串,然后调用zend_register_resource创建一个zend_resource资源对象。函数的第1个参数是我们关联的底层数据ptr,第2个参数是资源类型的ID:
ZEND_API zval *zend_list_insert(void *ptr, int type)
{
int index;
zval zv;
index = zend_hash_next_free_element(&EG(regular_list));
if (index == 0) {
index = 1;
}
ZVAL_NEW_RES(&zv, index, ptr, type);
return zend_hash_index_add_new(&EG(regular_list), index, &zv);
}
ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type)
{
zval *zv;
zv = zend_list_insert(rsrc_pointer, rsrc_type);
return Z_RES_P(zv);
}
创建一个特定类型的zend_resource对象,其实就是创建一个zend_resource结构并填充handle、ptr、type字段,之后追加到全局哈希表EG(regular_list)中即可。
index是哈希表EG(regular_list)的下一个空闲整形下标,ptr是我们分配的C字符串,type是之前注册的资源类型ID。通过ZVAL_NEW_RES宏可以创建一个zend_resource对象,并将这些信息赋值给zend_resource各个字段。最后,调用zend_hash_index_add_new即可将这个zend_resource资源对象保存到EG(regular_list)的index下标中去。
由此可见,所有的资源对象都顺序排列在全局哈希表(PHP的array)EG(regular_list)中,因此它们默认引用计数都是1。我们可以看一下EG(regular_list)这个哈希表的初始化过程:
void list_entry_destructor(zval *zv)
{
zend_resource *res = Z_RES_P(zv);
ZVAL_UNDEF(zv);
if (res->type >= 0) {
zend_resource_dtor(res);
}
efree_size(res, sizeof(zend_resource));
}
int zend_init_rsrc_list(void)
{
zend_hash_init(&EG(regular_list), 8, NULL, list_entry_destructor, 0);
return SUCCESS;
}
我们知道zend_hash_init可以传入一个value的析构函数,这里是list_entry_destructor。当从EG(regular_list)中删除一个key时,析构函数将被调用。
它首先取出zval的底层zend_resource,然后开始释放这个zend_resource的资源:
static void zend_resource_dtor(zend_resource *res)
{
zend_rsrc_list_dtors_entry *ld;
zend_resource r = *res;
res->type = -1;
res->ptr = NULL;
ld = zend_hash_index_find_ptr(&list_destructors, r.type);
if (ld) {
if (ld->list_dtor_ex) {
ld->list_dtor_ex(&r);
}
} else {
zend_error(E_WARNING, "Unknown list entry type (%d)", r.type);
}
}
释放1个资源对象,首先是去注册资源类型的哈希表list_destructors中找到对应的资源销毁回调函数,之后将zend_resource传给销毁函数进行释放。最后,会将zend_resource自身的内存通过efree释放。
总结起来,删除一个资源对象的的前提是其引用计数为0,删除资源对象的过程就是先通过资源类型哈希表找到销毁函数,然后回调完成底层数据的销毁,最后释放资源对象自身内存。
一般创建了资源对象之后,我们最有可能将其返回给用户,因此需要将zend_resource包装到zval内部,这一步记得增加额外的引用计数:
// wrappped with zval, refcount=2
zval res_zval;
ZVAL_RES(&res_zval, res);
zval_addref_p(&res_zval);
assert(GC_REFCOUNT(res) == 2);
而显式的释放一个资源对象有2种方法,第1种是直接操作zend_resource自身,其用法如下:
// release resource directly, left refcount=1
zend_list_delete(res);
assert(GC_REFCOUNT(res) == 1);
zend_list_delete类似于zend_string_release,它首先释放1个引用计数,如果引用计数降低为0,就执行资源对象的删除流程(上面已经提到过了,只需要从EG(regular_list)中删除它即可触发后续一系列基于回调的销毁流程):
ZEND_API int zend_list_delete(zend_resource *res)
{
if (--GC_REFCOUNT(res) <= 0) {
return zend_hash_index_del(&EG(regular_list), res->handle);
} else {
return SUCCESS;
}
}
因为我们在zval res_val中额外保存了1个引用计数,当前资源对象尚未销毁。下面,我们从zval中取出zend_resource对象的底层ptr:
// validate and get resource ptr
char *s = zend_fetch_resource_ex(&res_zval, MYEXT_STRING_RESOURCE_DTOR, myext_string_resource_id);
assert(strcmp(s, "i am a string resource") == 0);
zend_fetch_resource_ex可以从一个zval中的zend_resource对象中取出ptr,它只是额外做了一次资源类型的校验而已(如果类型不对,还会抛出一个错误信息):
ZEND_API void *zend_fetch_resource(zend_resource *res, const char *resource_type_name, int resource_type){
if (resource_type == res->type) {
return res->ptr;
}
if (resource_type_name) {
const char *space;
const char *class_name = get_active_class_name(&space);
zend_error(E_WARNING, "%s%s%s(): supplied resource is not a valid %s resource", class_name, space, get_active_function_name(), resource_type_name);
}
return NULL;
}
ZEND_API void *zend_fetch_resource_ex(zval *res, const char *resource_type_name, int resource_type){
const char *space, *class_name;
if (res == NULL) {
if (resource_type_name) {
class_name = get_active_class_name(&space);
zend_error(E_WARNING, "%s%s%s(): no %s resource supplied", class_name, space, get_active_function_name(), resource_type_name);
}
return NULL;
}
if (Z_TYPE_P(res) != IS_RESOURCE) {
if (resource_type_name) {
class_name = get_active_class_name(&space);
zend_error(E_WARNING, "%s%s%s(): supplied argument is not a valid %s resource", class_name, space, get_active_function_name(), resource_type_name);
}
return NULL;
}
return zend_fetch_resource(Z_RES_P(res), resource_type_name, resource_type);
}
最后,我们释放zval,此时zend_resource的引用计数将降低为0:
// release resource through zval, left refcount=0, zend_list_free is called
zval_ptr_dtor(&res_zval);
其背后的实现:
ZEND_API void ZEND_FASTCALL _zval_dtor_func(zend_refcounted *p ZEND_FILE_LINE_DC){
switch (GC_TYPE(p)) {
.....
case IS_RESOURCE: {
zend_resource *res = (zend_resource*)p;
/* destroy resource */
zend_list_free(res);
break;
}
可见,最终释放zend_resource是经过zend_list_free函数,它断言当前引用计数为0,并从EG(regular_list)中删除该zend_resource触发销毁流程:
ZEND_API int zend_list_free(zend_resource *res)
{
if (GC_REFCOUNT(res) <= 0) {
return zend_hash_index_del(&EG(regular_list), res->handle);
} else {
return SUCCESS;
}
}
来源:鱼儿的博客
Php学习之资源类型的使用详解
标签:
踩
(
0
)
赞
(
0
)
举报
评论
一句话评论(
0
)
登录后才能评论!
wjunxi
加入时间:
2017-06-15
已关注
关注此人
发短消息
文章分类
默认分类(
152
)
“
wjunxi
”关注的人------(
0
)
“
wjunxi
”的粉丝们------(
0
)
友情链接
兰亭集智
国之画
百度统计
站长统计
阿里云
chrome插件
新版天听网
关于我们
-
联系我们
-
留言反馈
© 2014
mamicode.com
版权所有 联系我们:gaon5@hotmail.com
迷上了代码!