php ziparc 扩展_怎样编写PHP扩展

什么是扩展?

每个PHP程序员必接触过扩展,PECL库提供超过100多个扩展,比较常用的memcache,apc,mysqli等。在php.ini文件中,extension_dir指示就是扩展路径。

为什么要扩展?

PHP的设计理念,开源语言,方便各个社区自行开发丰富的功能,互不影响,而且与PHP内部无耦合。

有些功能脚本语言无法实现或者实现代价较高,比如常驻内存的应用,以及算法复杂度较高的已有C程序,又不想用PHP重写。

出于效率的考虑。扩展由于实际上执行的是C程序,因此提高效率。

扩展如何执行?

PHP语言本身的结构分成两大部分:ZEND和PHP core。

zend可以比喻成车的引擎,负责php代码的解释和执行。PHP是车的框架,扩展是轮子,必须要依靠相应的扩展才能实现真正的功能。

Apache启动后(apachectl start),将初始化PHP core,然后加载每个扩展代码,并且调用每个扩展的MINIT例程,使得每个 扩展可以初始化内部变量,分配资源,注册资源处理器,向Zend注册自己的函数,以便于PHP代码中调用这些代码的时候可以知道这些扩展代码的位置。之 后,等待到达SAPI(在本文中,指apache)的请求,到达后,PHP要求zend初始化执行环境,PHP调用每个扩展的RINIT函数,使得扩展设 立自己的特定的环境变量,根据请求分配资源,执行其他任务。随后控制权转到Zend。Zend将代码解释执行,如果中间遇到扩展函数,Zend将变量绑定 给扩展,然后控制权转给扩展函数执行。扩展执行完毕以后,PHP调用每个扩展的RSHUTDOWN函数进行清理工作,Zend进行垃圾回收。对扩展期间的 变量进行unset。如果apache关闭,则调用每个扩展的模块关闭函数MSHUTDOWN,最后关闭core。

通 过执行过程,知道了扩展中的几个特殊函数:MINIT,MSHUTDOWN,RINIT,RSHUTDOWN。也知道了Zend既然负责执行PHP代码, 有它自己的内存管理,所以在扩展中一定要注意,应使用zend 或者PHP的API进行内存分配或者字符串拷贝等操作。

以下是Zend的内存分配API,后文会有实例演示:

左 侧是C的内存管理函数,中间和右侧是Zend的API,非持久性资源指页面请求结束后即会释放的资源,持久性资源则是无论页面请求是否结束都一直存在的资 源。必须使用zend的内存管理API,原因是zend依靠给变量打上自己的标记来表示是否需要在页面请求结束后释放,如果使用C的API分配,会导致 zend提前释放,导致crush。

怎样写扩展?

在php的源码下,存在一个ext/ext_skel脚本,该脚本负责生成扩展的框架代码。

假设在linux系统,web server为apache,当前路径下存在php源码

./php-5.2.12/ext/ext_skel –extname helloworld –skel=./php-5.2.12/ext/skeleton/

将在当前路径下创建helloworld目录。

helloworld目录下,存在3个文件:

php_helloworld.h   helloworld.c  helloworld.php

helloworld.php是测试扩展是否可用的PHP代码,与C程序一样,扩展函数的声明在php_helloworld.h,扩展函数的实现helloworld.c中,所以实质上,扩展是C程序,里面使用了大量Zend的宏和API。

运行扩展

在helloworld目录下,运行phpize,检查当前zend版本,PHP API版本,ZEND API版本信息。

随后生成config.m4以及config.w32(windows底下使用),以及configure程序。

打开config.m4,打开

PHP_ARG_ENABLE(helloworld, whether to enable helloworld support

[  --enable-helloworld           Enable helloworld support])

AC_DEFINE(HAVE_HELLOWORLDLIB,1,[Whether you have helloworld])

随后使用./configure –enable-helloworld

此时自动生成makefile,然后make,将在modules目录下存在helloworld.so。

mv helloworld.so到php的扩展路径下,重启apache

运行php helloworld.php,就可以看见:configurations字样,表示扩展已经成功。

扩展语法——数据类型

表 1:类型和用在zend_parse_parameters()中的字母代码

类型

代码

变量类型

Boolean

b

zend_bool

Long

l

long

Double

d

double

String

s

char*, int

Resource

r

zval*

Array

a

zval*

Object

o

zval*

zval

z

zval*

还有NULL,array object都表达成zend内部数据结构zval

扩展语法——基本语句

打开helloworld.c:

#include “php.h” ——每个扩展都必须包含的头文件,里面包含了zend的数据结构以及API定义

#include “php_ini.h” ——如果需要使用php.ini定义的变量,就需要包括

zend_function_entry helloworld_functions[] = {….}——这里声明自己的扩展函数,其中必须以PHP_FE(函数名),最后以{NULL, NULL, NULL}结束,向zend注册扩展函数。实际上PHP_FE宏将自动生成一个这样的函数声明:

void zif_函数名 (INTERNAL_FUNCTION_PARAMETERS),其中INTERNAL_FUNCTION_PARAMETERS是固定的,是zend执 行需要的信息,包括参数的个数,zval * return_value,以及返回的结果变量指针。

zend_module_entry helloworld_module_entry = {…}——这里声明上文提到的扩展的特殊函数,MINIT,MSHUTDONW,RINIT,RSHUTDOWN,注意第三项必须是上面的 helloworld_functions,即zend_function_entry

进入扩展函数本身的实现来看看:

PHP_FUNCTION(confirm_helloworld_compiled) ——每个扩展函数必须以PHP_FUNCTION宏包裹,括号里面是函数名。

char *arg = NULL;

int arg_len, len;

char *strg;

回忆前面提到的扩展类型,其中有一个string类型,对应到扩展里面是char* 和int,含义为如果要在扩展中声明一个字符串,就得声明两个变量,一个是char *指针,初始化必须是空,另外就是int len,即字符串的长度。

来看看,如何从用户的PHP代码中给扩展传递参数,我们知道,PHP是弱类型语言,没有参数的类型概念,但C有,因此需要使用zend的一个非常常用的API:zend_parse_parameters

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “s”, &arg, &arg_len) == FAILURE) {…}——将用户参数转换成相应的zend类型,并将参数的值放入前面声明的char *变量中,看起来很困惑,char *是空的,不要担心,zend分配了内存保存,并且在arg_len中保存了字符串的长度。如果参数的类型和期待的类型不一致,将导致FAILURE返回 值。

len = spprintf(&strg, 0, “Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.”, “helloworld”, arg);——使用了PHP的api spprintf,内部实现其实在zend中会创建缓冲区,建议包括printf这些流处理函数都使用php的api。

RETURN_STRINGL(strg, len, 0);——返回值也不能使用C的return strg语法,必须使用Zend规定的宏返回。

结合前面介绍的扩展基本数据类型,RETURN系列包括RETURN_LONG,RETURN_STRING,RETURN_BOOL,RETURN_NULL,RETURN_DOUBLE。

到这里,其实已经阐述完毕PHP扩展编写的基本要素。总结PHP扩展编写的步骤如下:

在php_helloworld.h中,增加:PHP_FUNCTION(confirm_helloworld_compiled);——声明

在helloworld.c中,增加:

zend_function_entry helloworld_functions[] = {增加一个PHP_FE(confirm_helloworld_compiled)}

增加confirm_helloworld_compiled的实现:PHP_FUNCTION(confirm_helloworld_compiled){}

并且已经介绍了最基本的如何获取参数,如何返回值。

扩展语法——MINIT,MSHUTDOWN,RINIT,RSHUTDOWN

我 们回到前面提过的几个特殊函数,前面说过了这些函数的运行时机。下面展示这些函数的应用场合。可能我们会有这样的应用,只需要在apache启动的时候初 始化一个值,然后所有页面请求共用该变量,或者需要一个页面请求内可见的变量,在一个页面请求内反复调用时该变量的值可以保持。前者需要使用 MINIT,MSHUTDOWN,后者则需要RINIT和RSHUTDOWN。

打开helloworld.c,发现框架代码有这样的代码:

/* {{{ PHP_INI

*/

/* PHP_INI_BEGIN()……PHP_INI_END()*/

只是被注释了!如果我们需要一个所有页面请求都共用的变量,需要将注释去掉。

PHP_INI_BEGIN()

PHP_INI_ENTRY(“helloworld.greeting”, “haha!jean”, PHP_INI_ALL,

NULL)——告诉扩展注册一个php.ini中可以有的变量helloworld.greeting,初始值为haha!jean,如果php.ini

存在该变量,则取php.ini中的值,PHP_INI_ALL表示任何时候PHP代码中可以通过ini_set来改变该变量值。

PHP_INI_END()

生成的扩展框架代码中,天然有PHP_MINIT_FUNCTION(helloworld),不过里面只是RETURN

SUCCESS而已。我们加上REGISTER_INI_ENTRIES();,同样的,在

PHP_MSHUTDOWN_FUNCTION(helloworld),加上相反的操作,注销变量

UNREGISTER_INI_ENTRIES();

新增加一个函数PHP_FUNCTION(hello_world),请按照前面总结的PHP扩展编写步骤增加相应的声明。

