PHP扩展开发进阶

​作者:wf (360电商技术)

在第一期PHP扩展开发入门中,简单的介绍了PHP的总体架构和执行机制,并具体说明了怎样开发和编译一个主要的PHP扩展,最后在PHP 5.3的环境下结合zend api高速编写了一个静态的PHP扩展.

然而仅仅编译一个PHP扩展是没有实际用途的,它仅仅是一个华丽的外壳,为了使扩展实现更强大的功能,须要在扩展中开发一些有用的功能函数.在这一章中,将会着重介绍PHP内核中变量的实现.在此基础上,才干将须要的功能,使用zend api在PHP扩展中实现.

1 PHP变量的实现

1.1变量的类型

PHP内核中通过zval结构体来存储变量,定义在Zend/zend.h文件中,仅仅有四个成员:

struct _zval_struct {

zvalue_value value; /* 变量的值 */

zend_uint refcount__gc;

zend_uchar type;    /* 变量当前的数据类型 */

zend_uchar is_ref__gc;

};

typedef struct _zval_struct zval;

//在Zend/zend_types.h里定义的:

typedef unsigned int zend_uint;

typedef unsigned char zend_uchar;

zval里的refcout__gc是zend_uint类型,也就是unsigned int型,is_ref__gc和type则是unsigned char型的.

保存变量值的value则是zvalue_value类型(PHP5),它是一个union结构体,能够节约存醋空间,相同定义在了Zend/zend.h文件中:

typedef union _zvalue_value {

long lval;  /* long value */

double dval;    /* double value */

struct {

char *val;

int len;

} str;

HashTable *ht;  /* hash table value */

zend_object_value obj;

} zvalue_value;

在以上基础上,PHP语言实现了8种数据类型,这些数据类型在内核中的分别相应于特定的常量,它们各自是:

IS_NULL 第一次使用的变量假设没有初始化过,则会自己主动的被赋予这个常量.

IS_BOOL布尔变量,有两个值,true或者false.

IS_LONG整型变量,在内核中是通过所在操作系统的signed long数据类型来表示.

IS_DOUBLE浮点变量,通过C语言中的signed double型变量来存储的.

IS_STRING字符串,PHP最经常使用的数据类型在内存中的存储和C语言几乎相同, 在这个变量的zval实现里会保存着指向这块内存的指针.与C不同的是,PHP内核还同一时候在zval结构里保存着这个字符串的实际长度, 这个设计使PHP能够在字符串中嵌入‘\0’字符,也使PHP的字符串是二进制安全的, 能够安全的存储二进制数据.

IS_ARRAY数组,用来存储复合数据的.在C语言中,一个数组仅仅能承载一种类型的数据,而PHP语言中的数组则灵活的多, 它能够承载随意类型的数据,这一切都是HashTable的功劳, 每一个HashTable中的元素都有两部分组成:索引与值, 每一个元素的值都是一个独立的zval.

IS_OBJECT对象,用来存储复合数据的,可是与数组不同的是, 对象还须要保存以下信息:方法,訪问权限,类常量以及其他的处理逻辑.

IS_RESOURCE有一些数据的内容无法直接呈现给PHP用户的, 比方与某台mysqlserver的链接.但用户还须要这类数据,因此PHP中提供了一种名为Resource(资源)的数据类型.

zval结构体里的type成员的值便是以上某个IS_*常量之中的一个.内核通过检測变量的这个成员值来知道他是什么类型的数据并做相应的兴许处理.

假设要检測一个变量的类型,zend头文件中定义了大量的宏,供检測和操作变量使用, 使用这些宏不但让的程序更易读,还具有更好的兼容性.

以_P一个p结尾的宏的參数大多是*zval型变量.此外获取变量类型的宏还有两个,各自是Z_TYPE和Z_TYPE_PP,前者的參数是zval型,而后者的參数则是**zval.

PHP内核例如以下实现gettype这个函数了:

//開始定义PHP语言中的函数gettype

PHP_FUNCTION(gettype)

{

//这个arg间接指向就是传给gettype函数的參数.是一个zval**结构

//所以要对他使用__PP后缀的宏.

zval **arg;

//这个if的操作主要是让arg指向參数~

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &arg) == FAILURE) return;

//调用Z_TYPE_PP宏来获取arg指向zval的类型.

//然后是一个switch结构,RETVAL_STRING宏代表这gettype函数返回的字符串类型的值

switch (Z_TYPE_PP(arg)) {

case IS_NULL:

RETVAL_STRING("NULL", 1);

break;

case IS_BOOL:

RETVAL_STRING("boolean", 1);

break;

case IS_LONG:

RETVAL_STRING("integer", 1);

break;

case IS_DOUBLE:

RETVAL_STRING("double", 1);

break;

case IS_STRING:

RETVAL_STRING("string", 1);

break;

case IS_ARRAY:

RETVAL_STRING("array", 1);

break;

case IS_OBJECT:

RETVAL_STRING("object", 1);

break;

case IS_RESOURCE:

{

char *type_name;

type_name = zend_rsrc_list_get_rsrc_type(Z_LVAL_PP(arg) TSRMLS_CC);

if (type_name) {

RETVAL_STRING("resource", 1);

break;

}

}

default:

RETVAL_STRING("unknown type", 1);

}

}

以上三个宏的定义在Zend/zend_operators.h里,定义各自是:

#define Z_TYPE(zval)        (zval).type

#define Z_TYPE_P(zval_p)    Z_TYPE(*zval_p)

#define Z_TYPE_PP(zval_pp)  Z_TYPE(**zval_pp)

1.2 变量的值

内核中针对具体的数据类型分别定义了相应的宏.

对IS_BOOL型的BVAL组合(Z_BVAL、Z_BVAL_P、Z_BVAL_PP).

对IS_DOUBLE的DVAL组合(Z_DVAL、ZDVAL_P、ZDVAL_PP)等等.

string型变量比較特殊,由于内核在保存String型变量时,不仅保存了字符串的值,还保存了它的长度, 所以它有相应的两种宏组合STRVAL和STRLEN,即:Z_STRVAL、Z_STRVAL_P、Z_STRVAL_PP与Z_STRLEN、Z_STRLEN_P、Z_STRLEN_PP.前一种宏返回的是char *型,即字符串的地址;后一种返回的是int型,即字符串的长度.

Array型变量的值事实上是存储在C语言实现的HashTable中的, 能够用ARRVAL组合宏(Z_ARRVAL, Z_ARRVAL_P, Z_ARRVAL_PP)这三个宏来訪问数组的值.

对,不仅存储属性的定义和属性的值,还存储着訪问权限和方法等信息.内核中定义了以下组合宏让方便的操作对象.OBJ_HANDLE:返回handle标识符.OBJ_HT:handle表.OBJCE:类定义.OBJPROP:HashTable的属性.OBJ_HANDLER:在OBJ_HT中操作一个特殊的handler方法.

资源型变量的值事实上就是一个整数,能够用RESVAL组合宏来訪问它,把它的值传给zend_fetch_resource函数,便能够得到这个资源的操作句柄,如mysql的链接句柄等.

有关值操作的宏都定义在./Zend/zend_operators.h文件中.

1.3创建PHP变量

在编写代码的时候,通过在内核中创建zval变量能够让用户在PHP语言里以变量的形式使用.最easy想到的办法便是创建一个zval指针, 然后申请一块内存并让指针指向它.内核给提供了相应的宏来处理这件事,这个宏的是:MAKE_STD_ZVAL(pzv).这个宏会用内核的方式来申请一块内存并将其地址付给pzv, 并初始化它的refcount和is_ref两个属性,更棒的是,它不但会自己主动的处理内存不足问题, 还会在内存中选个最优的位置来申请.

除了MAKE_STD_ZVAL()宏函数,ALLOC_INIT_ZVAL()宏函数也是用来干这件事的, 唯一的不同便是它会将pzv所指的zval的类型设置为IS_NULL;

申请完空间后,便能够给这个zval赋值了.基于已经介绍的宏, 或许须要Z_TYPE_P(p) = IS_NULL来设置其是null类型,并过Z_SOMEVAL形式的宏来为它赋值, 可是内核中提供一些宏来简化的操作,能够仅仅用一步便设置好zval的类型和值.

ZVAL_NULL(pvz);(IS_NULL型不用赋值,由于这个类型仅仅有一个值就是null)

ZVAL_BOOL(pzv, b);(将pzv所指的zval设置为IS_BOOL类型,值是b)

ZVAL_TRUE(pzv);(将pzv所指的zval设置为IS_BOOL类型,值是true)

ZVAL_FALSE(pzv);(将pzv所指的zval设置为IS_BOOL类型,值是false)

