>  기사  >  백엔드 개발  >  [번역] [php 확장 개발 및 임베디드] 7장 - 매개변수 허용

[번역] [php 확장 개발 및 임베디드] 7장 - 매개변수 허용

黄舟
黄舟원래의
2017-02-09 11:44:361161검색

권리 설명

이 번역은 이익 없이 제한 없이 자유롭게 배포할 수 있습니다.

몇 가지 "미리 보기" 예외를 제외하면 지금까지 다루신 확장 기능은 매우 간단합니다. 그러나 대부분의 함수에는 단지 하나의 목적만 있는 것은 아닙니다. 일반적으로 일부 매개변수를 전달하고 해당 값 및 기타 추가 처리에 따라 유용한 응답을 받기를 바랍니다.

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 엔진에서 지원하는 기본 유형 설명 문자로 구성된 형식 문자열 매개변수 허용되는 함수 매개변수를 설명하는 데 사용되는 일련의 문자 다음 표는 기본 유형 문자입니다.

[번역] [php 확장 개발 및 임베디드] 7장 - 매개변수 허용

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;  
}

integer와 long의 저장 공간은 일반적으로 동일하지만, int를 long* 매개변수로 역참조하려고 하면 특히 64비트 플랫폼에서 바람직하지 않은 결과가 발생할 수 있으며 오류가 더 많이 발생합니다. 따라서 다음 표 데이터 유형에 나열된 항목을 엄격히 사용하십시오.

[번역] [php 확장 개발 및 임베디드] 7장 - 매개변수 허용

다른 모든 복합 유형은 실제로 단순 zval로 해결된다는 점에 유의하세요. 복잡한 데이터 유형을 반환하기 위해 RETURN_*() 매크로 사용 모두 마찬가지로, C 공간에서 이러한 구조를 실제로 에뮬레이트할 수 없다는 점으로 인해 제한됩니다. 필요한 경우 배열을 stdClass의 객체로 변환하는 등 암시적 유형 변환도 수행합니다.

및 O 유형은 한 번의 호출에 두 개의 매개변수가 필요하므로 별도로 설명해야 합니다. 10장 "php4 개체" 및 11장 "php5 개체"에서 O에 대해 자세히 알아봅니다. s 유형의 경우 지정된 사람의 이름을 맞이할 수 있도록 5장 "첫 번째 확장"의 Sample_hello_world() 함수를 확장합니다. .

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");  
}

zend_parse_parameters() 함수에 전달된 매개변수가 너무 적어서 형식 문자열을 충족할 수 없거나 매개변수 중 하나를 요청된 유형으로 변환할 수 없기 때문에 실패할 수 있습니다. 자동으로 오류 메시지를 출력하므로 확장 프로그램에서는 이를 수행할 필요가 없습니다.

두 개 이상의 매개변수를 요청하려면 추가 문자를 포함하도록 형식 문자열을 확장하고 나중에 zend_parse_parameters의 스택에 추가 매개변수를 푸시해야 합니다. () 호출. 사용자 공간 함수 정의에서 동일한 동작을 가지며 왼쪽에서 오른쪽으로 구문 분석됩니다.

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");  
}

기본 유형 외에도 매개변수 처리 방법을 수정하는 데 사용되는 3개의 메타 문자가 있습니다. 다음 표에 표시된 대로:

[번역] [php 확장 개발 및 임베디드] 7장 - 매개변수 허용

선택적 매개변수

수정된 Sample_hello_world() 예제를 살펴보겠습니다. 다음 단계는 선택사항을 추가하는 것입니다. $greeting 매개변수:

function sample_hello_world($name, $greeting='Mr./Ms.') {  
    echo "Hello $greeting $name!\n";  
}

sample_hello_world()는 이제 $name 매개변수만 사용하거나 두 매개변수 모두 사용하여 호출할 수 있습니다.

sample_hello_world('Ginger Rogers','Ms.');  
sample_hello_world('Fred Astaire');

두 번째 매개변수가 전달되지 않으면 기본값이 사용됩니다. C 언어 구현에서는 선택적 매개변수도 비슷한 방식으로 지정됩니다.

이 기능을 완료하려면 zend_parse_parameters() 형식 문자열에 파이프 문자(|)를 사용해야 합니다. 파이프 문자의 왼쪽은 호출 스택에서 구문 분석되고, 파이프 문자 오른쪽의 모든 매개변수는 호출 스택에 매개변수가 제공되지 않은 경우 수정되지 않습니다(zend_parse_parameters( ) 형식 문자열) 예:

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");  
}

호출 시 선택적 매개변수의 값을 제공하지 않으면 초기 값이 수정되지 않으므로 선택적 매개변수에 대한 초기 기본값을 설정하는 것이 중요합니다. 대부분의 경우 초기 기본값은 NULL/0이지만 위의 예와 같이 기본값이 의미가 있는 경우도 있습니다.

IS_NULL

매우 간단한 IS_NULL 유형이라 할지라도 각 zval은 작은 메모리 공간을 차지하므로 메모리 공간을 할당하려면 약간의 (CPU) 클럭 사이클이 필요하며, 값을 초기화하고 더 이상 필요하지 않을 때 최종적으로 해제합니다.

많은 함수의 경우 호출 공간에서 NULL 매개변수를 사용하면 해당 매개변수가 중요하지 않은 것으로 표시되므로 이 프로세스는 의미가 없습니다. 다행히도 zend_parse_parameters()를 사용하면 느낌표(!)가 있는 경우 매개변수를 "NULL 허용"으로 표시할 수 있습니다. 형식 설명 문자 뒤에 추가되면 해당 매개변수가 전달된다는 의미입니다. NULL이 전달되면 zend_parse_parameters()가 호출될 때 해당 매개변수가 NULL 포인터로 설정됩니다. 그리고 없는 것:

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)를 참고하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.