IT技术博客大学习 共学习 共进步

PHP内核介绍及扩展开发指南―高级主题

搜索研发部官方博客 2011-06-21 13:37:53 浏览 4,583 次

1.1 使用数组

    曾讲到,PHP数组本质上就是个HashTable,因此访问数组就是对HashTable进行操作,Zend为我们提供的一组数组函数也只是对HashTable操作进行了简单包装而已。

    来看创建数组,由于数组也是存在于zval里的,因此要先用MAKE_STD_ZVAL()宏创建一个zval,之后调用如下宏将其转化为一个空数组:

array_init(zval*)

    接下来是朝数组中添加元素,这对关联数组元素和非关联数组元素要采用不同操作。

1.1.1 关联数组元素

    关联数组采用char*作为key,zval*作为value,可以使用如下宏将已有的zval加入数组或者更新已有元素:

int add_assoc_zval(zval *arr, char *key, zval *value)

    需要特别注意的是,Zend不会复制zval,只会简单的储存其指针,并且不关心任何引用计数,因此不能将其他变量的zval或者是栈上的zval传给它,只能用MAKE_STD_ZVAL()宏构建。

    Zend为常用的类型定义了相应的API,以简化我们的操作:

add_assoc_long(zval *array, char *key, long n);
add_assoc_bool(zval *array, char *key, int b);
add_assoc_resource(zval *array, char *key, int r);
add_assoc_double(zval *array, char *key, double d);
add_assoc_string(zval *array, char *key, char *str, int duplicate);
add_assoc_stringl(zval *array, char *key, char *str, uint length, int duplicate);
add_assoc_null(zval *array, char *key);

    当函数发现目标元素已经存在时,会首先递减其原zval的refcount,然后才插入新zval,这就保证了原zval引用信息的正确性。这种行为是通过HashTable.pDestructor(参见1.2.1)实现的,每次删除一个元素时,HashTable都将对被删元素调用这个函数指针,而数组为其HashTable设置的函数指针就是用来处理被删除zval的引用信息。

    另外,查看这些函数的源代码可以发现一个有意思的现象,它们没有直接使用HashTable操作,而是使用变量符号表操作,可见关联数组和变量符号表就是一种东西。

    Zend没有提供删除和获取数组元素的函数,此类操作只能使用HashTable函数或者是2.6节的变量符号表操作。

1.1.2非关联数组元素

    非关联数组没有key,使用index作为hash,相应函数和上面关联数组的十分类似:

add_index_zval(zval *array, uint idx, zval *value);
add_index_long(zval *array, uint idx, long n);
add_index_bool(zval *array, uint idx, int b);
add_index_resource(zval *array, uint idx, int r);
add_index_double(zval *array, uint idx, double d);
add_index_string(zval *array, uint idx, char *str, int duplicate);
add_index_stringl(zval *array, uint idx, char *str, uint length, int duplicate);
add_index_null(zval *array, uint idx);

    如果只是想插入值,而不指定index的话,可以使用如下函数:

add_next_index_zval(zval *array, zval *value);
add_next_index_long(zval *array, long n);
add_next_index_bool(zval *array, int b);
add_next_index_resource(zval *array, int r);
add_next_index_double(zval *array, double d);
add_next_index_string(zval *array, char *str, int duplicate);
add_next_index_stringl(zval *array, char *str, uint length, int duplicate);
add_next_index_null(zval *array);

1.2 使用资源

1.2.1 注册资源类型

    1.1.1节曾经提到,所谓资源就是内部数据的handle(但是这句话并不全对),使用资源是比较简单的,首先是注册一个资源类型:

int zend_register_list_destructors_ex(
rsrc_dtor_func_t ld,
rsrc_dtor_func_t pld,
char *type_name,
int module_number);

    第一个参数是函数指针,当资源不再被使用或者模块将被卸载时,Zend使用它来销毁资源,稍候再作介绍;第二个参数和第一个类似,只是它被用来销毁持久性资源(*);type_name是资源名称,用户可以使用var_dump函数来读取;module_number是模块号,在启动函数中可以获取该值。

    注册过程其实就是将我们传入的参数放到一个内部数据结构,然后把这个数据结构放入一个没有使用key的HashTable里,该函数返回的值,也就是所谓“资源类型id”,其实就是HashTable的index。

