ホームページ  >  記事  >  バックエンド開発  >  PHP 拡張リソースの使用法

PHP 拡張リソースの使用法

巴扎黑
巴扎黑オリジナル
2016-11-09 14:41:34891ブラウズ

まず、カーネル内の {resource} タイプの構造を説明します。
//すべてのリソースはそれを通じて実装されます。
typedef struct _zend_rsrc_list_entry
{
void *ptr;
int refcount;

のハンドルなど、いくつかのデータを操作する必要があることがよくあります。ファイル 、C の場合は単なるポインタです。
#include
int main(void)
{
FILE *fd = fopen("/home/jdoe/.plan", "r");
return 0; C 言語の stdio のファイル記述子は、開いている各ファイルに一致する変数であり、実際には FILE 型のポインタであり、プログラムがハードウェアと対話するときに使用されます。 fopen 関数を使用してファイルを開いてハンドルを取得すると、ハンドルを feof()、fread()、fwrite()、fclose() などの関数に渡すだけで、後続の操作を実行できます。ファイル上で。このデータは C 言語のスカラー データで直接表現できないため、ユーザーが PHP 言語でも使用できるようにするにはどうすればカプセル化できるでしょうか?これが PHP におけるリソース タイプ変数の役割です。したがって、これも zval 構造を通じてカプセル化されます。 リソース タイプの実装は複雑ではありません。その値は実際には単なる整数値に基づいて、カーネルはリソース プールに似た場所に移動して、最終的に必要なデータを見つけます。

リソースタイプ変数の使用
リソースタイプ変数も実装時にタイプ区別されます。ファイル ハンドルや mysql リンクなど、さまざまな種類のリソースを区別するには、それらに異なる分類名を付ける必要があります。まず、このカテゴリをプログラムに追加する必要があります。このステップは MINIT で実行できます:
#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "Shanzhai ファイル記述子"
static int le_sample_descriptor;
ZEND_MINIT_FUNCTION(sample)
{
le_sample_descriptor = zend_register_list_destructors_ex(NULL, N) ULL、PHP_SAMPLE_DESCRIPTOR_RES_NAME、モジュール番号);
成功を返します
} ;
//追加情報
#define register_list_destructors(ld, pld) zend_register_list_destructors((void (*)(void *))ld, (void (*)(void *))pld, module_number);
ZEND_API int zend_register_list_destructors(void ( *ld)(void *), void (*pld)(void *), int module_number);
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number);
次に、定義された MINIT を追加します。 stage 関数を拡張 module_entry に追加するには、元の "NULL, /* MINIT */" 行を置き換えるだけです:
ZEND_MINIT(sample), /* MINIT */
ZEND_MINIT_FUNCTION( ) マクロは、MINIT フェーズで関数を定義するのに役立ちます。これについては第 1 章ですでに説明しましたが、第 12 章と第 3 章でさらに詳しく説明します。この時点で知っておくべき重要なことは、MINIT メソッドは、拡張機能が最初にロードされるとき、およびリクエストが受信される前に 1 回実行されるということです。ここでは、その機会を利用してデストラクター関数に NULL 値を登録しました。この値はすぐに変更します。これ以降、一意の整数 ID によって識別されるリソース タイプです。zend_register_list_destructors_ex() 関数を見ると、zend_register_list_destructors() 関数も存在するのではないかと疑問に思うでしょう。はい、確かに、パラメーターに含まれるリソース カテゴリ名が前者より少ない関数が存在します。それでは、この 2 つの違いは何でしょうか?
eaco $re_1;
//resource(4) タイプ (copycat ファイルハンドル)

echo $re_2;
//resource(4) タイプ (Unknown)
リソースを作成
上のカーネルに登録しました次のステップでは、このタイプのリソース変数を作成します。次に、sample_open と呼ばれる fopen 関数を単純に再実装してみましょう。 、"ss"、&filename、&filename_len、&mode、&mode_len) == 失敗)
エラー_docref(NULL TSRMLS_CC, E_WARNING,"ファイル名またはモードの長さが無効です");
RETURN_FALSE;fp = fopen(ファイル名, モード);
if (!fp)
{ php_error_docref(NULL TSRMLS_CC, E_WARNING,"モード %s を使用して %s を開けません",ファイル名, モード); fp をリソース プールに追加し、le_sample_descriptor タイプとしてマークします。
ZEND_REGISTER_RESOURCE(return_value,fp,le_sample_descriptor);
}
前の章の知識を読んでいれば、コードの最後の行が何をしているのか推測できるはずです。さらに、このリソースをリソースを格納する HashTable に追加し、このリソースの対応するデジタル キーを return_value に割り当てます。

リソースはファイル ハンドルに限定されません。メモリの一部を適用し、それへのポインタをリソースとして使用できます。したがって、リソースはあらゆる種類のデータに対応できます。
資源を破壊する
世界のあらゆるものには喜びと悲しみ、生と死があります。資源をどのように破壊するかを議論する時が来ました。最も簡単な方法は、fclose を模倣して、sample_close() 関数を作成することです。この関数では、特定の {resource: 具体的には、PHP のリソース タイプ変数で表される値を指します} のリリースが行われます。

しかし、ユーザー側のスクリプトが unset() 関数を通じて特定のリソース タイプの変数を解放するとどうなるでしょうか?彼らは、その値が最終的に FILE ポインターに対応することを知らないため、fclose() 関数を使用して解放することはできません。PHP プログラムがハングアップして OS によってリサイクルされるまで、この FILE ハンドルはメモリー内に残る可能性があります。 。しかし、通常の Web 環境では、サーバーは長時間稼働します。 解決策はないのでしょうか?もちろんそうではありません。答えは NULL パラメーターにあります。これは、新しいリソース タイプを生成するために上で呼び出した zend_register_list_destructors_ex() 関数の最初のパラメーターと 2 番目のパラメーターです。両方のパラメータはそれぞれコールバック パラメータを表します。最初のコールバック関数は、スコープが終了するか unset() されるなど、スクリプト内の対応するタイプのリソース変数が解放されるときにトリガーされます。

2 番目のコールバック関数は、ロング リンク タイプに似たリソースで使用されます。つまり、このリソースは作成後常にメモリ内に存在し、リクエストの終了後に解放されません。これは、Web サーバー プロセスの終了時に呼び出されます。これは、MSHUTDOWN フェーズ中にカーネルによって呼び出されるのと同じです。永続リソースについては、次のセクションで詳しく説明します。

まず最初のコールバック関数を定義しましょう。
static void php_sample_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
FILE *fp = (FILE*)rsrc->ptr;
fclose(fp)
}
それから zend_register_list_destructors_ex() 関数をそれに置き換えます A パラメータは NULL :
le_sample_descriptor = zend_register_list_destructors_ex(
php_sample_descriptor_dtor,
NULL,
PHP_SAMPLE_DESCRIPTOR_RES_NAME,
module_number); ここで、上記のタイプのリソース変数がスクリプトで取得された場合、設定が解除された場合、またはスコープが実行されたためにカーネルによって解放された場合これがドロップされると、基礎となる php_sample_descriptor_dtor が前処理のためにカーネルによって呼び出されます。このように、sample_close() 関数はまったく必要ないようです。
$fp = sample_fopen("/home/jdoe/notes.txt", "r");
unset($fp)
?>
unset($fp) が実行されると、カーネルは自動的にcall php_sample_descriptor_dtor 関数は、この変数に対応する一部のデータをクリーンアップするために使用されます。 もちろん、物事はそれほど単純ではありません。この質問を念頭に置いて読み続けてください。
リソースのデコード
リソース変数をブックマークと比較しますが、ブックマークしかない場合はまったく効果がありません。ブックマークを通じて対応するページを見つける必要があります。リソース変数の場合、それを通じて対応する最終データを見つけることができなければなりません。
ZEND_FUNCTION(sample_fwrite)
{
FILE *fp;
char *data;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs" , &file_resource, & data_len) == 失敗 )
{ZEND_FETCH_RESOURCE(fp,FILE*,&file_resource,-1,PHP_SAMPLE_DESCRIPTOR_RES_NAME,le_sample_descriptor);

/* データを書き込み、
* ファイルに正常に書き込まれたバイト数を返します
*
RN_ LONG(fwrite(データ, 1, data_len, fp));
}
zend_parse_parameters() 関数の r プレースホルダーは、リソース タイプを受け取る変数を表し、そのキャリアは zval* です。次に、ZEND_FETCH_RESOURCE() マクロ関数を見てみましょう。
#define ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id,default_id, resource_type_name, resource_type)
rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC,default_id, resource_type_name, NULL,1, resource_type);
ZEND_VERIFY_RES CE(rsrc) );

//この例では、次のようになります:
fp = (FILE*) zend_fetch_resource(&file_descriptor TSRMLS_CC, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, NULL,1, le_sample_descriptor);
zend_fetch_resource( ) は、zend_hash_find() のカプセル化層です。デジタル キーを使用して、さまざまな {resources} を格納する HashTable 内の最終的な必要なデータを見つけた後、ZEND_VERIFY_RESOURCE() マクロ関数を使用してデータを検証します。上記のコードから、NULL と 0 はリソースとして使用できないことがわかります。 上記の例では、zend_fetch_resource() 関数は最初に le_sample_descriptor で表されるリソース タイプを取得します。リソースが存在しない場合、または受信した zval がリソース タイプ変数ではない場合、NULL を返し、対応するエラー メッセージをスローします。 最後の ZEND_VERIFY_RESOURCE() マクロ関数は、エラーが検出された場合に自動的に戻るため、エラー検出から離れて、プログラムのメイン ロジックに集中できるようになります。対応する FILE* を取得したので、fwrite() を使用してデータを書き込んでみましょう。 。
zend_fetch_resource() が失敗時にエラーを生成するのを避けるには、resource_type_name パラメーターに NULL を渡すだけで、表示する意味のあるエラー メッセージがなければ、zend_fetch_resource() は静かに失敗します。
別のメソッドを使用して、最終的に必要なデータを取得することもできます。 。

ZEND_FUNCTION(sample_fwrite)
{
FILE *fp;
int data_len,
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC)ファイルリソース、&データ、&データ_len) = = 失敗) {
php_error_docref(NULL TSRMLS_CC, E_WARNING," 無効なリソースが指定されました");
RETURN_FALSE
}
RETURN_LONG(fwrite(data, 1, data_len, fp))
}
使用するフォームを選択できます自分の習慣に従ってください。ただし、 ZEND_FETCH_RESOURCE() マクロ関数を使用することをお勧めします。

強制破壊
上記の未解決の疑問がまだ残っています。つまり、上記で実装したものと同様の unset($fp) は本当に全能なのでしょうか?もちろんそうではありません。次のコードを見てください。 );
?>
今回、$fp と $evil_log は zval を共有します。$fp は解放されますが、$evil_log はまだ使用されているため、その zval は解放されません。言い換えれば、$evil_log で表されるファイル ハンドルはまだ書き込み可能です。したがって、この種のエラーを回避するには、手動で閉じる必要があります。 Sample_close() 関数が存在する必要があります。
PHP_FUNCTION(sample_fclose)
{
FILE *fp;
zval *file_resource;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r",&file_resource) == FAILURE ) {
RE TURN_NULL();
}

/*実際に * FILE* リソースをフェッチする必要はありませんが、フェッチを実行すると
* 正しいリソース タイプを閉じていることを確認する機会が得られます
* */
。ZEND_FETCH_RESOURCE(fp, FILE*, &file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor);

/* リソースを強制的に自己破壊モードにします */
zend_hash_index_del(&EG(normal_list),Z_RESVAL_P(file_ resource)); RN_TRUE
この削除操作により、リソース データが HashTable に格納されていることも再度わかります。 zend_hash_index_find() や zend_hash_next_index_insert() などの関数を使用してリソースを格納するこの HashTable を操作することもできますが、これは決して良い考えではありません。後続のバージョンでは、PHP がこの部分の実装を変更する可能性があるため、上記の方法は機能しません。互換性を高めるために、標準のマクロ関数または API 関数を使用してください。 EG (register_list) の HashTable 内のデータを削除すると、dtor 関数がコールバックされます。この関数は、リソース変数のカテゴリに応じて、対応する dtor 関数実装を呼び出します。これは、zend_register_list_destructors_ex() 関数を呼び出すときの最初のパラメーターです。

多くの場所で、zend_list_delete() マクロ関数が、リソース データ自体の参照カウントを考慮するため、削除に特別に使用されているのが見られます。


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