C/C++ で PHP を拡張し、php_PHP チュートリアルに機能を追加します。

WBOY
リリース: 2016-07-21 15:16:50
オリジナル
857 人が閲覧しました

英語版のダウンロード: PHP 5 Power Programming http://www.jb51.net/books/61020.html

PHP の成功の主な理由の 1 つは、利用可能な拡張機能が多数あることです。 Web 開発者がどのようなニーズを持っているかに関係なく、それらは PHP 配布パッケージに含まれている可能性が最も高くなります。 PHP 配布パッケージには、さまざまなデータベース、グラフィックス ファイル形式、圧縮、および XML テクノロジ拡張機能をサポートする多くの拡張機能が含まれています。
拡張 API の導入により、PHP3 は大幅に進歩しました。拡張 API メカニズムにより、PHP 開発コミュニティは数十の拡張機能を簡単に開発できるようになりました。 2 つのバージョンを経た現在でも、API は PHP3 と非常によく似ています。拡張機能の主なアイデアは、PHP の内部メカニズムとスクリプト エンジン自体を拡張機能作成者から可能な限り隠し、開発者が API に精通していることのみを要求することです。

独自の PHP 拡張機能を作成する理由は 2 つあります。 1 つ目の理由は、PHP がまだサポートしていないテクノロジーをサポートする必要があるということです。これには通常、既製の C ライブラリをラップして PHP インターフェイスを提供することが含まれます。たとえば、FooBase というデータベースが市場に投入される場合、PHP から FooBase の C 関数ライブラリを呼び出すのに役立つ PHP 拡張機能を作成する必要があります。この作業は 1 人だけで行っても、(そうするなら) PHP コミュニティ全体で共有することもできます。 2 番目の、あまり一般的ではない理由は、パフォーマンスまたは機能上の理由から、ビジネス ロジックを作成する必要があることです。

上記の 2 つの理由があなたには関係がなく、自分には冒険心がないと感じる場合は、この章を飛ばしても問題ありません。

この章では、いくつかの拡張 API 関数を使用して、比較的単純な PHP 拡張を作成する方法を説明します。カスタム PHP 拡張機能の開発を検討しているほとんどの開発者にとって十分な情報が含まれています。プログラミング コースを学習する最良の方法の 1 つは、この章への手がかりとなる非常に単純な例のいくつかに取り組むことです。基本を理解したら、ドキュメントやソース コードを読んだり、メーリング リストのニュースグループのディスカッションに参加したりして、インターネット上で知識を深めていくことができます。したがって、この章では入門に重点を置きます。 UNIX では、ext_skel と呼ばれるスクリプトを使用して拡張機能のスケルトンが作成されます。スケルトン情報は、拡張機能のインターフェイスを記述する定義ファイルから取得されます。したがって、スケルトンを構築するには UNIX を使用する必要があります。 Windows 開発者は、ext_skel の代わりに Windows ext_skel_win32.php を使用できます。

ただし、この章で開発した拡張機能を使用して PHP をコンパイルする手順は、UNIX コンパイル システムのみを対象としています。この章の API の説明はすべて、UNIX および Windows 用に開発された拡張機能に関連しています。

この章を読み終えると、

•シンプルなビジネス ロジック拡張機能を構築する方法がわかります。
•C 関数ライブラリ、特に fopen() などのいくつかの標準的な C ファイル操作関数のパッケージ拡張を提案します。
クイック スタート
このセクションでは、スクリプト エンジンの基本構造に関する知識を紹介するのではなく、拡張機能について直接説明します。したがって、拡張機能の全体的な理解がすぐに得られなくても心配する必要はありません。 Web サイトを開発していて、文字列を n 回繰り返す関数が必要だとします。以下は PHP で書かれた例です。

コードをコピー コードは次のとおりです。

function self_concat($string, $n){
$result = "" for($i = 0; $i $result .= $string;
}
self_concat("One", 3) は "OneOneOne" を返します。 , 1) は "One" を返します



何らかの奇妙な理由で、この関数を時々呼び出す必要があり、また、長い文字列と大きな値 n を関数に渡す必要があるとします。これは、スクリプト内でかなりの量の文字列の連結とメモリの再割り当てが行われ、スクリプトの実行が大幅に遅くなる可能性があることを意味します。結果の文字列を保持するために十分な量のメモリをより迅速に割り当てて、$string を n 回繰り返すことができる関数があれば、ループの繰り返しごとにメモリを割り当てる必要はなくなります。
拡張機能の関数を作成する最初のステップは、拡張機能によって提供される関数プロトタイプを定義する関数定義ファイルを作成することです。この例では、関数を定義する関数プロトタイプ self_concat() が 1 行だけあります:




コードをコピーします
コードは次のとおりです: string self_concat(string str, int n)

関数定義ファイルの一般的な形式は、1 行の関数です。オプションのパラメーターを定義し、bool、float、int、array などの多数の PHP 型を使用できます。
PHP のオリジナル コード ディレクトリ ツリーに myfunctions.def ファイルとして保存します。

スケルトン コンストラクターを拡張して関数定義ファイルを実行します。コンストラクター スクリプトは ext_skel と呼ばれ、PHP オリジナル コード ディレクトリ ツリーの ext/ ディレクトリに配置されます (詳細については、PHP オリジナル コード メイン ディレクトリの README.EXT_SKEL を参照してください)。関数定義を myfunctions.def というファイルに保存し、拡張機能に myfunctions という名前を付けたいとします。次のコマンドを実行して拡張機能スケルトンを作成します




コードをコピーします
コードは次のとおりです:

./ext_skel --extname=myfunctions --proto=myfunctions.de


このコマンドは、ext/ ディレクトリの下に myfunctions/ ディレクトリを作成します。最初に行うことは、実際の C コードを作成してテストできるようにスケルトンをコンパイルすることです。拡張機能をコンパイルするには 2 つの方法があります:

• ロード可能なモジュールまたは DSO (Dynamic Shared Object) として使用する
• PHP への静的コンパイル
C/C++ で PHP を拡張し、php_PHP チュートリアルに機能を追加します。
PHP 拡張機能開発マップ

2 番目の方法は簡単に開始できるため、この章では次の方法を使用します。静的コンパイル。ロード可能な拡張モジュールのコンパイルに興味がある場合は、PHP ソース コードのルート ディレクトリにある README.SELF-CONTAINED_EXTENSIONS ファイルを読むことができます。拡張機能をコンパイルするには、拡張機能ディレクトリ ext/myfunctions/ 内の config.m4 ファイルを変更する必要があります。この拡張機能は外部 C ライブラリをラップしません。 --enable-myfunctions 構成スイッチのサポートを PHP ビルド システムに追加する必要があります (--with-extension スイッチは、ユーザーがパスを指定する必要がある拡張機能に使用されます)関連する C ライブラリ)。この構成を有効にするには、次の 2 行で自動生成されたコメントを削除します。

コードをコピーします コードは次のとおりです:

./ext_skel --extname=myfunctions --proto=myfunctions.def
PHP_ARG_ENABLE(myfunctions、myfunctions サポートを有効にするかどうか、
[ --enable -myfunctions myfunctions サポートを含める]


あとは、PHP オリジナルのコード ツリーのルート ディレクトリで ./buildconf を実行するだけです。このコマンドにより、新しい構成スクリプトが生成されます。 ./configure –help 出力情報が設定ファイルに含まれています。最後に、make
ext_skel を使用して PHP を再コンパイルします。拡張機能のスケルトンに追加されるのは、実装される self_concat() 関数と、myfunctions が PHP にコンパイルされるかどうかを確認する verify_myfunctions_compiled() 関数です。後者は、PHP 拡張機能の開発が完了した後に削除できます。

コードをコピーします。は次のとおりです:
printconfirm_myfunctions_compiled("myextension");
このスクリプトを実行すると、次のような出力が表示されます:



コードをコピーします
コードは次のとおりです。次のように:

「おめでとうございます! ext/myfunctions config.m4 が正常に変更されました。モジュール myfunctions が 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;
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str_len, &n) == FAILURE)
php_error( E_WARNING, "self_concat: まだ実装されていません");
}
/* }}} */



自動的に生成される 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 エンジンの値コンテナであることを知っておく必要があります [1]。変数がブール型、文字列、またはその他の型であるかどうかに関係なく、その情報は常に zval 共用体に含まれます。この章では、zval に直接アクセスせず、追加のマクロを介して操作します。以下は、後続のコードをよりよく理解できるように、C での zval を多少なりとも示しています。

コードをコピーします: typedef Union _zval{
double dval;
char *val;
HashTable *ht ;
zend_object_value obj;
}zval;



この例では、zval コンテナを使用する代わりに、基本型で zend_parse_parameters() を呼び出します。

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 には 2 つのパラメータが必要なので、参照 char * と int (str と str_len) を zend_parse_parameters() 関数に渡します。可能な限り、関数がバイナリ セーフな環境で動作するように、コード内では常に文字列長 str_len を使用することを忘れないでください。関数がバイナリ文字列を処理しなくても構わない場合を除き、strlen() と strcpy() を使用しないでください。バイナリ文字列は、null を含む文字列です。バイナリ形式には、画像ファイル、圧縮ファイル、実行可能ファイル、その他多くのファイルが含まれます。 "l" は引数を 1 つだけ取るので、それを n への参照として渡します。わかりやすくするために、スケルトン スクリプトによって生成される C 変数名は関数プロトタイプ定義ファイル内のパラメーター名と同じですが、これは必須ではありませんが、実際にはそうすることが推奨されます。
変換ルールに戻ります。 self_concat() 関数への次の 3 つの呼び出しは、str、str_len、n に同じ値を取得します。



コードをコピーする

コードは次のとおりです。 (321, "5");
self_concat("321", "5");
str は文字列 "321"、str_len は 3、n は 5 を指します。
str は文字列 "321"、str_len を指します。 3 に等しく、n は 5 に等しい PHP の関数に返される接続文字列を実装するコードを記述する前に、メモリ管理と関数値を返すために使用される API という 2 つの重要なトピックについて話しておく必要があります。 PHP内から。

メモリ管理


ヒープからメモリを割り当てるための PHP API は、標準の C API とほぼ同じです。拡張機能を記述するときは、C に対応する次の API 関数を使用します (説明は不要です):


コードをコピーします

コードは次のとおりです:

emalloc(size_t size);
efree(void *) ptr);
ecalloc(size_t nmemb, size_t size);
estrdup(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(); 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() マクロ パラメーターがいくつかの「???」であることがわかります。

www.bkjia.comtru​​ehttp://www.bkjia.com/PHPjc/325828.html技術記事英語版のダウンロード: PHP 5 Power Programming http://www.jb51.net/books/61020.html PHP の成功の主な理由の 1 つは、利用可能な拡張機能が多数あることです。 Web 開発者は何があっても...
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!