把扩展从PHP5升级到PHP-NG版 ( Upgrading PHP extensions from PHP5 to NG ) 译者:花生 (360电商技术组) 许多经常被使用的API 方法改变了,比如 HashTable 的 API ;这篇文章将尽可能多的将这些实际影响扩展和核心代码编写的地方列出来。在读这边文章之前
把扩展从PHP5升级到PHP-NG版
(Upgrading PHP extensions from PHP5 to NG)
译者:花生 (360电商技术组)
许多经常被使用的API方法改变了,比如HashTable的API;这篇文章将尽可能多的将这些实际影响扩展和核心代码编写的地方列出来。在读这边文章之前,强烈建议现将另一篇介绍PHPNG实现的文章phpng-int先好好阅读一下。
这篇文章不可能覆盖所有的情况。在这里将介绍一些被使用的最多最频繁的例子。我希望它能帮助大多数用户级的扩展升级。However if you did not find some information here, found a solution and think it may be useful for others - feel free to add your recipe。
· 尝试在PHPNG中编译你的扩展。查看编译错误和警告。这些信息起码能告诉你75%需要修改的地方。
· 在调试模式下编译和测试扩展(configure PHP with –enable-debug)。运行时使用assert()方法将会捕获一些错误信息。你还可以看到有关内存泄露的信息。
1. Zval:
· PHPNG不再使用zval的二级指针。大多数出现的zval**变量和参数都将改变成zval*。相应的,使用在这些变量上的宏Z_*_PP()也需要变成Z_*_P()。
· 在很多地方,PHPNG是直接使用zval(从而消除了分配与重新分配),在这些情况下,相应的zval*就需要转换成纯zval,相应的宏也需要从Z_*P转换成Z_*(),以及相应的创建宏从ZVAL_*(var,…)转换成ZVAL_*(&var,…)。在传递zval的地址或者使用&操作符的时候要始终小心。PHPNG几乎从来没有要求过传递zval*的地址。在某些地方,需要删除掉&操作符。
· 分配zval内存的宏ALLOC_ZVAL、ALLOC_INIT_ZVAL、MAKE_STD_ZVAL被删掉了。在大都数情况下,它们的使用表明zval*需要被转换成纯zval。宏INIT_PZVAL也被删除了,在大多数使用它的地方只需要删除掉就行了。
·
- zval *zv;
- ALLOC_INIT_ZVAL();
- ZVAL_LONG(zv, 0);
+ zval zv;
+ ZVAL_LONG(&zv, 0);
· zval结构也完全改变了,现在它的定义形式如下:
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* various IS_VAR flags */
} v;
zend_uint type_info;
} u1;
union {
zend_uint var_flags;
zend_uint next; /* hash collision chain */
zend_uint str_offset; /* string offset */
zend_uint cache_slot; /* literal cache slot */
} u2;
};
zend_value的定义如下:
typedef union _zend_value {
long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
} zend_value;
· PHPNG和以前的PHP5.x主要的区别是在处理标量和复杂类型时候的不同。PHP不再在堆上为标量分配空间,而是直接在VM的栈上、HashTable以及对象的内部。引用计数和垃圾回收不再作用于它们。标量没有引用计数器,并且不再支持宏Z_ADDREF*()、Z_DELREF*()、Z_REFCOUNT*()以及Z_SET_REFCOUNT*()。在大多数情况下,你在使用这些宏之前,需要检查zval是否支持它们。否则你会得到一个assert()或者直接崩溃。
- Z_ADDREF_P(zv)
+ if (Z_REFCOUNTED_P(zv)) {Z_ADDREF_P(zv);}
# or equivalently
+ Z_TRY_ADDREF_P(zv);
· zval的值应该使用宏ZVAL_COPY_VALUE()来复制。
· 在必要的时候可以使用宏ZVAL_COPY来复制和递增引用计数器。
· 使用宏ZVAL_DUP()来完成zval的复制(zval_copy_ctor)。
· 如果你将一个zval*转换成zval,并且使用NULL来预定义一个没有定义的值,那么你现在可以使用IS_UNDEF类型来替代前面的工作。你可以使用ZVAL_UNDEF(&zv)来进行设置,使用if (Z_ISUNDEF(zv))来进行检查。
· 如果你仅仅是想获取long/double/string的值而不改变原始的zval,你可以使用zval_get_long(zv)、zval_get_double(zv)以及zval_get_string(zv)- 这些API来简化代码
- zval tmp;
- ZVAL_COPY_VALUE(&tmp, zv);
- zval_copy_ctor(&tmp);
- convert_to_string(&tmp);
- // ...
- zval_dtor(&tmp);
+ zend_string *str = zval_get_string(zv);
+ // ...
+ STR_RELEASE(str);
查看zend_types.h文件可以获得更多更详细的信息:https://github.com/php/php-src/blob/phpng/Zend/zend_types.h
2. References
PHPNG中的zval不再需要is_ref标志。引用使用了一个独立复合的引用计数类型IS_REFERENCE来实现。你也可以一直使用宏Z_ISREF*()来检查一个zval是否为引用类型。其实,它只是检查给出的zval的类型是否为IS_REERENCE。用在is_ref上的宏Z_SET_ISREF*()、Z_UNSET_ISREF*()、Z_SET_ISREF_TO*()全部被删除了。在下面的方式中,他们的使用方法需要改变一下。
- Z_SET_ISREF_P(zv);
+ ZVAL_MAKE_REF(zv);
- Z_UNSET_ISREF_P(zv);
+ if (Z_ISREF_P(zv)) {ZVAL_UNREF(zv);}
之前的引用可以直接检查引用类型。现在我们必须通过宏Z_REFVAL*()间接的检查它。
- if (Z_ISREF_P(zv) && Z_TYPE_P(zv) == IS_ARRAY) {
+ if (Z_ISREF_P(zv) && Z_TYPE_P(Z_REFVAL_P(zv)) == IS_ARRAY) {
或者使用宏ZVAL_DEREF进行手动解引用
- if (Z_ISREF_P(zv)) {...}
- if (Z_TYPE_P(zv) == IS_ARRAY) {
+ if (Z_ISREF_P(zv)) {...}
+ ZVAL_DEREF(zv);
+ if (Z_TYPE_P(zv) == IS_ARRAY) {
3. Booleans
IS_BOOL不再存在,但是IS_TRUE和IS_FALSE分别成了两个类型:
- if ((Z_TYPE_PP(item) == IS_BOOL || Z_TYPE_PP(item) == IS_LONG) && Z_LVAL_PP(item)) {
+ if (Z_TYPE_P(item) == IS_TRUE || (Z_TYPE_P(item) == IS_LONG && Z_LVAL_P(item))) {
宏Z_BVAL*()被移除了。一定要注意,Z_LVAL*()的返回值是IS_FALSE还是IS_TRUE,这个是不确定的。
4. Strings
获取字符串的值或者长度还是使用宏Z_STRVAL*()和Z_STRLEN*()。但是要强调的是,表示字符串的数据结构是zend_sring(将在下面的章节详细描述)。通过宏Z_STR*()来访问zval中的zend_string。也可以通过宏Z_STRHASH*()得到字符串的hash值。
假释代码需要检查给定的字符串是否为常量字符串,现在需要使用zend_string(而不是char*)来完成:
- if (IS_INTERNED(Z_STRVAL_P(zv))) {
+ if (IS_INTERNED(Z_STR_P(zv))) {
创建字符串zval有一点变化。此前,类似ZVAL_STRING()的宏有一个额外的参数告知给定的字符是否需要被复制。现在,这些宏总是会创建zend_string结构,因此,这个参数就变得没用了。但是,如果它的实际值是0,你必须释放掉原始的字符串以防止内存泄露。
- ZVAL_STRING(zv, str, 1);
+ ZVAL_STRING(zv, str);
- ZVAL_STRINGL(zv, str, len, 1);
+ ZVAL_STRINGL(zv, str, len);
- ZVAL_STRING(zv, str, 0);
+ ZVAL_STRING(zv, str);
+ efree(str);
- ZVAL_STRINGL(zv, str, len, 0);
+ ZVAL_STRINGL(zv, str, len);
+ efree(str);
对于类似的宏像RETURN_STRING()、RETVAL_STRINGL()等,以及一些内部API函数也是如此。
- add_assoc_string(zv, key, str, 1);
+ add_assoc_string(zv, key, str);
- add_assoc_string(zv, key, str, 0);
+ add_assoc_string(zv, key, str);
+ efree(str);
直接使用zend_string的api以及直接从zend_string创建zval可以避免双重再分配。
- char * str = estrdup("Hello");
- RETURN_STRING(str);
+ zend_string *str = STR_INIT("Hello", sizeof("Hello")-1, 0);
+ RETURN_STR(str);
Z_STRVAL*()宏现在应该被用于只读对象。不应该再用它分配任何事物。更改独立的字符是可能的,但是在做之前,你需要确定这个字符串不是一个引用(它不是常量并且它的引用计数器为1),在完成了在原来的基础上修改字符串后,你需要重新计算它的hash值。
SEPARATE_ZVAL(zv);
Z_STRVAL_P(zv)[0] = Z_STRVAL_P(zv)[0] + ('A' - 'a');
+ STR_FORGET_HASH_VAL(Z_STR_P(zv))
zend_string API
Zend有一个新的zend_string**API,在zval中**zend_string是字符串实现的底层结构,these structures are also used throughout much of the codebase where char* and int were used before。
zend_string(not IS_STRING zvals)可以使用宏STR_INIT(char *val, int len, int persistent)来创建。通过str>val来访问实际的字符值,通过str>len获得字符串的长度。字符串的hash值使用宏STR_HASH_VAL()来获取。如有必要,它会重新计算hash值。
应该使用宏STR_RELEASE()释放字符串,这并不需要释放内存,因为同样的字符串可能会在其他地方被引用。
如果你要在某个地方保存zend_string的指针,那么你需要递增它的引用计数器或者使用宏STR_COPY(),它可以替你做这件事。在很多代码复制字符只是想保持值(不修改)的地方,可以使用这个宏替代。
- ptr->str = estrndup(Z_STRVAL_P(zv), Z_STRLEN_P(zv));
+ ptr->str = STR_COPY(Z_STR_P(zv));
...
- efree(str);
+ STR_RELEASE(str);
如果被复制的字符串需要做修改,可以使用宏STR_DUP()替代之前的操作:
- zend_string *str = estrndup(Z_STRVAL_P(zv), Z_STRLEN_P(zv));
+ char *str = STR_DUP(Z_STR_P(zv));
...
- efree(str);
+ STR_RELEASE(str);
使用了旧的宏的代码也需要被支持,因此,没必要都切换到新的。
在某些情况下,在不知道实际的字符串值之前分配字符串缓冲区是非常有意义的。你可以使用宏STR_ALLOC()和