Tag Archives: 扩展

php扩展开发学习笔记 8

hash

  在php中,许多东西都是hash表。除了显而易见的数组以外,其实对象的属性,函数入口表、变量的符号表等在php内部也都是使用hash表来保存的。事实上,hash表里可以放任何一种数据类型的指针,并不限于zval*。

  hash表的C结构叫HashTable。如果要创建一个HashTable,一般的做法如下:
HashTable *ht;
ALLOC_HASHTABLE(ht);
这样就会创建一个HashTable,并把地址放在ht里。光有一个HashTable结构还是不行的,还需要初始化。这就需要用到zend_hash_init函数。声明如下:
int zend_hash_init(
    HashTable *ht,
    uint nSize,
    hash_func_t pHashFunction,
    dtor_func_t pDestructor,
    zend_bool persistent
)
第一个参数就是一个HashTable指针,必须是已经分配好内存的。
nSize是初始大小,就是存放东西的个数。超过这个值时会自动扩展。如果这个值不是2的n次方,则会自动变成大于它的最小的2的n次方数。
pHashFunction是没用的,但为了向下兼容,这个参数还在那里。必须为NULL。
pDestructor是析构函数。一般使用ZVAL_PTR_DTOR。
最后那个参数persistent是表示是用emalloc还是pemalloc来分配内存。

  php的hash表可以有整数和字符串两种键,然后就有了两套访问hash表的函数。hash相关函数都以zend_hash_开头。例如:
int zend_hash_update(HashTable *ht, char *arKey, uint nKeyLen,  void *pData, uint nDataSize, void **pDest);
用来更新字符串键的值。arKey是键;nKenLen是键的长度,包括最后的’’;pData是值数据的指针,nDataSize是数据指针的大小。最后一个参数是用来指定值数据指针的保存位置的,对于更新操作一般用NULL即可而对于读取操作,则用来放取出的值的指针。一个调用的例子:
zend_hash_update(pHash, “hello”, sizeof(“hello”), &pZval, sizeof(zval*), NULL);
其他几个hash操作函数也大同小异。
zend_hash_index_update用来更新整数键的值,zend_hash_find和zend_hash_index_find分别用来读取字符串键和整数键所对应的值;zend_hash_exists和zend_hash_index_exists用来判断指定的键是否存在;zend_hash_del和zend_hash_index_del用于删除。还有许多其他的hash操作函数。zend_hash_num_elements用于得到元素的个数;zend_hash_clean用于清除全部元素;zend_hash_destroy除了清除全部元素外,还会释放由zend_hash_init所分配的内存,彻底销毁hash表。

 

php扩展开发学习笔记 7

引用计数

  php采用的内存管理和垃圾回收方法是基于引用计数的。之前说过,在zval结构里有一个refcount是表示引用计数,还有一个is_ref表示是否是个引用变量。那么php代码的实际运行中,又是如何处理的呢?

  比如这样的php代码:
$a = “hello”;
$b = $a;
这时候并不像很多人认为的那样,在内存里把”hello”这个字符串复制了一份,而只是把$b指向了和$a对应的同一个zval,然后把那个zval的refcount + 1。这样避免了一次内存拷贝。但如果在这之后改变了其中一个变量的值,比如$b.= ” world”;又会如何呢?这时候才会分配一个新的zval给$b,然后把原先那个zval的refcount – 1。这就是传说中的copy on write。就是说,在改变值得时候才会有内存拷贝。

  那么引用变量又会如何呢? 比如
$a = “hello”;
$b = &$a;
和前面一样,$a, $b还是指向同一个zval。只是还要把这个zval的is_ref置为1。之后再改变$a或者$b的时候就不会再发生拷贝。那么
$a = “hello”;
$b = &$a;
$c = $a;
这时又会如何呢?因为$c并不是一个引用变量,因此不能和$a, $b共用一个zval。因此在$c = $a的时候会直接产生一个新的zval。

  因此,在php中,使用引用对改善性能并不会有多少作用,通常情况下还会使情况更糟。所以,引用还是只在真正需要的时候才用为好。

  再说说垃圾回收。每个zval都有一个refcount表示它的变量的引用数。不管对于普通变量还是引用变量都是如此。refcount的初始值一般为1。每当增加一个引用时就+1,减少一个引用,比如unset时就会-1。当refcount为0的时候,php就会把它释放掉。这就是基于引用计数的垃圾回收方法。

使用zval

初始化zval
MAKE_STD_ZVAL(zval*);
这个宏的左右是创建一个zval,完成初始化(如将ref_count置为1,isref置为false)并把指针赋给参数。

赋值
写扩展的时候不可避免的要用到把一个zval复制到另一个zval,就是类似$a = $b;的操作。对于简单的值或许手动维护引用计数之类的还不算很麻烦但对于数组,对象之类的就需要一层层递归进去,因此就有了一个zval_copy_ctor来做着件事情。
原有一个zval* p_zval_b,
zval* p_zval_a;
MAKE_STD_ZVAL(p_zval_a); //初始化p_zval_a
*p_zval_a = *p_zval_b;
zval_copy_ctor(p_zval_a);
这里,zval_copy_ctor完成了类似赋值的操作,包括引用计数处理,对于hash值的成员处理等。

 释放一个zval则是使用zval_ptr_dtor(**zval)。注意它的参数。它会释放掉为这个zval所分配的内存。

 

php扩展开发学习笔记 6

内存管理

  在C语言里,分配内存有一组alloc函数,比如malloc、calloc、realloc。php为了更方便,更安全地管理内存,自己提供了一组内存分配的函数:emalloc、ecalloc、erealloc,此外还有estrdup用于拷贝内存,efree用于释放由前面那些函数分配的内存。他们的用法和标准C函数的一样。这组e开头的函数分配的内存空间只存在于一次请求。在请求结束后,php会自动释放掉。这就减少了由于没有释放掉内存而造成的内存泄露的可能。

  如果需要分配的内存在整个进程运行时都保持,而不是在请求处理结束后自动释放,则还有一组pe开头的函数,如pemalloc、pecalloc等。这组函数比标准C函数的多一个参数,即分配的内存是否持久,当这个参数为1时,即表示分配的是持久的内存,不会随着请求结束而自动释放。如:pemalloc(sizeof(long) * 100, 1);。

  此外,为了方便使用,还提供了safe_emalloc和safe_pemalloc这两个函数。 safe_emalloc的原型如下:void *safe_emalloc(size_t size, size_t count, size_t addtl);。参数分别表示单元的大小,单元的个数,以及偏移。实际分配的空间大小就是size * count + addtl。safe_pemalloc与之类似。这两个函数还有一个作用就是避免手工做上面的计算时,可能造成整数溢出,而导致分配的内存大小小于预期或者是个负数。

 

php扩展开发学习笔记 5

  有许多函数在php手册里把参数或返回值类型写为mixed。那么在扩展中是如何处理的呢?所谓mixed往往就是直接解析会返回zval。

  先看参数处理,还是前面用过的zend_parse_parameters:
zval* item;
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “z”, &item);
这很简单。

  然后看返回值,返回zval可以用RETURN_ZVAL宏。和前面的RETURN_LONG之类不同的是,这个宏有3个参数。第一个参数就是一个zval*。第二个和第三个参数都是0、1值。第二个参数是表示是否在返回前复制该值;第三个参数是表示在返回前是否调用析构函数。通常的用法是RETURN_ZVAL(p_zval, 1, 0);。为什么一般要把复制zval设为1呢?这和php的内存管理有关,关于内存管理后面会说。这里大概说下,因为php的垃圾回收是基于引用计数的。如果不复制一份,在函数结束后,因为返回值也是一个zval的指针,结束后php会把这个zval的引用计数-1,在一般情况下,这就会导致释放掉不该释放掉的内存,造成错误。

 

php扩展开发学习笔记 4

zval和zvalue_value

  之前一直跳过的数组和对象,以及需要返回或者处理多种类型时,都需要了解php内部是如何保存值的。

  php内部保存值的基本单位是zval。它的C结构定义如下:

typedef struct _zval_struct {
    zvalue_value value;
    zend_uint refcount;
    zend_uchar type;
    zend_uchar is_ref;
} zval;

其中is_ref表示这个值是否是一个引用。
type表示值类型:有IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT、IS_RESOURCE这8种。
refcount表示引用计数。
value存放这个zval的值。

  在判断一个zval的值时,可以用如下的代码:

zval *a;

if (Z_TYPE_P(a) == IS_LONG){
    …
}

  zvalue_value中保存了zval的值,它的C结构定义如下:

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
} zvalue_value;

  这是一个union型,其中的lval、dval、str、ht、obj分别表示整数、浮点、字符串、数组、对象。

  取
得一个zval的值当然也可以直接从它的value里直接取,不过更好的办法是通过一套宏来获取。Z_BVAL、Z_LVAL_P、Z_DVAL_PP这
样的形式。都以Z_开头,后面是类型,B就是boolean,L就是long,D就是double。后面可选的_P,_PP表示指针的层次。带_P是用来
取zval*的,带_PP是用来去zval**的。这些宏返回的就是这个zval的值所对应的C类型。对于字符串,则有两组宏Z_STRVAL、
Z_STRLEN。后面当然也可以加_P和_PP。分别返回char*和字符串的长度。这些宏使用很简单,例如:

zval* a;

long n;
n = Z_LVAL_P(a);

  前面用到的Z_TYPE_P其实也是一个类似的宏,用于取得zval*的类型

php扩展开发学习笔记 3

取得参数

  既然是函数总要能获取参数。在php扩展里能通过zend_parse_parameters()将参数解析为一些C的对应类型。


long foo;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “l”, &foo) == FAILURE) {
RETURN_NULL();
}
会试图解析参数为整数。将值放入foo。失败的时候,比如没有参数,或参数不能解析为整数时返回FAILURE。

这里的”l”表示整数。其他如”b”,”d”,”s”分别表示布尔,浮点和字符串。资源数组对象等以后再说。

其中,布尔对应的C类型是zend_bool。其实就是0,1。浮点对应的是double。

对于字符串,则需要传入两个参数,分别用于存放字符串的值和长度。如
char *name;
int name_len;
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “s”,
&name, &name_len);
此处由zend_parse_parameters分配的内存不需要手动释放。

如果需要解析多个参数,可以如下面的例子:

long foo;
char *name;
int name_len;
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “sl”,
&name, &name_len, &foo);

 

 

php扩展开发学习笔记 2

函数返回值

  php的api里定义了很多宏来实现扩展里函数的返回值。RETURN_BOOL、RETURN_LONG、RETURN_DOUBLE这3个宏分别用于返回对应的值。只在参数里填入需要返回的值即可。如RETURN_LONG(1024);。对于string,则有RETURN_STRING和RETURN_STRINGL。其中后者可以指定字符串的长度,前者只是以作为结束标志。第二个参数表示是否复制字符串的值。如果char数组是在栈空间内分配的(如作为局部变量的字符串常量)就需要指定为1。

  实际上,php的扩展中,函数的返回值是通过一个return_value的变量来传递的。如果煮一下PHP_FUNCTION所展开的内容的话,会发现return_value实际上是这个函数的一个参数。前面那几个宏所作的事情就是,对return_value赋值然后return。对return_value赋值则还有一组RETVAL_开头的宏。如RETVAL_BOOL等。

 

php扩展开发学习笔记 1

  开发环境设置见《用vc 2008编译php扩展》。

  写一个扩展最基本的就是编写函数。我这里是用skel生成了一个algorithm的扩展的骨架。

  php扩展中的函数用PHP_FUNCTION宏定义。首先在.h文件中写一个定义,如skel生成的代码为例:PHP_FUNCTION(confirm_algorithm_compiled);
然后在.c文件中写函数的实现。
PHP_FUNCTION(confirm_algorithm_compiled)
{
    //…
}
  这和传统的C编程很像。括号内就是函数的名字。这里没有参数列表,函数的参数是通过其他途径获取的。然后,还需要在扩展的函数入口表里添加一条:PHP_FE(confirm_algorithm_compiled, NULL)。这样在php里才能找到这个函数。这里FE应该就是function entry的缩写。

zend_function_entry algorithm_functions[] = {
    PHP_FE(confirm_algorithm_compiled, NULL)
    {NULL, NULL, NULL} /* Must be the last line in algorithm_functions[] */
};

  这里的{NULL, NULL, NULL}的作用如skel生成的代码中的注释所说,是函数入口表的结束标志。