ZVAL_LONG(pzv, l);(将pzv所指的zval设置为IS_LONG类型,值是l)

ZVAL_DOUBLE(pzv, d);(将pzv所指的zval设置为IS_DOUBLE类型,值是d)

ZVAL_STRINGL(pzv,str,len,dup);str和len两个參数非常好理解,由于内核中保存了字符串的地址和它的长度, 后面的dup的意思事实上非常easy,它指明了该字符串是否须要被复制.值为1 将先申请一块新内存并赋值该字符串,然后把新内存的地址复制给pzv, 为0时则是直接把str的地址赋值给zval.

ZVAL_RESOURCE约等于ZVAL_LONG,PHP中的资源类型的值事实上就是一个整数,所以ZVAL_RESOURCE和ZVAL_LONG的工作几乎相同, 仅仅只是它会把zval的类型设置为 IS_RESOURCE.

1.4变量的存储方式

当用户在PHP中定义了一个变量,内核会自己主动的把它的信息储存到一个用HashTable实现的符号表里.全局作用域的符号表是在调用扩展的RINIT方法(一般都是MINIT方法里)前创建的,并在RSHUTDOWN方法执行后自己主动销毁.

当用户在PHP中调用一个函数或者类的方法时,内核会创建一个新的符号表并激活之, 这也就是为什么无法在函数中使用在函数外定义的变量的原因 (由于它们分属两个符号表,一个当前作用域的,一个全局作用域的).假设不是在一个函数里,则全局作用域的符号表处于激活状态.

在Zend/zend_globals.h文件中定义了_zend_execution_globals结构体.

struct _zend_executor_globals

{...

HashTable symbol_table;

HashTable *active_symbol_table;

};

当中的 symbol_table元素能够通过EG宏来訪问,它代表着PHP的全局变量,如$GLOBALS,它是EG(symbol_table)的一层封装.与之相应,以下的active_symbol_table元素也能够通过EG(active_symbol_table)的方法来訪问,它代表的是处于当前作用域的变量符号表.

事实上这两个成员在_zend_executor_globals里尽管都代表HashTable, 但一个是真正的HashTable,而还有一个是一个指针.当在对HashTable进行操作的时候,往往是把它的地址传递给一些函数.假设要对EG(symbol_table)的结果进行操作,往往须要对它进行求址操作然后用它的地址作为被调用函数的參数.

<?php

$foo = 'bar';

?>

上面是一段PHP语言的样例,创建了一个变量,并把它的值设置为’bar’,在以后的代码中便能够使用$foo变量.相同的功能在内核中通过一下实现:

{

zval *fooval;

MAKE_STD_ZVAL(fooval);

ZVAL_STRING(fooval, "bar", 1);

ZEND_SET_SYMBOL( EG(active_symbol_table) ,  "foo" , fooval);

}

首先声明一个zval指针,并申请一块内存.然后通过ZVAL_STRING宏将值设置为’bar’,最后ZEND_SET_SYMBO的作用就是将这个zval增加到当前的符号表里去,并将其label定义成foo,这样就能够在PHP代码里通过$foo来使用它了.

1.4变量的检索

在PHP内核中能够通过zend_hash_find()函数来找到当前某个作用域下用户已经定义好的变量,它是内核提供的操作HashTable的API之中的一个.

{

zval **fooval;

if (zend_hash_find(

EG(active_symbol_table), //这个參数是地址,假设操作全局作用域,则须要&EG(symbol_table)

"foo",

sizeof("foo"),

(void**)&fooval

) == SUCCESS

)

{

php_printf("成功发现$foo!");

}

else

{

php_printf("当前作用域下无法发现$foo.");

}

}

首先定义了一个指向指针的指针,然后通过zend_hash_find去EG(active_symbol_table)作用域下寻找名称为foo($foo)的变量, 假设成功找到,此函数将返回SUCCESS.

内核定义HashTable这个结构,并非单单用来储存PHP语言里的变量的, 其他非常多地方都在应用HashTable.一个HashTable有非常多元素,在内核里叫做bucket.然而每一个bucket的大小是固定的, 所以假设想在bucket里存储随意数据时,最好的办法便是申请一块内存保存数据, 然后在bucket里保存它的指针.以zval *foo为例, 内核会先申请一块足够保存指针内存来保存foo,比方这块内存的地址是p,也就是p=&foo, 并在bucket里保存p,这时便明确了,p事实上就是zval**类型的.

