首页 后端开发 php教程 编写PHP扩展函数的参数

编写PHP扩展函数的参数

Dec 07, 2016 pm 03:27 PM
php

函数的参数 
最简单的获取函数调用者传递过来的参数便是使用zend_parse_parameters()函数。 zend_parse_parameters()函数的前几个参数我们直接用内核里宏来生成便可以了,形式为:ZEND_NUM_ARGS() TSRMLS_CC,注意两者之间有个空格,但是没有逗号。从名字可以看出,ZEND_NUM_ARGS()代表着参数的个数。 紧接着需要传递给zend_parse_parameters()函数的参数是一个用于格式化的字符串,就像printf的第一个参数一样。下面表示了最常用的几个符号。 
type_spec是格式化字符串,其常见的含义如下: 
参数   代表着的类型 
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   zval**类型 
f   表示函数、方法名称,PHP5.1里貌似木有... ... 
这个函数就像printf()函数一样,后面的参数是与格式化字符串里的格式一一对应的。一些基础类型的数据会直接映射成C语言里的类型。 
ZEND_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 is: %ld\n", foo); 
    RETURN_TRUE; 

一般来说,int和long这两种数据类型的数据往往是相同的,但也有例外情况。所以我们不应改把long的数组放在一个int里,尤其是在64位平台里,那将引发一些不容易排查的Bug。所以通过zend_parse_parameter()函数接收参数时,我们应该使用内核约定好的那些类型的变量作为载体。 
参数  对应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** 
注意,所有的PHP语言中的复合类型参数都需要zval*类型来作为载体,因为它们都是内核自定义的一些数据结构。我们一定要确认参数和载体的类型一致,如果需要,它可以进行类型转换,比如把array转换成stdClass对象。 s和O(字母大写欧)类型需要单独说一些,因为它们都需要两个载体。我们将在接下来的章节里了解php中对象的具体实现。这样我们改写一下我们在第五章定义的一个函数: 
function sample_hello_world($name) { 
    echo "Hello $name!\n"; 

在编写扩展时,我们需要用zend_parse_parameters()来接收这个字符串: 
ZEND_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()要接收的参数数量,它便会执行失败,并返回FAILURE。 
如果我们需要接收多个参数,可以直接在zend_parse_paramenters()的参数里罗列接收载体便可以了,如: 
function sample_hello_world($name, $greeting) { 
    echo "Hello $greeting $name!\n"; 

sample_hello_world('John Smith', 'Mr.'); 
在PHP扩展里应该这样来实现: 
ZEND_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"); 

除了上面定义的参数,还有其它的三个参数来增强我们接收参数的能力,如下: 
Type Modifier   Meaning 
|       它之前的参数都是必须的,之后的都是非必须的,也就是有默认值的。 
!       如果接收了一个PHP语言里的null变量,则直接把其转成C语言里的NULL,而不是封装成IS_NULL类型的zval。 
/       如果传递过来的变量与别的变量共用一个zval,而且不是引用,则进行强制分离,新的zval的is_ref__gc==0, and refcount__gc==1. 
函数参数的默认值 
现在让我们继续改写sample_hello_world(), 接下来我们使用一些参数的默认值,在php语言里就像下面这样: 
function sample_hello_world($name, $greeting='Mr./Ms.') { 
    echo "Hello $greeting $name!\n"; 

sample_hello_world('Ginger Rogers','Ms.'); 
sample_hello_world('Fred Astaire'); 
此时即可以只向sample_hello_world中传递一个参数,也可以传递完整的两个参数。 那同样的功能我们怎样在扩展函数里实现呢?我们需要借助zend_parse_parameters中的(|)参数,这个参数之前的参数被认为是必须的,之后的便认为是非必须的了,如果没有传递,则不会去修改载体。 
ZEND_FUNCTION(sample_hello_world) { 
    char *name; 
    int name_len; 
    char *greeting = "Mr./Mrs."; 
    int greeting_len = sizeof("Mr./Mrs.") - 1; 


    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,或者一个与函数逻辑有关的值。 每个zval,包括IS_NULL型的zval,都需要占用一定的内存空间,并且需要cpu的计算资源来为它申请内存、初始化,并在它们完成工作后释放掉。但是很多代码都都没有意识到这一点。有很多代码都会把一个null型的值包裹成zval的IS_NULL类型,在扩展开发里这种操作是可以优化的,我们可以把参数接收城C语言里的NULL。我们就这一个问题看以下代码: 
ZEND_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); 
    } 
    ... 

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

这两段代码乍看起来并没有什么很大的不同,但是第一段代码确实需要更多的cpu和内存资源。可能这个技巧在平时并没多大用,不过技多不压身,知道总比不知道好。 

Forced Separation 
当一个变量被传递给函数时候,无论它是否被引用,它的refcoung__gc属性都会加一,至少成为2。一份是它自己,另一份是传递给函数的copy。在改变这个zval之前,有时会需要提前把它分成实际意义上的两份copy。这就是"/"格式符的作用。它将把写时复制的zval提前分成两个完整独立的copy,从而使我们可以在下面的代码中随意的对其进行操作。否则我们可能需要不停的提醒自己对接收的参数进行分离等操作。Like the NULL flag, this modifier goes after the type it means to impact. Also like the NULL flag, you won't know you need this feature until you actually have a use for it. 

zend_get_arguments() 
如果你想让你的扩展能够兼容老版本的PHP,或者你只想以zval为载体来接收参数,便可以考虑使用zend_get_parameters()函数来接收参数。 zend_get_parameters()与zend_parse_parameters()不同,从名字上我们便可以看出,它直接获取,而不做解析。首先,它不会自动进行类型转换,所有的参数在扩展实现中的载体都需要是zval类型的,下面让我们来看一个最简单的例子: 
ZEND_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()在接收失败的时候,并不会自己抛出错误,它也不能方便的处理具有默认值的参数。 最后一点与zend_parse_parameters不同的是,它会自动的把所有符合copy-on-write的zval进行强制分离,生成一个崭新的copy送到函数内部。如果你希望用它其它的特性,而唯独不需要这个功能,可以去尝试一下用zend_get_parameters_ex()函数来接收参数。 为了不对copy-on-write的变量进行分离操作,zend_get_parameters_ex()的参数是zval**类型的,而不是zval*。 这个函数不太经常用,可能只会在你碰到一些极端问题时候才会想到它,而它用起来却很简单: 
ZEND_FUNCTION(sample_onearg) { 
    zval **firstarg; 
    if (zend_get_parameters_ex(1, &firstarg) == FAILURE) { 
        WRONG_PARAM_COUNT; 
    } 
    /* Do something with firstarg... */ 

注意zend_get_parameters_ex不需要ZEND_NUM_ARGS()作为参数,因为它是在是在后期加入的,那个参数已经不再需要了。 
上面例子中还使用了WRONG_PARAM_COUNT宏,它的功能是抛出一个E_WARNING级别的错误信息,并自动return。 

可变参数 
有两种其它的zend_get_parameter_**函数,专门用来解决参数很多或者无法提前知道参数数目的问题。想一下php语言中var_dump()函数的用法,我们可以向其传递任意数量的参数,它在内核中的实现其实是这样的: 
ZEND_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        php_var_dump(args[i], 1 TSRMLS_CC); 
    } 
    efree(args); 

程序首先获取参数数量,然后通过safe_emalloc函数申请了相应大小的内存来存放这些zval**类型的参数。这里使用了zend_get_parameters_array_ex()函数来把传递给函数的参数填充到args中。你可能已经立即想到,还存在一个名为zend_get_parameters_array()的函数,唯一不同的是它将zval*类型的参数填充到args中,并且需要ZEND_NUM_ARGS()作为参数。

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

适用于 Ubuntu 和 Debian 的 PHP 8.4 安装和升级指南 适用于 Ubuntu 和 Debian 的 PHP 8.4 安装和升级指南 Dec 24, 2024 pm 04:42 PM

PHP 8.4 带来了多项新功能、安全性改进和性能改进,同时弃用和删除了大量功能。 本指南介绍了如何在 Ubuntu、Debian 或其衍生版本上安装 PHP 8.4 或升级到 PHP 8.4

我后悔之前不知道的 7 个 PHP 函数 我后悔之前不知道的 7 个 PHP 函数 Nov 13, 2024 am 09:42 AM

如果您是一位经验丰富的 PHP 开发人员,您可能会感觉您已经在那里并且已经完成了。您已经开发了大量的应用程序,调试了数百万行代码,并调整了一堆脚本来实现操作

如何设置 Visual Studio Code (VS Code) 进行 PHP 开发 如何设置 Visual Studio Code (VS Code) 进行 PHP 开发 Dec 20, 2024 am 11:31 AM

Visual Studio Code,也称为 VS Code,是一个免费的源代码编辑器 - 或集成开发环境 (IDE) - 可用于所有主要操作系统。 VS Code 拥有针对多种编程语言的大量扩展,可以轻松编写

在PHP API中说明JSON Web令牌(JWT)及其用例。 在PHP API中说明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一种基于JSON的开放标准,用于在各方之间安全地传输信息,主要用于身份验证和信息交换。1.JWT由Header、Payload和Signature三部分组成。2.JWT的工作原理包括生成JWT、验证JWT和解析Payload三个步骤。3.在PHP中使用JWT进行身份验证时,可以生成和验证JWT,并在高级用法中包含用户角色和权限信息。4.常见错误包括签名验证失败、令牌过期和Payload过大,调试技巧包括使用调试工具和日志记录。5.性能优化和最佳实践包括使用合适的签名算法、合理设置有效期、

您如何在PHP中解析和处理HTML/XML? 您如何在PHP中解析和处理HTML/XML? Feb 07, 2025 am 11:57 AM

本教程演示了如何使用PHP有效地处理XML文档。 XML(可扩展的标记语言)是一种用于人类可读性和机器解析的多功能文本标记语言。它通常用于数据存储

php程序在字符串中计数元音 php程序在字符串中计数元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符组成的序列,包括字母、数字和符号。本教程将学习如何使用不同的方法在PHP中计算给定字符串中元音的数量。英语中的元音是a、e、i、o、u,它们可以是大写或小写。 什么是元音? 元音是代表特定语音的字母字符。英语中共有五个元音,包括大写和小写: a, e, i, o, u 示例 1 输入:字符串 = "Tutorialspoint" 输出:6 解释 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。总共有 6 个元

解释PHP中的晚期静态绑定(静态::)。 解释PHP中的晚期静态绑定(静态::)。 Apr 03, 2025 am 12:04 AM

静态绑定(static::)在PHP中实现晚期静态绑定(LSB),允许在静态上下文中引用调用类而非定义类。1)解析过程在运行时进行,2)在继承关系中向上查找调用类,3)可能带来性能开销。

什么是PHP魔术方法(__ -construct,__destruct,__call,__get,__ set等)并提供用例? 什么是PHP魔术方法(__ -construct,__destruct,__call,__get,__ set等)并提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些?PHP的魔法方法包括:1.\_\_construct,用于初始化对象;2.\_\_destruct,用于清理资源;3.\_\_call,处理不存在的方法调用;4.\_\_get,实现动态属性访问;5.\_\_set,实现动态属性设置。这些方法在特定情况下自动调用,提升代码的灵活性和效率。

See all articles