권리 설명
이 번역은 이익 없이 제한 없이 자유롭게 배포할 수 있습니다.
몇 가지 "미리 보기" 예외를 제외하면 지금까지 다루신 확장 기능은 매우 간단합니다. 그러나 대부분의 함수에는 단지 하나의 목적만 있는 것은 아닙니다. 일반적으로 일부 매개변수를 전달하고 해당 값 및 기타 추가 처리에 따라 유용한 응답을 받기를 바랍니다.
zend_parse_parameters()에 대한 자동 유형 변환
이전 장에서 본 반환 값과 마찬가지로 매개변수 값도 zval 참조에 대한 간접적인 액세스를 중심으로 진행됩니다. 이러한 zval* 값을 얻는 가장 쉬운 방법은 다음을 사용하는 것입니다. zend_parse_parameters() 함수.
zend_parse_parameters() 호출은 거의 항상 ZEND_NUM_ARGS() 매크로와 그 뒤에 널리 사용되는 TSRMLS_CC로 시작됩니다. 이름에서 짐작할 수 있듯이 ZEND_NUM_ARGS()는 전달된 매개변수의 실제 개수를 반환합니다. int 유형입니다. zend_parse_parameters() 내부 작동 방식이므로 이 값을 직접 알 필요는 없으므로 지금은 그냥 전달하면 됩니다.
zend_parse_parameters()의 다음 매개변수는 Zend 엔진에서 지원하는 기본 유형 설명 문자로 구성된 형식 문자열 매개변수 허용되는 함수 매개변수를 설명하는 데 사용되는 일련의 문자 다음 표는 기본 유형 문자입니다.
zend_parse_parameters() 나머지 매개변수는 형식 문자열에 따라 다릅니다. 지정된 유형 설명 간단한 유형의 경우 C 언어의 기본 유형을 직접 역참조합니다. 예를 들어 긴 데이터 유형은 다음과 같이 해결됩니다. >
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; }
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"); }
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"); }
function sample_hello_world($name, $greeting='Mr./Ms.') { echo "Hello $greeting $name!\n"; }
sample_hello_world('Ginger Rogers','Ms.'); sample_hello_world('Fred Astaire');
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"); }
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可以做到, 但是它和强制引用完全不同.
zend.ze1_compatiblity_mode 플래그가 설정되면 ZEND_ARG_OBJECT_INFO 라인의 by_ref 설정에 주의할 수 있기 때문입니다. 이 특별한 경우에는 객체를 처리할 때 참조 대신 복사본이 전달될 수 있습니다. , 실제 참조가 필요할 수 있으므로 이 플래그를 설정하면 이 영향에 대해 걱정할 필요가 없습니다.
의 매개변수 정보 매크로에 허용_null 옵션이 있다는 것을 잊지 마세요. NULL 허용에 대한 자세한 내용은 이전 장의 값으로 전달하는 컴파일 시간 섹션을 참조하세요.
물론, 유형 힌트에 대한 매개변수 정보 사용은 ZE2에서만 지원됩니다. 보고 있는 확장이 php4와 호환되기를 원한다면 zend_get_parameters()를 사용해야 합니다. 이런 방식으로 유형 확인은 함수 내부에만 배치할 수 있으며 Z_TYPE_P(값)을 테스트하거나 다음을 사용하여 수동으로 완료할 수 있습니다. 자동 타입 변환을 위해 2장에서 본 Convert_to_type() 메소드.
요약
이제 손이 좀 지저분해지겠지만, 사용자 공간과의 통신을 위한 함수형 코드는 간단한 입출력으로 구현된다. 함수에 대해 더 깊이 이해하고 제어 변수를 내부 함수 메서드 및 타이밍에 전달하는 방법을 배웠습니다.
다음 장에서는 배열 데이터 유형을 배우고 사용자가 어떻게 사용하는지 이해합니다. 공간 배열 표현은 내부 HashTable 구현에 매핑됩니다. 또한 이러한 복잡한 구조를 조작하는 데 사용되는 수많은 선택적 Zend 및 PHP API 함수도 볼 수 있습니다. php 확장 개발 및 임베디드] 7장 - 매개변수 허용 관련 내용은 PHP 중국어 웹사이트(www.php.cn)를 참고하세요!