検索
ホームページバックエンド開発PHPチュートリアル[通译][php扩展和嵌入式]第7章-接受参数

[翻译][php扩展和嵌入式]第7章-接受参数

全部翻译内容pdf文档下载地址: http://download.csdn.net/detail/lgg201/5107012

本书目前在github上由laruence(http://www.laruence.com)和walu(http://www.walu.cc)两位大牛组织翻译. 该翻译项目地址为: https://github.com/walu/phpbook

本书在github上的地址: https://github.com/goosman-lei/php-eae

未来本书将可能部分合并到phpbook项目中, 同时保留一份独立版本.


原书名:

原作者: Sara Golemon

译者: goosman.lei(雷果国)

译者Email: [email protected]

译者Blog: http://blog.csdn.net/lgg201

权利声明

此译本在不获利的情况下, 可以无限制自由传播.

除了几个"预览"的例外, 你迄今处理的扩展函数都很简单, 只有返回. 然而, 多数函数并非只有一个目的. 你通常会传递一些参数, 并希望接收到基于值和其他附加处理的有用的响应.

zend_parse_parameters()的自动类型转换

和上一章你看到的返回值一样, 参数的值也是围绕着对zval引用的间访展开的. 获取这些zval*的值最简单的方法就是使用zend_parse_parameters()函数.

调用zend_parse_parameters()几乎总是以ZEND_NUM_ARGS()宏接着是无所不在的TSRMLS_CC. ZEND_NUM_ARGS()从名字上可以猜到, 它返回int型的实际传递的参数个数. 由于zend_parse_parameters()内部工作的方法, 你可能不需要直接了解这个值, 因此现在只需要传递它.

zend_parse_parameters()的下一个参数是格式串参数, 它是由Zend引擎支持的基础类型描述字符组成的字符序列, 用来描述要接受的函数参数. 下表是基础的类型字符:


类型字符

用户空间数据类型

b

Boolean

l

Integer

d

Floating point

s

String

r

Resource

a

Array

o

Object instance

O

Object instance of a specified type

z

Non-specific zval

Z

Dereferenced non-specific zval


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;}

尽管integer和long的存储空间通常是一样大的, 但它们并不完全一致. 尝试将一个int类型的数据解引用到long *参数可能会带来不期望的结果, 尤其是在64位平台上更加容易出错. 因此, 请严格使用下表中列出的数据类型.


类型

C语言中的对应数据类型

b

zend_bool

l

long

d

double

s

char *, int

r

zval *

a

zval *

o

zval *

O

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个元字符用于修改参数处理方式. 如下表所示:


类型修改符

含义

|

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

!

!之前的一个修饰符对应的参数如果是NULL,提供的内部变量将被设置为真实的NULL指针.

/

/之前的一个修饰符对应的参数指定为写时拷贝,它将自动的隔离到新的zval(is_ref = 0, refcount = 1)


可选参数

我们再来看一看修订版的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 tsrmls_cc efree><p style="margin-top:0px; margin-bottom:0px; text-indent:25.2px; font-family:Helvetica"><span style="letter-spacing:0.0px">这里, var_dump()预分配了一个传给函数的参数个数大小的zval **指针向量. 接着使用zend_get_parameters_array_ex()一次性将参数摊入到该向量中. 你可能会猜到, 存在这个函数的另外一个版本: zend_get_parameters_array(), 它们仅有一点不同: 自动隔离, 返回zval *而不是zval **, 在第一个参数上要求传递ZEND_NUM_ARGS().</span></p>
<p style="margin-top:0px; margin-bottom:0px; font-size:24px; font-family:Helvetica"><span style="letter-spacing:0.0px"><strong>参数信息和类型暗示</strong></span></p>
<p style="margin-top:0px; margin-bottom:0px; text-indent:25.2px; font-family:Helvetica"><span style="letter-spacing:0.0px">在上一章已经简短的向你介绍了使用Zend引擎2的 参数信息结构进行类型暗示的概念. 我们应该记得, 这个特性是针对ZE2(Zend引擎2)的, 对于php4的ZE1(Zend引擎1)不可用.</span></p>
<p style="margin-top:0px; margin-bottom:0px; text-indent:25.2px; font-family:Helvetica"><span style="letter-spacing:0.0px">我们重新从ZE2的参数信息结构开始. 每个参数信息都使用ZEND_BEGIN_ARG_INFO()或ZEND_BEGIN_ARG_INFO_EX()宏开始, 接着是0个或多个ZEND_ARG_*INFO()行, 最后以ZEND_END_ARG_INFO()调用结束.</span></p>
<p style="margin-top:0px; margin-bottom:0px; text-indent:25.2px; font-family:Helvetica"><span style="letter-spacing:0.0px">这些宏的定义和基本使用可以在刚刚结束的第6章"返回值"的编译期引用传值一节中找到.</span></p>
<p style="margin-top:0px; margin-bottom:0px; text-indent:25.2px; font-family:Helvetica"><span style="letter-spacing:0.0px">假设你想要重新实现count()函数, 你可能需要创建下面这样一个函数:</span></p>
<pre name="code" class="cpp">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函数, 它们用于操纵这些复杂的结构体.


目录

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

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
誇大広告を超えて:今日のPHPの役割の評価誇大広告を超えて:今日のPHPの役割の評価Apr 12, 2025 am 12:17 AM

PHPは、特にWeb開発の分野で、最新のプログラミングで強力で広く使用されているツールのままです。 1)PHPは使いやすく、データベースとシームレスに統合されており、多くの開発者にとって最初の選択肢です。 2)動的コンテンツ生成とオブジェクト指向プログラミングをサポートし、Webサイトを迅速に作成および保守するのに適しています。 3)PHPのパフォーマンスは、データベースクエリをキャッシュおよび最適化することで改善でき、その広範なコミュニティと豊富なエコシステムにより、今日のテクノロジースタックでは依然として重要になります。

PHPの弱い参照は何ですか、そしていつ有用ですか?PHPの弱い参照は何ですか、そしていつ有用ですか?Apr 12, 2025 am 12:13 AM

PHPでは、弱い参照クラスを通じて弱い参照が実装され、ガベージコレクターがオブジェクトの回収を妨げません。弱い参照は、キャッシュシステムやイベントリスナーなどのシナリオに適しています。オブジェクトの生存を保証することはできず、ごみ収集が遅れる可能性があることに注意する必要があります。

PHPで__invoke Magicメソッドを説明してください。PHPで__invoke Magicメソッドを説明してください。Apr 12, 2025 am 12:07 AM

\ _ \ _ Invokeメソッドを使用すると、オブジェクトを関数のように呼び出すことができます。 1。オブジェクトを呼び出すことができるように\ _ \ _呼び出しメソッドを定義します。 2。$ obj(...)構文を使用すると、PHPは\ _ \ _ Invokeメソッドを実行します。 3。ロギングや計算機、コードの柔軟性の向上、読みやすさなどのシナリオに適しています。

同時性については、PHP 8.1の繊維を説明します。同時性については、PHP 8.1の繊維を説明します。Apr 12, 2025 am 12:05 AM

繊維はPhp8.1で導入され、同時処理機能が改善されました。 1)繊維は、コルーチンと同様の軽量の並行性モデルです。 2)開発者がタスクの実行フローを手動で制御できるようにし、I/O集約型タスクの処理に適しています。 3)繊維を使用すると、より効率的で応答性の高いコードを書き込むことができます。

PHPコミュニティ:リソース、サポート、開発PHPコミュニティ:リソース、サポート、開発Apr 12, 2025 am 12:04 AM

PHPコミュニティは、開発者の成長を支援するための豊富なリソースとサポートを提供します。 1)リソースには、公式のドキュメント、チュートリアル、ブログ、LaravelやSymfonyなどのオープンソースプロジェクトが含まれます。 2)StackOverFlow、Reddit、およびSlackチャネルを通じてサポートを取得できます。 3)開発動向は、RFCに従うことで学ぶことができます。 4)コミュニティへの統合は、積極的な参加、コード共有への貢献、および学習共有への貢献を通じて達成できます。

PHP対Python:違いを理解しますPHP対Python:違いを理解しますApr 11, 2025 am 12:15 AM

PHP and Python each have their own advantages, and the choice should be based on project requirements. 1.PHPは、シンプルな構文と高い実行効率を備えたWeb開発に適しています。 2。Pythonは、簡潔な構文とリッチライブラリを備えたデータサイエンスと機械学習に適しています。

PHP:それは死にかけていますか、それとも単に適応していますか?PHP:それは死にかけていますか、それとも単に適応していますか?Apr 11, 2025 am 12:13 AM

PHPは死にかけていませんが、常に適応して進化しています。 1)PHPは、1994年以来、新しいテクノロジーの傾向に適応するために複数のバージョンの反復を受けています。 2)現在、電子商取引、コンテンツ管理システム、その他の分野で広く使用されています。 3)PHP8は、パフォーマンスと近代化を改善するために、JITコンパイラおよびその他の機能を導入します。 4)Opcacheを使用してPSR-12標準に従って、パフォーマンスとコードの品質を最適化します。

PHPの未来:適応と革新PHPの未来:適応と革新Apr 11, 2025 am 12:01 AM

PHPの将来は、新しいテクノロジーの傾向に適応し、革新的な機能を導入することで達成されます。1)クラウドコンピューティング、コンテナ化、マイクロサービスアーキテクチャに適応し、DockerとKubernetesをサポートします。 2)パフォーマンスとデータ処理の効率を改善するために、JITコンパイラと列挙タイプを導入します。 3)パフォーマンスを継続的に最適化し、ベストプラクティスを促進します。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

EditPlus 中国語クラック版

EditPlus 中国語クラック版

サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SecLists

SecLists

SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強力な PHP 統合開発環境