You'll probably be tempted to believe that your internal function should return an immediate zval, ormore likelyallocate memory for a zval and return a zval* such as in the following code block:
PHP_FUNCTION(sample_long_wrong) { zval *retval; MAKE_STD_ZVAL(retval); ZVAL_LONG(retval, 42); return retval; }
Unfortunately, you'll be close, but ultimately wrong. Rather than forcing every function implementation to allocate a zval and return it, the Zend Engine pre-allocates this space before the method is called. It then initializes the zval's type to IS_NULL, and passes that value in the form of a parameter named return_value. Here's that same function again, done correctly:
PHP_FUNCTION(sample_long) { ZVAL_LONG(return_value, 42); return; }
Notice that nothing is directly returned by the PHP_FUNCTION() implementation. Instead, the return_value parameter is populated with appropriate data directly and the Zend Engine will process this into the value after the internal function has finished executing.
As a reminder, the ZVAL_LONG() macro is a simple wrapper around a set of assignment operations, in this case:
Z_TYPE_P(return_value) = IS_LONG; Z_LVAL_P(return_value) = 42;
Or more primitively:
return_value->type = IS_LONG; return_value->value.lval = 42;
Note
The is_ref and refcount properties of the return_value variable should almost never be modified by an internal function directly. These values are initialized and processed by the Zend Engine when it calls your function.
Let's take a look at this particular function in action by adding it to the sample extension from Chapter 5, "Your First Extension," just below the sample_hello_world() function. You'll also need to expand the php_sample_functions struct to contain a function entry for sample_long() as shown:
static function_entry php_sample_functions[] = { PHP_FE(sample_hello_world, NULL) PHP_FE(sample_long, NULL) { NULL, NULL, NULL } };
At this point the extension can be rebuilt by issuing make from the source directory or nmake php_sample.dll from the PHP source root for Windows.
If all has gone well, you can now run PHP and exercise your new function:
$ php -r 'var_dump(sample_long());'
Wrap Your Macros Tightly
In the interest of readable, maintainable code, the ZVAL_*() macros have duplicated counterparts that are specific to the return_value variable. In each case, the ZVAL portion of the macro is replaced with the term RETVAL, and the initial parameterwhich would otherwise denote the variable being modifiedis omitted.
In the prior example, the implementation of sample_long() can be reduced to the following:
PHP_FUNCTION(sample_long) { RETVAL_LONG(42); return; }
Table 6.1 lists the RETVAL family of macros as defined by the Zend Engine. In all cases except two, the RETVAL macro is identical to its ZVAL counterpart with the initial return_value parameter removed.
Generic ZVAL Macro |
return_value Specific Counterpart |
---|---|
ZVAL_NULL(return_value) |
RETVAL_NULL() |
ZVAL_BOOL(return_value, bval) |
RETVAL_BOOL(bval) |
ZVAL_TRUE(return_value) |
RETVAL_TRUE |
ZVAL_FALSE(return_value) |
RETVAL_FALSE |
ZVAL_LONG(return_value, lval) |
RETVAL_LONG(lval) |
ZVAL_DOUBLE(return_value, dval) |
RETVAL_DOUBLE(dval) |
ZVAL_STRING(return_value, str, dup) |
RETVAL_STRING(str, dup) |
ZVAL_STRINGL(return_value, str, len, dup) |
RETVAL_STRINGL(str,len,dup) |
ZVAL_RESOURCE(return_value, rval) |
RETVAL_RESOURCE(rval) |
Note
Notice that the trUE and FALSE macros have no parentheses. These are considered aberrations within the Zend/PHP coding standards but are retained primarily for backward compatibility. If you build an extension and receive an error reading undefined macro RETVAL_TRUE(), be sure to check that you did not include these parentheses.
Quite often, after your function has come up with a return value it will be ready to exit and return control to the calling scope. For this reason there exists one more set of macros designed specifically for internal functions: The RETURN_*() family.
PHP_FUNCTION(sample_long) { RETURN_LONG(42); }
Although it's not actually visible, this function still explicitly returns at the end of the RETURN_LONG() macro call. This can be tested by adding a php_printf() call to the end of the function:
PHP_FUNCTION(sample_long) { RETURN_LONG(42); php_printf("I will never be reached. "); }
The php_printf(), as its contents suggest, will never be executed because the call to RETURN_LONG() implicitly leaves the function.
Like the RETVAL series, a RETURN counterpart exists for each of the simple types shown in Table 6.1. Also like the RETVAL series, the RETURN_TRUE and RETURN_FALSE macros do not use parentheses.
More complex types, such as objects and arrays, are also returned through the return_value parameter; however, their nature precludes a simple macro based approach to creation. Even the resource type, while it has a RETVAL macro, requires additional work to generate. You'll see how to return these types later on in Chapters 8 through 11.
Is It Worth the Trouble?
One underused feature of the Zend Internal Function is the return_value_used parameter. Consider the following piece of userspace code:
function sample_array_range() { $ret = array(); for($i = 0; $i < 1000; $i++) { $ret[] = $i; } return $ret; } sample_array_range();
Because sample_array_range() is called without storing the result into a variable, the workand memorybeing used to create a 1,000 element array is completely wasted. Of course, calling sample_array_range() in this manner is silly, but wouldn't it be nice to know ahead of time that its efforts will be in vain?
Although it's not accessible to userspace functions, an internal function can conditionally skip otherwise pointless behavior like this depending on the setting of the return_value_used parameter common to all internal functions.
PHP_FUNCTION(sample_array_range) { if (return_value_used) { int i; /* Return an array from 0 - 999 */ array_init(return_value); for(i = 0; i < 1000; i++) { add_next_index_long(return_value, i); } return; } else { /* Save yourself the effort */ php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Static return-only function called without processing output"); RETURN_NULL(); } }
To see this function operate, just add it to your growing sample.c source file and toss in a matching entry to your php_sample_functions struct:
PHP_FE(sample_array_range, NULL)
Returning Reference Values
As you already know from working in userspace, a PHP function may also return a value by reference. Due to implementation problems, returning references from an internal function should be avoided in versions of PHP prior to 5.1 as it simply doesn't work. Consider the following userspace code fragment:
function &sample_reference_a() { /* If $a does not exist in the global scope yet, * create it with an initial value of NULL */ if (!isset($GLOBALS['a'])) { $GLOBALS['a'] = NULL; } return $GLOBALS['a']; } $a = 'Foo'; $b = sample_reference_a(); $b = 'Bar';
In this code fragment, $b is created as a reference of $a just as if it had been set using $b = &$GLOBALS['a']; orbecause it's being done in the global scope anywayjust $b = &$a;.
When the final line is reached, both $a and $bwhich you'll recall from Chapter 3, "Memory Management," are looking at the same actual valuecontain the value 'Bar'. Let's look at that same function again using an internals implementation:
#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) PHP_FUNCTION(sample_reference_a) { zval **a_ptr, *a; /* Fetch $a from the global symbol table */ if (zend_hash_find(&EG(symbol_table), "a", sizeof("a"), (void**)&a_ptr) == SUCCESS) { a = *a_ptr; } else { /* $GLOBALS['a'] doesn't exist yet, create it */ ALLOC_INIT_ZVAL(a); zend_hash_add(&EG(symbol_table), "a", sizeof("a"), &a, sizeof(zval*), NULL); } /* Toss out the old return_value */ zval_ptr_dtor(return_value_ptr); if (!a->is_ref && a->refcount > 1) { /* $a is in a copy-on-write reference set * It must be separated before it can be used */ 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; } /* Promote to full-reference and increase refcount */ a->is_ref = 1; a->refcount++; *return_value_ptr = a; } #endif /* PHP >= 5.1.0 */
The return_value_ptr parameter is another common parameter passed to all internal functions and is a zval** containing a pointer to return_value. By calling zval_ptr_dtor() on it, the default return_value zval* is freed. You're then free to replace it with a new zval* of your choosing, in this case the variable $a, which has been promoted to is_ref and optionally separated from any non-full reference pairings it might have had.
If you were to compile and run this code now, however, you'd get a segfault. In order to make it work, you'll need to add a structure to your php_sample.h file:
#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 */
Then use that structure when you declare your function in 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 */
This structure, which you'll learn more about later in this chapter, provides vital hints to the Zend Engine function call routine. In this case it tells the ZE that return_value will need to be overridden, and that it should populate return_value_ptr with the correct address. Without this hint, ZE will simply place NULL in return_value_ptr, which would make this particular function crash when it reached zval_ptr_dtor().
Note
Each of these code fragments has been wrapped in an #if block to instruct the compiler that support for them should only be enabled if the PHP version is greater than or equal to 5.1. Without these conditional directives, the extension would not be able to compile on PHP4 (because several elements, including return_value_ptr, do not exist), and would fail to function properly on PHP 5.0 (where a bug causes reference returns to be copied by value).
Returning Values by Reference |
The PHP Life Cycle
Variables from the Inside Out
Memory Management
Setting Up a Build Environment
Your First Extension
Returning Values
Accepting Parameters
Working with Arrays and HashTables
The Resource Data Type
PHP4 Objects
PHP5 Objects
Startup, Shutdown, and a Few Points in Between
INI Settings
Accessing Streams
Implementing Streams
Diverting the Stream
Configuration and Linking
Extension Generators
Setting Up a Host Environment
Advanced Embedding
Appendix A. A Zend API Reference
Appendix B. PHPAPI
Appendix C. Extending and Embedding Cookbook
Appendix D. Additional Resources