编写PHP扩展三步曲.docx

上传人:b****5 文档编号:8020311 上传时间:2023-01-28 格式:DOCX 页数:18 大小:24.94KB
下载 相关 举报
编写PHP扩展三步曲.docx_第1页
第1页 / 共18页
编写PHP扩展三步曲.docx_第2页
第2页 / 共18页
编写PHP扩展三步曲.docx_第3页
第3页 / 共18页
编写PHP扩展三步曲.docx_第4页
第4页 / 共18页
编写PHP扩展三步曲.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

编写PHP扩展三步曲.docx

《编写PHP扩展三步曲.docx》由会员分享,可在线阅读,更多相关《编写PHP扩展三步曲.docx(18页珍藏版)》请在冰豆网上搜索。

编写PHP扩展三步曲.docx

编写PHP扩展三步曲

编写PHP扩展三步曲

简介

如果你正在阅读这份指南,你大概已经有兴趣为php编写扩展了。

如果没有兴趣...,唔,或许当你看完本文,你会发现这是一件你未曾尝试过的非常有趣的事情。

这份指南假设你熟悉PHP语言和php解释器的编写语言:

C。

让我们来说说为什么要编写PHP扩展:

1、由于抽象程度的问题,一些库或系统调用不能被PHP直接调用。

2、在一些不常见的情况,你需要控制PHP的行为。

3、你已经写好一些PHP代码,但你想让它运行得更快,体积更小,占有更少的内存。

4、你打算销售一些独特的、聪明的代码,非常重要的是:

它能够执行,但不能查看源代码。

这些都是很常见的原因,但是为了创建扩展,你首先需要理解什么是扩展。

什么是扩展

如果你使用过PHP,那你就已经使用过扩展。

除了很少例外,PHP里的每个用户空间(userspace)函数,都来自一个或多个扩展。

这些函数中的很大部分属于“标准扩展”--共有超过400个这样的函数。

PHP源码包中有86个扩展,每个扩展平均有大约30个函数。

照这样计算,大约有2500个函数。

如果你觉得这些还不够,在PECL库中,还提供了超过100个其他扩展,而且在互联网上还能找到更多。

“所有的函数都来自扩展,那还剩下什么?

”。

我听见你这样问。

“扩展到底是什么?

PHP的核心又是什么呢?

PHP的核心由两大部分组成。

在低层次是theZend引擎(ZE)。

ZE的任务是:

将人们可读的脚本转化成机器可读的符号,并在进程空间中执行这些符号。

ZE还处理内存管理、变量作用域、分发函数调用。

另外一大部分是PHP。

PHP处理通讯,和跟SAPI层(服务器端应用编程接口,通常指以下主机环境--Apache,IIS,CLI,CGI等)的绑定。

它也为safe_mode和open_basedir检查提供一个统一的控制层,比如,通过fopen、fread、fwrite进行文件和网络操作的字节流层。

生命周期

当SAPI启动时,例如响应操作/usr/local/apache/bin/apachectlstart,PHP开始初始化它的核心子系统。

在启动过程的后期,它装载每个扩展,并调用扩展的模块初始化函数(MINIT)。

这就给了每个扩展初始化内部变量、分配资源、注册资源处理器、向ZE注册函数的机会,这样,如果脚本调用这些函数,ZE就知道怎么去执行。

接下来,PHP等待SAPI层的页面处理请求。

在用作CGI或CLI时,这会立即发生并只执行一次。

当运行在Apache、IIS或其他完整的web服务器SAPI下时,它在用户请求页面时触发,重复执行,并有可能并发执行。

不用关心请求是怎样进来的,PHP向ZE请求设置一个脚本运行的环境,然后调用每个扩展的请求初始化函数(RINIT)。

RINIT给扩展一个设置特定环境变量、分配特定资源、执行其他任务例如审核的机会。

一个RINIT的简单例子是sessions扩展,如果session.auto_start选项被打开,RINIT会自动触发用户空间函数session_start(),构造$_SESSION变量。

一旦请求被初始化了,ZE将PHP脚本转换成符号,进而转换成可执行的操作码去执行。

一旦操作码调用一个扩展函数,ZE就将参数打包传递给扩展函数,临时交出控制权,直至执行完成。