1.2.1 注册资源

    注册完资源类型后,就可以注册一个该类型的资源了:

ZEND_REGISTER_RESOURCE(
rsrc_result,
rsrc_pointer,
rsrc_type)

    src_pointer是个指针类型,就是你的资源的handle, 通常是指向内部数据的指针,当然也可以是index或者其它标志符;rsrc_type是上面获取的资源类型id;rsrc_result是个已有的zval,注册完成后,资源的id就被放入该zval,同时其type也被设为IS_RESOURCE,通常是传入return_value,以将资源返回给用户。

    在内部,Zend使用如下数据结构表示一个资源:

typedef struct _zend_rsrc_list_entry {
	void *ptr;
	int type;
	int refcount;
} zend_rsrc_list_entry;

    ptr和type就是我们在上面传入的参数;refcount是引用计数,由Zend维护,当引用减到0时,Zend会销毁该资源。不出所料的是,这个数据结构也被组织在一个HashTable里,并且没有使用key,仅仅使用index――这就是zval里存放的东西。现在资源的整个脉络已经清晰:通过zval可以获得资源id,通过资源id可以获得资源handle和资源类型id,通过资源类型id可以获得资源的销毁函数。

     现在讲一下销毁函数:

typedef void (*rsrc_dtor_func_t)(
zend_rsrc_list_entry *rsrc
TSRMLS_DC);

    rsrc是需要被销毁的资源,我们在函数的实现中可以通过它获得资源的handle,并且加以处理,比如释放内存块、关闭数据库连接或是关闭文件描述符等。

1.2.3 获取资源

    当创建了资源后,用户通常都要调用创建者提供的函数来操作资源,此时我们需要从用户传入的zval中取出资源:

ZEND_FETCH_RESOURCE(
rsrc,  rsrc_type,
passed_id, default_id,
resource_type_name, resource_type)

    首个参数用于接收handle值,第二个参数是handle值的类型,这个函数会扩展成“rsrc = (rsrc_type) zend_fetch_resource(…)”,因此应该保证rsrc是rsrc_type类型的;passed_id是用户传入的zval,这里使用zval**类型,函数从中取得资源id;default_id用来直接指定资源id,如果该值不是-1,则使用它,并且忽略passed_id,所以通常应该使用-1;resource_type_name是资源名称,当获取资源失败时,函数使用它来输出错误信息;resource_type是资源类型,如果取得的资源不是该类型的,则函数返回NULL,这用于防止用户传入一个其他类型资源的zval。

    不过,这个宏确实比较难用,用其底层的宏反倒更加容易些:

zend_list_find(id, type)

    id是要查找的资源id;type是int*类型,用于接收取出的资源的类型,可以用它来判断这是不是我们想要的资源;函数最后返回资源的handle,失败返回NULL。

1.2.4 维护引用计数

    通常,当用户对资源类型的PHP变量执行赋值或是unset之类操作时,Zend会自动维护资源的引用计数。但有时,我们也需要手动进行,比如我们要复用一个数据库连接或者用户调用我们提供的close操作关闭一个文件,此时可以使用如下宏:

zend_list_addref(id)
zend_list_delete(id)

    id是资源id,这两个宏分别增加和减少目标资源的引用计数,第二个宏还会在引用计数减到0时,调用先前注册的函数销毁资源。

建议继续学习

  1. linux内核研究笔记(一)内存管理 – page介绍 (阅读 10,322)
  2. chrome扩展应用开发教程之开发chrome应用基础 (阅读 5,602)
  3. PHP内核介绍及扩展开发指南―Extensions 的编写 (阅读 5,422)
  4. Linux内核协议栈对于timewait状态的处理 (阅读 4,763)
  5. PHP内核介绍及扩展开发指南―基础知识 (阅读 4,702)
  6. 我的内核配置文件 (阅读 4,681)
  7. chrome扩展应用开发教程之调试和打包上线 (阅读 4,422)
  8. 深入理解PHP原理之扩展载入过程 (阅读 4,381)
  9. Mediawiki扩展编写实战 (阅读 4,381)
  10. 编写python的C语言扩展 (阅读 4,322)