编写PHP扩展三步曲Word格式.docx
- 文档编号:21156582
- 上传时间:2023-01-28
- 格式:DOCX
- 页数:18
- 大小:24.94KB
编写PHP扩展三步曲Word格式.docx
《编写PHP扩展三步曲Word格式.docx》由会员分享,可在线阅读,更多相关《编写PHP扩展三步曲Word格式.docx(18页珍藏版)》请在冰豆网上搜索。
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"
php.h"
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,
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
NULL,
PHP_HELLO_WORLD_VERSION,
STANDARD_MODULE_PROPERTIES
#ifdefCOMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
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("
);
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()行。
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)
PHP_FUNCTION(hello_long)
PHP_FUNCTION(hello_double)
PHP_FUNCTION(hell_bool)
PHP_FUNCTION(hello_null)
你也需要在头文件php_hello.h加入函数的原型声明,这样才能正确编译:
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);
...
现在,用下面的代码替换hello.c中的hello_module_entry
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
PHP_INI_BEGIN()
PHP_INI_ENTRY("
hello.greeting"
"
PHP_INI_ALL,NULL)
PHP_INI_END()
PHP_MINIT_FUNCTION(hello)
REGISTER_INI_ENTRIES();
returnSUCCESS;
PHP_MSHUTDOWN_FUNCTION(hello)
UNREGISTER_INI_ENTRIES();
现在,你需要在hello.c的include块后加入一个include项,以获取INI文件支持:
php_ini.h"
最后,需要修改hello_world函数,以使用INI值:
RETURN_STRING(INI_STR("
),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型的全局变量。
每次
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 编写 PHP 扩展 三步曲