ホームページ > バックエンド開発 > PHPチュートリアル > [翻訳] [php 拡張機能と埋め込み] 第 7 章 - パラメータの受け入れ

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

WBOY
リリース: 2016-06-13 12:52:59
オリジナル
972 人が閲覧しました

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

完全な翻訳コンテンツ PDF ドキュメントのダウンロード アドレス: http://download.csdn.net/detail/lgg201/5107012

この本は現在、laruence (http://www.laruence.com) と walu (http://www.walu.cc) によって github 上で翻訳されています。翻訳プロジェクトのアドレスは https://github.com です。 /walu/phpbook

Github 上のこの本のアドレス: https://github.com/goosman-lei/php-eae

将来的には、この本は独立したバージョンを保持したまま、部分的に phpbook プロジェクトに統合される可能性があります。


元のタイトル:

原作者:サラ・ゴーレモン

翻訳者: goosman.lei (Lei Guo)

翻訳者のメールアドレス: lgg860911@yahoo.com.cn

翻訳者ブログ: http://blog.csdn.net/lgg201

権利表明

この翻訳は、いかなる利益も得ることなく、制限なく自由に配布することができます。

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

zend_parse_parameters() の自動型変換

前の章で見た戻り値と同様に、パラメータの値も zval 参照への間接的なアクセスを中心に展開します。これらの zval* の値を取得する最も簡単な方法は、zend_parse_parameters を使用することです。 () 関数。

zend_parse_parameters() の呼び出しは、ほとんどの場合、ZEND_NUM_ARGS() マクロで始まり、その後に普遍的な TSRMLS_CC が続きます。その名前から推測できるように、ZEND_NUM_ARGS() の内部動作により、実際のパラメータ数が返されます。 zend_parse_parameters() メソッドでは、おそらく値を直接知る必要はないので、とりあえず

を渡してください。

zend_parse_parameters() の次のパラメータはフォーマット文字列パラメータです。これは、Zend エンジンでサポートされる基本的な型記述文字で構成される文字シーケンスであり、受け入れられる関数パラメータを記述するために使用されます。次の表は基本的な型です。文字:


文字を入力してください

ユーザー空間のデータ型

b

ブール値

l

整数

d

浮動小数点

文字列

r

リソース

a

配列

オブジェクト インスタンス

指定された型のオブジェクト インスタンス

z

非特定の zval

Z

非特定の zval を間接参照


zend_parse_parameters() の残りのパラメータは、フォーマット文字列で指定された型の説明によって異なります。たとえば、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;
}
ログイン後にコピー

通常、integer と long の記憶域スペースは同じですが、まったく同じではありません。int 型データを long* パラメーターに逆参照しようとすると、特に 64 ではエラーが発生しやすくなります。 -bit プラットフォームでは、以下の表にリストされているデータ型を厳密に使用してください。


タイプ

C言語での対応するデータ型

b

zend_bool

l

長い

d

ダブル

char *, int

r

zval *

a

zval *

zval *

zval *、zend_class_entry *

z

zval *

Z

zval *


注意, 所有其他的复杂类型实际上都解析为简单的zval. 这样做的原因和不使用RETURN_*()宏返回复杂数据类型一样, 都是受限于无法真正的模拟C空间中的这些结构. zend_parse_parameters()能为你的函数所做的, 是确保你接收到的zval *是正确的类型. 如果需要, 它甚至会执行隐式的类型转换, 比如将数组转换为stdClass的对象.

s和O类型需要单独说明, 因为它们一次调用需要两个参数. 在第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()调用的后面其他参数压栈. 参数和它们在用户空间函数定义的行为一致, 从左向右解析.

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');
ログイン後にコピー

当不传递第二个参数时, 使用默认值. 在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 Vs. NULL

每个zval, 即便是非常简单的IS_NULL类型, 都会占用一块很小的内存空间. 从而, 它就需要一些(CPU)时钟周期去分配内存空间, 初始化值, 最后认为不再需要它的时候释放它.

对于很多函数, 在调用空间使用NULL参数只是标记参数不重要, 因此这个处理就没有意义. 幸运的是zend_parse_parameters()允许参数被标记为"允许NULL", 如果在一个格式描述字符后加上感叹号(!), 则表示对应参数如果传递了NULL, 则将zend_parse_parameters()调用时对应的参数设置为真正的NULL指针. 考虑下面两段代码, 一个有这个修饰符, 一个没有:

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_compatiblity_mode标记时, 你可能关心ZEND_ARG_OBJECT_INFO一行的by_ref设置. 这种特殊情况下, 对象可能仍然传递的是一个拷贝而不是引用. 因为你在处理对象的时候, 可能需要的是一个真正的引用, 因此设置这个标记你就不用担心这一方面的影响.

不要忘记了数组和对象的参数信息宏中有一个allow_null选项. 关于允许NULL的细节请参考前一章的编译期引用传值一节.

当然, 使用使用参数信息进行类型暗示只在ZE2中支持, 如果你想让你看的扩展兼容php4, 需要使用zend_get_parameters(), 这样就只能将类型验证放到函数内部, 手动的通过测试Z_TYPE_P(value)或使用第2章看到的convert_to_type()方法进行自动类型转换来完成.

小结

现在你的手头可能已经有点脏乱了, 和用户空间通信的功能代码通过简单的输入/输出函数实现. 已经比较深入的了解了zval的引用计数系统, 并学习了控制变量传递到你的内部函数方法和时机.

下一章将开始学习数组数据类型, 并了解用户空间的数组表示怎样映射到内部的HashTable实现. 此外还将看到一大批可选的Zend以及php api函数, 它们用于操纵这些复杂的结构体.


目录

上一章: 返回值
下一章: 在数组和哈希表上工作

类型修改符

含义

|

接下来是可选参数了.当指定它时,所有之前的参数都被认为是必须的,所有后续的参数都被认为是可选的.

!

! の前の修飾子に対応するパラメータが NULL の場合、 によって提供される内部変数は実数の に設定されます。 NULLポインタ.

/

/ の前の修飾子に対応するパラメーターはコピーオンライト として指定されており、 は自動的に新しい zval(is_ref = 0、refcount = 1)

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート