[翻訳][php拡張機能の開発と組み込み] 第6章 - 戻り値

黄舟
リリース: 2023-03-05 16:16:02
オリジナル
1233 人が閲覧しました

戻り値

ユーザー空間関数は return キーワードを使用して呼び出し空間に情報を返します。これは C 言語の構文と同じです。

例:

function sample_long() {  
  return 42;  
}  
$bar = sample_long();
ログイン後にコピー

sample_long() が呼び出されると、42 が返され、設定されますC 言語での同等のコードは次のとおりです:

int sample_long(void) {  
  return 42;  
}  
void main(void) {  
  int bar = sample_long();  
}
ログイン後にコピー

もちろん、C 言語では、関数が呼び出される内容が常にわかっており、関数のプロトタイプに基づいて戻り値が返されるため、戻り値を定義する必要があります。 PHP ユーザー空間で処理される場合、変数の型は動的であり、第 2 章「変数の入出力」で紹介した zval の型に依存します。

return_value 変数

関数は zval を直接返すか、zval のメモリ空間を割り当てて zval *.

PHP_FUNCTION(sample_long_wrong)  
{  
    zval *retval;  
  
    MAKE_STD_ZVAL(retval);  
    ZVAL_LONG(retval, 42);  
  
    return retval;  
}
ログイン後にコピー

を返す必要があります。残念ながら、これは正しくありません。代わりに、Zend エンジンが zval を割り当てて返す必要があります。関数呼び出しの前にこのスペースを事前に割り当てます。次に、zval の型を IS_NULL に初期化し、その値をパラメータ名 return_value として渡します。以下が正しいアプローチです:

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 エンジンによって初期化され、処理されます。この特別な関数を見て、第 5 章「最初の拡張機能」で作成したサンプル拡張機能に追加します。この関数をsample_hello_world() 関数の下に追加し、sample_long() を php_sample_functions 構造体に追加するだけです。 make を実行して拡張機能を再構築します

すべてが OK であれば、php を実行して新しい関数をテストできます:

static function_entry php_sample_functions[] = {  
    PHP_FE(sample_hello_world, NULL)  
    PHP_FE(sample_long, NULL)  
    { NULL, NULL, NULL }  
};
ログイン後にコピー

よりコンパクトなマクロをラップする

コードの可読性と保守性の観点から、 ZVAL_*() には繰り返しの部分があります。この場合、マクロの ZVAL を RETVAL に置き換えることで、呼び出し時に return_value を省略できます

前の例では、sample_long() の実装コードは次のように削減できます:

$ php -r 'var_dump(sample_long());
ログイン後にコピー

次の表は、Zend エンジンの RETVAL ファミリのマクロを示しています。2 つの特殊なものを除き、RETVAL マクロは、return_value パラメータが削除されている点を除き、対応する ZVAL ファミリ マクロと同じです。

[翻訳][php拡張機能の開発と組み込み] 第6章 - 戻り値 TRUE マクロと FALSE マクロには括弧が含まれていないことに注意してください。これは Zend/PHP コーディング標準からの逸脱であり、拡張機能のビルドに失敗すると、未定義のマクロというエラー メッセージが表示されます。 RETVAL_TRUE() は、コード内でこれら 2 つのマクロを記述するときに括弧を誤って記述していないか確認してください。通常、関数は戻り値を処理するときに終了し、呼び出し元のスコープに制御を戻す準備ができています。その理由は、他のいくつかのマクロが内部関数が返されるように設計されているためです: RETURN_*() ファミリー マクロ。

PHP_FUNCTION(sample_long)  
{  
    RETVAL_LONG(42);  
    return;  
}
ログイン後にコピー

見えませんが、この関数は RETURN_LONG() マクロが呼び出された後に返されます。テストする関数 php_printf() の終わり:

PHP_FUNCTION(sample_long)  
{  
    RETURN_LONG(42);  
}
ログイン後にコピー

php_printf()。内容で説明されているように、RETURN_LONG() 呼び出しは暗黙的に関数を終了します。

RETVAL シリーズと同様に、前の表にリストされているすべての単純な型。 RETURN_TRUE マクロと RETURN_FALSE マクロも、対応する RETURN マクロで括弧を使用しません

オブジェクトや配列などのより複雑な型も return_value パラメーターを通じて返されます。単純なマクロ。リソースタイプの場合でも、RETVAL マクロがありますが、実際にリソースタイプを返すには追加の作業が必要です。これらのタイプを返す方法については、第 8 章から第 11 章で説明します。まだ使用されていない関数の機能は return_value_used パラメータです。次のユーザー空間コードを考えてみましょう:

PHP_FUNCTION(sample_long)  
{  
    RETURN_LONG(42);  
    php_printf("I will never be reached.\n");  
}
ログイン後にコピー

sample_array_range() は結果を変数に格納せずに呼び出されるため、ここで作成された 1000 要素の配列空間は完全に無駄になります。もちろん、このようにsample_array_range()を呼び出すのは愚かですが、将来を予測する良い方法がなければどうするでしょうか?

ユーザー空間関数にはアクセスできませんが、内部関数はすべての内部関数に共通の return_value_used パラメーターに依存できます。設定では、このような無意味な動作を条件付きでスキップします。

function sample_array_range() {  
    $ret = array();  
    for($i = 0; $i < 1000; $i++) {  
        $ret[] = $i;  
    }  
    return $ret;  
}  
sample_array_range();
ログイン後にコピー

この関数の動作を確認するには、この関数をsample.cソースファイルに追加し、php_sample_functions構造体で公開するだけです:

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();  
    }  
}
ログイン後にコピー

翻訳メモ: 関数の戻り値の処理方法についてユーザー空間で使用されていない値は、Zend/zend_vm_execute.h 内の zend_do_fcall_common_helper_SPEC 関数を読み取ることができます。内部関数呼び出しを処理した後、関数の戻り値が使用されているかどうかを確認します。 、その後、対応するリリースが実行されます。

基準値を返します

从用户空间的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)!


ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート