@@ -94,7 +94,7 @@ Hello, I'm my_func_2
9494```
9595大功告成,函数已经能够正常工作了,后续的工作就是不断完善handler实现扩展自己的功能了。
9696
97- #### 7.6.1.2 函数参数
97+ #### 7.6.1.2 函数参数解析
9898上面我们定义的函数没有接收任何参数,那么扩展定义的内部函数如何读取参数呢?首先回顾下函数参数的实现:用户自定义函数在编译时会为每个参数创建一个` zend_arg_info ` 结构,这个结构用来记录参数的名称、是否引用传参、是否为可变参数等,在存储上函数参数与局部变量相同,都分配在zend_execute_data上,且最先分配的就是函数参数,调用函数时首先会进行参数传递,按参数次序依次将参数的value从调用空间传递到被调函数的zend_execute_data,函数内部像访问普通局部变量一样通过存储位置访问参数,这是用户自定义函数的参数实现。
9999
100100内部函数与用户自定义函数最大的不同在于内部函数就是一个普通的C函数,除函数参数以外在zend_execute_data上没有其他变量的分配,函数参数是从PHP用户空间传到函数的,它们与用户自定义函数完全相同,包括参数的分配方式、传参过程,也是按照参数次序依次分配在zend_execute_data上,所以在扩展中定义的函数直接按照顺序从zend_execute_data上读取对应的值即可,PHP中通过` zend_parse_parameters() ` 这个函数解析zend_execute_data上保存的参数:
@@ -122,17 +122,165 @@ PHP_FUNCTION(my_func_1)
122122
123123注意:解析时除了整形、浮点型、布尔型和NULL是直接硬拷贝value外,其它解析到的变量只能是指针,arr为zend_execute_data上param_1的地址,即:` arr = ¶m_1 ` ,所以图中arr、param_1之间用的不是箭头指向,也就是说参数始终存储在zend_execute_data上,内部函数要用只能从zend_execute_data上取。接下来详细介绍下` zend_parse_parameters() ` 不同类型的解析用法。
124124
125- (1)整形、浮点型、布尔型、NULL
125+ __ (1)整形:l、L __
126126
127- (2)数组
127+ 整形通过"l"、"L"标识,表示解析的参数为整形,解析到的变量类型必须是` zend_long ` ,不能解析其它类型,如果输入的参数不是整形将按照类型转换规则将其转为整形:
128+ ``` c
129+ zend_long lval;
130+
131+ if (zend_parse_parameters(ZEND_NUM_ARGS(), " l" , &lval){
132+ ...
133+ }
134+ printf ("lval:%d\n", lval);
135+ ```
136+ 如果在标识符后加"!",即:"l!"、"L!",则必须再提供一个zend_bool变量的地址,通过这个值可以判断传入的参数是否为NULL,如果为NULL则将要解析到的zend_long值设置为0,同时zend_bool设置为1:
137+ ```c
138+ zend_long lval; //如果参数为NULL则此值被设为0
139+ zend_bool is_null; //如果参数为NULL则此值为1,否则为0
140+
141+ if(zend_parse_parameters(ZEND_NUM_ARGS(), "l!", &lval, &is_null){
142+ ...
143+ }
144+ ```
145+ 具体的解析过程:
146+ ``` c
147+ // zend_API.c #line:519
148+ case ' l' :
149+ case ' L' :
150+ {
151+ //这里获取解析到的变量地址取的是zend_long *,所以只能解析到zend_long
152+ zend_long *p = va_arg(*va, zend_long *);
153+ zend_bool *is_null = NULL;
154+
155+ //后面加"!"时check_null为1
156+ if (check_null) {
157+ is_null = va_arg(*va, zend_bool *);
158+ }
159+
160+ if (!zend_parse_arg_long(arg, p, is_null, check_null, c == 'L')) {
161+ return "integer";
162+ }
163+ }
164+ ```
165+ ``` c
166+ static zend_always_inline int zend_parse_arg_long (zval * arg, zend_long * dest, zend_bool * is_null, int check_null, int cap)
167+ {
168+ if (check_null) {
169+ * is_null = 0;
170+ }
171+ if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) {
172+ //传参为整形,无需转化
173+ * dest = Z_LVAL_P(arg);
174+ } else if (check_null && Z_TYPE_P(arg) == IS_NULL) {
175+ //传参为NULL
176+ * is_null = 1;
177+ * dest = 0;
178+ } else if (cap) {
179+ //"L"的情况
180+ return zend_parse_arg_long_cap_slow(arg, dest);
181+ } else {
182+ //"l"的情况
183+ return zend_parse_arg_long_slow(arg, dest);
184+ }
185+ return 1;
186+ }
187+ ```
188+ > __Note:__ "l"与"L"的区别在于,当传参不是整形且转为整形后超过了整形的大小范围时,"L"将值调整为整形的最大或最小值,而"l"将报错,比如传的参数是字符串"9223372036854775808",转整形后超过了unsigned int64的最大值:0xFFFFFFFFFFFFFFFF,"L"将解析为0xFFFFFFFFFFFFFFFF。
189+
190+ __(2)布尔型:b__
191+
192+ 通过"b"标识符表示将传入的参数解析为布尔型,解析到的变量必须是zend_bool:
193+ ```c
194+ zend_bool ok;
195+
196+ if(zend_parse_parameters(ZEND_NUM_ARGS(), "b", &ok, &is_null) == FAILURE){
197+ ...
198+ }
199+ ```
200+ "b!"的用法与整形的完全相同,也必须再提供一个zend_bool的地址用于获取传参是否为NULL,如果为NULL,则zend_bool为0,用于获取是否NULL的zend_bool为1。
201+
202+ __ (3)浮点型:d__
203+
204+ 通过"d"标识符表示将参数解析为浮点型,解析的变量类型必须为double:
205+ ``` c
206+ double dval;
207+
208+ if (zend_parse_parameters(ZEND_NUM_ARGS(), " d" , &dval) == FAILURE){
209+ ...
210+ }
211+ ```
212+ 具体解析过程不再展开,"d!"与整形、布尔型用法完全相同。
213+
214+ __ (4)字符串:s、S、p、P__
215+
216+ 字符串解析有两种形式:char* 、zend_string,其中"s"将参数解析到` char* ` ,且需要额外提供一个size_t类型的变量用于获取字符串长度,"S"将解析到zend_string:
217+ ``` c
218+ char *str;
219+ size_t str_len;
220+
221+ if (zend_parse_parameters(ZEND_NUM_ARGS(), " s" , &str, &str_len) == FAILURE){
222+ ...
223+ }
224+ ```
225+ ``` c
226+ zend_string *str;
227+
228+ if (zend_parse_parameters(ZEND_NUM_ARGS(), " S" , &str) == FAILURE){
229+ ...
230+ }
231+ ```
232+ "s!"、"S!"与整形、布尔型用法不同,字符串时不需要额外提供zend_bool的地址,如果参数为NULL,则char* 、zend_string将设置为NULL。除了"s"、"S"之外还有两个类似的:"p"、"P",从解析规则来看主要用于解析路径,实际与普通字符串没什么区别,尚不清楚这俩有什么特殊用法。
233+
234+ __ (5)数组:a、A、h、H__
235+
236+ 数组的解析也有两类,一类是解析到zval层面,另一类是解析到HashTable,其中"a"、"A"解析到的变量必须是zval,"h"、"H"解析到HashTable,这两类是等价的:
237+ ``` c
238+ zval *arr; // 必须是zval指针,不能是zval arr,因为参数保存在zend_execute_data上,arr为此空间上参数的地址
239+ HashTable *ht;
240+
241+ if (zend_parse_parameters(ZEND_NUM_ARGS(), " ah" , &arr, &ht) == FAILURE){
242+ ...
243+ }
244+ ```
245+ 具体解析过程:
246+ ``` c
247+ case ' A' :
248+ case ' a' :
249+ {
250+ //解析到zval *
251+ zval **p = va_arg(*va, zval **);
252+
253+ if (!zend_parse_arg_array(arg, p, check_null, c == 'A')) {
254+ return "array";
255+ }
256+ }
257+ break ;
258+
259+ case ' H' :
260+ case ' h' :
261+ {
262+ //解析到HashTable *
263+ HashTable **p = va_arg(*va, HashTable **);
264+
265+ if (!zend_parse_arg_array_ht(arg, p, check_null, c == 'H')) {
266+ return "array";
267+ }
268+ }
269+ break ;
270+ ```
271+ "a!"、"A!"、"h!"、"H!"的用法与字符串一致,也不需要额外提供别的地址,如果传参为NULL,则对应解析到的zval* 、HashTable* 也为NULL。
272+ > __ Note:__
273+ >
274+ > 1、"a"与"A"当传参为数组时没有任何差别,它们的区别在于:如果传参为对象"A"将按照对象解析到zval,而"a"将报错
275+ >
276+ > 2、"h"与"H"当传参为数组时同样没有差别,当传参为对象时,"H"将把对象的成员参数数组解析到目标变量,"h"将报错
128277
129- (3)对象
278+ __ (3)对象 __
130279
131- (4)资源
280+ __ (4)资源 __
132281
133- (5)字符串
134282
135- (6)其它标识符
283+ __ (6)其它标识符 __
136284
137285#### 7.6.1.3 函数返回值
138286
0 commit comments