Rights Statement
This translation can be freely disseminated without restriction without making a profit.
With a few "preview" exceptions, the extension functions you've dealt with so far have been simple, just returning. However, most functions are not It has only one purpose. You usually pass some parameters and hope to receive a useful response based on the value and other additional processing.
The automatic type conversion of zend_parse_parameters()
is the same as the return value you saw in the previous chapter, parameters The values of are also centered around indirect access to zval references. The easiest way to obtain the values of these zval* is to use the zend_parse_parameters() function.
Calling zend_parse_parameters() is almost always preceded by the ZEND_NUM_ARGS() macro followed by the ubiquitous TSRMLS_CC . ZEND_NUM_ARGS() As you can guess from the name, it returns the actual number of parameters passed as an int. Due to the internal working method of zend_parse_parameters(), you may not need to know this value directly, so you only need to pass it now.
The next parameter of zend_parse_parameters() is the format string parameter, which is a character sequence composed of basic type description characters supported by the Zend engine, used to describe the function parameters to be accepted. The following table is the basic type characters:
The remaining parameters of zend_parse_parameters() depend on the type description specified in your format string. For simple types, dereference directly to the base type of C language. For example, the long data type is decoded as follows:
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; }
Although integer and long The storage spaces of are usually the same size, but they are not exactly the same. Trying to dereference an int type data into a long * parameter may bring undesirable results, especially on 64-bit platforms, which is more error-prone. Therefore , please strictly use the data types listed in the table below.
Note that all other complex types are actually parsed into simple zvals. The reason for this and not using RETURN_*() macros to return complex data types Again, both are limited by the inability to truly simulate these structures in C space. What zend_parse_parameters() can do for your function is ensure that the zval* you receive is of the correct type. It will even execute Implicit type conversion, such as converting an array to an object of stdClass.
s and O types need to be explained separately, because they require two parameters in one call. In Chapter 10 "php4 objects" and Chapter 11 "php5 objects" You will learn more about O. For the s type, we extend the sample_hello_world() function in Chapter 5 "Your First Extension" so that it can greet the specified person's name.
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"); }
The zend_parse_parameters() function may fail because the function is passed too few parameters to satisfy the format string, or because one of the parameters cannot be converted to the requested type. In this case, it will automatically print an error message, so your extension cannot You need to do this.
To request more than 1 parameter, you need to expand the format string to include other characters, and push other parameters after the zend_parse_parameters() call. The parameters behave consistent with their definition in the user space function, from left Parse to the right.
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"); }
In addition to the basic type, there are 3 metacharacters used to modify the parameter processing method. As shown in the following table:
Optional parameters
Let’s take a look at the revised sample_hello_world() Example, the next step is to add an optional $greeting parameter:
function sample_hello_world($name, $greeting='Mr./Ms.') { echo "Hello $greeting $name!\n"; }
sample_hello_world() can now be called with only the $name parameter, or with both parameters.
sample_hello_world('Ginger Rogers','Ms.'); sample_hello_world('Fred Astaire');
When the second parameter is not passed, Use the default value. In the C language implementation, optional parameters are also specified in a similar way.
To complete this function, we need to use the pipe character (|) in the zend_parse_parameters() format string. The parameters on the left side of the pipe character are from Call stack parsing, all parameters on the right side of the pipe character will not be modified if they are not provided in the call stack (the corresponding parameters after the zend_parse_parameters() format string). For example:
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"); }
Unless the value of the optional parameter is provided when calling , otherwise its initial value will not be modified, so it is very important to set an initial default value for the optional parameter. In most cases, its initial default value is NULL/0, but sometimes, such as the above example, the default is Meaningful other values.
IS_NULL Vs. NULL
Each zval, even the very simple IS_NULL type, occupies a small memory space. Therefore, it requires some (CPU) clock cycles to allocate memory space , initialize the value, and finally release it when it is no longer needed.
For many functions, using NULL parameters in the call space just marks the parameters as unimportant, so this process is meaningless. Fortunately zend_parse_parameters() allows parameters to be marked For "allow NULL", if an exclamation point (!) is added after a format description character, it means that if the corresponding parameter is passed NULL, the corresponding parameter when zend_parse_parameters() is called will be set to a real NULL pointer. Consider the following two paragraphs Code, one has this modifier, one does not:
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可以做到, 但是它和强制引用完全不同.
Because when the zend.ze1_compatiblity_mode flag is set, you may care about the by_ref setting of the ZEND_ARG_OBJECT_INFO line. In this special case, the object may still be passed a copy instead of a reference. Because when you are dealing with the object, you may need A real reference, so you don't have to worry about this impact by setting this flag.
Don't forget that there is an allow_null option in the parameter information macro of arrays and objects. For details about allowing NULL, please refer to the compilation time in the previous chapter Reference passing by value section.
Of course, using parameter information for type hinting is only supported in ZE2. If you want the extension you are looking at to be compatible with php4, you need to use zend_get_parameters(), so you can only put type verification in the function. Internally, this is done manually by testing Z_TYPE_P(value) or automatic type conversion using the convert_to_type() method seen in Chapter 2.
Summary
By now you may have a bit of a mess on your hands, communicating with user space Functional code is implemented through simple input/output functions. I have a deeper understanding of zval's reference counting system, and learned the methods and timing of controlling variables to be passed to your internal functions.
The next chapter will start to learn the array data type, And understand how the user space array representation maps to the internal HashTable implementation. In addition, you will also see a large number of optional Zend and PHP api functions, which are used to manipulate these complex structures.
That’s it [Translation] [php Extended Development and Embedded] Chapter 7 - Accepting parameters, please pay attention to the PHP Chinese website (m.sbmmt.com) for more related content!