ホームページ >php教程 >php手册 >C/C を使用して PHP を拡張し、PHP に機能を追加します

C/C を使用して PHP を拡張し、PHP に機能を追加します

WBOY
WBOYオリジナル
2016-06-13 11:58:35837ブラウズ

英語版のダウンロード: 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; }
return $result;
}
self_concat("One", 3) は "OneOneOne" を返します。
self_concat("One", 1) は "One" を返します。

何らかの奇妙な理由で、この関数を時々呼び出す必要があり、また、長い文字列と大きな値 n を関数に渡す必要があるとします。これは、スクリプト内でかなりの量の文字列の連結とメモリの再割り当てが行われ、スクリプトの実行が大幅に遅くなる可能性があることを意味します。結果の文字列を保存するために大きくて十分なメモリをより速く割り当てて、$string を n 回繰り返すことができる関数があれば、ループの繰り返しごとにメモリを割り当てる必要はなくなります。


拡張機能の関数を作成する最初のステップは、拡張機能によって提供される関数プロトタイプを定義する関数定義ファイルを作成することです。この例では、関数を定義する関数プロトタイプ self_concat() が 1 行だけあります:




コードをコピー

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



関数定義ファイルの一般的な形式は、1 行に 1 つの関数です。オプションのパラメーターを定義し、bool、float、int、array などの多数の PHP 型を使用できます。


PHP のオリジナル コード ディレクトリ ツリーに myfunctions.def ファイルとして保存します。

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




Copy Code

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


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



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

• ロード可能なモジュールまたは DSO (Dynamic Shared Object) として
• 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 Include myfunctions support]



あとは、 PHP オリジナルのコード ツリーのルート ディレクトリで ./buildconf を実行すると、新しい構成オプションが構成ファイルに含まれているかどうかを ./configure –help の出力で確認できます。 --enable-myfunctions で PHP を再構成します。最後に、

ext_skel で 2 つの PHP 関数を追加します。 () 関数と、myfunction が PHP にコンパイルされているかどうかを検出するために使用されるconfirm_myfunctions_compiled() 関数は、PHP 拡張機能の開発が完了した後に削除できます。

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

printconfirm_myfunctions_compiled("myextension");


これを実行します。スクリプトは次のような出力を生成します:



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

"おめでとうございます! ext/myfunctions

config.m4 が正常に変更されました。モジュール myfunctions が PHP にコンパイルされました。


さらに、ext_skel スクリプトは myfunctions.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(); str_len;
long n;
if ( zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return
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{
long lval; >double dval;
char *val;
}str;
zend_object_value



この例では、zval コンテナーを使用する代わりに、基本型で zend_parse_parameters() を呼び出し、ネイティブ C 型の関数パラメーターの値を取得します。

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 は同じ値を取得します:





コードをコピー

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

self_concat("321", 5); self_concat("321", "5"); str は文字列 "321"、str_len は 3、n は 5 を指します。



接続文字を実装するコードを作成する PHP 関数に文字列を返す前に、メモリ管理と PHP 内から関数値を返すために使用される API という 2 つの重要なトピックについて説明する必要があります。


メモリ管理


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


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

emalloc(size_t size); efree(void *ptr); 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 str_len
char *result;結果の文字列へ * /
char *ptr; /* コピー先の次の場所のポイント */
int result_length; /* 結果の文字列の長さ */
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
/* 結果の長さを計算します */
result_length = (str_len * n); */
result = (char *) emalloc(result_length 1);
/* 結果の先頭のポイント */
ptr =
while (n--) {
/* str を結果にコピーします */
memcpy(ptr, str, str_len);
/* 次に書き込む位置を指すように ptr をインクリメントします */
ptr = str_len; 🎜>}
/* 結果は null で終了します。文字列はバイナリ文字列であっても常に 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 として保存 - には次の内容が含まれます:

Copy code コードは次のとおりです。


resource file_open(string filename, string mode)

bool file_close(resource filehandle)

string file_read (リソース ファイルハンドル, int サイズ)

bool file_write(リソース ファイルハンドル, 文字列バッファ)

bool file_eof(リソース ファイルハンドル)


次のステップuse 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_list_entry *rsrc TSRMLS_DC){
FILE *fp = (FILE *) rsrc->ptr;
fclose(fp);

PHP_MINIT_FUNCTION() に登録行を追加すると、次のコードのようになります。


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

PHP_MINIT_FUNCTION(myfile){
/* INI エントリがある場合は、これらの行のコメントを解除します。

REGISTER_INI_ENTRIES();
*/

le_myfile = zend_register_list_destructors_ex(myfile_dtor,N) ULL,"標準-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 値にアクセスするためのマクロ

すべてのマクロには 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;
char *result; , " 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] = '
表 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() 呼び出しのコメントをそれぞれ解除する必要があります。

2 つのグローバル変数の例の 1 つにアクセスするのは、拡張子に MYFILE_G(global_value) と MYFILE_G(global_string) を記述するだけです。

php.ini に以下の 2 行を記述すると、MYFILE_G (global_value) の値は 99 になります。

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


; 次の行は INI エントリ myfile.global_value を設定します。 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) マクロと TSRMLS_CC (呼び出しとコンマの CC) マクロを使用できます。前者はコンテキストが単一の引数として受け取られる場合にのみ使用する必要があり、後者は複数の引数を受け入れる関数に使用する必要があります。後者の場合、命名に従ってコンマがコンテキストの前に置かれるため、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 は非常に豊富で、オブジェクト指向の拡張機能を開発できます。多くの高度な機能に関するドキュメントはほとんどありません。もちろん、この章で学んだ基本知識に基づいて、既存のソース コードを参照することで多くのことを学ぶことができます。

詳細については、http://www.php.net/manual/en/zend.php にある PHP マニュアルの「拡張 PHP」の章を参照してください。あるいは、PHP 自体の開発に重点を置いている PHP 開発者メーリング リスト Internals@lists.php.net に参加することも検討してください。新しい拡張機能生成ツール 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() マクロ パラメーターがいくつかの「???」であることがわかります。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。