if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)return ; 注意 自動生成されたコードは、関数の戻り値 FAILUER (成功は SUCCESS) を検出して、関数が成功したかどうかを判断します。失敗した場合はすぐに戻り、zend_parse_parameters() が警告メッセージをトリガーします。この関数は文字列 l と整数 n を受け取ることを目的としているため、その型標識として「sl」が指定されています。 s には 2 つのパラメータが必要なので、参照 char * と int (str と str_len) を zend_parse_parameters() 関数に渡します。可能な限り、関数がバイナリ セーフな環境で動作するように、コード内では常に文字列長 str_len を使用することを忘れないでください。関数がバイナリ文字列を処理しなくても構わない場合を除き、strlen() と strcpy() を使用しないでください。バイナリ文字列は、null を含む文字列です。バイナリ形式には、画像ファイル、圧縮ファイル、実行可能ファイル、その他多くのファイルが含まれます。 "l" は引数を 1 つだけ取るので、それを n への参照として渡します。わかりやすくするために、スケルトン スクリプトによって生成される C 変数名は関数プロトタイプ定義ファイル内のパラメーター名と同じですが、これは必須ではありませんが、実際にはそうすることが推奨されます。
/* {{{ proto string self_concat(string str, int n)
*/
PHP_FUNCTION(self_concat)
}
char *str = NULL
int argc = ZEND_NUM_ARGS(); long n;
char *result; /* 結果の文字列を指す */
char *ptr; /* 結果の文字列の長さ */
if ( zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
/* 結果の長さを計算する */
result_length = (str_len * n);
/* 結果にメモリを割り当てるresult = (char * ) emalloc(result_length + 1);
/* 結果の先頭のポイント */
ptr = result;
/* str を結果にコピーします */
memcpy (ptr, str, str_len);
/* 次に書き込みたい位置を指すように ptr をインクリメントします。 */
ptr += str_len;
/* 結果は常に null で終了します。バイナリ文字列の場合 */
*ptr = '
resource file_open(string filename, string mode)
file_open() //2 つの文字列 (ファイル名とモード) を受け取り、ファイルのリソース ハンドルを返します。
bool file_close(resource filehandle)
file_close() //リソース ハンドルを受け取り、操作が成功したかどうかを示す true/false を返します。
string file_read(resource filehandle, int size)
file_read() //リソースハンドルと読み取った総バイト数を受け取り、読み取った文字列を返します。
bool file_write(resource filehandle, stringbuffer)
file_write() //リソース ハンドルと書き込まれた文字列を受け取り、操作が成功したかどうかを示す true/false を返します。
bool file_eof(resource filehandle)
file_eof() //リソースハンドルを受け取り、ファイルの終わりに到達したかどうかを示す true/false を返します。
したがって、関数定義ファイル - ext/ ディレクトリに myfile.def として保存 - には次の内容が含まれます:
コードをコピーします コードは次のとおりです:
resource file_open(stringファイル名、文字列モード)
bool file_close(リソース ファイルハンドル)
string file_read(リソース ファイルハンドル、int サイズ)
bool file_write(リソース ファイルハンドル、文字列バッファ)
bool file_eof(リソース ファイルハンドル)
次のステップ、 ext_skel スクリプトを使用します。 ext./ 元のコード ディレクトリで次のコマンドを実行します。
コードをコピーします。 コードは次のとおりです。
./ext_skel --extname=myfile --proto=myfile.de
次に、前の例に従って、新しく作成したスクリプトをコンパイルする手順を示します。 FETCH_RESOURCE() マクロ行を含むコンパイル エラーが発生するため、スケルトン スクリプトは正常にコンパイルされません。スケルトン拡張機能をスムーズにコンパイルするには、エラー行をコメント アウトするだけです [3]。
リソース
リソースは、あらゆる情報を保持できる抽象データ構造です。前述したように、この情報には通常、ファイル ハンドル、データベース接続構造、その他の複雑なタイプのデータが含まれます。
リソースを使用する主な理由は次のとおりです: リソースは集中キューによって管理されており、PHP 開発者がスクリプトで明示的に解放しない場合には自動的に解放されます。
たとえば、スクリプトを作成し、スクリプト内で mysql_connect() を呼び出して MySQL 接続を開くとしますが、データベース接続リソースが使用されなくなった場合、mysql_close() は呼び出されません。 PHP では、リソース メカニズムがリソースをいつ解放すべきかを検出し、現在のリクエストの終了時、または通常はそれ以前にリソースを解放できます。これにより、メモリ リークを軽減するための「防弾」メカニズムが実現します。このようなメカニズムがないと、いくつかの Web リクエストの後、Web サーバーで多くのメモリ リソースがリークし、サーバーのクラッシュやエラーが発生する可能性があります。
リソースタイプを登録する
リソースを使用するには? Zend Engine を使用すると、リソースの使用が非常に簡単になります。最初に行う必要があるのは、リソースをエンジンに登録することです。この API 関数を使用します:
int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)
この関数はリソース タイプ 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_ent_entry *RSRC TSRMLS_DC) {file *fp = (file *) rsrc- & gt;
}行 行 行 行php_minit_function () に追加すると、以下のコードのようになります:
コードをコピーします: 次のように
コード:
Php_minit_function (MyFile) {
/* エントリがある場合は、行 D_init_module_globals ( myfile, php_myfile_init_globals,NULL); REGISTER_INI_ENTRIES(); */
le_myfile = zend_register_list_destructors_ex(myfile_dtor,"standard-c-file", module_number);
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値にアクセスするためのマクロ
すべてのマクロには 3 つの形式があります。1 つは zval を受け入れ、もう 1 つは zval *s を受け入れ、最後のマクロは zval **s を受け入れます。それらの違いは、最初のものには接尾辞がなく、zval * には接尾辞 _P (ポインターを表す) があり、最後の zval ** には接尾辞 _PP (2 つのポインターを表す) が付いていることです。
これで、file_read() 関数と file_write() 関数を個別に完了するのに十分な情報が得られました。考えられる実装は次のとおりです。
コードをコピーします。 コードは次のとおりです。
PHP_FUNCTION(file_read){
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL; fp;
char *result;
size_t bytes_read;
if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle,&size) == FAILURE) {
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "標準 -cfile", le_myfile);
result = (char *) emalloc(size+1);
result[bytes_read] = '
表 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 = 9
线程安全资源管理宏
现在,你肯定注意到以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有更多的特性。
此外你还可以关注风雪之隅, 会有更多相关知识更新.
词汇表
binary safe |
二进制安全 |
context |
上下文 |
extensions |
扩展 |
entry |
条目 |
skeleton |
骨架 |
Thread-Safe Resource Manager TSRM |
线程安全资源管理器 |
[1] 翻訳者が書いた内容を参照してください。
[2] 翻訳者: phpcli プログラムを使用して、コンソールで php ファイルを実行できます。
[3] 翻訳者: 生成された FETCH_RESOURCE() マクロ パラメーターがいくつかの「???」であることがわかります。
http://www.bkjia.com/PHPjc/325828.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/325828.html技術記事英語版のダウンロード: PHP 5 Power Programming http://www.jb51.net/books/61020.html PHP の成功の主な理由の 1 つは、利用可能な拡張機能が多数あることです。 Web 開発者は何があっても...