该函数中,只有RETURN_STRING(INI_STR(“helloworld.greeting”), 1)。

INI_STR表示获取greeting变量的值,注意如果是整数,要使用INI_LONG,类似的还有INI_DOUBLE,INI_BOOL。第二个

参数1是一个很重要的概念,表示是否需要拷贝一份,并且将拷贝返回。因为INI变量是不存在于zend空间的,因此需要zend拷贝并返回。

至此,我们修改了helloworld.c文件,请按照前面运行扩展的步骤重新编译一次。并且在helloworld.php中,使用$str2 = hello_world(),就可以得到$str2为”haha!jean!”

再来演示RINIT和RSHUTDOWN这对函数。

要声明一个页面请求内可重复利用的变量,需要在php_helloworld.h中,加上:

ZEND_BEGIN_MODULE_GLOBALS(helloworld)——声明了扩展中页面请求内可共用的变量

long counter;

zend_bool direction;——注意要使用zend提供的扩展数据类型

ZEND_END_MODULE_GLOBALS(helloworld)

回到helloworld.c中,

ZEND_DECLARE_MODULE_GLOBALS(helloworld)——声明存在页面请求内共用变量

在前面的PHP_INI_BEGIN()包裹中,加上:

STD_PHP_INI_ENTRY(“helloworld.direction”, “1″, PHP_INI_ALL,

OnUpdateBool, direction, zend_helloworld_globals,

helloworld_globals)——与前面的PHP_INI_ENTRY略有不同,指定了写时转换类型函数OnUpdateBool,以及这些变

量的数据结构zend_helloworld_globals,以及变量名。

为了使用RINIT,必须要先增加一个:

static void php_helloworld_init_globals(zend_helloworld_globals *helloworld_globals){

helloworld_globals->direction = 0;

}

这是MINIT中必须要调用一个初始化函数,该函数可以为空(如果没有特殊值需要赋值),但是一定要定义。

在MINIT函数中,最前面增加一行:

ZEND_INIT_MODULE_GLOBALS(helloworld, php_helloworld_init_globals, NULL); ——对页面请求内的全局变量初始化。

在RINIT函数中,可以使用:

HELLOWORLD_G(counter)=0;——引用注册的long counter变量,必须要加上宏HELLWORLD是大写扩展名。

在实际使用的函数中,比如定义一个函数PHP_FUNCTION(hello_long)

可以HELLOWORLD_G(counter)++,在PHP代码中反复运行hello_long,可以发现该值是不断累加的,而不是一个扩展函数内的局部变量。

至此,已经演示了MINIT,MSHUTDOWN,RINIT,RSHUTDOWN函数的使用方法,扩展的基本写法已经掌握了。

扩展语法——与C库联合编译

前面提到扩展一个很重要的用途,就是使得PHP代码可以直接调用C函数,而且写的方式和调用PHP函数一样简单。如何做到呢?

假设我们有一个很简单的C程序,即:

void hello(char *s)

{

if( s== NULL )

return;

strcpy(s, “hello!world!
\n”);

}

简单的功能,需要传递一个已经分配好空间的字符串,然后将hello!world的内容拷贝到字符串中。

将该函数编译成C静态库,生成libhello.a文件。

我们在helloworld.c中写一个简单的PHP_FUNCTION(hello_c)函数,注意按照前面步骤加上该有的声明:

char * buf = NULL;

int buf_len = 256;

buf = emalloc(buf_len);——用到了ZEND内存管理API

if( buf == NULL ){ RETURN_FALSE;}

hello(buf);

RETURN_STRING(buf,0);——不用为1,因为不需要再拷贝一次

在自动生成的makefile中,加上-L路径/ -lhello即可

重新编译,再运行,可以得到hello!world!字样

至此,可以知道扩展是为了让zend调用C函数,因此类似是C函数的wrapper,扩展的编写其实是轻量级的。

扩展高级语法

才数据类型里面提到了,array, object,resource这3中变量,即可以直接将php的array传递给C函数。这部分有大量的zend

api为你从PHP接到的array,object以及resource转换成zend能识别的zval结构。感兴趣的同学可以直接参照:

到此,相信完全可以自己编写PHP扩展了。

大家感兴趣的问题

1.  扩展是否可以调用其他扩展?

2. INI变量存放在哪里?zend空间,PHP空间到底是什么概念?多个页面请求修改INI变量是否会有问题?

3. 是否可以访问其他扩展的INI变量?

4. 没有zend之前是否可以编写扩展?

延伸话题

将PHP反编译成C++/C代码,使得PHP的执行变成编译语言的执行过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值