So far, we haven’t examined the source or header files that were created by the ext_skel script. Now it’s time to dive in and make them useful. The header file is a very standard PHP extension header file and requires no modification for this particular extension. The header file will be revisited in the full example later in this document.
The implementation file, first_test.c, must be edited to provide the desired functionality. To begin, locate the implementation of the calcpi function. Initially, the implementation looks like the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
| {{{ proto double calcpi(int iterations)
Calculate Pi */
PHP_FUNCTION(calcpi)
{
int argc = ZEND_NUM_ARGS();
long iterations;
if (zend_parse_parameters(argc TSRMLS_CC, "l", &iterations) == FAILURE)
return;
php_error(E_WARNING, "calcpi: not yet implemented");
}
/* }}} */ |
Code Fragment 4: Unedited implementation of the calcpi() function.
Lines 1, 2 and 13 are comments that provide some human readable breaks and documentation within the source. Line 3 is the function prototype, simplified by a C macro. Lines 5 and 6 declare some required local variables and line 11 is the line that provides the warning message indicating that the function is not yet implemented. The most valuable line that was automatically generated is line 8. This single line is the function that ensures that the parameters passed from the PHP script are consistent with the function prototype and then populates the local C variables appropriately. This function will be discussed in greater detail later.
To make this function actually do what we want, we must provide a real implementation and remove the extraneous warning. Using a simplistic PI calculation algorithm, the function becomes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| /* {{{ proto double calcpi(int iterations)
Calculate Pi */
PHP_FUNCTION(calcpi)
{
int argc = ZEND_NUM_ARGS();
long iterations;
int index, hits;
double randx, randy, distance, value;
if (zend_parse_parameters(argc TSRMLS_CC, "l", &iterations) == FAILURE)
return;
hits = 0;
for ( index = 0; index < iterations; index++ )
{
randx = rand();
randy = rand();
randx /= RAND_MAX;
randy /= RAND_MAX;
distance = sqrt( ( randx * randx ) + (randy * randy ) );
if ( distance <= 1.0 )
{
hits++;
}
value = ( (double) hits / (double) index );
value *= 4.0;
}
value = ( (double) hits / (double) iterations );
value *= 4.0;
RETVAL_DOUBLE( value );
}
/* }}} */ |
Code Fragment 5: Full implementation of the calcpi() function.
As can be seen from the above code, the warning message has been removed and a simple PI
calculation algorithm has been added. The most significant change from the extension perspective is
line 35, which assigns the return value and indicates its type as DOUBLE.
Now calling the function:
echo ' print( calcpi( 10000 ) . "\n" ); ?>' | ./sapi/cli/php
Returns the following (or similar, based on the calculated value):
3.15
The implementation for the reverse function is below. Note that none of the implementations for this test extension are to be considered models for efficient C programming.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| /* {{{ proto string reverse(string input)
Reverse the input string */
PHP_FUNCTION(reverse)
{
char *input = NULL;
int argc = ZEND_NUM_ARGS();
int input_len;
char* workstr;
int index;
if (zend_parse_parameters(argc TSRMLS_CC, "s", &input, &input_len) == FAILURE)
return;
workstr = (char*) emalloc(input_len + 1);
memset( workstr, 0, input_len + 1 );
for ( index = 0; index < input_len; index++ )
{
workstr[index] = input[input_len - ( index + 1 )];
}
RETVAL_STRING( workstr, 1 );
efree( workstr );
}
/* }}} */ |
Code Fragment 6: Implementation of the reverse() function.
This function demonstrates two things. First, this function demonstrates using the emalloc and efree memory management routines in place of the standard C malloc/free functions. These memory management routines are preferred when developing PHP extensions as they allow the Zend engine to manage the entire memory pool which allows the engine to determine when a block is in use and automatically free unused blocks and blocks with lost references, preventing memory leaks. There are several more memory management routines available for your use, all of which will be discussed later.
The second concept demonstrated in the above example is how to return a string to PHP. The RETVAL_STRING macro accepts two parameters, the string itself (character pointer) and a Boolean flag indicating whether or not to duplicate the string using estrdup. In the above example, I allocate the memory for a working buffer, create the reversed string and then set the return value using the flag to duplicate the string. Lastly, I free the working buffer. Alternately, I could have simply allocated the working buffer and returned it directly without duplication, but I personally feel it is better to free memory I allocate for internal use and let the Zend engine deal with freeing its own internal memory allocation that results from using the RETVAL_STRING macro as I did above. That said, if I were absolutely interested in performance, I would eliminate the additional memory allocation and buffer copying and simply note my reasoning in the code.
The implementation of the uniquechars function is below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| /* {{{ proto array uniquechars(string input [, bool case_sensitive])
Return the unique characters in the input string */
PHP_FUNCTION(uniquechars)
{
char *input = NULL;
int argc = ZEND_NUM_ARGS();
int input_len;
zend_bool case_sensitive;
char* workbuf;
int index, work_index;
if (zend_parse_parameters(argc TSRMLS_CC, "s|b", &input, &input_len, &case_sensitive) == FAILURE)
return;
if ( argc == 1 )
{
case_sensitive = 1;
}
work_index = 0;
workbuf = (char*) emalloc( input_len + 1 );
memset( workbuf, 0, input_len + 1 );
for ( index = 0; index < input_len; index++ )
{
if ( case_sensitive )
{
if ( !strchr( workbuf, input[index] ) )
{
workbuf[work_index] = input[index];
work_index++;
}
}
else
{
if ( !strchr( workbuf, tolower( input[index] ) ) )
{
workbuf[work_index] = tolower( input[index] );
work_index++;
}
}
}
array_init( return_value );
for ( index = 0; index < input_len; index++ )
{
if ( workbuf[index] != '\0' )
{
add_next_index_stringl( return_value, &workbuf[index], 1, 1 );
}
}
efree( workbuf );
}
/* }}} */ |
Code Fragment 7: Implementation of the uniquechars() function.
The above function works by first creating a standard C character array containing all of the unique characters in lines 21 through 42. It sets the case_sensitive flag to the default (true) in lines 15 through 18 if the parameter is not passed at all. The really interesting code, though, is in lines 44 through 50 where the PHP array variable is created and populated.
Line 44 initializes the return value as an array. In the previous two examples, the return_value variable was used opaquely in the RETVAL_XXX functions, but it is important to note that every function that is prototyped using the PHP_FUNCTION macro has a standard return variable called return_value. It is easiest to manipulate this value using the RETVAL_XXX or RETURN_XXX macros, but those macros do not provide array functionality.
Once the array variable is initialized, items are added to the array (in this example) using the add_next_index_stringl function. This function takes four parameters:
- The array variable itself
- A character pointer representing the string to add
- The length of the string being added
- Whether to duplicate the string being added
There are many array manipulation functions available, all of which are discussed in depth later in this document.
Now that the new extension is fully implemented, it becomes possible to test it in PHP. The following is a sample PHP script and the output generated by the script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <?php
print( 'Calculating PI using 10 iterations: ' . calcpi( 10 ) . "\n" );
print( 'Calculating PI using 100 iterations: ' . calcpi( 100 ) . "\n" );
print( 'Calculating PI using 1,000 iterations: ' . calcpi( 1000 ) . "\n" );
print( 'Calculating PI using 10,000 iterations: ' . calcpi( 10000 ) . "\n" );
print( 'Calculating PI using 100,000 iterations: ' . calcpi( 100000 ) . "\n" );
print( "\n" );
print( 'Reverse of "Zend Engine" is "' . reverse( 'Zend Engine' ) . "\"\n" );
print( "\n" );
print( 'The unique characters in "Zend Engine" (case sensitive) are: ' . implode( ',', uniquechars( 'Zend Engine' ) ) . "\n" );
print( 'The unique characters in "Zend Engine" (case insensitive) are: ' . implode(',', uniquechars( 'Zend Engine', false ) ) . "\n" );
print( "\n" );
?> |
Code Fragment 8: PHP script for testing the first_test extension.
Calculating PI using 10 iterations: 3.2
Calculating PI using 100 iterations: 3.2
Calculating PI using 1,000 iterations: 3.248
Calculating PI using 10,000 iterations: 3.1356
Calculating PI using 100,000 iterations: 3.1358
Reverse of "Zend Engine" is "enignE dneZ"
The unique characters in "Zend Engine" (case sensitive) are: Z,e,n,d, ,E,g,i
The unique characters in "Zend Engine" (case insensitive) are: z,e,n,d, ,g,i
Results/Output 1: Output of the test PHP script.