php 包裹方案,PHP5 使用资源包裹第三方扩展源码解读

PHP 扩展开发的文章,我均已更新至《TIPI》(下面的博文可能已经过时,以 TIPI 上的内容为准)。

1. 注册资源类型

1.0 添加的资源释放函数static void zmk_file_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){

FILE *fp = (FILE *) rsrc->ptr;

fclose(fp);

}

注意到传入的参数类型为zend_rsrc_list_entry *,也就是说资源的结构体为zend_rsrc_list_entry。其定义为typedef struct _zend_rsrc_list_entry {

void *ptr;

int type;

int refcount;

} zend_rsrc_list_entry;

文件位置:Zend/zend_list.h

1.1 注册类型

然后我们在PHP_MINIT_FUNCTION()里执行了le_zmk_file = zend_register_list_destructors_ex(zmk_file_dtor, NULL, RESOURCE_TYPE_ZMK_FILE, module_number);

资源类型注册函数ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)

{

zend_rsrc_list_dtors_entry lde;

lde.list_dtor = NULL;/* old style destructors 这里直接设置 NULL 表示不再使用了*/

lde.plist_dtor = NULL;/* old style destructors */

lde.list_dtor_ex = ld;/* new style destructors */

lde.plist_dtor_ex = pld;/* new style destructors */

lde.module_number = module_number;

lde.resource_id = list_destructors.nNextFreeElement;

lde.type = ZEND_RESOURCE_LIST_TYPE_EX;

lde.type_name = type_name;

if (zend_hash_next_index_insert(&list_destructors, (void *) &lde, sizeof(zend_rsrc_list_dtors_entry), NULL)==FAILURE) {

return FAILURE;

}

return list_destructors.nNextFreeElement-1;

}

list_destructors是一个全局静态HashTable,资源类型注册时,实际是将一个zend_rsrc_list_dtors_entry存放入list_destructors的arBuckets中,该结构体中包含的该种资源释放函数指针、持久资源的释放函数指针,资源类型名称,该资源在 hashtable 中的索引依据 (resource_id)等。

而这里的resource_id则是该函数的返回值,所以后面我们在解析该类型变量时,都需要将resource_id带上。

2. 资源的初始化

2.1 调用 ZEND_REGISTER_RESOURCE 执行注册

然后在file_open里面执行了ZEND_REGISTER_RESOURCE(return_value, fp, le_zmk_file);

fp是打开文件的指针,le_zmk_file就是上面注册资源类型返回的resource_id。

而ZEND_REGISTER_RESOURCE宏实际调用的是下面的函数:ZEND_API int zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type)

{

int rsrc_id;

rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type);

if (rsrc_result) {

rsrc_result->value.lval = rsrc_id;

rsrc_result->type = IS_RESOURCE;

}

return rsrc_id;

}ZEND_API int zend_list_insert(void *ptr, int type)

{

int index;

zend_rsrc_list_entry le;

TSRMLS_FETCH();

le.ptr=ptr;

le.type=type;

le.refcount=1;

index = zend_hash_next_free_element(&EG(regular_list));

zend_hash_index_update(&EG(regular_list), index, (void *) &le, sizeof(zend_rsrc_list_entry), NULL);

return index;

}

也就是把一个zend_rsrc_list_entry的实体放入了EG(regular_list)表中。

前两步我绘制了一张图来表示

571d882504825b01eb66a99b84606afe.png

3. 使用资源

3.1 通过 ZEND_FETCH_RESOURCE 解析还原资源本质

例如代码中的ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, filehandle_id, RESOURCE_TYPE_ZMK_FILE, le_zmk_file);

目的是将传入的filehandle转换为原始的fp指针。

源码分析#define ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type)  \

rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC, default_id, resource_type_name, NULL, 1, resource_type);   \

ZEND_VERIFY_RESOURCE(rsrc);

我们发现在解析资源的最后还做了资源验证。我们先说资源的解析,下面我简化了一些本次执行不相关的代码,使得逻辑更加清晰简单//zend_fetch_resource(&filehandle TSRMLS_DC, -1, RESOURCE_TYPE_ZMK_FILE, NULL, 1, le_zmk_file);

ZEND_API void *zend_fetch_resource(zval **passed_id TSRMLS_DC, int default_id, char *resource_type_name, int *found_resource_type, int num_resource_types, ...)

{

int id;

int actual_resource_type;

void *resource;

va_list resource_types;

int i;

char *space;

char *class_name;

if (default_id==-1) { /* use id */

id = (*passed_id)->value.lval;

} else {

id = default_id;

}

resource = zend_list_find(id, &actual_resource_type);

va_start(resource_types, num_resource_types);

for (i=0; i

if (actual_resource_type == va_arg(resource_types, int)) {

va_end(resource_types);

if (found_resource_type) {

*found_resource_type = actual_resource_type;

}

return resource;

}

}

va_end(resource_types);

return NULL;

}

该函数是支持可变参数的,所以最后传入了num_resource_types和...,这里只传入了一个resource_type,见函数上方的注释代码,而关于可变参数的原理,可以参考http://mengkang.net/678.htmlZEND_API void *_zend_list_find(int id, int *type TSRMLS_DC)

{

zend_rsrc_list_entry *le;

if (zend_hash_index_find(&EG(regular_list), id, (void **) &le)==SUCCESS) {

*type = le->type;

return le->ptr;

} else {

*type = -1;

return NULL;

}

}

返回了le->ptr也就是原始的File *指针,用图表示(省略部分参数的传递,请配合代码理解)

683.html

4. 删除资源

4.1 通过 zend_list_delete 删除资源

从上篇的例子中我们在file_close()中调用了zend_list_delete来完成资源的删除。分析其源码执行的过程#define zend_list_delete(id)       _zend_list_delete(id TSRMLS_CC)ZEND_API int _zend_list_delete(int id TSRMLS_DC)

{

zend_rsrc_list_entry *le;

if (zend_hash_index_find(&EG(regular_list), id, (void **) &le)==SUCCESS) {

if (--le->refcount<=0) {

return zend_hash_index_del(&EG(regular_list), id);

} else {

return SUCCESS;

}

} else {

return FAILURE;

}

}

把引用计数(refcount)减一,然后检查refcount是否到0了,如果到0,就真正的从EG(regular_list)列表删除。

那么上文中说到的,最初注册的资源的释放回调函数是如何执行的呢?

原来是通过zend_hash_init初始化了EG(regular_list)的pDestructorint zend_init_rsrc_list(TSRMLS_D)

{

if (zend_hash_init(&EG(regular_list), 0, NULL, list_entry_destructor, 0)==SUCCESS) {

EG(regular_list).nNextFreeElement=1;   /* we don't want resource id 0 */

return SUCCESS;

} else {

return FAILURE;

}

}

唯一调用zend_hash_init的地方为Zend/zend_compile.c里的init_compilervoid list_entry_destructor(void *ptr)

{

zend_rsrc_list_entry *le = (zend_rsrc_list_entry *) ptr;

zend_rsrc_list_dtors_entry *ld;

TSRMLS_FETCH();

if (zend_hash_index_find(&list_destructors, le->type, (void **) &ld)==SUCCESS) {

switch (ld->type) {

case ZEND_RESOURCE_LIST_TYPE_STD:

if (ld->list_dtor) {

(ld->list_dtor)(le->ptr);

}

break;

case ZEND_RESOURCE_LIST_TYPE_EX:

if (ld->list_dtor_ex) {

ld->list_dtor_ex(le TSRMLS_CC);

}

break;

EMPTY_SWITCH_DEFAULT_CASE()

}

} else {

zend_error(E_WARNING,"Unknown list entry type in request shutdown (%d)", le->type);

}

}

而list_entry_destructor的作用就是接受zend_rsrc_list_entry指针,然后根据其类型(也就是我们定义的le_zmk_file)在list_destructors中找到其对应的zend_rsrc_list_dtors_entry,然后对传入的资源执行释放函数,例如我们这里的ld->list_dtor_ex(le TSRMLS_CC)。

而在zend_hash_index_del宏对应的删除函数中则正好执行了ht->pDestructor(p->pData)。ZEND_API int zend_hash_del_key_or_index(HashTable *ht, const char *arKey, uint nKeyLength, ulong h, int flag)

{

uint nIndex;

Bucket *p;

IS_CONSISTENT(ht);

if (flag == HASH_DEL_KEY) {

h = zend_inline_hash_func(arKey, nKeyLength);

}

nIndex = h & ht->nTableMask;

p = ht->arBuckets[nIndex];

while (p != NULL) {

if ((p->h == h)

&& (p->nKeyLength == nKeyLength)

&& ((p->nKeyLength == 0) /* Numeric index (short circuits the memcmp() check) */

|| !memcmp(p->arKey, arKey, nKeyLength))) { /* String index */

...

if (ht->pDestructor) {

ht->pDestructor(p->pData);

}

...

ht->nNumOfElements--;

return SUCCESS;

}

p = p->pNext;

}

return FAILURE;

}

683.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值