PHP扩展开发指南
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 注册资源
注册完资源类型后,就可以注册一个该类型的资源了:
1 |
ZEND_REGISTER_RESOURCE( |
2 |
rsrc_result, |
3 |
rsrc_pointer, |
4 |
rsrc_type) |
src_pointer是个指针类型,就是你的资源的handle, 通常是指向内部数据的指针,当然也可以是index或者其它标志符;rsrc_type是上面获取的资源类型id;rsrc_result是个已有的zval,注册完成后,资源的id就被放入该zval,同时其type也被设为IS_RESOURCE,通常是传入return_value,以将资源返回给用户。
在内部,Zend使用如下数据结构表示一个资源:
1 |
typedef struct _zend_rsrc_list_entry { |
2 |
void *ptr; |
3 |
int type; |
4 |
int refcount; |
5 |
} zend_rsrc_list_entry; |
ptr和type就是我们在上面传入的参数;refcount是引用计数,由Zend维护,当引用减到0时,Zend会销毁该资源。不出所料的是,这个数据结构也被组织在一个HashTable里,并且没有使用key,仅仅使用index——这就是zval里存放的东西。现在资源的整个脉络已经清晰:通过zval可以获得资源id,通过资源id可以获得资源handle和资源类型id,通过资源类型id可以获得资源的销毁函数。 现在讲一下销毁函数:
1 |
typedef void (*rsrc_dtor_func_t)( |
2 |
zend_rsrc_list_entry *rsrc |
3 |
TSRMLS_DC); |
rsrc是需要被销毁的资源,我们在函数的实现中可以通过它获得资源的handle,并且加以处理,比如释放内存块、关闭数据库连接或是关闭文件描述符等。
1.2.3 获取资源
当创建了资源后,用户通常都要调用创建者提供的函数来操作资源,此时我们需要从用户传入的zval中取出资源:
1 |
ZEND_FETCH_RESOURCE( |
2 |
rsrc, rsrc_type, |
3 |
passed_id, default_id, |
4 |
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。
不过,这个宏确实比较难用,用其底层的宏反倒更加容易些:
1 |
zend_list_find(id, type) |
id是要查找的资源id;type是int*类型,用于接收取出的资源的类型,可以用它来判断这是不是我们想要的资源;函数最后返回资源的handle,失败返回NULL。
1.2.4 维护引用计数
通常,当用户对资源类型的PHP变量执行赋值或是unset之类操作时,Zend会自动维护资源的引用计数。但有时,我们也需要手动进行,比如我们要复用一个数据库连接或者用户调用我们提供的close操作关闭一个文件,此时可以使用如下宏:
1 |
zend_list_addref(id) |
2 |
zend_list_delete(id) |
id是资源id,这两个宏分别增加和减少目标资源的引用计数,第二个宏还会在引用计数减到0时,调用先前注册的函数销毁资源。