原文: http://devzone.zend.com/public/view/tag/ExtensionPart I: PHP と Zend の紹介 拡張機能の作成 I - PHP と Zend の入門 http://devzone.zend.com/article/ 1021-Extension -Writing-Part-I- Introduction-to-PHP-and-ZendPart II: パラメーター、配列、および ZVAL -Part-II-Parameters-Arrays-and-ZVALsPart II: パラメーター、配列、および ZVAL [続き]拡張機能の作成_II - パラメーター、配列、および ZVALs[続き]http://devzone.zend.com/article/1023-Extension-Writing-Part-II-Parameters-Arrays-and-ZVALs-Continuedパート III: リソース拡張機能の作成_III - リソースhttp://devzone.zend.com/article/1024-Extension-Writing-Part-III-Resources
拡張機能の作成 I: PHP および Zend 拡張機能のチュートリアル by Sara Golemon 2005 年 2 月 28 日月曜日
はじめに拡張子?ライフサイクル メモリ割り当て ビルド環境をセットアップする Hello World 拡張機能をビルドする 初期セットアップ (INI) グローバル値 グローバル値としての初期セットアップ (INI) チェック (コード) 整合性 次は何ですか?
はじめに このチュートリアルを読んでいるあなたは、PHP 言語の拡張機能の作成に興味があるかもしれません。そうでなかった場合は...まあ、おそらくこの関心について知らなかったかもしれませんが、この記事を終えるまでにわかるでしょう。このチュートリアルでは、読者が PHP 言語とそのインタプリタが実装されている言語について基本的に精通していることを前提としています。C. まず、PHP 拡張機能を作成する理由を指定します。 PHP 言語自体の抽象化レベルによって制限されているため、特定のライブラリやオペレーティング システム固有の呼び出しに直接アクセスすることはできません。通常とは異なる方法で PHP の動作をカスタマイズしたいとします。既存の PHP コードがいくつかありますが、その方が高速で小さく、メモリ消費量も少なくなることがわかっています。素晴らしいコードが販売されており、購入者はそれを使用できますが、重要なことに、ソース コードを見ることができません。これらはすべて非常に正当な理由ですが、拡張機能を作成する前に、まず拡張機能とは何かを理解する必要があります。拡張機能とは何ですか? PHP を使用したことがある場合は、間違いなく拡張機能を使用したことがあるはずです。いくつかの例外を除いて、すべてのユーザー空間関数は異なる拡張機能で編成されます。これらの関数の多くは標準拡張機能となり、合計で 400 を超えます。 PHP 自体には 86 個の拡張機能 (元のテキストが書かれたとき - 翻訳注釈) が付属しており、それぞれに平均約 30 個の関数が含まれています。数学演算用の関数は約 2500 あります。これだけでは十分ではないかのように、PECL リポジトリはさらに 100 を超える拡張機能を提供しており、さらに多くの拡張機能がインターネット上で見つかります。 「拡張機能以外に何があるの?」という疑問が聞こえます。 「拡張機能の中身は何ですか? PHP の「コア」とは何ですか? PHP のコアは 2 つの部分で構成されます。最下層は Zend Engine (ZE) です。 ZE は、人間が読めるスクリプトを機械が読めるシンボルに解析し、プロセス空間内でこれらのシンボルを実行します。 ZE は、メモリ管理、変数のスコープ設定、およびスケジューラ呼び出しも処理します。もう 1 つの部分は PHP カーネルで、SAPI 層 (通常は Apache、IIS、CLI、CGI などのホスト環境に関係するサーバー アプリケーション プログラミング インターフェイス) をバインドし、SAPI 層との通信を処理します。また、フロー層が fopen()、fread()、fwrite() などのユーザー空間関数をファイルやネットワーク I/O に接続するのと同様に、safe_mode および open_basedir 検出のための一貫した制御層も提供します。ライフサイクル たとえば /usr/local/apache/bin/apachectl start に応答して特定の SAPI が開始されると、PHP はカーネル サブシステムの初期化から開始します。スタートアップ ルーチンの終わり近くで、各拡張機能のコードがロードされ、そのモジュール初期化ルーチン (MINIT) が呼び出されます。これにより、各拡張機能は内部変数の初期化、リソースの割り当て、リソース ハンドラーの登録、および独自の関数の ZE への登録が可能になり、スクリプトが関数を呼び出すときにどのコードを実行するかを ZE が認識できるようになります。次に、PHP は、SAPI レイヤーがページの処理を要求するのを待ちます。 CGI や CLI などの SAPI の場合、これはただちに 1 回だけ発生します。 Apache、IIS、またはその他の成熟した Web サーバー SAPI の場合、これはリモート ユーザーがページをリクエストするたびに発生するため、何度も (場合によっては同時に) 繰り返されることになります。リクエストの生成方法に関係なく、PHP はまず ZE にスクリプトのランタイム環境のセットアップを依頼し、次に各拡張機能のリクエスト初期化 (RINIT) 関数を呼び出します。 RINIT は、拡張機能に特定の環境変数を設定したり、要求に応じてリソースを割り当てたり、監査などの他のタスクを実行したりする機会を与えます。セッション拡張における RINIT の役割の典型的な例は、session.auto_start オプションが有効になっている場合、RINIT がユーザー空間の session_start() 関数と事前にアセンブルされた $_SESSION 変数を自動的にトリガーすることです。リクエストが開始されると、ZE が制御を引き継ぎ、PHP スクリプトをシンボルに変換し、最終的にはオペコードに変換して段階的に実行します。オペコードが拡張関数を呼び出す必要がある場合、ZE はパラメーターを関数にバインドし、関数が完了するまで一時的に制御を放棄します。スクリプトの実行後、PHP は各拡張機能のシャットダウン要求 (RSHUTDOWN) 関数を呼び出して、最終的なクリーンアップ作業 (セッション変数のディスクへの保存など) を実行します。次に、ZE はクリーンアップ プロセス (ガベージ コレクション) を実行し、前のリクエストで使用されたすべての変数を効果的に unset() します。完了すると、PHP は SAPI からの他のドキュメント要求またはシャットダウン信号を待ち続けます。 CGI や CLI などの SAPI の場合、「次のリクエスト」がないため、SAPI はすぐにシャットダウンを開始します。シャットダウン中、PHP は各拡張機能を再度繰り返し、モジュール シャットダウン (MSHUTDOWN) 関数を呼び出し、最終的に独自のカーネル サブシステムをシャットダウンします。このプロセスは最初は難しく聞こえるかもしれませんが、拡張機能を実際に動作させると、徐々に理解できるようになります。メモリ割り当て 不適切に作成された拡張機能によるメモリの損失を防ぐために、ZE は追加のフラグを使用して独自の内部メモリ マネージャーを実装し、永続性を示します。永続的に割り当てられたメモリは、単一のリクエストよりも長く持続することを目的としています。対照的に、リクエスト中の非永続的な割り当ては、解放 (メモリ) 関数が呼び出されるかどうかに関係なく、リクエストの終了時に解放されます。たとえば、ユーザー空間変数は、リクエストが終了すると役に立たなくなるため、非永続変数として割り当てられます。ただし、理論的には、拡張機能は ZE に依存して、ページ リクエストの終了時に非永続メモリを自動的に解放できますが、これはお勧めできません。割り当てられたメモリは長期間再利用されないため、それに関連付けられたリソースが適切に閉じられない可能性があります。また、口を拭かずに食事をするのは悪い習慣です。割り当てられたすべてのデータが適切にクリーンアップされていることを確認するのは、実際には非常に簡単であることが後でわかります。従来のメモリ割り当て関数 (外部ライブラリでのみ使用する必要がある) と、PHP/ZE の永続メモリ割り当て関数および非永続メモリ割り当て関数を簡単に比較してみましょう。従来の非永続的永続 malloc(count)calloc(count, num) emalloc(count)ecalloc(count, num) pemalloc(count, 1)*pecalloc(count, num, 1) strdup(str)strndup(str, len) estrdup(str)estrndup(str, len) pestrdup(str, 1)pemalloc() & memcpy() free(ptr) efree(ptr) pefree(ptr, 1)realloc(ptr, newsize) erealloc(ptr, newsize) ) perrealloc(ptr, newsize, 1)malloc(count * num + extr)**safe_emalloc(count, num, extr)safe_pemalloc(count, num, extr)* pemalloc() ファミリには、次のことを可能にする「persistent」フラグが含まれています。 implement 対応する非永続関数の機能。例: emalloc(1234) は pemalloc(1234, 0) と同じです。 **safe_emalloc() と (PHP 5 では)safe_pemalloc() は、整数のオーバーフローを防ぐために追加のチェックを実行します。
ビルド環境のセットアップ PHP と Zend Engine の内部動作について少し学習したので、さらに詳しく掘り下げて何かを構築し始めたいと思うでしょう。これを行う前に、必要なビルド ツールをいくつか集め、目的に適した環境をセットアップする必要があります。まず、PHP 自体と、それに必要なビルド ツールのセットが必要です。ソースから PHP を構築するのが初めての場合は、http://www.php.net/install.unix を参照することをお勧めします。 (Windows 用の PHP 拡張機能の開発については、今後の記事で取り上げます)。ただし、PHP のバイナリ ディストリビューションを使用するのは少し危険であり、これらのバージョンでは、開発中に便利な ./configure の 2 つの重要なオプションが無視される傾向があります。最初の --enable-debug。このオプションは、追加のシンボル情報を PHP 実行可能ファイルにコンパイルします。これにより、セグメンテーション違反が発生した場合、そこからコア ダンプ ファイルを取得し、gdb を使用して、セグメンテーション違反が発生した場所と理由をトレースして見つけることができます。別のオプションは PHP のバージョンによって異なります。このオプションの名前は、PHP 4.3 では --enable-experimental-zts、PHP 5 以降のバージョンでは --enable-maintainer-zts です。このオプションを使用すると、PHP はマルチスレッド環境で実行されていると認識し、非マルチスレッド環境では無害な一般的なプログラム エラーを捕捉できるようになりますが、拡張機能をマルチスレッド環境で使用するのは安全ではなくなります。これらの追加オプションを使用して PHP をコンパイルし、開発サーバー (またはワークステーション) にインストールしたら、最初の拡張機能をそれに追加できます。 Hello World 必要な Hello World プログラムを完全に無視できるプログラミング入門はありますか?この例では、作成する拡張機能は、「Hello World」を含む文字列を返す単純な関数をエクスポートします。 PHP では、これを行うことができます: 次に、これを PHP 拡張機能に転送します。まず、PHP ソース ツリーのディレクトリ ext/ に hello という名前のディレクトリを作成し、そのディレクトリに chdir を作成します。実際、このディレクトリは PHP ソース ツリーの内外のどこにでも配置できますが、今後の記事で登場する無関係な概念を説明するためにここに配置することにします。ここで 3 つのファイルを作成する必要があります。hello_world 関数を含むソース ファイル、PHP が拡張機能をロードするために使用する参照を含むヘッダー ファイル、および phpize が拡張機能のコンパイルを準備するために使用する設定ファイルです。config.m4PHP_ARG_ENABLE(hello, Hello World サポートを有効にするかどうか,[ --enable-hello Hello World サポートを有効にする])if test "$PHP_HELLO" = "yes";次に AC_DEFINE(HAVE_HELLO, 1, [Hello World があるかどうか]) PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)fi php_hello.h#ifndef PHP_HELLO_H#define PHP_HELLO_H 1#define PHP_HELLO_WORLD_VERSION "1.0"#define PHP_HELLO_WORLD_EXTNAME "hello 「PHP_FUNCTION」 (hello_world);extern zend_module_entry hello_module_entry;#define phpext_hello_ptr &hello_module_entry#endif hello.c#ifdef HAVE_CONFIG_H#include "config.h"#endif#include "php.h"#include "php_hello.h"static function_entry hello_functions[] = { PHP_FE(hello_world, NULL){NULL, NULL, NULL}};zend_module_entry hello_module_entry = {#if ZEND_MODULE_API_NO >= 20010901STANDARD_MODULE_HEADER,#endifPHP_HELLO_WORLD_EXTNAME,hello_functions,NULL,NULL,NULL,NULL,NULL,#if ZEND_ MODULE_API_NO >= 20010901PHP_HELLO_WORLD_VERSION、 #endifSTANDARD_MODULE_PROPERTIES};#ifdef COMPILE_DL_HELLOZEND_GET_MODULE(hello)#endifPHP_FUNCTION(hello_world){RETURN_STRING("Hello World", 1);} 上の例の展示では、見られた代コードの多くは PHP に導入されています。最後の 4 行だけが「実際に行われたコード」として認識され、ユーザー空間の画面と一度だけやり取りされます。的 PHP 代コード,そして一看就懂:注意事項一名 hello_world の関数数让関数数返字符列:“Hello World”....嗯....1? 那个 1 は怎么回事儿?回忆一下, ZE には 1 つの内部ストレージ管理レイヤが含まれており、これにより、割り当てられたリソースがマップ終了時に確実に解放されるようになります。二次解放 (二重解放) の実行は、セグメント化された領域を再解放しないようにするため、よくある原因の 1 つです。 (例として挙げた「Hello World」など)は、プログラム空間に存在するため、何らかのプロセス(プロセス)によって転送されるデータブロックではありません。便宜上、安全に解放できますが、内部の関数の文字列が内部で自動的に割り当てられ、埋められ、返され、無視されるため、2 番目のパラメータ RETURN_STRING() で、コピー文字列が必要かどうかを指定できます。概念、下の代コード部分と上のバージョン等: PHP_FUNCTION(hello_world){char *str;str = estrdup("Hello World");RETURN_STRING(str, 0);} このバージョンでは、手作業が最終です転送されるスクリプトの文字列「Hello World」を内部に割り当て、次にこれを RETURN_STRING() に「与える」ことで、2 番目のパラメータ 0 を使用して、独自に作成する必要がないサブ本を指定できます。この拡張の最後のステップは、拡張をアクティブにロードできるモジュールとして構築することです。上のコードが正しくコピーされている場合は、ext/hello/ で 3 つのコマンドを実行するだけです: $ phpize$ ./configure - -enable-hello$ で各コマンドを実行すると、ext/hello/modules/ 内にファイル hello.so が表示されます。 usr/local/lib/php/extensions/、php.ini を確認してください)、extension=hello.so を PHP の起動時に追加するために、php.ini を追加します。 CGI/CLI の場合は、次に PHP を実行します。結果が発生します。Apache などの Web サーバー SAPI の場合は、Web サーバーを再起動する必要があります。 World は、すでにダウンロードされている拡張中の関数 hello_world() がこの文字列を返し、エコー コマンドがその内容を出力します (この例は関数の結果です)。 RETURN_LONG()、浮点値は RETURN_DOUBLE()、true/false 値は RETURN_BOOL()、RETURN_NULL() を使用しますか? c の function_entry 構造に、対応する実行 PHP_FE() が追加され、ファイルの尾部にいくつかの PHP_FUNCTION() が追加されます。 PHP_FE(hello_bool, NULL)PHP_FE(hello_null, NULL){NULL, NULL, NULL}};PHP_FUNCTION(hello_long){RETURN_LONG(42);}PHP_FUNCTION(hello_double){RETURN_DOUBLE(3.1415926535);}PHP_FUNCTION(hello_bool){RETURN_BOOL( 1);}PHP_FUNCTION(hello_null){RETURN_NULL();} また、文件php_hello.h中の関数hello_world()のテンプレートを追加する必要があります。 PHP_FUNCTION(hello_long);PHP_FUNCTION(hello_double);PHP_FUNCTION(hello_bool);PHP_FUNCTION(hello_null); ファイルconfig.m4が変更されていないため、今回はphpizeおよび./configureステップを直接スキップして技術的に安全になります。ただし、この時点で、ビルド手順全体をもう一度実行して、正しくビルドされることを確認してください。また、最後のステップで単に make するのではなく、make clean all を呼び出して、すべてのソース ファイルが確実に再構築されるようにする必要があります。繰り返しますが、これまでのところ、行う変更の種類によってはこれら (手順) は必要ありませんが、混乱するよりは安全を確保した方が良いでしょう。モジュールが構築されたら、それを拡張機能ディレクトリに再度コピーして、古いバージョンを置き換えます。この時点で、PHP インタープリタを再度呼び出し、スクリプトを渡すだけで新しく追加された関数をテストできます。実際、なぜ今それをやらないのでしょうか?ここで待ってるよ...終わった?わかりましたecho の代わりに var_dump() を使用して各関数の出力を表示すると、hello_bool() が true を返すことに気づくかもしれません。これは、RETURN_BOOL() の値 1 で表される結果です。 PHP スクリプトと同様、整数値 0 は FALSE に等しく、他の整数は TRUE に等しくなります。慣例として、拡張機能の作成者は通常 1 を使用しており、あなたも同じようにすることが推奨されていますが、それに制限されると感じる必要はありません。さらに読みやすくするために、マクロ RETURN_TRUE および RETURN_FALSE も使用できます。今回は RETURN_TRUE を使用します。 PHP_FUNCTION(hello_bool){RETURN_TRUE;} ここには括弧がありません。その場合、RETURN_TRUE と RETURN_FALSE は他の RETURN_*() マクロと比べて異常なので、これに引っかからないように注意してください。上記の各例では、コピーするかどうかを示す 0 または 1 を渡していないことに気づいたかもしれません。これは、このような単純な小さなスカラーの場合、追加のメモリを割り当てたり解放したりする必要がないためです (変数コンテナ自体を除きます。これについてはパート 2 で詳しく説明します)。他にも 3 つの戻り値の型があります。 Resource ( mysql_connect()、fsockopen()、および ftp_connect() によって返される値の名前と同様ですが、これらに限定されません)、配列 (HASH とも呼ばれます)、およびオブジェクト (new キーワードによって返されます)。これらについては、パート 2 で変数について詳しく説明するときに説明します。初期セットアップ (INI) Zend Engine は、INI 値を管理する 2 つの方法を提供します。ここではより単純な方法を見て、次に、グローバル データを扱う際のより洗練された、より複雑な方法を検討してみましょう。 php.ini で拡張機能の値 hello.greeting を定義するとします。この値には、hello_world() 関数で使用される挨拶文字列が保持されます。 hello.c と php_hello.h にコードを追加し、hello_module_entry 構造にいくつかの重要な変更を加える必要があります。まず、ファイル php_hello.h のユーザー空間関数のプロトタイプ宣言の近くに次のプロトタイプを追加します: PHP_MINIT_FUNCTION(hello);PHP_MSHUTDOWN_FUNCTION(hello);PHP_FUNCTION(hello_world);PHP_FUNCTION(hello_long);PHP_FUNCTION(hello_double);PHP_FUNCTION(hello_bool) ;PHP_FUNCTION (hello_null); 次に、ファイル hello.c に移動し、hello_module_entry の現在のバージョンを削除し、次のリストに置き換えます: zend_module_entry hello_module_entry = {#if ZEND_MODULE_API_NO >= 20010901STANDARD_MODULE_HEADER,#endifPHP_HELLO_WORLD_EXTNAME,hello _functions,PHP_MINIT (こんにちは),PHP_MSHUTDOWN(hello) ,NULL,NULL,NULL,#if ZEND_MODULE_API_NO >= 20010901PHP_HELLO_WORLD_VERSION,#endifSTANDARD_MODULE_PROPERTIES};PHP_INI_BEGIN()PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, PHP_INI_END() PHP_MINIT_FUNCTION( hello){REGISTER_INI_ENTRIES() ;return SUCCESS;}PHP_MSHUTDOWN_FUNCTION(hello){UNREGISTER_INI_ENTRIES();return SUCCESS;} ここで、ファイル hello.c の先頭にある #include の隣に #include を追加するだけです。正しい INI 対応ヘッダー ファイルを取得できることを確認します。 #ifdef HAVE_CONFIG_H#include "config.h"#endif#include "php.h"#include "php_ini.h"#include "php_hello.h" 最後に、次のように変更できます。 INI の値を使用する関数 hello_world: PHP_FUNCTION(hello_world ){RETURN_STRING(INI_STR("hello.greeting"), 1);} INI_STR() から返された値をコピーする必要があることに注意してください。これは、PHP 変数スタックに関する限り、それが静的文字列であるためです。実際、返された文字列を変更しようとすると、PHP 実行環境が不安定になったり、クラッシュしたりすることがあります。このセクションの最初の変更では、MINIT と MSHUTDOWN という 2 つの非常によく知られた関数が導入されています。前述したように、これらのメソッドはそれぞれ、SAPI の最初の起動時と最終的なシャットダウン時に呼び出されます。これらはリクエスト中またはリクエストの間に呼び出されません。この場合、拡張機能で定義されたエントリを php.ini に登録するために使用されます。この一連のチュートリアルの後半では、MINIT 関数と MSHUTDOWN 関数を使用してリソース、オブジェクト、およびストリーム プロセッサを登録する方法も説明します。 INI_STR() は、hello.greeting エントリの現在の文字列値を取得するために関数 hello_world() で使用されます。以下の表に示すように、他の型 (long、double、boolean) の値を取得するための同様の関数も存在します。これらは php.ini ファイルで提供されます ( .htaccess または ini_set() ディレクティブによって変更される前) は、php.ini で設定されたとおりに、参照される INI 設定の値を提供します - 翻訳注)。現在の値 プリミティブ値の型 INI_STR(name) INI_ORIG_STR(name) char * (NULL 終了)INI_INT(name) INI_ORIG_INT(name) signed longINI_FLT(name) INI_ORIG_FLT(name) signed doubleINI_BOOL(name) INI_ORIG_BOOL(name) PHP_INI_ENTRY( で渡される zend_bool ) には、php.ini ファイルで使用される名前文字列が含まれます。名前空間の競合を避けるには、関数の場合と同じ規則を使用する必要があります。つまり、hello.greeting の場合と同様に、すべての値の前に拡張機能の名前を付けます。単に慣例として、拡張機能の名前とよりわかりやすい初期化子名を区切るためにピリオドが使用されます。 2 番目のパラメータは初期値 (デフォルト値?-Annotation) であり、数値であるかどうかに関係なく、char* 型の文字列である必要があります。これは主に、.ini ファイルの元の値がテキストであり、他のすべての値がテキスト ファイルとして保存されているという事実に基づいています。後続のスクリプトで使用する INI_INT()、INI_FLT()、または INI_BOOL() は型変換を受けます。渡される 3 番目の値はアクセス モード修飾子です。これは、この INI 値をいつ、どこで変更できるかを決定するビットマスク フィールドです。 register_globals などの一部では、設定はリクエストの開始時 (スクリプトが実行可能になる前) にのみ意味をなすため、スクリプト内で ini_set() を使用して値を変更することはできません。その他、allow_url_fopen などの設定は、ini_set() ディレクティブまたは .htaccess ディレクティブのいずれを使用しても、共有ホスティング環境のユーザーに変更させたくない管理設定です。このパラメータの一般的な値は PHP_INI_ALL で、値をどこでも変更できることを示します。次に、PHP_INI_SYSTEM|PHP_INI_PERDIR があり、この設定は php.ini ファイルまたは .htaccess ファイルの Apache ディレクティブを通じて変更できるが、ini_set() では変更できないことを示します。あるいは、値を php.ini ファイル内でのみ変更でき、他の場所では変更できないことを示す PHP_INI_SYSTEM を使用することもできます。 4 番目のパラメーターは、初期設定が変更されたとき (例: ini_set() を使用したとき) にメソッド コールバックをトリガーできるようにする点を除いて、ここでは無視します。これにより、拡張機能は設定が変更されたときにより正確な制御を実行したり、新しい設定に基づいて関連する動作をトリガーしたりできるようになります。グローバルな値の拡張では、多くの場合、特定のリクエスト全体にわたって値を追跡し、同時に発生する可能性のある他のリクエストから値を分離しておく必要があります。非マルチスレッド SAPI では、ソース ファイル内でグローバル変数を宣言し、必要に応じてアクセスするだけです。問題は、PHP がマルチスレッド Web サーバー (Apache 2 や IIS など) で実行されるように設計されているため、各スレッドで使用されるグローバル値を独立した状態に保つ必要があることです。 PHP は、TSRM (スレッド セーフ リソース管理、スレッド セーフ リソース マネージャー) 抽象化レイヤー (ZTS (Zend Thread Safety、Zend スレッド セーフティ) とも呼ばれる) を使用することでこれを大幅に簡素化します。実際、この時点ですでに TSRM の一部を使用していましたが、それに気づいていなかっただけです。 (あまり熱心に見ないでください。このシリーズが進むにつれて随所で目にすることになります。) 他のグローバル スコープと同様に、スレッドセーフ スコープを作成する最初のステップは、それを宣言することです。この例では、値 0 の長いグローバル値を宣言します。 hello_long() が呼び出されるたびに、値は 1 ずつ増分されて返されます。 php_hello.h ファイルの #define PHP_HELLO_H ステートメントの後に次のコード セグメントを追加します。 #ifdef ZTS#include "TSRM.h"#endifZEND_BEGIN_MODULE_GLOBALS(hello)long counter;ZEND_END_MODULE_GLOBALS(hello)#ifdef ZTS#define HELLO_G(v) TSRMG (hello_globals_id, zend_hello_globals *, v)#else#define HELLO_G(v) (hello_globals.v)#endif 今回は RINIT メソッドも使用するため、ヘッダー ファイルでそのプロトタイプを宣言する必要があります。 PHP_MINIT_FUNCTION(hello); PHP_MSHUTDOWN_FUNCTION( hello);PHP_RINIT_FUNCTION(hello); 次に、ファイル hello.c に戻り、インクルード コード ブロックの直後に次のコードを追加します。 #ifdef HAVE_CONFIG_H#include "config.h"#endif#include "php.h "#include "php_ini.h"#include "php_hello.h"ZEND_DECLARE_MODULE_GLOBALS(hello) hello_module_entry を変更し、PHP_RINIT(hello) を追加します: zend_module_entry hello_module_entry = {#if ZEND_MODULE_API_NO >= 20010901STANDARD_MODULE_HEADER,#endif PHP_H ELLO_WORLD_EXTNAME、hello_functions、PHP_MINIT(こんにちは) ,PHP_MSHUTDOWN(hello : s *hello_globals) {} php_rinit_function (hello) {hello_g (counter) = 0; 成功を返します;} php_minit_function (hello) {ZEND_INIT_GLOBALOBAL S (hello, php_hello_init_globals, null); 成功を返します。最後に、次の値を使用する hello_long () 関数を変更できます: PHP_FUNCTION(hello_long){HELLO_G(counter)++;RETURN_LONG(HELLO_G(counter));} php_hello.h に追加したコードでは、2 つのマクロ - ZEND_BEGIN_MODULE_GLOBALS を使用しました。 () および ZEND_END_MODULE_GLOBALS( ) - 長い変数を含む zend_hello_globals という名前の構造体を作成するために使用されます。次に、条件付きで HELLO_G() を定義して、スレッド プールから値を取得するか、非マルチスレッド環境用にコンパイルしている場合はグローバル スコープから値を取得します。 hello.c では、ZEND_DECLARE_MODULE_GLOBALS() マクロを使用して、zend_hello_globals 構造体をインスタンス化します。この構造体は、真にグローバルであるか (ビルドがスレッドセーフでない場合)、このスレッドのリソース プールのメンバーです。拡張機能の作成者としては、Zend エンジンが違いを処理してくれるため、違いについて心配する必要はありません。最後に、MINIT の ZEND_INIT_MODULE_GLOBALS() を使用して、スレッドセーフなリソース ID を割り当てます。それが何であるかについてはまだ心配する必要はありません。お気づきかもしれませんが、php_hello_init_globals() は実際には変数 counter を 0 に初期化する RINIT を宣言するだけで何も実行しません。なぜ?重要なのは、これら 2 つの関数がいつ呼び出されるかです。 php_hello_init_globals() は、新しいプロセスまたはスレッドを開始するときにのみ呼び出されます。ただし、各プロセスは複数のリクエストを処理できるため、この関数で変数 counter を 0 に初期化すると、最初のページリクエストでのみ実行されます。同じプロセスに対する後続のページ要求では、以前にここに保存されていたカウンタ変数の値が引き続き取得されるため、0 からカウントを開始することはありません。ページ要求ごとにカウンタ変数を 0 に初期化するには、前に見たように、各ページ要求の前に呼び出される RINIT 関数を実装します。後で必要になるため、この時点で php_hello_init_globals() 関数を組み込みます。 ZEND_INIT_MODULE_GLOBALS() でこの初期化関数に NULL を渡すと、非マルチスレッド プラットフォームでセグメンテーション違反が発生します。グローバル値としての初期設定 (INI) PHP_INI_ENTRY() で宣言された php.ini 値は文字列として解析され、必要に応じて INI_INT()、INI_FLT()、および INI_BOOL() を使用して他の形式に変換されることを思い出してください。一部の設定では、これを行うと、スクリプトの実行中にこれらの値を読み取るときに、多くの不必要な重複作業が発生します。幸いなことに、ZE に INI 値を特定のデータ型として保存させ、その値が変更された場合にのみ型変換を実行させることができます。別の INI 値を宣言して試してみましょう。今回は変数カウンタが増加しているか減少しているかを示すブール値です。まず、php_hello.h の MODULE_GLOBALS ブロックを次のコードに変更します: ZEND_BEGIN_MODULE_GLOBALS(hello)long counter;zend_bool direct;ZEND_END_MODULE_GLOBALS(hello) 次に、次のように PHP_INI_BEGIN() ブロックを変更して INI 値を宣言します。 )PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direct, zend_hello_globals, hello_globals)PHP_INI_END() 次に、次のコードで init_globals を初期化します。メソッドの設定: static void php_hello_init_globals(zend_hello_globals *hello_globals){hello_globals->direction = 1;} そして最後に、この初期設定を hello_long() に適用して、インクリメントするかデクリメントするかを決定します。 PHP_FUNCTION(hello_long){if (HELLO_G) (方向)) {HELLO_G(counter)++;} else {HELLO_G(counter)--;}RETURN_LONG(HELLO_G(counter));} 以上です。 INI_ENTRY セクションで指定された OnUpdateBool メソッドは、php.ini、.htaccess、またはスクリプトの ini_set() を介して提供された値を、スクリプト内で直接アクセスできるように、適切な TRUE/FALSE 値に自動的に変換します。 STD_PHP_INI_ENTRY の最後の 3 つのパラメータは、どのグローバル変数を変更するか、拡張グローバル (スコープ) の構造がどのようなものであるか、およびこれらの変数を保持するグローバル スコープの名前が何であるかを PHP に指示します。 (コード) 完全性のチェック これまでの 3 つのファイルは、以下のリストのようになっているはずです。 (一部の項目は読みやすくするために移動および再構成されています。)config.m4PHP_ARG_ENABLE(hello, Hello World サポートを有効にするかどうか,[ --enable-hello Hello World サポートを有効にする])if test "$PHP_HELLO" = "yes"; then AC_DEFINE(HAVE_HELLO, 1, [Hello World があるかどうか]) PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)fi php_hello.h#ifndef PHP_HELLO_H#define PHP_HELLO_H 1#ifdef ZTS#include "TSRM.h"#endifZEND_BEGIN_MODULE_GLOBALS( hello)ロングカウンター;zend_bool方向;ZEND_END_MODULE_GLOBALS(hello)#ifdef ZTS#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)#else#define HELLO_G(v) (hello_globals.v)#endif#define PHP_HELLO_WORLD_VERSION "1.0 "#define PHP_HELLO_WORLD_EXTNAME "hello"PHP_MINIT_FUNCTION(hello);PHP_MSHUTDOWN_FUNCTION(hello);PHP_RINIT_FUNCTION(hello);PHP_FUNCTION(hello_world);PHP_FUNCTION(hello_long);PHP_FUNCTION(hello_double);PHP_FUNC TION(hello_null);extern zend_module_entry hello_module_entry ;#define phpext_hello_ptr &hello_module_entry#endif hello.c#ifdef HAVE_CONFIG_H#include "config.h"#endif#include "php.h"#include "php_ini.h"#include "php_hello.h"ZEND_DECLARE_MODULE_GLOBALS(hello)static function_entry hello_functions [] = {PHP_FE(hello_world, NULL)PHP_FE(hello_long, NULL)PHP_FE(hello_double, NULL)PHP_FE(hello_bool, NULL)PHP_FE(hello_null, NULL){NULL, NULL, NULL}};zend_module_entry hello_module_entry = {#if ZEND_MODULE_API_NO >= 20010901STANDARD_MODULE_HEADER,#endifPHP_HELLO_WORLD_EXTNAME,hello_functions,PHP_MINIT(hello),PHP_MSHUTDOWN(hello),PHP_RINIT(hello),NULL,NULL,#if ZEND_MODULE_API_NO >= 20010901PHP_HELLO_WORLD_VER SION,#endifSTANDARD_MODULE_PROPERTIES};#ifdef COMPILE_DL_HELLOZEND_GET_MODULE(こんにちは)#endifPHP_INI_BEGIN( )PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, 方向, zend_hello_globals, hello_globals)PHP_INI_END()static void php_hello_init_globals(zend_hello_globals * hello_globals ){hello_globals->direction = 1;}PHP_RINIT_FUNCTION(hello){HELLO_G(counter) = 0;return SUCCESS;}PHP_MINIT_FUNCTION(hello){ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);REGISTER_INI_ENTRIES();return SUCCESS;}PHP _MSHUTDOWN_FUNCTION (hello){UNREGISTER_INI_ENTRIES();return SUCCESS;}PHP_FUNCTION(hello_world){RETURN_STRING("Hello World", 1);}PHP_FUNCTION(hello_long){if (HELLO_G(方向)) {HELLO_G(カウンター)++;} else {HELLO_G(カウンター)--;}RETURN_LONG(HELLO_G(カウンター));}PHP_FUNCTION(hello_double){RETURN_DOUBLE(3.1415926535);}PHP_FUNCTION(hello_bool){RETURN_BOOL(1);}PHP_FUNCTION(hello_null){RETURN_NULL();}この教程では、関数の出力、戻り値、初期設定 (INI) および (ゲスト端末) 要求中の内部状態の追跡を含む、単一の PHP 拡張の構造を詳しく調べます。関数が使用されるとき、プログラムのパラメータから受け取った zend_parse_parameters を使用して、関数の内部構造と、関数、オブジェクト、および本を含む追加の結果をどのように返すかを調べます。教則で提示されたリソースなどの種類。