使用C开发包裹第三方的PHP扩展

一. 快速上手

建立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_resultzval *, which should be set with the registered resource information. zval * 设置为已注册资源信息
rsrc_pointerPointer to our resource data. 资源数据指针
rsrc_typeThe 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_typersrc的类型,用于在内部把资源转换成正确的类型
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;
}
&#91;/c&#93;

<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_PPZval类型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);

本文节选: http://www.laruence.com/2009/04/28/719.html

Leave a Comment

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: