>  기사  >  백엔드 개발  >  [번역][php 확장 개발 및 내장] 6장 - 반환 값

[번역][php 확장 개발 및 내장] 6장 - 반환 값

黄舟
黄舟원래의
2017-02-09 11:40:301170검색

반환값

사용자 공간 함수는 return 키워드를 사용하여 호출 공간에 정보를 반환합니다. 이는 C 언어의 구문과 동일합니다.

예:

function sample_long() {  
  return 42;  
}  
$bar = sample_long();

sample_long()이 호출되면 42가 반환되고 $bar 변수에 설정됩니다. C 언어의 해당 코드는 다음과 같습니다.

int sample_long(void) {  
  return 42;  
}  
void main(void) {  
  int bar = sample_long();  
}

물론 C 언어에서는 항상 함수를 알고 있습니다. 호출되는 것이 무엇이며, 함수 프로토타입을 기반으로 반환되므로 이에 따라 반환 결과가 저장되는 변수를 정의해야 합니다. PHP 사용자 공간에서 처리할 때 변수 유형은 동적이며 2장 "에 의존합니다. Ins and Out of Variables" 도입된 zval 유형.

return_value 변수

내부 함수가 직접 zval을 반환하거나 zval 메모리 공간을 할당하고 zval *을 반환해야 한다고 생각할 수도 있습니다.

PHP_FUNCTION(sample_long_wrong)  
{  
    zval *retval;  
  
    MAKE_STD_ZVAL(retval);  
    ZVAL_LONG(retval, 42);  
  
    return retval;  
}

안타깝게도 이것은 올바르지 않습니다. 모든 함수 구현에서 zval을 할당하고 반환하도록 하는 대신 Zend 엔진은 함수 호출 전에 이 공간을 미리 할당한 다음 zval의 유형을 IS_NULL로 초기화합니다. 올바른 방법은 다음과 같습니다.

PHP_FUNCTION(sample_long)  
{  
    ZVAL_LONG(return_value, 42);  
    return;  
}

PHP_FUNCTION() 구현은 값을 직접 반환하지 않고 대신 return_value 매개변수에 적절한 데이터를 넣습니다. 내부 함수가 실행된 후에 Zend 엔진이 이를 처리합니다.

알림: ZVAL_LONG() 매크로는 다중 할당 작업을 위한 래퍼입니다:

Z_TYPE_P(return_value) = IS_LONG;  
Z_LVAL_P(return_value) = 42;

또는 더 직접적으로:

return_value->type = IS_LONG;  
return_value->value.lval = 42;

return_value의 is_ref 및 refcount 속성은 내부 함수에 의해 직접 수정되어서는 안 됩니다. 이러한 값은 함수 호출 시 Zend 엔진에 의해 초기화되고 처리됩니다.

이제 이 특수 항목을 살펴보겠습니다. function 을 사용하여 5장 "첫 번째 확장"에서 만든 샘플 확장에 추가하세요. 이 함수를 Sample_hello_world() 함수 아래에 추가하고 php_sample_functions 구조에 Sample_long()을 추가하세요.

static function_entry php_sample_functions[] = {  
    PHP_FE(sample_hello_world, NULL)  
    PHP_FE(sample_long, NULL)  
    { NULL, NULL, NULL }  
};

이제 실행할 수 있습니다. make를 사용하여 확장 프로그램을 다시 빌드하세요.

모든 것이 정상이면 PHP를 실행하고 새 기능을 테스트할 수 있습니다.

$ php -r 'var_dump(sample_long());

더 컴팩트한 매크로로 래핑

코드 가독성과 측면에서 유지 관리 측면에서 ZVAL_*() 매크로: return_value 변수에 반복되는 부분이 있습니다. 이 경우 매크로의 ZVAL을 RETVAL로 바꾸면 호출 시 return_value를 생략할 수 있습니다.

이전 예에서 구현 Sample_long()의 코드는 다음과 같이 축소될 수 있습니다.

PHP_FUNCTION(sample_long)  
{  
    RETVAL_LONG(42);  
    return;  
}

다음 표에는 Zend 엔진의 RETVAL 계열 매크로가 나열되어 있습니다. 두 가지 특별한 매크로를 제외하고 RETVAL 매크로는 return_value 매개변수가 삭제됩니다. , 해당 ZVAL 계열 매크로와 동일합니다.

[번역][php 확장 개발 및 내장] 6장 - 반환 값


TRUE 및 FALSE 매크로에는 대괄호가 없습니다. 이는 Zend/PHP 코드 표준 편차를 고려하여 이전 버전과의 호환성을 유지하기 위한 것입니다. 확장 기능을 빌드하지 못하고 정의되지 않은 매크로 RETVAL_TRUE() 오류 메시지가 표시되면 코드에 이를 작성하십시오. 두 개의 매크로를 사용할 때 괄호가 잘못되었습니다.

일반적으로 함수가 반환 값을 처리할 때쯤에는 종료하고 호출 범위로 제어를 반환할 준비가 됩니다. 이러한 이유로 일부 다른 매크로는 다음과 같습니다. 반환하는 데 사용됨: RETURN_*() 계열 매크로.

PHP_FUNCTION(sample_long)  
{  
    RETURN_LONG(42);  
}

볼 수는 없지만 실제로 이 함수는 RETURN_LONG() 매크로가 호출된 후에 반환됩니다. function )을 사용하여

PHP_FUNCTION(sample_long)  
{  
    RETURN_LONG(42);  
    php_printf("I will never be reached.\n");  
}

php_printf()를 테스트합니다. 콘텐츠에 설명된 대로 RETURN_LONG() 호출이 암시적으로 함수를 종료하기 때문입니다.

이전 표에 나열된 RETVAL 시리즈와 동일합니다. 각 단순 유형에는 해당 RETURN 매크로가 있습니다. 또한 RETURN_TRUE 및 RETURN_FALSE 매크로는 괄호를 사용하지 않습니다.

객체 및 배열과 같은 더 복잡한 유형도 return_value 매개변수를 통해 반환됩니다. , 이는 본질적으로 간단한 매크로를 사용하여 생성할 수 없습니다. RETVAL 매크로가 있더라도 실제로 리소스 유형을 반환하려면 8장부터 11장까지에서 이러한 유형을 반환하는 방법을 살펴보겠습니다.

수고할 가치가 있나요?

아직 사용되지 않은 Zend 내장 함수 기능 중 하나는 return_value_used 매개변수입니다. 다음 사용자 공간 코드를 고려하세요.

function sample_array_range() {  
    $ret = array();  
    for($i = 0; $i < 1000; $i++) {  
        $ret[] = $i;  
    }  
    return $ret;  
}  
sample_array_range();

sample_array_range() 호출 결과가 저장되지 않기 때문입니다. 물론, 이렇게 Sample_array_range()를 호출하는 것은 어리석은 일이지만, 미래를 예측할 수 있는 좋은 방법이 없다면 어떻게 할 수 있을까요?

사용자 공간 함수에 접근할 수 없더라도 내부 함수는 모든 내부 함수에 공통적인 return_value_used 매개변수 설정에 의존하여 이러한 무의미한 동작을 조건부로 건너뛸 수 있습니다.

PHP_FUNCTION(sample_array_range)  
{  
    if (return_value_used) {  
        int i;  
        /* 返回从0到999的数组 */  
        array_init(return_value);  
        for(i = 0; i < 1000; i++) {  
            add_next_index_long(return_value, i);  
        }  
        return;  
    } else {  
        /* 提示错误 */  
        php_error_docref(NULL TSRMLS_CC, E_NOTICE,  
               "Static return-only function called without processing output");  
        RETURN_NULL();  
    }  
}

이것을 보려면 함수를 작동하려면 추가하기만 하면 됩니다. 이 함수를 Sample.c 소스 파일에 추가하고 php_sample_functions 구조에 노출합니다:

PHP_FE(sample_array_range, NULL)

번역 참고: 사용자 공간에서 사용되지 않는 함수 반환 값을 처리하는 방법에 대해서는 zend_do_fcall_common_helper_SPEC 함수를 읽을 수 있습니다. Zend/zend_vm_execute.h에서 내부 함수 호출을 처리한 후 함수의 반환 값이 사용되었는지 확인합니다.

반환 참조 값

从用户空间的php工作中你可能已经知道了, php函数还可以以引用方式返回值. 由于实现问题, 在php 5.1之前应该避免在内部函数中返回引用, 因为它不能工作. 考虑下面的用户空间代码片段:

function &sample_reference_a() {  
    /* 如果全局空间没有变量$a, 就以初始值NULL创建它 */  
    if (!isset($GLOBALS[&#39;a&#39;])) {  
        $GLOBALS[&#39;a&#39;] = NULL;  
    }  
    return $GLOBALS[&#39;a&#39;];  
}  
$a = &#39;Foo&#39;;  
$b = sample_reference_a();  
$b = &#39;Bar&#39;;

在这个代码片段中, 就像使用$b = &$GLOBALS['a];或者由于在全局空间, 使用$b = &$a; 将$b创建为$a的一个引用.

回顾第3章"内存管理", 在到达最后一行时, $a和$b实际上包含相同的值'Bar'. 现在我们看看内部实现这个函数:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
PHP_FUNCTION(sample_reference_a)  
{  
    zval **a_ptr, *a;  
  
  
    /* 从全局符号表查找变量$a */  
    if (zend_hash_find(&EG(symbol_table), "a", sizeof("a"),  
                                          (void**)&a_ptr) == SUCCESS) {  
        a = *a_ptr;  
    } else {  
        /* 如果不存在$GLOBALS[&#39;a&#39;]则创建它 */  
        ALLOC_INIT_ZVAL(a);  
        zend_hash_add(&EG(symbol_table), "a", sizeof("a"), &a,  
                                              sizeof(zval*), NULL);  
    }  
    /* 废弃旧的返回值 */  
    zval_ptr_dtor(return_value_ptr);  
    if (!a->is_ref && a->refcount > 1) {  
        /* $a需要写时复制, 在使用之前, 必选先隔离 */  
        zval *newa;  
        MAKE_STD_ZVAL(newa);  
        *newa = *a;  
        zval_copy_ctor(newa);  
        newa->is_ref = 0;  
        newa->refcount = 1;  
        zend_hash_update(&EG(symbol_table), "a", sizeof("a"), &newa,  
                                                 sizeof(zval*), NULL);  
        a = newa;  
    }  
    /* 将新的返回值设置为引用方式并增加refcount */  
    a->is_ref = 1;  
    a->refcount++;  
    *return_value_ptr = a;  
}  
#endif /* PHP >= 5.1.0 */

return_value_ptr参数是所有内部函数都会传递的另外一个公共参数, 它是zval **类型, 包含了指向return_value的指针. 通过在它上面调用zval_ptr_dtor(), 默认的return_value的zval *将被释放. 接着可以自由的选择一个新的zval *去替代它, 在这里选择了变量$a, 选择性的进行zval隔离后, 将它的is_ref设置为1, 设置到return_value_ptr中.

如果现在编译运行这段代码, 无论如何你会得到一个段错误. 为了使它可以工作, 你需要在php_sample.h中增加下面的代码:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
static  
    ZEND_BEGIN_ARG_INFO_EX(php_sample_retref_arginfo, 0, 1, 0)  
    ZEND_END_ARG_INFO ()  
#endif /* PHP >= 5.1.0 */

译注: 译者使用的php-5.4.9中, ZEND_BEGIN_ARG_INFO_EX的宏定义中已经包含了static修饰符, 因此本书示例中相应的需要进行修改, 请读者在阅读过程中注意这一点.

接着, 在php_sample_functions中声明你的函数时使用这个结构:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
    PHP_FE(sample_reference_a, php_sample_retref_arginfo)  
#endif /* PHP >= 5.1.0 */

这个结构你将在本章后面详细学习, 用来向Zend引擎提供函数调用的重要暗示信息. 这里它告诉Zend引擎return_value需要被覆写, 应该从return_value_ptr中得到正确的地址. 如果没有这个暗示, Zend引擎会简单的在return_value_ptr中设置NULL, 这可能使得在执行到zval_ptr_dtor()时程序崩溃.

这段代码每一个片段都包裹在#if块中, 它指示编译器只有在PHP版本大于等于5.1时才启用这个支持. 如果没有这些条件指令, 这个扩展将不能在php4上编译(因为在return_value_ptr中包含的一些元素不存在), 在php5.0中不能提供正确的功能(有一个bug导致返回的引用被以值的方式拷贝)

引用方式返回值

使用return(语法)结构将值和变量回传给调用方是没有问题的, 但是, 有时你需要从一个函数返回多个值. 你可以使用数组(我们将在第8章"使用数组和哈希表工作")达到这个目的, 或者你可以使用参数栈返回值.

调用时引用传值

一种简单的引用传递变量方式是要求调用时在参数变量名前使用取地址符(&), 如下用户空间代码:

function sample_byref_calltime($a) {  
    $a .= &#39; (modified by ref!)&#39;;  
}  
$foo = &#39;I am a string&#39;;  
sample_byref_calltime(&$foo);  
echo $foo;

参数变量名前的取地址符(&)使得发送给函数的是$foo实际的zval, 而不是它的内容拷贝.这就使得函数可以通过传递的这个参数修改这个值来返回信息. 如果调用sample_byref_calltime()时没有在$foo前面使用取地址符(&), 则函数内的修改并不会影响原来的变量.

在C层面重演这个行为并不需要特殊的编码. 在你的sample.c源码文件中sample_long()后面创建下面的函数:

PHP_FUNCTION(sample_byref_calltime)  
{  
    zval *a;  
    int addtl_len = sizeof(" (modified by ref!)") - 1;  
  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &a) == FAILURE) {  
        RETURN_NULL();  
    }  
    if (!a->is_ref) {  
        /* 不是以引用方式传值则不做任何事离开函数 */  
        return;  
    }  
    /* 确保变量是字符串 */  
    convert_to_string(a);  
    /* 扩大a的缓冲区以使可以保存要追加的数据 */  
    Z_STRVAL_P(a) = erealloc(Z_STRVAL_P(a),  
        Z_STRLEN_P(a) + addtl_len + 1);  
    memcpy(Z_STRVAL_P(a) + Z_STRLEN_P(a),  
    " (modified by ref!)", addtl_len + 1);  
    Z_STRLEN_P(a) += addtl_len;  
}

和往常一样, 这个函数需要增加到php_sample_functions结构中.

PHP_FE(sample_byref_calltime,        NULL)

译注: 运行时引用传值在译者使用的版本(php-5.4.9 ZendEngine 2.4.0)中已经被完全废弃. 早前的版本可以在php.ini中使用allow_call_time_pass_reference指令启用. 测试时请注意版本问题.

编译期引用传值

更常用的方式是编译期引用传值. 这里函数的参数定义为只能以引用方式使用, 传递常量或其他中间值(比如函数调用的结果)将导致错误, 因为那样函数就没有地方可以存储结果值去回传了. 用户空间的编译期引用传值代码如下:

function sample_byref_compiletime(&$a) {  
    $a .= &#39; (modified by ref!)&#39;;  
}  
$foo = &#39;I am a string&#39;;  
sample_byref_compiletime($foo);  
echo $foo;

如你所见, 这和调用时引用传值的差别仅在于取地址符的位置不同. 在C的层面上去看这个函数时, 函数代码上是完全相同的. 唯一的区别在于php_sample_functions块中对函数的声明:

PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo)

php_sample_byref_arginfo是一个常量结构, 你需要在使用之前先定义它.

实际上, 编译期引用传值中, 对is_ref检查的代码可以删除, 因为总能保证它是引用方式的. 不过这里留着它也不会有什么危害.

在Zend引擎1(php4)中, 这个结构是一个简单的char *列表, 第一个元素指定了长度, 接下来是描述每个函数参数的标记集合.

static unsigned char php_sample_byref_arginfo[] =  
                                { 1, BYREF_FORCE };

这里, 1表示向量只包含了一个参数的信息. 后面的元素就顺次描述参数特有的标记信息, 第二个元素描述第一个参数. 如果涉及到第二个或第三个参数, 对应的就需要在这个向量中增加第三个和第四个元素. 参数信息的可选值如下表:

[번역][php 확장 개발 및 내장] 6장 - 반환 값

在Zend引擎2(php 5+)中, 你将使用一种更加可扩展的结构, 它包含类更多的信息, 比如最小和最大参数要求, 类型暗示, 是否强制引用等.

首先, 参数信息结构使用两个宏中的一个定义. 较简单的一个是ZEND_BEGIN_ARG_INFO(), 它需要两个参数:

ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference)

name非常简单, 就是扩展中其他地方使用这个结构时的名字, 当前这个例子我们使用的名字是: php_sample_byref_arginfo

pass_rest_by_reference的含义和BYREF_FORCE_REST用在Zend引擎1的参数信息向量最后一个元素时一致. 如果这个参数设置为1, 所有没有在结构中显式描述的参数都被认为是编译期引用传值的参数.

还有一种可选的开始宏, 它引入了两个Zend引擎1没有的新选项, 它是ZEND_BEGIN_ARG_INFO_EX():

ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference,  
                             required_num_args)

当然, name和pass_rest_by_reference和前面所说的是相同的含义. 本章前面也提到了, return_reference是告诉Zend你的函数需要用自己的zval覆写return_value_ptr.

最后一个参数, required_num_args, 它是另外一种类型的暗示, 用来告诉Zend在某种被认为是不完整的调用时完全的跳过函数调用.

在你拥有了一个恰当的开始宏后, 接下来就可以是0个或多个ZEND_ARG_*INFO元素. 这些宏的类型和用法如下表. 

[번역][php 확장 개발 및 내장] 6장 - 반환 값

最后, 所有使用Zend引擎2的宏设置的参数信息结构必须使用ZEND_END_ARG_INFO()结束. 对于你的sample函数, 你需要选择一个如下的结构:

ZEND_BEGIN_ARG_INFO(php_sample_byref_arginfo, 0)  
    ZEND_ARG_PASS_INFO(1)  
ZEND_END_ARG_INFO()

为了让扩展兼容Zend引擎1和2, 需要使用#ifdef语句为两者均定义arg_info结构:

#ifdef ZEND_ENGINE_2  
static  
    ZEND_BEGIN_ARG_INFO(php_sample_byref_arginfo, 0)  
        ZEND_ARG_PASS_INFO(1)  
    ZEND_END_ARG_INFO()  
#else /* ZE 1 */  
static unsigned char php_sample_byref_arginfo[] =  
                                { 1, BYREF_FORCE };  
#endif

注意, 这些代码片段是集中在一起的, 现在是时候创建一个真正的编译期引用传值实现了. 首先, 我们将为Zend引擎1和2定义的php_sample_byref_arginfo块放到头文件php_sample.h中.

接下来, 可以有两种选择, 一种是将PHP_FUNCTION(sample_byref_calltime)拷贝一份, 并重命名为PHP_FUNCTION(sample_byref_compiletime), 接着在php_sample_functions中增加一行PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo)

这种方式简单移动, 并且在一段时候后修改时, 更加不容易产生混淆. 因为这里只是示例代码, 因此, 我们可以稍微放松点, 使用你在上一章学的PHP_FALIAS()避免代码重复.

这样, 就不是赋值PHP_FUNCTION(sample_byref_calltime), 而是在php_sample_functions中直接增加一行:

PHP_FALIAS(sample_byref_compiletime, sample_byref_calltime,  
    php_sample_byref_arginfo)

回顾第5章, 这将创建一个名为sample_byref_compiletime()的用户空间函数, 它对应的内部实现是sample_byref_calltime()的代码. php_sample_byref_arginfo是这个版本的特殊之处.

小结

本章你看到了怎样从一个内部函数返回值, 包括直接返回值和引用方式返回, 以及通过参数栈引用返回. 此外还简单了解了Zend引擎2的参数类型暗示结构zend_arg_info.

下一章你将会继续探究接受基本的zval参数以及使用zend_parse_parameters()强大的类型戏法.

以上就是 [翻译][php扩展开发和嵌入式]第6章-返回值的内容,更多相关内容请关注PHP中文网(www.php.cn)!


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