Maison > Article > développement back-end > [Traduction][Développement d'extensions php et embarquées] Chapitre 7 - Acceptation des paramètres
Déclaration des droits
Cette traduction peut être diffusée librement sans restriction et sans profit.
À l'exception de quelques exceptions "d'aperçu", les fonctions d'extension que vous avez traitées jusqu'à présent sont très simples, retournez simplement. Cependant, la plupart des fonctions n'ont pas qu'un seul objectif. Vous transmettez généralement certains paramètres et espérez recevoir une réponse utile basée sur la valeur et d'autres traitements supplémentaires.
Conversion de type automatique pour zend_parse_parameters()
Tout comme les valeurs de retour que vous avez vues dans le chapitre précédent, les valeurs des paramètres tournent également autour de l'accès indirect à la référence zval. Le moyen le plus simple d'obtenir les valeurs de ces zval* est d'utiliser. la fonction zend_parse_parameters().
L'appel de zend_parse_parameters() commence presque toujours par la macro ZEND_NUM_ARGS() suivie de l'omniprésent TSRMLS_CC Comme vous pouvez le deviner d'après le nom, ZEND_NUM_ARGS() renvoie le nombre réel de paramètres transmis. int type. Puisque zend_parse_parameters() internal La façon dont cela fonctionne, vous n'avez probablement pas besoin de connaître cette valeur directement, il vous suffit donc de la transmettre pour l'instant
Le paramètre suivant de zend_parse_parameters() est le. paramètre de chaîne de format, qui est composé des caractères de description de type de base pris en charge par le moteur Zend. Une séquence de caractères utilisée pour décrire les paramètres de fonction à accepter. Le tableau suivant présente les caractères de type de base :
.
zend_parse_parameters() Les paramètres restants dépendent de votre chaîne de format La description du type spécifié. Pour les types simples, déréférencez directement au type de base du langage C. Par exemple, le type de données long est résolu comme suit :
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; }Bien que l'espace de stockage de integer et long soit généralement le même, ils ne sont pas complètement cohérents. Tenter de déréférencer des données de type int vers un paramètre long * peut entraîner des résultats indésirables, en particulier sur les plates-formes 64 bits. . Il est plus sujet aux erreurs. Par conséquent, veuillez utiliser strictement les colonnes du tableau suivant en dehors du type de données. Notez que tous les autres types complexes se résolvent en fait en simples. zvals. La raison pour cela et le fait de ne pas utiliser les macros RETURN_*() pour renvoyer des types de données complexes sont également soumis à l'incapacité d'émuler véritablement ces structures dans l'espace C. Ce que zend_parse_parameters() peut faire pour votre fonction, c'est garantir que zval. * que vous recevez est du type correct. Si nécessaire, même la conversion de type implicite sera effectuée, comme la conversion d'un tableau en un objet de types s et O, car ils doivent être expliqués séparément. nécessitent deux paramètres en un seul appel. Dans le chapitre 10 "Objet php4" et dans le chapitre 11 "Objet php5", vous en apprendrez plus sur O. Pour le type s, nous étendons la fonction sample_hello_world() au chapitre 5 "Votre première extension". afin qu'il puisse être utilisé avec le Dites bonjour par nom spécifié.
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"); }La fonction zend_parse_parameters() peut échouer parce que la fonction transmet trop peu de paramètres pour satisfaire la chaîne de format, ou parce que l'un des paramètres ne peut pas être converti au type demandé. Dans ce cas, un message d'erreur sera généré automatiquement, votre extension n'a donc pas besoin de le faire Pour demander plus d'un paramètre, vous devez développer la chaîne de format. pour inclure des caractères supplémentaires et supprimer les paramètres restants dans l'appel à zend_parse_parameters(). Les paramètres se comportent de la même manière qu'ils sont définis dans les fonctions de l'espace utilisateur, analysés de gauche à droite. type de base, il y a 3 métacaractères utilisés pour modifier la méthode de traitement des paramètres comme le montre le tableau suivant :
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"); }Paramètres facultatifs
Jetons un coup d'œil à. l'exemple sample_hello_world() révisé. L'étape suivante consiste à ajouter un paramètre $greeting facultatif :
sample_hello_world() peut désormais être appelé avec uniquement le paramètre $name, ou avec les deux paramètres. Lorsque le deuxième paramètre n'est pas passé, la valeur par défaut est utilisée. Dans l'implémentation du langage C, les paramètres facultatifs sont également spécifiés de la même manièrefunction sample_hello_world($name, $greeting='Mr./Ms.') { echo "Hello $greeting $name!\n"; }
Pour compléter cette fonction, nous devons. utilisez le caractère pipe (|) dans la chaîne de format zend_parse_parameters(). Le côté gauche du caractère pipe Les paramètres sont analysés à partir de la pile d'appels. Si les paramètres à droite de tous les caractères pipe ne sont pas fournis dans la pile d'appels, ils le seront. ne sera pas modifié (les paramètres correspondants après la chaîne de format zend_parse_parameters()). Par exemple :
sample_hello_world('Ginger Rogers','Ms.'); sample_hello_world('Fred Astaire');
sauf si appelé La valeur du paramètre optionnel est fournie lorsque la valeur est fournie, sinon sa valeur initiale ne sera pas fournie. être modifié. Par conséquent, il est très important de définir une valeur par défaut initiale pour le paramètre facultatif. Dans la plupart des cas, sa valeur par défaut initiale est NULL/0, mais parfois, par exemple, dans l'exemple ci-dessus, la valeur par défaut est d'autres valeurs significatives. .
IS_NULL Vs. NULL
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"); }Chaque zval, même un type IS_NULL très simple, occupera un petit espace mémoire. Il faut donc quelques cycles d'horloge (CPU) pour allouer de l'espace mémoire et s'initialiser. la valeur, et enfin la libérer lorsqu'elle n'est plus nécessaire. Pour de nombreuses fonctions, l'utilisation des paramètres NULL dans l'espace d'appel n'est qu'un paramètre de marqueur. Pas important, donc ce processus n'a aucun sens, zend_parse_parameters(). permet aux paramètres d'être marqués comme "autoriser NULL". Si un point d'exclamation (!) est ajouté après un caractère de description de format, cela signifie que si le paramètre correspondant est passé NULL, alors définissez les paramètres correspondants lors de l'appel de zend_parse_parameters() à real NULL. pointeurs. Considérez les deux morceaux de code suivants, un avec ce modificateur et un sans :
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可以做到, 但是它和强制引用完全不同.
Parce que lorsque l'indicateur zend.ze1_compatiblity_mode est défini, vous pouvez vous soucier du paramètre by_ref de la ligne ZEND_ARG_OBJECT_INFO. Dans ce cas particulier, l'objet peut toujours recevoir une copie au lieu d'une référence. objet, vous pourriez avoir besoin d'une vraie référence, vous n'avez donc pas à vous soucier de l'impact de cela si vous définissez cet indicateur
N'oubliez pas qu'il existe une option allow_null dans la macro d'informations sur les paramètres. de tableaux et d'objets. Pour plus de détails sur l'autorisation de NULL, veuillez vous référer à la section La référence au moment de la compilation passant par valeur dans le chapitre précédent
Bien sûr, l'utilisation des informations sur les paramètres pour les astuces de type n'est prise en charge que dans ZE2. vous voulez que l'extension que vous regardez soit compatible avec php4, vous devez utiliser zend_get_parameters(), de cette façon, la vérification de type ne peut être placée qu'à l'intérieur de la fonction, et elle peut être complétée manuellement en testant Z_TYPE_P(value) ou en utilisant la méthode convert_to_type() vue au chapitre 2 pour la conversion automatique de type.
Résumé
Maintenant, vos mains peuvent être un peu en désordre. Le code fonctionnel pour communiquer avec l'espace utilisateur est implémenté via une simple saisie/. fonctions de sortie. Vous avez une compréhension plus approfondie du système de comptage de références de zval et avez appris à transmettre des variables de contrôle à vos méthodes de fonction internes et à votre timing.
Le chapitre suivant commencera à apprendre le type de données du tableau et à comprendre comment le La représentation du tableau de l'espace utilisateur correspond à l'implémentation interne de HashTable. De plus, vous verrez également un grand nombre de fonctions API Zend et PHP facultatives, elles sont utilisées pour manipuler ces structures complexes.
Ce qui précède est [Traduction]. [Développement d'extensions php et intégré] Chapitre 7 - Acceptation des paramètres Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !