[Traduction] [développement d'extensions php et embarquées] Chapitre 6 - Valeur de retour

黄舟
Libérer: 2023-03-05 16:16:02
original
1232 Les gens l'ont consulté

Valeur de retour

La fonction de l'espace utilisateur utilise le mot-clé return pour renvoyer des informations à son espace appelant, ce qui est la même que la syntaxe du langage C

Par exemple :

function sample_long() {  
  return 42;  
}  
$bar = sample_long();
Copier après la connexion
Lorsque sample_long() est appelé, 42 est renvoyé et défini sur la variable $bar. Le code équivalent en langage C est le suivant :

int sample_long(void) {  
  return 42;  
}  
void main(void) {  
  int bar = sample_long();  
}
Copier après la connexion
Bien sûr, en langage C. vous savez toujours quelle est la fonction appelée et est renvoyée en fonction du prototype de fonction, vous devez donc définir la variable où le résultat du retour est stocké. Lorsqu'il est traité dans l'espace utilisateur PHP, le type de variable est dynamique et dépend de. Chapitre 2 "Contenu variable" Le type de zval introduit dans "Hors du monde".

Variable return_value

Vous pensez peut-être que votre fonction interne doit renvoyer directement un zval, ou allouer une mémoire zval space et renvoyez le zval comme suit * .

PHP_FUNCTION(sample_long_wrong)  
{  
    zval *retval;  
  
    MAKE_STD_ZVAL(retval);  
    ZVAL_LONG(retval, 42);  
  
    return retval;  
}
Copier après la connexion
Malheureusement, cela est incorrect plutôt que de forcer chaque implémentation de fonction à allouer un zval et à le renvoyer, le moteur Zend pré-alloue cet espace avant l'appel de la fonction. . Le zval est alors Le type est initialisé à IS_NULL et la valeur est passée comme nom de paramètre return_value. Voici l'approche correcte :

PHP_FUNCTION(sample_long)  
{  
    ZVAL_LONG(return_value, 42);  
    return;  
}
Copier après la connexion
Il convient de noter que l'implémentation de PHP_FUNCTION() le fait. ne renvoie directement aucune valeur. Au lieu de cela, il insère directement les données appropriées dans le paramètre return_value, et le moteur Zend les traitera après l'exécution de la fonction interne.

Rappel amical : la macro ZVAL_LONG() est un wrapper. pour les opérations d'affectation multiples :

Z_TYPE_P(return_value) = IS_LONG;  
Z_LVAL_P(return_value) = 42;
Copier après la connexion
Ou plus directement :

return_value->type = IS_LONG;  
return_value->value.lval = 42;
Copier après la connexion
Les attributs is_ref et refcount de return_value ne doivent pas être modifiés directement par les fonctions internes. Ces valeurs​​sont. initialisé et traité par le moteur Zend lors de l'appel de votre fonction.

Jetons maintenant un coup d'œil à cette fonction spéciale et ajoutons-la à l'exemple d'extension créé au chapitre 5 "Votre première extension". sample_hello_world() et modification sample_long() ajoutée à la structure php_sample_functions :

static function_entry php_sample_functions[] = {  
    PHP_FE(sample_hello_world, NULL)  
    PHP_FE(sample_long, NULL)  
    { NULL, NULL, NULL }  
};
Copier après la connexion
Nous pouvons maintenant exécuter make pour reconstruire l'extension.

Si tout est OK, vous pouvez exécuter php et testez la nouvelle fonction :

$ php -r 'var_dump(sample_long());
Copier après la connexion
Encapsulage de macros plus compactes

En termes de lisibilité et de maintenabilité du code, il y a des parties répétées dans les macros ZVAL_*() : la variable return_value Dans celle-ci. Dans ce cas, la macro remplace ZVAL par RETVAL, et nous pouvons omettre return_value lors de l'appel.

Dans l'exemple précédent, le code d'implémentation de sample_long() peut être réduit à ce qui suit :

PHP_FUNCTION(sample_long)  
{  
    RETVAL_LONG(42);  
    return;  
}
Copier après la connexion
Le tableau suivant répertorie la famille de macros RETVAL dans le moteur Zend a été publiée. À l'exception de deux macros spéciales, la macro RETVAL est la même que la macro de la famille ZVAL correspondante sauf que le paramètre return_value est supprimé.

[Traduction] [développement dextensions php et embarquées] Chapitre 6 - Valeur de retour

Notez que les macros VRAI et FAUX n'ont pas de parenthèses afin de tenir compte des écarts par rapport aux normes de codage Zend/PHP, et la principale est conservée. Maintenez la compatibilité ascendante. Si la construction de votre extension échoue, fermez. Lorsque vous recevez le message d'erreur macro non définie RETVAL_TRUE(), veuillez confirmer si vous avez écrit les parenthèses par erreur lors de l'écriture de ces deux macros dans le code.

Habituellement, lorsque votre fonction traite la valeur de retour, elle est prête à quitter et renvoie le contrôle à la portée appelante. Pour cette raison, certaines autres macros sont conçues pour que les fonctions internes renvoient : les macros de la famille RETURN_*(). >Bien que vous ne puissiez pas la voir, cette fonction sera effectivement renvoyée après l'appel de la macro RETURN_LONG(). Nous pouvons ajouter php_printf() à la fin de la fonction pour tester :

php_printf(. ), comme décrit dans le contenu, car RETURN_LONG() appelle implicitement la fonction end.

PHP_FUNCTION(sample_long)  
{  
    RETURN_LONG(42);  
}
Copier après la connexion
Comme la série RETVAL, chaque type simple répertorié dans le tableau précédent a une macro RETURN correspondante. Les macros RETURN_TRUE et RETURN_FALSE n'utilisent pas de parenthèses.

Des types plus complexes, tels que des objets et des tableaux, sont également renvoyés via le paramètre return_value ; cependant, ils ne peuvent intrinsèquement pas être créés via des macros simples, même s'ils sont de type ressource. il a la macro RETVAL, renvoie en fait le type de ressource. Cela nécessite un travail supplémentaire. Vous verrez comment renvoyer ces types dans les chapitres 8 à 11.
PHP_FUNCTION(sample_long)  
{  
    RETURN_LONG(42);  
    php_printf("I will never be reached.\n");  
}
Copier après la connexion

Est-ce que cela en vaut la peine ?

Un. La fonctionnalité intrinsèque de Zend qui n'est pas encore utilisée est les paramètres return_value_used. Considérez le code d'espace utilisateur suivant :

Parce que sample_array_range() ne stocke pas le résultat dans une variable lorsqu'il est appelé, l'espace du tableau de 1000 éléments. créé ici sera complètement inutile. Bien sûr, appeler sample_array_range() comme ceci est stupide, mais que pouvez-vous faire sans un bon moyen de prédire l'avenir

Bien que les fonctions de l'espace utilisateur ne soient pas accessibles, les fonctions internes le sont ? comptez sur le paramètre return_value_used commun à toutes les fonctions internes. Paramètres, ignorez conditionnellement un tel comportement dénué de sens

function sample_array_range() {  
    $ret = array();  
    for($i = 0; $i < 1000; $i++) {  
        $ret[] = $i;  
    }  
    return $ret;  
}  
sample_array_range();
Copier après la connexion
Pour voir le fonctionnement de cette fonction, ajoutez simplement cette fonction à votre fichier source sample.c et exposez-la. to Dans la structure php_sample_functions :

Remarque de traduction : Concernant la façon de gérer les valeurs de retour de fonction qui ne sont pas utilisées dans l'espace utilisateur, vous pouvez lire la fonction zend_do_fcall_common_helper_SPEC dans Zend/zend_vm_execute.h après le traitement. l'appel de fonction interne, il vérifiera si la valeur de retour de cette fonction est utilisée, sinon, elle est libérée en conséquence.

PHP_FUNCTION(sample_array_range)  
{  
    if (return_value_used) {  
        int i;  
        /* 返回从0到999的数组 */  
        array_init(return_value);  
        for(i = 0; i < 1000; i++) {  
            add_next_index_long(return_value, i);  
        }  
        return;  
    } else {  
        /* 提示错误 */  
        php_error_docref(NULL TSRMLS_CC, E_NOTICE,  
               "Static return-only function called without processing output");  
        RETURN_NULL();  
    }  
}
Copier après la connexion
Valeur de référence de retour

从用户空间的php工作中你可能已经知道了, php函数还可以以引用方式返回值. 由于实现问题, 在php 5.1之前应该避免在内部函数中返回引用, 因为它不能工作. 考虑下面的用户空间代码片段:

function &sample_reference_a() {  
    /* 如果全局空间没有变量$a, 就以初始值NULL创建它 */  
    if (!isset($GLOBALS[&#39;a&#39;])) {  
        $GLOBALS[&#39;a&#39;] = NULL;  
    }  
    return $GLOBALS[&#39;a&#39;];  
}  
$a = &#39;Foo&#39;;  
$b = sample_reference_a();  
$b = &#39;Bar&#39;;
Copier après la connexion

在这个代码片段中, 就像使用$b = &$GLOBALS['a];或者由于在全局空间, 使用$b = &$a; 将$b创建为$a的一个引用.

回顾第3章"内存管理", 在到达最后一行时, $a和$b实际上包含相同的值'Bar'. 现在我们看看内部实现这个函数:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
PHP_FUNCTION(sample_reference_a)  
{  
    zval **a_ptr, *a;  
  
  
    /* 从全局符号表查找变量$a */  
    if (zend_hash_find(&EG(symbol_table), "a", sizeof("a"),  
                                          (void**)&a_ptr) == SUCCESS) {  
        a = *a_ptr;  
    } else {  
        /* 如果不存在$GLOBALS[&#39;a&#39;]则创建它 */  
        ALLOC_INIT_ZVAL(a);  
        zend_hash_add(&EG(symbol_table), "a", sizeof("a"), &a,  
                                              sizeof(zval*), NULL);  
    }  
    /* 废弃旧的返回值 */  
    zval_ptr_dtor(return_value_ptr);  
    if (!a->is_ref && a->refcount > 1) {  
        /* $a需要写时复制, 在使用之前, 必选先隔离 */  
        zval *newa;  
        MAKE_STD_ZVAL(newa);  
        *newa = *a;  
        zval_copy_ctor(newa);  
        newa->is_ref = 0;  
        newa->refcount = 1;  
        zend_hash_update(&EG(symbol_table), "a", sizeof("a"), &newa,  
                                                 sizeof(zval*), NULL);  
        a = newa;  
    }  
    /* 将新的返回值设置为引用方式并增加refcount */  
    a->is_ref = 1;  
    a->refcount++;  
    *return_value_ptr = a;  
}  
#endif /* PHP >= 5.1.0 */
Copier après la connexion

return_value_ptr参数是所有内部函数都会传递的另外一个公共参数, 它是zval **类型, 包含了指向return_value的指针. 通过在它上面调用zval_ptr_dtor(), 默认的return_value的zval *将被释放. 接着可以自由的选择一个新的zval *去替代它, 在这里选择了变量$a, 选择性的进行zval隔离后, 将它的is_ref设置为1, 设置到return_value_ptr中.

如果现在编译运行这段代码, 无论如何你会得到一个段错误. 为了使它可以工作, 你需要在php_sample.h中增加下面的代码:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
static  
    ZEND_BEGIN_ARG_INFO_EX(php_sample_retref_arginfo, 0, 1, 0)  
    ZEND_END_ARG_INFO ()  
#endif /* PHP >= 5.1.0 */
Copier après la connexion

译注: 译者使用的php-5.4.9中, ZEND_BEGIN_ARG_INFO_EX的宏定义中已经包含了static修饰符, 因此本书示例中相应的需要进行修改, 请读者在阅读过程中注意这一点.

接着, 在php_sample_functions中声明你的函数时使用这个结构:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
    PHP_FE(sample_reference_a, php_sample_retref_arginfo)  
#endif /* PHP >= 5.1.0 */
Copier après la connexion

这个结构你将在本章后面详细学习, 用来向Zend引擎提供函数调用的重要暗示信息. 这里它告诉Zend引擎return_value需要被覆写, 应该从return_value_ptr中得到正确的地址. 如果没有这个暗示, Zend引擎会简单的在return_value_ptr中设置NULL, 这可能使得在执行到zval_ptr_dtor()时程序崩溃.

这段代码每一个片段都包裹在#if块中, 它指示编译器只有在PHP版本大于等于5.1时才启用这个支持. 如果没有这些条件指令, 这个扩展将不能在php4上编译(因为在return_value_ptr中包含的一些元素不存在), 在php5.0中不能提供正确的功能(有一个bug导致返回的引用被以值的方式拷贝)

引用方式返回值

使用return(语法)结构将值和变量回传给调用方是没有问题的, 但是, 有时你需要从一个函数返回多个值. 你可以使用数组(我们将在第8章"使用数组和哈希表工作")达到这个目的, 或者你可以使用参数栈返回值.

调用时引用传值

一种简单的引用传递变量方式是要求调用时在参数变量名前使用取地址符(&), 如下用户空间代码:

function sample_byref_calltime($a) {  
    $a .= &#39; (modified by ref!)&#39;;  
}  
$foo = &#39;I am a string&#39;;  
sample_byref_calltime(&$foo);  
echo $foo;
Copier après la connexion

参数变量名前的取地址符(&)使得发送给函数的是$foo实际的zval, 而不是它的内容拷贝.这就使得函数可以通过传递的这个参数修改这个值来返回信息. 如果调用sample_byref_calltime()时没有在$foo前面使用取地址符(&), 则函数内的修改并不会影响原来的变量.

在C层面重演这个行为并不需要特殊的编码. 在你的sample.c源码文件中sample_long()后面创建下面的函数:

PHP_FUNCTION(sample_byref_calltime)  
{  
    zval *a;  
    int addtl_len = sizeof(" (modified by ref!)") - 1;  
  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &a) == FAILURE) {  
        RETURN_NULL();  
    }  
    if (!a->is_ref) {  
        /* 不是以引用方式传值则不做任何事离开函数 */  
        return;  
    }  
    /* 确保变量是字符串 */  
    convert_to_string(a);  
    /* 扩大a的缓冲区以使可以保存要追加的数据 */  
    Z_STRVAL_P(a) = erealloc(Z_STRVAL_P(a),  
        Z_STRLEN_P(a) + addtl_len + 1);  
    memcpy(Z_STRVAL_P(a) + Z_STRLEN_P(a),  
    " (modified by ref!)", addtl_len + 1);  
    Z_STRLEN_P(a) += addtl_len;  
}
Copier après la connexion

和往常一样, 这个函数需要增加到php_sample_functions结构中.

PHP_FE(sample_byref_calltime,        NULL)
Copier après la connexion

译注: 运行时引用传值在译者使用的版本(php-5.4.9 ZendEngine 2.4.0)中已经被完全废弃. 早前的版本可以在php.ini中使用allow_call_time_pass_reference指令启用. 测试时请注意版本问题.

编译期引用传值

更常用的方式是编译期引用传值. 这里函数的参数定义为只能以引用方式使用, 传递常量或其他中间值(比如函数调用的结果)将导致错误, 因为那样函数就没有地方可以存储结果值去回传了. 用户空间的编译期引用传值代码如下:

function sample_byref_compiletime(&$a) {  
    $a .= &#39; (modified by ref!)&#39;;  
}  
$foo = &#39;I am a string&#39;;  
sample_byref_compiletime($foo);  
echo $foo;
Copier après la connexion

如你所见, 这和调用时引用传值的差别仅在于取地址符的位置不同. 在C的层面上去看这个函数时, 函数代码上是完全相同的. 唯一的区别在于php_sample_functions块中对函数的声明:

PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo)
Copier après la connexion

php_sample_byref_arginfo是一个常量结构, 你需要在使用之前先定义它.

实际上, 编译期引用传值中, 对is_ref检查的代码可以删除, 因为总能保证它是引用方式的. 不过这里留着它也不会有什么危害.

在Zend引擎1(php4)中, 这个结构是一个简单的char *列表, 第一个元素指定了长度, 接下来是描述每个函数参数的标记集合.

static unsigned char php_sample_byref_arginfo[] =  
                                { 1, BYREF_FORCE };
Copier après la connexion

这里, 1表示向量只包含了一个参数的信息. 后面的元素就顺次描述参数特有的标记信息, 第二个元素描述第一个参数. 如果涉及到第二个或第三个参数, 对应的就需要在这个向量中增加第三个和第四个元素. 参数信息的可选值如下表:

[Traduction] [développement dextensions php et embarquées] Chapitre 6 - Valeur de retour

在Zend引擎2(php 5+)中, 你将使用一种更加可扩展的结构, 它包含类更多的信息, 比如最小和最大参数要求, 类型暗示, 是否强制引用等.

首先, 参数信息结构使用两个宏中的一个定义. 较简单的一个是ZEND_BEGIN_ARG_INFO(), 它需要两个参数:

ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference)
Copier après la connexion

name非常简单, 就是扩展中其他地方使用这个结构时的名字, 当前这个例子我们使用的名字是: php_sample_byref_arginfo

pass_rest_by_reference的含义和BYREF_FORCE_REST用在Zend引擎1的参数信息向量最后一个元素时一致. 如果这个参数设置为1, 所有没有在结构中显式描述的参数都被认为是编译期引用传值的参数.

还有一种可选的开始宏, 它引入了两个Zend引擎1没有的新选项, 它是ZEND_BEGIN_ARG_INFO_EX():

ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference,  
                             required_num_args)
Copier après la connexion

当然, name和pass_rest_by_reference和前面所说的是相同的含义. 本章前面也提到了, return_reference是告诉Zend你的函数需要用自己的zval覆写return_value_ptr.

最后一个参数, required_num_args, 它是另外一种类型的暗示, 用来告诉Zend在某种被认为是不完整的调用时完全的跳过函数调用.

在你拥有了一个恰当的开始宏后, 接下来就可以是0个或多个ZEND_ARG_*INFO元素. 这些宏的类型和用法如下表.

[Traduction] [développement dextensions php et embarquées] Chapitre 6 - Valeur de retour

最后, 所有使用Zend引擎2的宏设置的参数信息结构必须使用ZEND_END_ARG_INFO()结束. 对于你的sample函数, 你需要选择一个如下的结构:

ZEND_BEGIN_ARG_INFO(php_sample_byref_arginfo, 0)  
    ZEND_ARG_PASS_INFO(1)  
ZEND_END_ARG_INFO()
Copier après la connexion

为了让扩展兼容Zend引擎1和2, 需要使用#ifdef语句为两者均定义arg_info结构:

#ifdef ZEND_ENGINE_2  
static  
    ZEND_BEGIN_ARG_INFO(php_sample_byref_arginfo, 0)  
        ZEND_ARG_PASS_INFO(1)  
    ZEND_END_ARG_INFO()  
#else /* ZE 1 */  
static unsigned char php_sample_byref_arginfo[] =  
                                { 1, BYREF_FORCE };  
#endif
Copier après la connexion

注意, 这些代码片段是集中在一起的, 现在是时候创建一个真正的编译期引用传值实现了. 首先, 我们将为Zend引擎1和2定义的php_sample_byref_arginfo块放到头文件php_sample.h中.

接下来, 可以有两种选择, 一种是将PHP_FUNCTION(sample_byref_calltime)拷贝一份, 并重命名为PHP_FUNCTION(sample_byref_compiletime), 接着在php_sample_functions中增加一行PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo)

这种方式简单移动, 并且在一段时候后修改时, 更加不容易产生混淆. 因为这里只是示例代码, 因此, 我们可以稍微放松点, 使用你在上一章学的PHP_FALIAS()避免代码重复.

这样, 就不是赋值PHP_FUNCTION(sample_byref_calltime), 而是在php_sample_functions中直接增加一行:

PHP_FALIAS(sample_byref_compiletime, sample_byref_calltime,  
    php_sample_byref_arginfo)
Copier après la connexion

回顾第5章, 这将创建一个名为sample_byref_compiletime()的用户空间函数, 它对应的内部实现是sample_byref_calltime()的代码. php_sample_byref_arginfo是这个版本的特殊之处.

小结

本章你看到了怎样从一个内部函数返回值, 包括直接返回值和引用方式返回, 以及通过参数栈引用返回. 此外还简单了解了Zend引擎2的参数类型暗示结构zend_arg_info.

下一章你将会继续探究接受基本的zval参数以及使用zend_parse_parameters()强大的类型戏法.

以上就是 [翻译][php扩展开发和嵌入式]第6章-返回值的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal