【原创】PHP扩展开发进阶

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开源技术相关信息。欢迎关注微信!

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

时间: 2024-10-16 04:21:16

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

【原创】PHP扩展开发入门

PHP扩展开发入门 作者:wf (360电商技术组) 在我们编写自己的第一个php扩展之前,先了解一下php的总体架构和执行机制. php的架构如图1所看到的. 当中一个重要的就是SAPI(server端应用编程端口),它使得PHP能够和其它应用进行数据交互,把外部错综复杂的外部环境进行抽象化,为内部的php提供一套固定和统一的接口.使得php自身不受外部影响,保持一定的独立性.常见的SAPI有CGI.FastCGI.Shell的CLI,apache的mod_php5,IIS的ISAPI. 另外

《VTK图形图像开发进阶》出版啦!!!

承蒙各位同行的支持,我们在CSDN专栏<VTK应用开发>的基础上,前后历时大约2年多时间,整理出版了<VTK图形图像开发进阶>一书,该书由机械工业出版社出版,张晓东.罗火灵 编著,将于近期(预计2015年4月中旬)可以订购啦~~~! 原创的VTK中文教程 从VTK数据结构到开发进阶,深入浅出 通过丰富的实例分析VTK在图形图像处理中的应用 详解Qt与MFC平台下VTK应用程序开发 全面剖析VTK设计框架,便于用户对VTK进行扩展 本书系统地介绍了VTK基础知识与开发技术,帮助VTK

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

iOS原生地图开发进阶——使用导航和附近兴趣点检索 iOS中的mapKit框架对国际化的支持非常出色.在前些篇博客中,对这个地图框架的基础用法和标注与覆盖物的添加进行了详细的介绍,这篇博客将介绍两个更加实用的功能的开发:线路导航与兴趣点搜索.前几篇博客的链接如下: 地图基础用法详解:http://my.oschina.net/u/2340880/blog/415360. 添加大头针与自定义标注:http://my.oschina.net/u/2340880/blog/415441. 添加地图覆盖

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

FROM : http://www.csdn.net/article/2014-08-01/2820986 由CSDN和<程序员>杂志联合主办的 2014年微信开发者大会 将于8月23日在北京举行.作为一线微信开发商云集.专注在开发实践方面的顶级技术活动,演讲话题极为丰富,涵盖了微信开发不同维度的多个层内容 (首批议程发布),包括:企业服务号开发和高级应用.企业号开发.如何与业务系统对接.各种高级接口功能.智能客服与LBS.HTML5社交应用.微信支付.微信电商开发等多方面(查看 参加微信开发

Android应用开发进阶篇-场景文字识别

由于研究生毕业项目需要完成一个基于移动终端的场景文字识别系统,虽然离毕业尚早,但出于兴趣的缘故,近一段抽时间完成了这样一套系统.基本的架构如下: 客户端:Android应用实现拍摄场景图片,大致划出感兴趣文字区域,通过socket通信上传服务器端识别; 服务器端:Python server进行socket通信监听,连通后调用文字识别引擎(exe可执行程序),将识别结果返回; 下面是系统运行示例图: 1. 客户端 包含两个Activity,: MainActivity主界面如上图左1,选择拍摄后调

编程开发进阶更重要的是掌握的核心设计思维[图]

编程开发进阶更重要的是掌握的核心设计思维[图]:"单独写一个琐碎的代码块就等同于弹奏音阶一样,不幸的是,弹奏音阶并不能教会你任何关于音乐的东西,并且非常枯燥"这是 Eric S. Raymond 在他的文章<How to learn Hacking>中所描述的一段话.作为一个已经从入门走到进阶的编程者,我非常赞同他所说的这句话.然而,大部分老师,课本和大学课程都会通过琐碎的代码块练习来教授编程知识.即使这样的编程练习可以让你明白条件语句和循环的工作机制以及如何编写一个基本的

手把手完成商业级社交App开发 进阶Android高级工程师教程

手把手完成商业级社交App开发 进阶Android高级工程师 资源获取链接:点击获取完整教程 最近两周在忙于开发一个社交App,因为之前做过一点儿社交方面的东西,就被拉去做API后端了,一个人头一次完整的去搭这么一套东西,上面也没有PM和各种催促,过程还是很轻松愉快充满乐趣的,现在后端已经基本完成,下周会进入联调测试的阶段,有些东西想写一写记录一下,先从技术选型开始. 基本产品形态 产品的基础功能无非是所有社交App都具备的那些东西,新鲜事.好友关系(同微博一样,单向follow).地理位置(当

Rails 5 开发进阶

Rails 5 开发进阶:https://www.gitbook.com/book/kelby/rails-beginner-s-guide/details cancan : http://blog.xdite.net/posts/2012/07/30/cancan-rule-engine-authorization-based-library-1/ Ruby官方文档翻译(Ruby官方文档中文版) : http://blog.csdn.net/liuk10/article/details/509

php扩展开发

本文的环境是windows下开发php版本5.3 1.下载php5.3的源码包和php5.3的二进制包(平时使用的php程序包) 2.下载cygwin,并默认安装在c:\cygwin,因为生成php扩展框架的程序需要cygwin 3.假设php扩展的工作目录为d:\php,解压源码包和二进制包到这个目录 4.cmd命令行在d:\php\ext目录中运行 php ext_skel_win32.php --extname=yourext (如果php.exe程序的路劲未加入系统环境变量则需要自己指定