假设zend_hash_find()函数找到了须要的数据,它将返回SUCCESS常量, 并把它的地址赋给在调用zend_hash_find()函数传递的fooval參数, 也就是说此时fooval就指向了要找的数据.假设没有找到,那它不会对fooval參数做不论什么改动,并返回FAILURE常量.

就去符号表里找变量而言,SUCCESS和FAILURE仅代表这个变量是否存在而已.

1.6类型转换

如今已经能够从符号表中获取用户在PHP语言里定义的变量了,也就能够对变量进行类型转换了.C语言中的类型转换细则,让人非常头疼.可是变量的类型转换就是如此重要,假设没有,那的代码就会是以下这样了:

void display_zval(zval *value)

{

switch (Z_TYPE_P(value)) {

case IS_NULL:

/* 假设是NULL,则不输出不论什么东西 */

break;

case IS_BOOL:

/* 假设是bool类型,而且true,则输出1,否则什么也不干 */

if (Z_BVAL_P(value)) {

php_printf("1");

}

break;

case IS_LONG:

/* 假设是long整型,则输出数字形式 */

php_printf("%ld", Z_LVAL_P(value));

break;

case IS_DOUBLE:

/* 假设是double型,则输出浮点数 */

php_printf("%f", Z_DVAL_P(value));

break;

case IS_STRING:

/* 假设是string型,则二进制安全的输出这个字符串 */

PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value));

break;

case IS_RESOURCE:

/* 假设是资源,则输出Resource #10 格式的东东 */

php_printf("Resource #%ld", Z_RESVAL_P(value));

break;

case IS_ARRAY:

/* 假设是Array,则输出Array5个字母!

*/

php_printf("Array");

break;

case IS_OBJECT:

php_printf("Object");

break;

default:

/* Should never happen in practice,

* but it's dangerous to make assumptions

*/

php_printf("Unknown");

break;

}

}

上面的代码和直接<?php echo $foo;?>这个简单到极点的php语句来比,实在是太复杂了.为此内核中提供了非常多专门的函数来帮助实现类型转换,这一类函数有一个统一的形式convert_to_*()

//将随意类型的zval转换成字符串

void change_zval_to_string(zval *value)

{

convert_to_string(value);

}

//其他主要的类型转换函数

ZEND_API void convert_to_long(zval *op);

ZEND_API void convert_to_double(zval *op);

ZEND_API void convert_to_null(zval *op);

ZEND_API void convert_to_boolean(zval *op);

ZEND_API void convert_to_array(zval *op);

ZEND_API void convert_to_object(zval *op);

ZEND_API void _convert_to_string(zval *op ZEND_FILE_LINE_DC);

#define convert_to_string(op) if ((op)->type != IS_STRING) { _convert_to_string((op) ZEND_FILE_LINE_CC); }

convert_to_string是一个宏函数,调用了另外一个函数.

另外没有convert_to_resource()的转换函数,由于资源的值在用户层面上,根本就没有意义,内核不会对它的值(不是指那个数字)进行转换.

PHP的echo的时候会先把变量转换成字符串,而convert_to_string的參数是zval*的,可是内核在进行数据转换时破坏了原来数据的值.这里就涉及到PHP内核的内存管理和引用计数了.

-------------------------------------------------------------------------------------

黑夜路人,一个关注开源技术、乐于学习、喜欢分享的程序猿

博客:http://blog.csdn.net/heiyeshuwu

微博:http://weibo.com/heiyeluren

微信:heiyeluren2012

想获取很多其他IT开源技术相关信息。欢迎关注微信!

微信二维码扫描高速关注本号码:

tp=webp&wxfrom=5" style="max-width: 100%; height: auto !important; margin: 0px; padding: 0px; box-sizing: border-box !important; word-wrap: break-word !important; width: auto !important; visibility: visible !important;" alt="" />