当脚本执行完毕,PHP调用每个扩展的请求结束函数(RSHUTDOWN),以执行一些清除任务(例如将session变量保存到磁盘上)。

接下来,ZE运行一个清除进程(垃圾收集),高效地对前一个请求中用到的每个变量执行unset()操作。

一旦完成,PHP等待SAPI请求另一个文档或发送一个shutdown信号。

在CGI和CLI的SAPI中,没有下一次请求,因此SAPI立即执行shutdown。

在shutdown时,PHP会调用扩展的模块Shutdown函数(MSHUTDOWN),最终关闭核心子系统。

整个过程在刚开始时听起来让人退却,但是一旦你深入一个正常运行的扩展中,这些过程就会变得显而易见。

内存分配

在编写扩展时,为了避免内存泄漏,ZE通过一个表明持久的附加标志提供了自身内部的内存管理。

“持久分配”的内存可以在单个页面请求结束后继续存在。

与此相对的“非持久分配”,内存被分配后,在请求结束时,无论是否调用释放函数都将被释放。

例如,用户空间的变量,都应该使用非持久分配,因为在请求结束后,它们就没有意义了。

在理论上,扩展可以依靠ZE在每个页面请求结束时自动释放非持久分配的内存,但这样做是不推荐的。

内存分配将保持未回收状态比较长一段时间,内存关联的资源很少能正确释放,弄得一团糟却不清除它,这是不好的习惯。

你逐渐会发现,实际上很容易保证所有分配得数据都被正确释放。

让我们简要比较一下传统的内存分配函数和PHP/ZE的持久、非持久内存分配:

传统

malloc(count)calloc(count,num)

strdup(str)strndup(str,len)

free(ptr)

realloc(ptr,newsize)

malloc(count*num+extr)

非持久

emalloc(count)ecalloc(count,num)

estrdup(str)estrndup(str,len)

efree(ptr)

erealloc(ptr,newsize)

safe_emalloc(count,num,extr)

持久

pemalloc(count,1)pecalloc(count,num,1)

pestrdup(str,1)pemalloc()&memcpy()

pefree(ptr,1)

perealloc(ptr,newsize,1)

safe_pemalloc(count,num,extr)

配置编辑环境

现在你已经接触到了一些隐藏在PHP和Zend引擎下的理论,我打赌你已经想要开始构建它们了。

然而,在你能构建之前,你需要收集一些必要的工具,并配置环境。

首先,你需要PHP,设置构建工具需要PHP。

如果你对buildPHP源代码还不熟悉,你建议你看看(为Windows开发PHP扩展将在后面的文档中讲述)。

跟使用PHP的二进制分发包相比,源代码版本有两点非常重要。

/configure选项在开发过程中非常顺手。

首先是--enable-debug。

这个选项将使用附加的符号信息编译PHP,这样,如果一个错误发生,你能收集到coredump,这样就可以使用gdb来进行跟踪分析错误为什么会发生。

另一个选项根据版本的不同有不同的名称。

在PHP4.3中,名为--enabled-experimental-zts,在PHP5和后续版本中,叫做--enable-maintainer-zts。

这个选项可以让PHP知道它运行在多线程的环境中,这样你能捕获到在单线程的环境中没有问题的编程错误。

这样你的扩展就能在多线程的环境中安全使用。

一旦你用这些特定的选项编译完PHP并安装好它,你就能开始你的第一个扩展了。

HelloWorld

没有必不可少的“HelloWorld”应用,哪一个入门教程是完整的?

在这个实例中,你将编写一个扩展,导出一个返回“HelloWorld”字符串的函数。

在PHP代码中,你可能会这样写:

php

functionhello_world(){

return'HelloWorld';

}

?

>

现在你将它转换成一个扩展。

首先,让我们在PHP源代码目录树下的ext目录中创建一个名为hello的目录,并进入这个目录。

事实上,这个目录也可以在PHP目录树外存在,但是我希望你把它放在这里,在后面的文档中会介绍一些不相关的概念。

你需要创建3个文件:

一个源文件,包含hello_world函数;一个头文件,包含PHP装载这个扩展的引用;一个配置文件,它会被phpize用来准备扩展的编译环境。

