ホームページ  >  記事  >  バックエンド開発  >  [翻訳] [php拡張機能の開発と組み込み] 第7章 - パラメータの受け入れ

[翻訳] [php拡張機能の開発と組み込み] 第7章 - パラメータの受け入れ

黄舟
黄舟オリジナル
2017-02-09 11:44:361204ブラウズ

権利声明

この翻訳は、利益を得ることなく、制限なく自由に広めることができます。

これまで扱ってきた拡張関数は、いくつかの「プレビュー」例外を除いて、単純な、ただ返すだけのものでした。目的は 1 つだけです。通常、いくつかのパラメーターを渡し、その値やその他の追加の処理に基づいて有用な応答を受け取ることを期待します。

zend_parse_parameters() の自動型変換は、で見た戻り値と同じです。前の章のパラメータの値も、zval 参照への間接的なアクセスを中心としています。これらの zval* の値を取得する最も簡単な方法は、zend_parse_parameters() 関数を使用することです。名前から推測できるように、 ZEND_NUM_ARGS() は、 zend_parse_parameters() が内部的に動作する方法のため、必要ない場合もありますが、その前には ZEND_NUM_ARGS() マクロが続きます。この値を直接知るには、この値を渡すだけで済みます

zend_parse_parameters() の次のパラメータは、Zend エンジンによってサポートされる基本的な型記述文字で構成される文字シーケンスです。次の表は、受け入れられる関数パラメータを示しています:

zend_parse_parameters() の残りのパラメータは、フォーマット文字列で指定された型の説明に依存します。例えば、C言語のlongデータ型は次のようにデコードされます:

PHP_FUNCTION(sample_getlong)  
{  
    long foo;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,  
                         "l", &foo) == FAILURE) {  
        RETURN_NULL();  
    }  
    php_printf("The integer value of the parameter you "  
             "passed is: %ld\n", foo);  
    RETURN_TRUE;  
}
[翻訳] [php拡張機能の開発と組み込み] 第7章 - パラメータの受け入れintegerとlongですが、通常、記憶領域は同じサイズですが、int型データをlongに逆参照しようとしています。 * パラメーターは、特にエラーが発生しやすい 64 ビット プラットフォームでは望ましくない結果を引き起こす可能性があるため、以下の表にリストされているデータ型を厳密に使用してください

他のすべての複雑な型は実際に解析されることに注意してください。単純な zvals であり、複雑なデータ型を返すために RETURN_*() マクロを使用しない理由はいずれにせよ、C 空間でこれらの構造を真にエミュレートできないため、関数に対して zend_parse_parameters() ができることは次のとおりです。受け取った zval * は正しい型です。必要に応じて、配列を stdClass のオブジェクトに変換するなど、暗黙的な型変換も実行されます。

[翻訳] [php拡張機能の開発と組み込み] 第7章 - パラメータの受け入れ 型と O 型は 2 つのパラメーターが必要なため、個別に説明する必要があります。第 10 章「php4 オブジェクト」と第 11 章「php5 オブジェクト」で、O について詳しく説明します。s 型については、第 5 章「最初の拡張」で、sample_hello_world() 関数を拡張して、挨拶できるようにします。指定された人の名前。

function sample_hello_world($name) {  
    echo "Hello $name!\n";  
}  
/* 在C中, 你将使用zend_parse_parameters()函数接受一个字符串 */  
PHP_FUNCTION(sample_hello_world)  
{  
    char *name;  
    int name_len;  
  
  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",  
                        &name, &name_len) == FAILURE) {  
        RETURN_NULL();  
    }  
    php_printf("Hello ");  
    PHPWRITE(name, name_len);  
    php_printf("!\n");  
}

zend_parse_parameters() 関数は、フォーマット文字列を満たすには関数に渡されるパラメーターが少なすぎるため、またはパラメーターの 1 つを要求された型に変換できないために失敗する可能性があります。この場合、関数は自動的に変換されます。エラー メッセージを出力するため、拡張機能はこれを行う必要があります。

複数のパラメーターをリクエストするには、他の文字を含めるようにフォーマット文字列を拡張し、 zend_parse_parameters() 呼び出しの後に他のパラメーターをプッシュする必要があります。ユーザー空間関数での定義と一致しています。左から Parse から右に向かっています。

function sample_hello_world($name, $greeting) {  
    echo "Hello $greeting $name!\n";  
}  
sample_hello_world('John Smith', 'Mr.');  
Or:  
PHP_FUNCTION(sample_hello_world)  
{  
    char *name;  
    int name_len;  
    char *greeting;  
    int greeting_len;  
  
  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",  
      &name, &name_len, &greeting, &greeting_len) == FAILURE) {  
        RETURN_NULL();  
    }  
    php_printf("Hello ");  
    PHPWRITE(greeting, greeting_len);  
    php_printf(" ");  
    PHPWRITE(name, name_len);  
    php_printf("!\n");  
}

次の表に示すように、基本タイプに加えて、パラメーター処理メソッドを変更するために使用される 3 つのメタキャラクターがあります。

オプションのパラメータ

改訂されたsample_hello_world()の例を見てみましょう。次のステップは、オプションの$greetingパラメータを追加することです:

function sample_hello_world($name, $greeting='Mr./Ms.') {  
    echo "Hello $greeting $name!\n";  
}

sample_hello_world()は、$nameパラメータのみ、または両方のパラメータを使用して呼び出すことができるようになりました。

sample_hello_world('Ginger Rogers','Ms.');  
sample_hello_world('Fred Astaire');
[翻訳] [php拡張機能の開発と組み込み] 第7章 - パラメータの受け入れ2 番目のパラメータが渡されない場合は、デフォルト値を使用します。C 言語の実装では、オプションのパラメータも同様の方法で指定します。

この関数を完了するには、パイプ文字 (|) を使用する必要があります。 zend_parse_parameters() フォーマット文字列では、パイプ文字の左側のパラメータは呼び出しスタック解析からのものであり、パイプ文字の右側のすべてのパラメータは、呼び出しスタック (対応する) に提供されていない場合は変更されません。 zend_parse_parameters() フォーマット文字列の後のパラメータ) 例:

PHP_FUNCTION(sample_hello_world)  
{  
    char *name;  
    int name_len;  
    char *greeting = "Mr./Mrs.";  
    int greeting_len = sizeof("Mr./Mrs.") - 1;  
  
    /* 如果调用时没有传递第二个参数, 则greeting和greeting_len保持不变. */  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",  
      &name, &name_len, &greeting, &greeting_len) == FAILURE) {  
        RETURN_NULL();  
    }  
    php_printf("Hello ");  
    PHPWRITE(greeting, greeting_len);  
    php_printf(" ");  
    PHPWRITE(name, name_len);  
    php_printf("!\n");  
}

を呼び出すときにオプションのパラメータの値が指定されていない場合、その初期値は変更されないため、パラメータの初期デフォルト値を設定することが非常に重要です。ほとんどの場合、その初期デフォルト値は NULL/0 ですが、場合によっては、非常に単純な IS_NULL タイプであっても、デフォルトが

IS_NULL または NULL である場合があります。占有するメモリ空間が小さいため、メモリ空間を割り当て、値を初期化し、最終的に不要になったときに解放するために、いくつかの (CPU) クロック サイクルが必要になります。幸いなことに、zend_parse_parameters() ではパラメータを重要でないとしてマークすることができます。「NULL を許可」の場合、形式記述文字の後に感嘆符 (!) が追加されている場合、対応するパラメータがNULL が渡された場合、zend_parse_parameters() が呼び出されたときの対応するパラメーターは実際の NULL ポインターに設定されます。次の 2 つのコード段落を考えてください。1 つはこの修飾子を使用し、もう 1 つは使用しません。

PHP_FUNCTION(sample_arg_fullnull)  
{  
    zval *val;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z",  
                                    &val) == FAILURE) {  
        RETURN_NULL();  
    }  
    if (Z_TYPE_P(val) == IS_NULL) {  
        val = php_sample_make_defaultval(TSRMLS_C);  
    }  
...  
PHP_FUNCTION(sample_arg_nullok)  
{  
    zval *val;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!",  
                                    &val) == FAILURE) {  
        RETURN_NULL();  
    }  
    if (!val) {  
        val = php_sample_make_defaultval(TSRMLS_C);  
    }  
...

这两个版本的代码其实没什么不同, 前者表面上看起来需要更多的处理时间. 通常, 这个特性并不是很有用, 但最好还是知道有这么回事.

强制隔离

当一个变量传递到一个函数中后, 无论是否是引用传值, 它的refcount至少是2; 一个是变量自身, 另外一个是传递给函数的拷贝. 因此在修改zval之前(如果直接在参数传递进来的zval上), 将它从它所属的非引用集合中分离出来非常重要.

如果没有"/"格式修饰符, 这将是一个单调重复的工作. 这个格式修饰符自动的隔离所有写时拷贝的引用传值参数, 这样, 你的函数中就可以为所欲为了. 和NULL标记一样, 这个修饰符放在它要修饰的格式描述字符后面. 同样和NULL标记一样, 直到你真的有使用它的地方, 你可能才知道你需要这个特性.

zend_get_arguments()

如果你正在设计的代码计划在非常旧的php版本上工作, 或者你有一个函数, 它只需要zval *, 就可以考虑使用zend_get_parameters()的API调用.

zend_get_parameters()调用与它对应的新版本有一点不同. 首先, 它不会自动执行类型转换; 而是所有期望的参数都是zval *数据类型. 下面是zend_get_parameters()的最简单用法:

PHP_FUNCTION(sample_onearg)  
{  
    zval *firstarg;  
    if (zend_get_parameters(ZEND_NUM_ARGS(), 1, &firstarg)  
                                        == FAILURE) {  
        php_error_docref(NULL TSRMLS_CC, E_WARNING,  
            "Expected at least 1 parameter.");  
        RETURN_NULL();  
    }  
    /* Do something with firstarg... */  
}

其次, 你可能已经注意到, 它需要手动处理错误消息, zend_get_parameters()并不会在失败的时候输出错误文本. 它对可选参数的处理也很落后. 如果你让它抓取4个参数, 最好至少给它提供4个参数, 否则可能返回FAILURE.

最后, 它不像parse, 这个get变种会自动的隔离所有写时复制的引用集合. 如果你想要跳过自动隔离, 就要使用它的兄弟接口: zend_get_parameters_ex().

除了不隔离写时复制集合, zend_get_parameters_ex()还有一个不同点是返回zval **指针而不是直接的zval *. 它们的差别同样可能直到你使用的时候才知道需要它们, 不过它们最终的使用非常相似:

PHP_FUNCTION(sample_onearg)  
{  
    zval **firstarg;  
    if (zend_get_parameters_ex(1, &firstarg) == FAILURE) {  
        WRONG_PARAM_COUNT;  
    }  
    /* Do something with firstarg... */  
}

要注意的是_ex版本不需要ZEND_NUM_ARGS()参数. 这是因为增加_ex的版本时已经比较晚了, 当时Zend引擎已经不需要这个参数了.

在这个例子中, 你还使用了WRONG_PARAM_COUNT宏, 它用来处理E_WARNING错误消息的显示已经自动的离开函数.

处理任意数目的参数

还有两个zend_get_parameters()族的函数, 用于解出zval *和zval **指针集合, 它们适用于有很多参数或运行时才知道参数个数的引用场景.

考虑var_dump()函数, 它用来展示传递给它的任意数量的变量的内容:

PHP_FUNCTION(var_dump)  
{  
    int i, argc = ZEND_NUM_ARGS();  
    zval ***args;  
  
  
    args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0);  
    if (ZEND_NUM_ARGS() == 0 ||  
        zend_get_parameters_array_ex(argc, args) == FAILURE) {  
        efree(args);  
        WRONG_PARAM_COUNT;  
    }  
    for (i=0; i<argc; i++) {  
        php_var_dump(args[i], 1 TSRMLS_CC);  
    }  
    efree(args);  
}

这里, var_dump()预分配了一个传给函数的参数个数大小的zval **指针向量. 接着使用zend_get_parameters_array_ex()一次性将参数摊入到该向量中. 你可能会猜到, 存在这个函数的另外一个版本: zend_get_parameters_array(), 它们仅有一点不同: 自动隔离, 返回zval *而不是zval **, 在第一个参数上要求传递ZEND_NUM_ARGS().

参数信息和类型暗示

在上一章已经简短的向你介绍了使用Zend引擎2的 参数信息结构进行类型暗示的概念. 我们应该记得, 这个特性是针对ZE2(Zend引擎2)的, 对于php4的ZE1(Zend引擎1)不可用.

我们重新从ZE2的参数信息结构开始. 每个参数信息都使用ZEND_BEGIN_ARG_INFO()或ZEND_BEGIN_ARG_INFO_EX()宏开始, 接着是0个或多个ZEND_ARG_*INFO()行, 最后以ZEND_END_ARG_INFO()调用结束.

这些宏的定义和基本使用可以在刚刚结束的第6章"返回值"的编译期引用传值一节中找到.

假设你想要重新实现count()函数, 你可能需要创建下面这样一个函数:

PHP_FUNCTION(sample_count_array)  
{  
    zval *arr;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a",  
                                    &arr) == FAILURE) {  
        RETURN_NULL();  
    }  
    RETURN_LONG(zend_hash_num_elements(Z_ARRVAL_P(arr)));  
}

zend_parse_parameters()会做很多工作, 以保证传递给你的函数是一个真实的数组. 但是, 如果你使用zend_get_parameters()函数或某个它的兄弟函数, 你就需要自己在函数中做类型检查. 当然, 你也可以使用类型暗示! 通过定义下面这样一个arg_info结构:

static  
    ZEND_BEGIN_ARG_INFO(php_sample_array_arginfo, 0)  
        ZEND_ARG_ARRAY_INFO(0, "arr", 0)  
    ZEND_END_ARG_INFO()

并在你的php_sample_function结构中声明该函数时使用它:

PHP_FE(sample_count_array, php_sample_array_arginfo)

这样就将类型检查的工作交给了Zend引擎. 同时你给了你的参数一个名字(arr), 它可以在产生错误消息时被使用, 这样在使用你API的脚本发生错误时, 更加容易跟踪问题.

在第6章第一次介绍参数信息结构时, 你可能注意到了对象, 也可以使用ARG_INFO宏进行类型暗示. 只需要在参数名后增加一个额外的参数来说明类型名(类名)

static  
    ZEND_BEGIN_ARG_INFO(php_sample_class_arginfo, 0)  
        ZEND_ARG_OBJECT_INFO(1, "obj", "stdClass", 0)  
    ZEND_END_ARG_INFO()

要注意到这里的第一个参数(by_ref)被设置为1. 通常来说这个参数对对象来说并不是很重要, 因为ZE2中所有的对象默认都是引用方式的, 对它们的拷贝必须显式的通过clone来实现. 在函数调用内部强制clone可以做到, 但是它和强制引用完全不同.

zend.ze1_compatibility_mode フラグが設定されている場合、この特殊な場合でもオブジェクトには参照ではなくコピーが渡される可能性があるためです。実際の参照が必要な場合があるため、このフラグを設定することでこの影響を心配する必要はありません。配列とオブジェクトのパラメータ情報マクロに、allow_null オプションがあることを忘れないでください。NULL の許可については、こちらを参照してください。前の章の「値による参照の受け渡し」セクションのコンパイル時間を参照してください。もちろん、型ヒントのためのパラメーター情報の使用は ZE2 でのみサポートされています。 zend_get_parameters() を使用する必要があるため、内部的には、Z_TYPE_P(value) をテストするか、第 2 章で説明した Convert_to_type() メソッドを使用して自動型変換をテストすることによってのみ型検証を行うことができます。ここまでで、ユーザー空間との通信に少し混乱が生じているかもしれません。 関数コードは、単純な入出力関数を通じて実装されます。私は zval の参照カウント システムについてより深く理解し、変数を制御する方法とタイミングを学びました。

次の章では、配列のデータ型について学習し、ユーザー空間の配列表現が内部の HashTable 実装にどのようにマッピングされるかを理解します。さらに、多数のオプションの Zend とこれらの複雑な構造を操作するために使用される PHP API 関数

以上 [翻訳] [php 拡張開発と組み込み] 第 7 章 - パラメータの受け入れについては、PHP 中国語 Web サイト (www.php.cn) を参照してください。関連性のあるコンテンツ!

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