【原创】PHP扩展开发进阶的更多相关文章

  1. 【原创】PHP扩展开发入门

    PHP扩展开发入门 作者:wf (360电商技术组) 在我们编写自己的第一个php扩展之前,先了解一下php的总体架构和执行机制. php的架构如图1所看到的. 当中一个重要的就是SAPI(serve ...

  2. iOS原生地图开发进阶——使用导航和附近兴趣点检索

    iOS原生地图开发进阶——使用导航和附近兴趣点检索 iOS中的mapKit框架对国际化的支持非常出色.在前些篇博客中,对这个地图框架的基础用法和标注与覆盖物的添加进行了详细的介绍,这篇博客将介绍两个更 ...

  3. firefox 扩展开发笔记(三):高级ui交互编程

    firefox 扩展开发笔记(三):高级ui交互编程 前言 前两篇链接 1:firefox 扩展开发笔记(一):jpm 使用实践以及调试 2:firefox 扩展开发笔记(二):进阶开发之移动设备模拟 ...

  4. 处女作《Web全栈开发进阶之路》出版了!

    书中源码下载地址:https://github.com/qinggee/WebAdvanced 01. 当初决定写博客的原因非常的纯洁:只要每个月写上 4 篇以上博客,月底的绩效奖金就多 500 块. ...

  5. [转]抢先Mark!微信公众平台开发进阶篇资源集锦

    FROM : http://www.csdn.net/article/2014-08-01/2820986 由CSDN和<程序员>杂志联合主办的 2014年微信开发者大会 将于8月23日在 ...

  6. 4-MySQL DBA笔记-开发进阶

    第4章 开发进阶 本章将介绍一些重中之重的数据库开发知识.在数据库表设计中,范式设计是非常重要的基础理论,因此本章把它放在最前面进行讲解,而这其中又会涉及另一个重要的概念——反范式设计.接下来会讲述M ...

  7. iOS开发系列--App扩展开发

    概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...

  8. PHP 原创视频教程-网站开发新手视频教程

    PHP 原创视频教程-网站开发新手视频教程 有偿招徒弟,,视频免费提供. 本视频教程,面向的是毫无经验的新手,快速上手的. 第一次做视频做的不好的,请各位看官多多包含. 第一部分,HTML 视频教程 ...

  9. PHP 扩展开发(将自己的一些代码封装成PHP扩展函数)

    今天时间不多,先给个地址,能搜到我这篇blog的朋友先看看我最近在看的一些文章.资料吧: 我的环境是 lnmp1.1 的 (LNMP一键安装包),所以要进行PHP扩展开发首先应该对环境配置和shell ...

随机推荐

  1. cogs 1143. [石门中学2009] 切割树

    1143. [石门中学2009] 切割树 ★   输入文件:treecut.in   输出文件:treecut.out   简单对比时间限制:1 s   内存限制:128 MB treecut 题目描 ...

  2. HDU 4394 BFS

    M2%10x=N (x=0,1,2,3....) 给出N.找到最小的满足条件的M 因为:N的个位仅仅由M的个位决定.N十位由M的个位和十位决定,N的百位由M的个位十位百位决定.以此类推 全部从个位開始 ...

  3. 在Eclipse中创建Maven多模块项目

    在Eclipse中创建Maven多模块项目1,创建多模块项目选择File>New>Project,打开New Project窗口,选择Maven>Maven Project,选择下一 ...

  4. JavaScript 中对变量和函数声明提前的演示样例

    如题所看到的,看以下的演示样例(能够使用Chrome浏览器,然后F12/或者右键,审查元素.调出开发人员工具,进入控制台console输入)(使用技巧: 控制台输入时Shift+Enter能够中途代码 ...

  5. 为代码减负之&lt;三&gt;视图(SQL)

    在设计数据库时为了降低数据冗余.一般都会依照三范式去设计,但有时我们在查询时须要通过一字段获取跟这 个字段相关联的好几个字段.可是他们又分布在不同的表中,这时候假设依照正常途径走的话须要同一时候查询好 ...

  6. jetty服务器数据源配置JNDI-Oracle,MySQL,SQLServer,DB2等 (转)

    下载jetty 下载jetty服务器(8.1.0.RC2),解压到任意目录下 http://dist.codehaus.org/jetty/jetty-hightide-8.1.0/jetty-hig ...

  7. git服务器搭建-gitosis

    需求 硬件需求:一台Ubuntu或者debian电脑(虚拟机),能通过网络访问到. 软件需求:git-core, gitosis, openssh-server, openssh-client, Ap ...

  8. Spinner与适配器模式总结

    今天开始编辑我的第一篇博客. ------------------------------------------------------------------------------------- ...

  9. 「JavaSE 重新出发」05.02 泛型数组列表、包装类

    泛型数组列表 ArrayList 是一个采用类型参数(type parameter)的泛型类(generic class). java ArrayList<Employee> staff ...

  10. JS 有一张0.0001米的纸,对折多少次可以达到珠穆朗玛峰的高度8848;

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...