config.m4

PHP_ARG_ENABLE(hello,whethertoenableHelloWorldsupport,

[--enable-helloEnableHelloWorldsupport])

iftest"$PHP_HELLO"="yes";then

AC_DEFINE(HAVE_HELLO,1,[WhetheryouhaveHelloWorld])

PHP_NEW_EXTENSION(hello,hello.c,$ext_shared)

fi

php_hello.h

#ifndefPHP_HELLO_H

#definePHP_HELLO_H1

#definePHP_HELLO_WORLD_VERSION"1.0"

#definePHP_HELLO_WORLD_EXTNAME"hello"

PHP_FUNCTION(hello_world);

externzend_module_entryhello_module_entry;

#definephpext_hello_ptr&hello_module_entry

#endif

hello.c

#ifdefHAVE_CONFIG_H

#include"config.h"

#endif

#include"php.h"

#include"php_hello.h"

staticfunction_entryhello_functions[]={

PHP_FE(hello_world,NULL)

{NULL,NULL,NULL}

};

zend_module_entryhello_module_entry={

#ifZEND_MODULE_API_NO>=20010901

STANDARD_MODULE_HEADER,

#endif

PHP_HELLO_WORLD_EXTNAME,

hello_functions,

NULL,

NULL,

NULL,

NULL,

NULL,

#ifZEND_MODULE_API_NO>=20010901

PHP_HELLO_WORLD_VERSION,

#endif

STANDARD_MODULE_PROPERTIES

};

#ifdefCOMPILE_DL_HELLO

ZEND_GET_MODULE(hello)

#endif

PHP_FUNCTION(hello_world)

{

RETURN_STRING("HelloWorld",1);

}

你在上面的扩展示例中看到的大多数代码都是胶水代码-将扩展声明给PHP,并建立他们之间的通信环境。

只有最后4行代码很象之前我们写的PHP代码:

1、声明函数名为hello_world

2、函数将字符串“HelloWorld”作为返回值

3、...嗯?

关于1,那到底是什么?

回忆一下,ZE包含了一个内存管理层,以保证分配的资源在脚本退出的时候被释放。

在内存管理中,一个大问题是两次释放同一个内存块。

这种行为叫做doublefreeing,就象访问不再属于自己的内存,这是一个引起错误的常见原因。

同样的,你不想ZE释放一个静态的字符串缓冲(例如我们扩展示例里的HelloWorld),因为它存在在代码空间,而并不是属于任务进程的数据块。

RETURN_STRING()能假定任何字符串都需要拷贝传递,这样就能被安全释放;但是因为在函数内部为字符串分配内存,动态填充,然后返回它,这并不罕见,RETURN_STRING()允许我们指定是否有必要生成一个字符串的拷贝。

为了演示这种概念,下面的代码片断等同于在刚才你看到的代码:

PHP_FUNCTINO(hello_world){

char*str;

str=estrdup("HelloWorld");

RETURN_STRING(str,0);

}

在这个版本中,你为字符串“HelloWorld”手动分配了内存,最终会被传回调用脚本,然后把内存交给RETURN_STRING。

使用第二个参数值0表明它不需要创建自己的拷贝。

构建你的扩展

练习的最后一步将把扩展构建成动态加载的模块。

如果你正确拷贝了上面的代码,只需要在ext/hello/目录下执行3个命令:

phpize

./configure--enable-hello

make

在运行了这些命令后,ext/hello/modules/目录下会生成hello.so文件。

现在你就可以象其他扩展一样,把它拷贝到你的扩展目录(缺省为/usr/local/lib/php/extensions,检查php.ini进行确认),并在php.ini中增加一行extension=hello.so,以在PHP启动的时候载入它。

对于CGI或CLI的SAPI,这意味着下次运行PHP的时候会生效;对于Web服务器SAPI,例如Apache,在下次Web服务器重启的时候生效。

现在让我们通过命令行来试试:

php-r'echohello_world();'

如果所有的步骤都正确,你应该能看到脚本输出的HelloWorld了,因为扩展中的hello_world函数返回了这个字符串,echo命令就将它显示出来。

返回其它类型的值也类似这种形式,使用RETURN_LONG()返回整型,RETURN_DOUBLE()返回浮点型,RETURN_BOOL()返回布尔型,RETURN_NULL()返回空值。

让我们来看看hello.c中在function_entry结构加入的PHP_PE行,以及在文件尾部加入的PHP_FUNCTION()行。

staticfunction_entryhello_functions[]={

PHP_PE(hello_world,NULL)

PHP_PE(hello_long,NULL)

PHP_PE(hello_double,NULL)

PHP_PE(hello_bool,NULL)

PHP_PE(hello_null,NULL)

{NULL,NULL,NULL}

};

PHP_FUNCTION(hello_long)

PHP_FUNCTION(hello_double)

PHP_FUNCTION(hell_bool)

PHP_FUNCTION(hello_null)

你也需要在头文件php_hello.h加入函数的原型声明,这样才能正确编译:

PHP_FUNCTION(hello_world);

PHP_FUNCTION(hello_long);

PHP_FUNCTION(hello_double);

PHP_FUNCTION(hello_bool);

PHP_FUNCTION(hello_null);

因为你不需要修改config.m4文件,因此在技术上跳过phpize和./configure过程,直接make是安全的。

然而,为了得到正确的build,我建议你还是执行整个三个步骤。

另外,你应该调用makecleanall而不是简单的make,来保证所有的源文件都被重新build。

一旦模块被构建出来,你要再次把它拷贝到你的扩展目录,替换掉老版本。

到这一步,你可以再次调用PHP解释器,用简单的脚本测试你新增的函数。

实际上,你为什么不现在就这么做呢?

我等着你...

完成了?

非常好。

如果你曾用var_dump代替echo查看每个函数的输出,你可能已经注意到hello_bool()返回true。

那就是传递给RETURN_BOOL()的值1得到的。

就像在PHP脚本中,整数0等同FALSE,其它整数等同TRUE。

扩展的编写者按照惯例通常使用1来返回TRUE,我也建议你这么做,只要不拘泥与此就行了。

为了获得更好的可读性,可以使用RETURN_TRUE和RETURN_FALSE宏。

我们使用RETURN_TRUE重新实现hello_bool():

PHP_FUNCTION(hello_bool)

注意,使用RETURN_TRUE和RETURN_FALSE的时候,没有圆括号,跟其它的RETURN_*()宏不一样。

因此,注意不要犯这样的错误!

你可能已经注意到,上面的每个代码我们都没有传递0或1来指出值是否需要拷贝。

这是因为对于这些简单的标量来说,没有附加的内存需要分配和释放。

还有3个附加的返回类型:

RESOURCE(用来返回mysql_connect(),fsockopen(),ftp_connect()),ARRAY(返回HASH),OBJECT(通过关键字new来返回)。

当我们深入了解这些变量时,我们将在第二章中进行讨论。

INI设置

Zend引擎提供了两种管理INI值的方式。

现在我们先看看简单一些的方式,完整阐释它;对于更复杂一些的方式,在我们有机会获取全局变量时再讨论它。

假定你想为你的扩展在php.ini中定义一个值hello.greeting,这样在你的hello_world()函数中可以获取到这个值用来输出。

你需要在hello.c和php_hello.h中对hello_module_entry结构做出一些改动。

在php_hello.h靠近函数原型声明的地方加入新的原型:

PHP_MINIT_FUNCTION(hell);

PHP_MSHUTDONW_FUNCTION(hello);

PHP_FUNCTION(hello_world);

...

现在,用下面的代码替换hello.c中的hello_module_entry

zend_module_entryhello_module_entry={

#ifZEND_MODULE_API_NO>=20010901

STANDARD_MODULE_HEADER,

#endif

PHP_HELLO_WORLD_EXTNAME,

hello_functions,

PHP_MINIT(hello),

PHP_MSHUTDOWN(hello),

NULL,

NULL,

NULL,

#ifZEND_MODULE_API_NO>=20010901

PHP_HELLO_WORLD_VERSION,

#endif

STANDARD_MODULE_PROPERTIES

};

PHP_INI_BEGIN()

PHP_INI_ENTRY("hello.greeting","HelloWorld",PHP_INI_ALL,NULL)

PHP_INI_END()

PHP_MINIT_FUNCTION(hello)

{

REGISTER_INI_ENTRIES();

returnSUCCESS;

}

PHP_MSHUTDOWN_FUNCTION(hello)

{

UNREGISTER_INI_ENTRIES();

returnSUCCESS;

}

现在,你需要在hello.c的include块后加入一个include项,以获取INI文件支持:

#ifdefHAVE_CONFIG_H

#include"config.h"

#endif

#include"php.h"

#include"php_ini.h"

#include"php_hello.h"

最后,需要修改hello_world函数,以使用INI值:

PHP_FUNCTION(hello_world)

{

RETURN_STRING(INI_STR("hello.greeting"),1);

}

注意,你拷贝了INI_STR()的返回值。

这是因为它一个静态变量。

事实上,如果你尝试修改这个返回值,PHP扩展环境将变得不稳定,甚至崩溃。

在本节里,第一个变化是引入了两个方法MINIT和MSHUTDOWN,你将对他们非常熟悉。

正如以前提及的,它们会由SAPI在启动初始化和关闭时分别调用。

它们不会在请求间和请求中被调用。

在这个例子中,你用它来注册在扩展中定义的php.ini入口(entries),在后续的讨论中,你将看到如何使用MINIT和MSHUTDOWN函数注册resource,object和streamhandlers。

在你的hello_world()函数中,你使用INI_STR()获取hello.greeting的当前字符串值。

获取long、double、boolean型值的函数,连同获取默认值的函数,如下表所示。

当前值:

INI_STR(name)INI_INT(name)INI_FLT(name)INI_BOOL(name)

默认值:

INI_ORIG_STR(name)INI_ORIG_INT(name)INI_ORIG_FLT(name)INI_ORIG_BOOL(name)

类型:

char*(NULLterminated)signedlongsigneddoublezend_bool

传递给PHP_INI_ENTRY()的第一个参数是在php.ini中定义的entry的名称字符串。

为了避免命名冲突,你应使用与函数相同的习惯:

对所有值加上以扩展名命名的前缀,例如hello.greeting。

作为一个习惯,使用句点将后面的ini设置名称的描述部分分隔开。

第二个参数是初始值,无论它是不是一个数字型值,都传递一个char*的字符串。

这样做主要的原因是ini文件本身是文本化的。

你可以使用INI_INT(),INI_FLT(),INI_BOOL()在自己的脚本里进行类型转换。

第三个参数是访问模式修饰符。

这是一个掩码字段,用来决定这个INI值何时、何处可以被修改。

例如register_globals,并不能简单的在脚本中使用ini_set()来进行修改,因为这些设置只在请求startup时--脚本运行之前被使用。

其它的设置,例如allow_url_fopen,在共享的主机环境中,即使是通过ini_set()或是通过.htaccess,你也并不想让用户随意改变它。

这个参数的典型值是PHP_INI_ALL,表明它的值可以在任何地方修改。

PHP_INI_SYSTEM|PHP_INI_PERDIR表示可以在php.ini文件中修改,也可以通过.htaccess文件修改,但不能通过ini_set()修改。

PHP_INI_SYSTEM表示只能在php.ini中进行修改。

第四个参数允许在ini设置改变时(例如ini_set()),触发一个回调函数,我们现在略过不讲。

当设置发生变化时,这允许扩展执行更精确的控制,或者根据新值触发相关动作。

全局数值

扩展经常需要跟踪一个值,保持该值与其它的请求无关,即使是这些请求是发生在同一时刻。

在非多线程的SAPI中可能很简单:

只需要声明一个全局变量,在需要是访问它就行了。

可能存在的问题是,PHP被设计成可以运行在多线程的Web服务器中(如Apache2和IIS),这需要保持全局数值的线程安全。

PHP采用TSRM(ThreadSafeResourceManagement)抽象层进行了极大地简化,有时也被称为ZTS(ZendThreadSafety)。

实际上,你已经使用过TSRM,只是不知道而已。

(不要艰难地试图找出它,很快你就会发现,它隐藏在每个地方。

创建一个线程安全地全局变量的第一步是,用global声明一个变量。

在下面的例子中,声明了一个long型的全局变量。

每次

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1