PHP取得成功的一个主要缘由之一是她拥有大量的可用扩展。web开发者不管有何种需求,这种需求最有可能在PHP发行包里找到。PHP发行包包括支持各类数据库,图形文件格式,压缩,XML技术扩展在内的许多扩展。
扩展API的引入使PHP3取得了巨大的进展,扩展API机制使PHP开发社区很容易的开发出几十种扩展。如今,两个版本过去了,API仍然和PHP3时的很是类似。扩展主要的思想是:尽量的从扩展编写者那里隐藏PHP的内部机制和脚本引擎自己,仅仅须要开发者熟悉API。
有两个理由须要本身编写PHP扩展。第一个理由是:PHP须要支持一项她还未支持的技术。这一般包括包裹一些现成的C函数库,以便提供PHP接口。
例如,若是一个叫FooBase的数据库已推出市场,你须要创建一个PHP扩展帮助你从PHP里调用FooBase的C函数库。这个工做可能仅由一我的完成,然
后被整个PHP社区共享(若是你愿意的话)。第二个不是很广泛的理由是:你须要从性能或功能的缘由考虑来编写一些商业逻辑。
若是以上的两个理由都和你没什么关系,同时你感受本身没有冒险精神,那么你能够跳过本章。
本章教你如何编写相对简单的PHP扩展,使用一部分扩展API函数。对于大多数打算开发自定义PHP扩展开发者而言,它含概了足够的资料。学习一门编程课程的最好方法之一就是动手作一些极其简单的例子,这些例子正是本章的线索。一旦你明白了基础的东西,你就能够在互联网上经过阅读文挡、原代码或参加邮件列表新闻组讨论来丰富本身。所以,本章集中在让你如何开始的话题。在UNIX下一个叫ext_skel的脚本被用于创建扩展的骨架,骨架信息从一个描述扩展接口的定义文件中取得。所以你须要利用UNIX来创建一个骨架。Windows开发者可使用Windows ext_skel_win32.php代替ext_skel。
然而,本章关于用你开发的扩展编译PHP的指导仅涉及UNIX编译系统。本章中全部的对API的解释与UNIX和Windows下开发的扩展都有联系。
当你阅读完这章,你能学会如何
☞ 创建一个简单的商业逻辑扩展。
☞ .建议个C函数库的包裹扩展,尤为是有些标准C文件操做函数好比fopen()php
快速开始html
本节没有介绍关于脚本引擎基本构造的一些知识,而是直接进入扩展的编码讲解中,所以不要担忧你没法马上得到对扩展总体把握的感受。假设你正在开发一个网站,须要一个把字符串重复n次的函数。下面是用PHP写的例子:mysql
function self_concat($string, $n)程序员
{web
$result = "";算法
for ($i = 0; $i < $n; $i++) {sql
$result .= $string;数据库
}编程
return $result;数组
}
self_concat("One", 3) returns "OneOneOne".
self_concat("One", 1) returns "One".
假设因为一些奇怪的缘由,你须要时常调用这个函数,并且还要传给函数很长的字符串和大值n。这意味着在脚本里有至关巨大的字符串链接量和内存从新分配过程,以致显著地下降脚本执行速度。若是有一个函数可以更快地分配大量且足够的内存来存放结果字符串,而后把$string重复n次,就不须要在每次循环迭代中分配内存。
为扩展创建函数的第一步是写一个函数定义文件,该函数定义文件定义了扩展对外提供的函数原形。该例中,定义函数只有一行函数原形self_concat() :
string self_concat(string str, int n)
函数定义文件的通常格式是一个函数一行。你能够定义可选参数和使用大量的PHP类型,包括: bool, float, int, array等。
保存为myfunctions.def文件至PHP原代码目录树下。
该是经过扩展骨架(skeleton)构造器运行函数定义文件的时机了。该构造器脚本叫ext_skel,放在PHP原代码目录树的ext/目录下(PHP原码主目录下的README.EXT_SKEL提供了更多的信息)。假设你把函数定义保存在一个叫作myfunctions.def的文件里,并且你但愿把扩展取名为myfunctions,运行下面的命令来创建扩展骨架
./ext_skel --extname=myfunctions --proto=myfunctions.def
这个命令在ext/目录下创建了一个myfunctions/目录。你要作的第一件事情也许就是编译该骨架,以便编写和测试实际的C代码。编译扩展有两种方法:
☞ 做为一个可装载模块或者DSO(动态共享对象)
☞ 静态编译到PHP
由于第二种方法比较容易上手,因此本章采用静态编译。若是你对编译可装载扩展模块感兴趣,能够阅读PHP原代码根目录下的README.SELF-CONTAINED_EXTENSIONS文件。为了使扩展可以被编译,须要修改扩展目录ext/myfunctions/下的config.m4文件。扩展没有包裹任何外部的C库,你须要添加支持--enable-myfunctions配置开关到PHP编译系统里(–with-extension 开关用于那些须要用户指定相关C库路径的扩展)。能够去掉自动生成的下面两行的注释来开启这个配置。
PHP_ARG_ENABLE(myfunctions, whether to enable myfunctions support,
[ --enable-myfunctions Include myfunctions support])
如今剩下的事情就是在PHP原代码树根目录下运行./buildconf,该命令会生成一个新的配置脚本。经过查看./configure --help输出信息,能够检查新的配置选项是否被包含到配置文件中。如今,打开你喜爱的配置选项开关和--enable-myfunctions从新配置一下PHP。最后的但不是最次要的是,用make来从新编译PHP。
ext_skel应该把两个PHP函数添加到你的扩展骨架了:打算实现的self_concat()函数和用于检测myfunctions 是否编译到PHP的confirm_myfunctions_compiled()函数。完成PHP的扩展开发后,能够把后者去掉。
<?php
print confirm_myfunctions_compiled("myextension");
?>
运行这个脚本会出现相似下面的输出:
"Congratulations! You have successfully modified ext/myfunctions
config.m4. Module myfunctions is now compiled into PHP."
另外,ext_skel脚本生成一个叫myfunctions.php的脚本,你也能够利用它来验证扩展是否被成功地编译到PHP。它会列出该扩展所支持的全部函数。
如今你学会如何编译扩展了,该是真正地研究self_concat()函数的时候了。
下面就是ext_skel脚本生成的骨架结构:
/* {{{ proto string self_concat(string str, int n)
*/
PHP_FUNCTION(self_concat)
}
char *str = NULL;
int argc = ZEND_NUM_ARGS();
int str_len;
long n;
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
php_error(E_WARNING, "self_concat: not yet implemented");
}
/* }}} */
自动生成的PHP函数周围包含了一些注释,这些注释用于自动生成代码文档和vi、Emacs等编辑器的代码折叠。函数自身的定义使用了宏PHP_FUNCTION(),该宏能够生成一个适合于Zend引擎的函数原型。逻辑自己分红语义各部分,取得调用函数的参数和逻辑自己。
为了得到函数传递的参数,可使用zend_parse_parameters()API函数。下面是该函数的原型:
zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, …);
第一个参数是传递给函数的参数个数。一般的作法是传给它ZEND_NUM_ARGS()。这是一个表示传递给函数参数总个数的宏。第二个参数是为了线程安全,老是传递TSRMLS_CC宏,后面会讲到。第三个参数是一个字符串,指定了函数指望的参数类型,后面紧跟着须要随参数值更新的变量列表。由于PHP采用松散的变量定义和动态的类型判断,这样作就使得把不一样类型的参数转化为指望的类型成为可能。例如,若是用户传递一个整数变量,可函数须要一个浮点数,那么zend_parse_parameters()就会自动地把整数转换为相应的浮点数。若是实际值没法转换成指望类型(好比整形到数组形),会触发一个警告。
下表列出了可能指定的类型。咱们从完整性考虑也列出了一些没有讨论到的类型。
类型指定符 |
对应的C类型 |
描述 |
l |
long |
符号整数 |
d |
double |
浮点数 |
s |
char *, int |
二进制字符串,长度 |
b |
zend_bool |
逻辑型(1或0) |
r |
zval * |
资源(文件指针,数据库链接等) |
a |
zval * |
联合数组 |
o |
zval * |
任何类型的对象 |
O |
zval * |
指定类型的对象。须要提供目标对象的类类型 |
z |
zval * |
无任何操做的zval |
为了容易地理解最后几个选项的含义,你须要知道zval是Zend引擎的值容器。不管这个变量是布尔型,字符串型或者其余任何类型,其信息总会包含在一个zval联合体中。本章中咱们不直接存取zval,而是经过一些附加的宏来操做。下面的是或多或少在C中的zval, 以便咱们能更好地理解接下来的代码。
typedef union _zval {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zval;
在咱们的例子中,咱们用基本类型调用zend_parse_parameters(),以本地C类型的方式取得函数参数的值,而不是用zval容器。
为了让zend_parse_parameters()可以改变传递给它的参数的值,并返回这个改变值,须要传递一个引用。仔细查看一下self_concat():
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
注意到自动生成的代码会检测函数的返回值FAILUER(成功即SUCCESS)来判断是否成功。若是没有成功则当即返回,而且由zend_parse_parameters()负责触发警告信息。由于函数打算接收一个字符串l和一个整数n,因此指定 ”sl” 做为其类型指示符。s须要两个参数,因此咱们传递参考char * 和 int (str 和 str_len)给zend_parse_parameters()函数。不管何时,记得老是在代码中使用字符串长度str_len来确保函数工做在二进制安全的环境中。不要使用strlen()和strcpy(),除非你不介意函数在二进制字符串下不能工做。二进制字符串是包含有nulls的字符串。二进制格式包括图象文件,压缩文件,可执行文件和更多的其余文件。”l” 只须要一个参数,因此咱们传递给它n的引用。尽管为了清晰起见,骨架脚本生成的C变量名与在函数原型定义文件中的参数名同样;这样作不是必须的,尽管在实践中鼓励这样作。
回到转换规则中来。下面三个对self_concat()函数的调用使str, str_len和n获得一样的值:
self_concat("321", 5);
self_concat(321, "5");
self_concat("321", "5");
str points to the string "321", str_len equals 3, and n equals 5.
str 指向字符串"321",str_len等于3,n等于5。
在咱们编写代码来实现链接字符串返回给PHP的函数前,还得谈谈两个重要的话题:内存管理、从PHP内部返回函数值所使用的API。
内存管理
用于从堆中分配内存的PHP API几乎和标准C API同样。在编写扩展的时候,使用下面与C对应(所以没必要再解释)的API函数:
emalloc(size_t size);
efree(void *ptr);
ecalloc(size_t nmemb, size_t size);
erealloc(void *ptr, size_t size);
estrdup(const char *s);
estrndup(const char *s, unsigned int length);
在这一点上,任何一位有经验的C程序员应该象这样思考一下:“什么?标准C没有strndup()?”是的,这是正确的,由于GNU扩展一般在Linux下可用。estrndup()只是PHP下的一个特殊函数。它的行为与estrdup()类似,可是能够指定字符串重复的次数(不须要结束空字符),同时是二进制安全的。这是推荐使用estrndup()而不是estrdup()的缘由。
在几乎全部的状况下,你应该使用这些内存分配函数。有一些状况,即扩展须要分配在请求中永久存在的内存,从而不得不使用malloc(),可是除非你知道你在作什么,你应该始终使用以上的函数。若是没有使用这些内存函数,而相反使用标准C函数分配的内存返回给脚本引擎,那么PHP会崩溃。
这 些函数的优势是:任何分配的内存在偶然状况下若是没有被释放,则会在页面请求的最后被释放。所以,真正的内存泄漏不会产生。然而,不要依赖这一机制,从调 试和性能两个缘由来考虑,应当确保释放应该释放的内存。剩下的优势是在多线程环境下性能的提升,调试模式下检测内存错误等。
还有一个重要的缘由,你不须要检查这些内存分配函数的返回值是否为null。当内存分配失败,它们会发出E_ERROR错误,从而决不会返回到扩展。
从PHP函数中返回值
扩展API包含丰富的用于从函数中返回值的宏。这些宏有两种主要风格:第一种是RETVAL_type()形式,它设置了返回值但C代码继续执行。这一般使用在把控制交给脚本引擎前还但愿作的一些清理工做的时候使用,而后再使用C的返回声明 ”return” 返回到PHP;后一个宏更加广泛,其形式是RETURN_type(),他设置了返回类型,同时返回控制到PHP。下表解释了大多数存在的宏。
设置返回值而且结束函数 |
设置返回值 |
宏返回类型和参数 |
RETURN_LONG(l) |
RETVAL_LONG(l) |
整数 |
RETURN_BOOL(b) |
RETVAL_BOOL(b) |
布尔数(1或0) |
RETURN_NULL() |
RETVAL_NULL() |
NULL |
RETURN_DOUBLE(d) |
RETVAL_DOUBLE(d) |
浮点数 |
RETURN_STRING(s, dup) |
RETVAL_STRING(s, dup) |
字符串。若是dup为1,引擎会调用estrdup()重复s,使用拷贝。若是dup为0,就使用s |
RETURN_STRINGL(s, l, dup) |
RETVAL_STRINGL(s, l, dup) |
长度为l的字符串值。与上一个宏同样,但由于s的长度被指定,因此速度更快。 |
RETURN_TRUE |
RETVAL_TRUE |
返回布尔值true。注意到这个宏没有括号。 |
RETURN_FALSE |
RETVAL_FALSE |
返回布尔值false。注意到这个宏没有括号。 |
RETURN_RESOURCE(r) |
RETVAL_RESOURCE(r) |
资源句柄。 |
完成self_concat()
如今你已经学会了如何分配内存和从PHP扩展函数里返回函数值,那么咱们就可以完成self_concat()的编码:
/* {{{ proto string self_concat(string str, int n)
*/
PHP_FUNCTION(self_concat)
}
char *str = NULL;
int argc = ZEND_NUM_ARGS();
int str_len;
long n;
char *result; /* Points to resulting string */
char *ptr; /* Points at the next location we want to copy to */
int result_length; /* Length of resulting string */
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
/* Calculate length of result */
result_length = (str_len * n);
/* Allocate memory for result */
result = (char *) emalloc(result_length + 1);
/* Point at the beginning of the result */
ptr = result;
while (n--) {
/* Copy str to the result */
memcpy(ptr, str, str_len);
/* Increment ptr to point at the next position we want to write to */
ptr += str_len;
}
/* Null terminate the result. Always null-terminate your strings
even if they are binary strings */
*ptr = '/0';
/* Return result to the scripting engine without duplicating it*/
RETURN_STRINGL(result, result_length, 0);
}
/* }}} */
如今要作的就是从新编译一下PHP,这样就完成了第一个PHP函数。
让我门检查函数是否真的工做。在最新编译过的PHP树下执行下面的脚本:
<?php
for ($i = 1; $i <= 3; $i++) {
print self_concat("ThisIsUseless", $i);
print "/n";
}
?>
你应该获得下面的结果:
ThisIsUseless
ThisIsUselessThisIsUseless
ThisIsUselessThisIsUselessThisIsUseless
实例小结
你已经学会如何编写一个简单的PHP函数。回到本章的开头,咱们提到用C编写PHP功能函数的两个主要的动机。第一个动机是用C实现一些算法来提升性能和扩展功能。前一个例子应该可以指导你快速上手这种类型扩展的开发。第二个动机是包裹三方函数库。咱们将在下一步讨论。
包裹第三方的扩展
本节中你将学到如何编写更有用和更完善的扩展。该节的扩展包裹了一个C库,展现了如何编写一个含有多个互相依赖的PHP函数扩展。
动机 也许最多见的PHP扩展是那些包裹第三方C库的扩展。这些扩展包括MySQL或Oracle的数据库服务库,libxml2的 XML技术库,ImageMagick 或GD的图形操纵库。
在本节中,咱们编写一个扩展,一样使用脚原本生成骨架扩展,由于这能节省许多工做量。这个扩展包裹了标准C函数fopen(), fclose(), fread(), fwrite()和 feof().
扩展使用一个被叫作资源的抽象数据类型,用于表明已打开的文件FILE*。你会注意到大多数处理好比数据库链接、文件句柄等的PHP扩展使用了资源类型,这是由于引擎本身没法直接“理解”它们。咱们计划在PHP扩展中实现的C API列表以下:
FILE *fopen(const char *path, const char *mode);
int fclose(FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int feof(FILE *stream);
咱们实现这些函数,使它们在命名习惯和简单性上符合PHP脚本。若是你曾经向PHP社区贡献过代码,你被指望遵循一些公共习俗,而不是跟随C库里的API。并非全部的习俗都写在PHP代码树的CODING_STANDARDS文件里。这便是说,此功能已经从PHP发展的很早阶段即被包含在PHP中,而且与C库API相似。PHP安装已经支持fopen(), fclose()和更多的PHP函数。
如下是PHP风格的API:
resource file_open(string filename, string mode)
file_open()接收两个字符串(文件名和模式),返回一个文件的资源句柄。
bool file_close(resource filehandle)
file_close()接收一个资源句柄,返回真/假指示是否操做成功。
string file_read(resource filehandle, int size)
file_read()接收一个资源句柄和读入的总字节数,返回读入的字符串。
bool file_write(resource filehandle, string buffer)
file_write接收一个资源句柄和被写入的字符串,返回真/假指示是否操做成功。
bool file_eof(resource filehandle)
file_eof()接收一个资源句柄,返回真/假指示是否到达文件的尾部。
所以,咱们的函数定义文件——保存为ext/目录下的myfile.def——内容以下:
resource file_open(string filename, string mode)
bool file_close(resource filehandle)
string file_read(resource filehandle, int size)
bool file_write(resource filehandle, string buffer)
bool file_eof(resource filehandle)
下一步,利用ext_skel脚本在ext./ 原代码目录执行下面的命令:
./ext_skel --extname=myfile --proto=myfile.def
而后,按照前一个例子的关于编译新创建脚本的步骤操做。你会获得一些包含FETCH_RESOURCE()宏行的编译错误,这样骨架脚本就没法顺利完成编译。为了让骨架扩展顺利经过编译,把那些出错行注释掉便可。
资源 资源是一个能容纳任何信息的抽象数据结构。正如前面提到的,这个信息一般包括例如文件句柄、数据库链接结构和其余一些复杂类型的数据。
使用资源的主要缘由是由于:资源被一个集中的队列所管理,该队列能够在PHP开发人员没有在脚本里面显式地释放时能够自动地被释放。
举个例子,考虑到编写一个脚本,在脚本里调用mysql_connect()打开一个MySQL链接,但是当该数据库链接资源再也不使用时却没有调用mysql_close()。在PHP里,资源机制可以检测何时这个资源应当被释放,而后在当前请求的结尾或一般状况下更早地释放资源。这就为减小内存泄漏赋予了一个“防弹”机制。若是没有这样一个机制,通过几回web请求后,web服务器也许会潜在地泄漏许多内存资源,从而致使服务器当机或出错。
注册资源类型 如何使用资源?Zend引擎让使用资源变地很是容易。你要作的第一件事就是把资源注册到引擎中去。使用这个API函数:
int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)
这个函数返回一个资源类型id,该id应当被做为全局变量保存在扩展里,以便在必要的时候传递给其余资源API。ld:该资源释放时调用的函数。pld用于在不一样请求中始终存在的永久资源,本章不会涉及。type_name是一个具备描述性类型名称的字符串,module_number为引擎内部使用,当咱们调用这个函数时,咱们只须要传递一个已经定义好的module_number变量。
回到咱们的例子中来:咱们会添加下面的代码到myfile.c原文件中。该文件包括了资源释放函数的定义,此资源函数被传递给zend_register_list_destructors_ex()注册函数(资源释放函数应该提前添加到文件中,以便在调用zend_register_list_destructors_ex()时该函数已被定义):
static void myfile_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
FILE *fp = (FILE *) rsrc->ptr;
fclose(fp);
}
把注册行添加到PHP_MINIT_FUNCTION()后,看起来应该以下面的代码:
PHP_MINIT_FUNCTION(myfile)
{
/* If you have INI entries, uncomment these lines
ZEND_INIT_MODULE_GLOBALS(myfile, php_myfile_init_globals,NULL);
REGISTER_INI_ENTRIES();
*/
le_myfile = zend_register_list_destructors_ex(myfile_dtor,NULL,"standard-c-file", module_number);
return SUCCESS;
}
l 注意到le_myfile是一个已经被ext_skel脚本定义好的全局变量。
PHP_MINIT_FUNCTION()是一个先于模块(扩展)的启动函数,是暴露给扩展的一部分API。下表提供可用函数简要的说明。
函数声明宏
函数声明宏 |
语义 |
PHP_MINIT_FUNCTION() |
当PHP被装载时,模块启动函数即被引擎调用。这使得引擎作一些例如资源类型,注册INI变量等的一次初始化。 |
PHP_MSHUTDOWN_FUNCTION() |
当PHP彻底关闭时,模块关闭函数即被引擎调用。一般用于注销INI条目 |
PHP_RINIT_FUNCTION() |
在每次PHP请求开始,请求前启动函数被调用。一般用于管理请求前逻辑。 |
PHP_RSHUTDOWN_FUNCTION() |
在每次PHP请求结束后,请求前关闭函数被调用。常常应用在清理请求前启动函数的逻辑。 |
PHP_MINFO_FUNCTION() |
调用phpinfo()时模块信息函数被呼叫,从而打印出模块信息。 |
新建和注册新资源 咱们准备实现file_open()函数。当咱们打开文件获得一个FILE *,咱们须要利用资源机制注册它。下面的主要宏实现注册功能:
ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type);
参考表格对宏参数的解释
ZEND_REGISTER_RESOURCE 宏参数
宏参数 |
参数类型 |
rsrc_result |
zval *, which should be set with the registered resource information. zval * 设置为已注册资源信息 |
rsrc_pointer |
Pointer to our resource data. 资源数据指针 |
rsrc_type |
The resource id obtained when registering the resource type. 注册资源类型时得到的资源id |
文件函数 如今你知道了如何使用ZEND_REGISTER_RESOURCE()宏,而且准备好了开始编写file_open()函数。还有一个主题咱们须要讲述。
当PHP运行在多线程服务器上,不能使用标准的C文件存取函数。这是由于在一个线程里正在运行的PHP脚本会改变当前工做目录,所以另一个线程里的脚本使用相对路径则没法打开目标文件。为了阻止这种错误发生,PHP框架提供了称做VCWD (virtual current working directory 虚拟当前工做目录)宏,用来代替任何依赖当前工做目录的存取函数。这些宏与被替代的函数具有一样的功能,同时是被透明地处理。在某些没有标准C函数库平台的状况下,VCWD框架则不会获得支持。例如,Win32下不存在chown(),就不会有相应的VCWD_CHOWN()宏被定义。
VCWD列表
标准C库 |
VCWD宏 |
说明 |
getcwd() |
VCWD_GETCWD() |
|
fopen() |
VCWD_FOPEN |
|
open() |
VCWD_OPEN() |
用于两个参数的版本 |
open() |
VCWD_OPEN_MODE() |
用于三个参数的open()版本 |
creat() |
VCWD_CREAT() |
|
chdir() |
VCWD_CHDIR() |
|
getwd() |
VCWD_GETWD() |
|
realpath() |
VCWD_REALPATH() |
|
rename() |
VCWD_RENAME() |
|
stat() |
VCWD_STAT() |
|
lstat() |
VCWD_LSTAT() |
|
unlink() |
VCWD_UNLINK() |
|
mkdir() |
VCWD_MKDIR() |
|
rmdir() |
VCWD_RMDIR() |
|
opendir() |
VCWD_OPENDIR() |
|
popen() |
VCWD_POPEN() |
|
access() |
VCWD_ACCESS() |
|
utime() |
VCWD_UTIME() |
|
chmod() |
VCWD_CHMOD() |
|
chown() |
VCWD_CHOWN() |
编写利用资源的第一个PHP函数
实现file_open()应该很是简单,看起来像下面的样子:
PHP_FUNCTION(file_open)
{
char *filename = NULL;
char *mode = NULL;
int argc = ZEND_NUM_ARGS();
int filename_len;
int mode_len;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename,&filename_len, &mode, &mode_len) == FAILURE) {
return;
}
fp = VCWD_FOPEN(filename, mode);
if (fp == NULL) {
RETURN_FALSE;
}
ZEND_REGISTER_RESOURCE(return_value, fp, le_myfile);
}
你可能会注意到资源注册宏的第一个参数return_value,可此地找不到它的定义。这个变量自动的被扩展框架定义为zval * 类型的函数返回值。先前讨论的、可以影响返回值的RETURN_LONG() 和RETVAL_BOOL()宏确实改变了return_value的值。所以很容易猜到程序注册了咱们取得的文件指针fp,同时设置return_value为该注册资源。
访问资源 须要使用下面的宏访问资源(参看表对宏参数的解释)
ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id,
resource_type_name, resource_type);
ZEND_FETCH_RESOURCE 宏参数
参数 |
含义 |
rsrc |
资源值保存到的变量名。它应该和资源有相同类型。 |
rsrc_type |
rsrc的类型,用于在内部把资源转换成正确的类型 |
passed_id |
寻找的资源值(例如zval **) |
default_id |
若是该值不为-1,就使用这个id。用于实现资源的默认值。 |
resource_type_name |
资源的一个简短名称,用于错误信息。 |
resource_type |
注册资源的资源类型id |
使用这个宏,咱们如今可以实现file_eof():
PHP_FUNCTION(file_eof)
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) ==FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-c-file",le_myfile);
if (fp == NULL) {
RETURN_FALSE;
}
if (feof(fp) <= 0) {
/* Return eof also if there was an error */
RETURN_TRUE;
}
RETURN_FALSE;
删除一个资源 一般使用下面这个宏删除一个资源:
int zend_list_delete(int id)
传递给宏一个资源id,返回SUCCESS或者FAILURE。若是资源存在,优先从Zend资源列队中删除,该过程当中会调用该资源类型的已注册资源清理函数。所以,在咱们的例子中,没必要取得文件指针,调用fclose()关闭文件,而后再删除资源。直接把资源删除掉便可。
使用这个宏,咱们可以实现file_close():
PHP_FUNCTION(file_close)
{
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL;
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE) {
return;
}
if (zend_list_delete(Z_RESVAL_P(filehandle)) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
你确定会问本身Z_RESVAL_P()是作什么的。当咱们使用zend_parse_parameters()从参数列表中取得资源的时候,获得的是zval的形式。为了得到资源id,咱们使用Z_RESVAL_P()宏获得id,而后把id传递给zend_list_delete()。
有一系列宏用于访问存储于zval值(参考表的宏列表)。尽管在大多数状况下zend_parse_parameters()返回与c类型相应的值,咱们仍但愿直接处理zval,包括资源这一状况。
Zval访问宏
宏 |
访问对象 |
C 类型 |
Z_LVAL, Z_LVAL_P, Z_LVAL_PP
|
整型值 |
long |
Z_BVAL, Z_BVAL_P, Z_BVAL_PP
|
布尔值 |
zend_bool |
Z_DVAL, Z_DVAL_P, Z_DVAL_PP
|
浮点值 |
double |
Z_STRVAL, Z_STRVAL_P, Z_STRVAL_PP
|
字符串值 |
char * |
Z_STRLEN, Z_STRLEN_P, Z_STRLEN_PP
|
字符串长度值 |
int |
Z_RESVAL, Z_RESVAL_P,Z_RESVAL_PP |
资源值 |
long |
Z_ARRVAL, Z_ARRVAL_P, Z_ARRVAL_PP
|
联合数组 |
HashTable * |
Z_TYPE, Z_TYPE_P, Z_TYPE_PP
|
Zval类型 |
Enumeration (IS_NULL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_BOOL, IS_RESOURCE)
|
Z_OBJPROP, Z_OBJPROP_P, Z_OBJPROP_PP
|
对象属性hash(本章不会谈到) |
HashTable * |
Z_OBJCE, Z_OBJCE_P, Z_OBJCE_PP
|
对象的类信息(本章不会谈到) |
zend_class_entry |
用于访问zval值的宏 全部的宏都有三种形式:一个是接受zval s,另一个接受zval *s,最后一个接受zval **s。它们的区别是在命名上,第一个没有后缀,zval *有后缀_P(表明一个指针),最后一个 zval **有后缀_PP(表明两个指针)。
如今,你有足够的信息来独立完成 file_read()和 file_write()函数。这里是一个可能的实现:
PHP_FUNCTION(file_read)
{
int argc = ZEND_NUM_ARGS();
long size;
zval *filehandle = NULL;
FILE *fp;
char *result;
size_t bytes_read;
if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle,&size) == FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
result = (char *) emalloc(size+1);
bytes_read = fread(result, 1, size, fp);
result[bytes_read] = '/0';
RETURN_STRING(result, 0);
}
PHP_FUNCTION(file_write)
{
char *buffer = NULL;
int argc = ZEND_NUM_ARGS();
int buffer_len;
zval *filehandle = NULL;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "rs", &filehandle,&buffer, &buffer_len) == FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
if (fwrite(buffer, 1, buffer_len, fp) != buffer_len) {
RETURN_FALSE;
}
RETURN_TRUE;
}
测试扩展 你如今能够编写一个测试脚原本检测扩展是否工做正常。下面是一个示例脚本,该脚本打开文件test.txt,输出文件类容到标准输出,创建一个拷贝test.txt.new。
<?php
$fp_in = file_open("test.txt", "r") or die("Unable to open input file/n");
$fp_out = file_open("test.txt.new", "w") or die("Unable to open output file/n");
while (!file_eof($fp_in)) {
$str = file_read($fp_in, 1024);
print($str);
file_write($fp_out, $str);
}
file_close($fp_in);
file_close($fp_out);
?>
你可能但愿在扩展里使用全局C变量,不管是独自在内部使用或访问php.ini文件中的INI扩展注册标记(INI在下一节中讨论)。由于PHP是为多线程环境而设计,因此没必要定义全局变量。PHP提供了一个建立全局变量的机制,能够同时应用在线程和非线程环境中。咱们应当始终利用这个机制,而不要自主地定义全局变量。用一个宏访问这些全局变量,使用起来就像普通全局变量同样。
用于生成myfile工程骨架文件的ext_skel脚本建立了必要的代码来支持全局变量。经过检查php_myfile.h文件,你应当发现相似下面的被注释掉的一节,
ZEND_BEGIN_MODULE_GLOBALS(myfile)
int global_value;
char *global_string;
ZEND_END_MODULE_GLOBALS(myfile)
你能够把这一节的注释去掉,同时添加任何其余全局变量于这两个宏之间。文件后部的几行,骨架脚本自动地定义一个MYFILE_G(v)宏。这个宏应当被用于全部的代码,以便访问这些全局变量。这就确保在多线程环境中,访问的全局变量仅是一个线程的拷贝,而不须要互斥的操做。
为了使全局变量有效,最后须要作的是把myfile.c:
ZEND_DECLARE_MODULE_GLOBALS(myfile)
注释去掉。
你也许但愿在每次PHP请求的开始初始化全局变量。另外,作为一个例子,全局变量已指向了一个已分配的内存,在每次PHP请求结束时须要释放内存。为了达到这些目的,全局变量机制提供了一个特殊的宏,用于注册全局变量的构造和析构函数(参考表对宏参数的说明):
ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)
表 ZEND_INIT_MODULE_GLOBALS 宏参数
参数 |
含义 |
module_name
|
与传递给ZEND_BEGIN_MODULE_GLOBALS()宏相同的扩展名称。 |
globals_ctor
|
构造函数指针。在myfile扩展里,函数原形与void php_myfile_init_globals(zend_myfile_globals *myfile_globals)相似 |
globals_dtor
|
析构函数指针。例如,php_myfile_init_globals(zend_myfile_globals *myfile_globals) |
你能够在myfile.c里看到如何使用构造函数和ZEND_INIT_MODULE_GLOBALS()宏的示例。
添加自定义INI指令
INI文件(php.ini)的实现使得PHP扩展注册和监听各自的INI条目。若是这些INI条目由php.ini、Apache的htaccess或其余配置方法来赋值,注册的INI变量老是更新到正确的值。整个INI框架有许多不一样的选项以实现其灵活性。咱们涉及一些基本的(也是个好的开端),借助本章的其余材料,咱们就可以应付平常开发工做的须要。
经过在PHP_INI_BEGIN()/PHP_INI_END()宏之间的STD_PHP_INI_ENTRY()宏注册PHP INI指令。例如在咱们的例子里,myfile.c中的注册过程应当以下:
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("myfile.global_value", "42", PHP_INI_ALL, OnUpdateInt, global_value, zend_myfile_globals, myfile_globals)
STD_PHP_INI_ENTRY("myfile.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_myfile_globals, myfile_globals)
PHP_INI_END()
除了STD_PHP_INI_ENTRY()其余宏也可以使用,但这个宏是最经常使用的,能够知足大多数须要(参看表对宏参数的说明):
STD_PHP_INI_ENTRY(name, default_value, modifiable, on_modify, property_name, struct_type, struct_ptr)
STD_PHP_INI_ENTRY 宏参数表
参数 |
含义 |
name |
INI条目名 |
default_value |
若是没有在INI文件中指定,条目的默认值。默认值始终是一个字符串。 |
modifiable |
设定在何种环境下INI条目能够被更改的位域。能够的值是: • PHP_INI_SYSTEM. 可以在php.ini或http.conf等系统文件更改 • PHP_INI_PERDIR. 可以在 .htaccess中更改 • PHP_INI_USER. 可以被用户脚本更改 • PHP_INI_ALL. 可以在全部地方更改 |
on_modify |
处理INI条目更改的回调函数。你不需本身编写处理程序,使用下面提供的函数。包括: • OnUpdateInt • OnUpdateString • OnUpdateBool • OnUpdateStringUnempty • OnUpdateReal |
property_name |
应当被更新的变量名 |
struct_type |
变量驻留的结构类型。由于一般使用全局变量机制,因此这个类型自动被定义,相似于zend_myfile_globals。 |
struct_ptr |
全局结构名。若是使用全局变量机制,该名为myfile_globals。 |
最后,为了使自定义INI条目机制正常工做,你须要分别去掉PHP_MINIT_FUNCTION(myfile)中的REGISTER_INI_ENTRIES()调用和PHP_MSHUTDOWN_FUNCTION(myfile)中的UNREGISTER_INI_ENTRIES()的注释。
访问两个示例全局变量中的一个与在扩展里编写MYFILE_G(global_value) 和MYFILE_G(global_string)同样简单。
若是你把下面的两行放在php.ini中,MYFILE_G(global_value)的值会变为99。
; php.ini – The following line sets the INI entry myfile.global_value to 99.
myfile.global_value = 99
线程安全资源管理宏
如今,你确定注意到以TSRM(线程安全资源管理器)开头的宏随处使用。这些宏提供给扩展拥有独自的全局变量的可能,正如前面提到的。
当编写PHP扩展时,不管是在多进程或多线程环境中,都是依靠这一机制访问扩展本身的全局变量。若是使用全局变量访问宏(例如MYFILE_G()宏),须要确保TSRM上下文信息出如今当前函数中。基于性能的缘由,Zend引擎试图把这个上下文信息做为参数传递到更多的地方,包括PHP_FUNCTION()的定义。正由于这样,在PHP_FUNCTION()内当编写的代码使用访问宏(例如MYFILE_G()宏)时,不须要作任何特殊的声明。然而,若是PHP函数调用其余须要访问全局变量的C函数,要么把上下文做为一个额外的参数传递给C函数,要么提取上下文(要慢点)。
在须要访问全局变量的代码块开头使用TSRMLS_FETCH()来提取上下文。例如:
void myfunc()
{
TSRMLS_FETCH();
MYFILE_G(myglobal) = 2;
}
若是但愿让代码更加优化,更好的办法是直接传递上下文给函数(正如前面叙述的,PHP_FUNCTION()范围内自动可用)。可使用TSRMLS_C(C表示调用Call)和TSRMLS_CC(CC边式调用Call和逗号Comma)宏。前者应当用于仅当上下文做为一个单独的参数,后者应用于接受多个参数的函数。在后一种状况中,由于根据取名,逗号在上下文的前面,因此TSRMLS_CC不能是第一个函数参。
在函数原形中,能够分别使用TSRMLS_D和TSRMLS_DC宏声名正在接收上下文。
下面是前一例子的重写,利用了参数传递上下文。
void myfunc(TSRMLS_D)
{
MYFILE_G(myglobal) = 2;
}
PHP_FUNCTION(my_php_function)
{
…
myfunc(TSRMLS_C);
…
}
总 结
如今,你已经学到了足够的东西来建立本身的扩展。本章讲述了一些重要的基础来编写和理解PHP扩展。Zend引擎提供的扩展API至关丰富,使你可以开发面向对象的扩展。几乎没有文档谈几许多高级特性。固然,依靠本章所学的基础知识,你能够经过浏览现有的原码学到不少。
更多关于信息能够在PHP手册的扩展PHP章节http://www.php.net/manual/en/zend.php中找到。另外,你也能够考虑加入PHP开发者邮件列表internals@ lists.php.net,该邮件列表围绕开发PHP 自己。你还能够查看一下新的扩展生成工具——PECL_Gen(http://pear.php.net/package/PECL_Gen),这个工具正在开发之中,比起本章使用的ext_skel有更多的特性。