PHP扩展开发篇,我已经更新到《TIPI》这篇文章沿用上一篇文章PHP7使用资源包第三方扩展实现PHP7使用资源包第三篇-方扩展原理分析注册资源类型源代码[c]ZEND_APIintzend_register_list_destructors_ex(rsrc_dtor_func_tld,rsrc_dtor_func_tpld,constchar*type_name,intmodule_number){zend_rsrc_list_dtors_entry*lde;zvalzv;lde=malloc(sizeof_tor_rs-d_)>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);==NULL){返回失败;}returnlist_destructors.nNextFreeElement-1;}其中[c]ZVAL_PTR(&zv,lde);相当于[c]zv.value.ptr=(lde);zv.u1.type_info=IS_PTR;list_destructors是一个全局静态HashTable。注册资源类型时,在list_destructors的arData中存储了一个zval结构变量zv,zv的value.ptr指向zend_rsrc_list_dtors_entry*lde,lde指针包含的资源释放函数,持久化资源释放函数指针、资源类型名称、资源在hashtable中的索引依据(resource_id)等,这里的resource_id是函数的返回值,所以我们后面解析这类变量的时候需要带上resource_id。整个注册步骤可以概括为下图:资源注册[c]ZEND_APIzend_resource*zend_register_resource(void*rsrc_pointer,intrsrc_type){zval*zv;zv=zend_list_insert(rsrc_pointer,rsrc_type);returnZ_RES_P(zv);}该函数的作用是返回zend_list_insert返回的zval中的资源指针。Z_RES_P宏在Zend/zend_types.h中定义。重点分析zend_list_insert[c]ZEND_APIzval*zend_list_insert(void*ptr,inttype){intindex;zvalzv;index=zend_hash_next_free_element(&EG(regular_list));如果(索引==0){索引=1;}ZVAL_NEW_RES(&zv,索引,ptr,类型);returnzend_hash_index_add_new(&EG(regular_list),index,&zv);}其中,zend_hash_next_free_element宏返回&EG(regular_list)表的nNextFreeElement,作为后面索引查询的依据。ZVAL_NEW_RES宏是PHP7中的一套新东西,它加载一个资源到zval中,因为PHP7中的Bucket只能存放zval。[c]#defineZVAL_NEW_RES(z,h,p,t)做{\zend_resource*_res=\(zend_resource*)emalloc(sizeof(zend_resource));\zval*__z;\GC_REFCOUNT(_res)=1;\GC_TYPE_INFO(_res)=IS_RESOURCE;\_res->handle=(h);\_res->type=(t);\_res->ptr=(p);\__z=(z);\Z_RES_P(__z)=_res;\Z_TYPE_INFO_P(__z)=IS_RESOURCE_EX;\}while(0)代码比较清晰,先根据h,p,t创建新资源,然后将z一起存储在zval结构体中。(最后两个宏刚刚在前面讨论过。)最后一个是zend_hash_index_add_new宏。跟踪代码发现相当于调用[c]_zend_hash_index_add_or_update_i(&EG(regular_list),index,&zv,HASH_ADD|HASH_ADD_NEWZEND_FILE_LINE_RELAY_CC)关于PHP7HashTable的具体操作,这里不再详细分析,和前面的数据结构后面会更新章节。整个注册逻辑如下:资源源码分析[c]ZEND_APIvoid*zend_fetch_resource(zend_resource*res,constchar*resource_type_name,intresource_type){if(resource_type==res->type){returnres->ptr;}if(resource_type_name){constchar*space;constchar*class_name=get_active_class_name(&space);zend_error(E_WARNING,"%s%s%s():提供的资源不是有效的%s资源",class_name,space,get_active_function_name(),resource_type_name);}returnNULL;}在上面的例子中,我们这样解析[c](FILE*)zend_fetch_resource(Z_RES_P(filehandle),TIPI_FILE_TYPE,le_tipi_file)首先,通过Z_RES_P宏zend_resource获取文件句柄zval变量。然后zend_fetch_resource只是比较zend_resource的类型是否和我们期望的资源类型一致,然后返回zend_resource的*ptr,最后转换成FILE*指针。PHP7对资源的解析比PHP5更简单、更快,这要归功于其zval结构的改变。原来的PHP5需要通过EG(regular_list)查找,如下图:现在PHP7的解析直接从zval解析zend_resource,如下图:删除资源源代码分析[c]ZEND_APIintzend_list_close(zend_resource*res){如果(GC_REFCOUNT(res)<=0){returnzend_list_free(res);}elseif(res->type>=0){zend_resource_dtor(res);}returnSUCCESS;}与PHP5不同的是,这里并不是每次进来都会将自己的引用计数减一,而是直接调用zend_resource_dtor函数。[c]staticvoidzend_resource_dtor(zend_resource*res){zend_rsrc_list_dtors_entry*ld;zend_resourcer=*res;res->type=-1;res->ptr=NULL;ld=zend_hash_index_find_ptr(&list_destructors,r.type);如果(ld){如果(ld->list_dtor_ex){ld->list_dtor_ex(&r);}}else{zend_error(E_WARNING,"Unknownlistentrytype(%d)",r.type);}}如果引用计数已经等于0或小于0,则从EG(regular_list)[c]ZEND_APIintzend_list_free(zend_resource*res){if(GC_REFCOUNT(res)<=0){returnzend_hash_index_del(&EG(regular_list),res->handle);}else{返回成功;}}原理图还是参考上面注册的资源类型,注册资源图。首先通过type反转list_destructors中zend_resource和index层的关系,找到该类资源的释放回调函数,然后对该资源执行释放回调函数。后续从EG(regular_list)中删除是以res->handler为索引的。
