Flylib.com

Books Software

 
 
 

Returning Values by Reference


Returning Values by Reference

Using the return construct to send values and variable references back from a function is all well and good, but sometimes you want to return multiple values from a function. You could use an array to do this, which we'll explore in Chapter 8, "Working with Arrays and Hashtables," or you can return values back through the parameter stack.

Call-time Pass-by-ref

One of the simpler ways to pass variables by reference is by requiring the calling scope to include an ampersand (&) with the parameter such as in the following piece of userspace code:

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


The ampersand (&) placed in the parameter call causes the actual zval used by $foo , rather than a copy of its contents, to be sent to the function. This allows the function to modify the value in place and effectively return information through its passed parameter. If sample_byref_calltime() hadn't been called with the ampersand placed in front of $foo , the changes made inside the function would not have affected the original variable.

Repeating this endeavor in C requires nothing particularly special. Create the following function after sample_long() in your sample.c source file:

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) {
        /* parameter was not passed by reference,
         * leave without doing anything
         */
        return;
    }
    /* Make sure the variable is a string */
    convert_to_string(a);
    /* Enlarge a's buffer to hold the additional data */
    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;
}


As always, this function needs to be added to the php_sample_functions structure:

PHP_FE(sample_byref_calltime,        NULL)


Compile-time Pass-by-ref

The more common way to pass by reference is by using compile-time pass-by-ref. Here, the parameters to a function are declared to be for reference use only and attempts to pass constants or intermediate valuessuch as the result of a function callwill result in an error because there is nowhere for the function to store the resulting value back into. A userspace compile-time pass-by-ref function might look something like the following:

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


As you can see, this varies from the calltime version only in the placement of the referencing ampersand. When looking at this function in C, the implementation in terms of function code is entirely identical. The only true difference is in how it is declared in the php_sample_functions block:

PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo)


where php_sample_byref_arginfo is a (verbosely named) constant structure which you'll obviously need to define before this entry will compile.

Note

The check for is_ref could actually be left out of the compile-time version because it will always failand not exitbut it causes no harm to leave it in for now.


In Zend Engine 1 (PHP4), this will be a simple char* list made up of a length byte followed by a set of flags pertaining to each of a function's parameters in turn .

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


Here, the 1 indicates that the vector only contains argument info for one parameter. The argument-specific arg info then follows in subsequent elements with the first arg going in the second element as shown. If there had been a second or third argument involved, their flags would have gone in the third and fourth elements respectively and so on. Possible values for a given argument's element are shown in Table 6.2.

Table 6.2. Zend Engine 1 Arg Info Constants

Reference Type

Meaning

BYREF_NONE

Pass-by-ref is never allowed on this parameter. Attempts to use call-time pass-by-ref will be ignored and the parameter will be copied instead.

BYREF_FORCE

Arguments are always passed by reference regardless of how the function is called. This is equivalent to using an ampersand in a userspace function parameter declaration.

BYREF_ALLOW

Argument passing by reference is determined by call-time semantics. This is equivalent to ordinary userspace function declaration.

BYREF_FORCE_REST

The current argument and all subsequent arguments will have BYREF_FORCE applied. This flag may only be the last arg info flag in the list. Placing additional flags after BYREF_FORCE_REST will result in undefined behavior.


In Zend Engine 2 (PHP5+), you'll use a much more extensive structure containing information such as minimum and maximum parameter requirements, type hinting, and whether or not to force referencing.

First the arg info struct is declared using one of two macros. The simpler macro, ZEND_BEGIN_ARG_INFO() , takes two parameters:

ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference)


name is quite simply how this struct will be referred to within the extension, in this case: php_sample_byref_arginfo .

pass_rest_by_reference takes on the same meaning here as using BYREF_FORCE_REST as the last element of a Zend Engine 1 arg info vector. If this parameter is set to 1, all arguments not explicitly described within the struct will be assumed to be compile-time pass-by-ref arguments.

The alternative begin macro, which introduces two new options not found in the Zend Engine 1 version, is ZEND_BEGIN_ARG_INFO_EX() :

ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference,
                             required_num_args)


name and pass_rest_by_reference have the same meanings here of course. return_reference , as you saw earlier in the chapter, gives a hint to Zend that your function will be overriding return_value_ptr with your own zval.

The final argument, required_num_args , is another shortcut hint to Zend that allows it to skip certain function calls entirely when that function's prototype is known to be incompatible with how its being called.

After you have a suitable begin macro in place, it may be followed by zero or more ZEND_ARG_*INFO elements. The types and usages of these macros are shown in Table 6.3. Lastly, all arg info structs using the Zend Engine 2 macros must terminate their list using ZEND_END_ARG_INFO() . For your sample function, you might select a final structure that looks like the following:

Table 6.3. ZEND_ARG_INFO Family of Macros

Arg Info macro

Purpose

ZEND_ARG_PASS_INFO(by_ref)

by_ref hereas in all subsequent macrosis a binary option indicating whether the corresponding parameter should be forced as pass-by-reference . Setting this option to 1 is equivalent to using BYREF_FORCE in a Zend Engine 1 vector.

ZEND_ARG_INFO(by_ref, name)

This macro provides an additional name attribute used by internally generated error messages and the reflection API. It should be set to something helpful and non-cryptic.

ZEND_ARG_ARRAY_INFO(by_ref, name, allow_null) ZEND_ARG_OBJ_INFO(by_ref, name, classname, allow_null)

These two macros provide argument type hinting to internal functions specifying that either an array or particular class instance is expected as the parameter. Setting allow_null to a non-zero value will allow the calling scope to pass a NULL value in place of an array/object.


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


In order to make extensions that are compatible with both ZE1 and ZE2, it's necessary to use an #ifdef statement and define the same arg_info structure for both, in this case:

#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


Now that all the pieces are gathered together, it's time to create an actual compile-time pass-by-reference implementation. First let's put the block defining php_sample_byref_arginfo for ZE1 and ZE2 into the header file php_sample.h .

Next, you could take two approaches: One approach would be to copy and paste the PHP_FUNCTION(sample_byref_calltime) implementation and rename it to PHP_FUNCTION(sample_byref_compiletime) , and then add a PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo) line to php_sample_functions .

This approach is straightforward and probably less prone to confusion when making changes years from now. Because this is just sample code, however, you can play a little looser and avoid code duplication by using PHP_FALIAS() , which you saw last chapter.

This time, rather than making a duplicate of PHP_FUNCTION(sample_byref_calltime) , add a single line to php_sample_functions :

PHP_FALIAS(sample_byref_compiletime, sample_byref_calltime,
    php_sample_byref_arginfo)


As you'll recall from Chapter 5, this creates a userspace function called sample_byref_compiletime() with an internal implementation using sample_byref_calltime() 's code. The addition of php_sample_byref_arginfo makes this version unique.