Contents
一. 快速上手
建立php扩展, 我们可以直接使用源代码目录下的ext_skel生成一个初步的框架. 第一步我们需要给他函数定义文件, 该函数定义文件定义了扩展对外提供的函数原形。函数定义文件的一般格式是一个函数一行。你可以定义可选参数和使用大量的PHP类型,包括: bool, float, int, array等。
resource SFileOpenArchive(string name, int priority, int flags)
保存为myfunctions.def文件至PHP原代码目录树下。
该是通过扩展骨架(skeleton)构造器运行函数定义文件的时机了。该构造器脚本叫ext_skel,放在PHP原代码目录树的ext/目录下(PHP原码主目录下的README.EXT_SKEL提供了更多的信息)。假设你把函数定义保存在一个叫做myfunctions.def的文件里,而且你希望把扩展取名为myfunctions,运行下面的命令来建立扩展骨架.
./ext_skel --extname=myfunctions --proto=myfunctions.def
二. 资源
资源是一个能容纳任何信息的抽象数据结构。正如前面提到的,这个信息通常包括例如文件句柄、数据库连接结构和其他一些复杂类型的数据。
使用资源的主要原因是因为:资源被一个集中的队列所管理,该队列可以在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; }
注意到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函数
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; } [/c] <h2>删除一个资源</h2> 通常使用下面这个宏删除一个资源: [c] 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; }
测试
$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);
0 Comments.