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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
|
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision: 297078 $ -->
<chapter xml:id="internals2.funcs" xmlns="http://docbook.org/ns/docbook">
<title>Writing functions</title>
<para>
One core element of an extension are functions which are exported to the
PHP userland. Even when you're planning to write object-oriented extensions
you are advised to read this chapter as most of the information is valid
for methods, too.
</para>
<para>
When adding a function to an extension, for instance after using the
<link linkend="internals2.buildsys.skeleton">ext_skel</link> script to
create the raw infrastructure, you can create a function by implementing it
as a C function and then providing an entry for the extension's function
table. The function entry can contain a pointer to an argument information
structure. Providing this information ins not strictly necessary, unless
you plan to accept parameters by reference or want to return a reference, but
provides information that can be accessed via PHP's <link
linkend="book.reflection">Reflection API</link>. As you will see below the
parameters aren't passed as direct function parameters to the implementation
but passed on a stack, which is checked by the function's implementation
which can't directly serve as source for this information.
</para>
<example xml:id="internals2.funcs.index.minimalext">
<title>Minimal PHP extension with one function</title>
<programlisting role="c">
<![CDATA[
/* {{{ proto void hello_world()
Do nothing */
PHP_FUNCTION(hello_world)
{
}
/* }}} */
/* {{{ arginfo_hello_world */
ZEND_BEGIN_ARG_INFO(arginfo_hello_world, 0)
ZEND_END_ARG_INFO()
/* }}} */
/* {{{ demo_functions */
function_entry demo_functions[] = {
PHP_FE(hello_world, arginfo_hello_world)
{NULL, NULL, NULL}
}
/* }}} */
/* {{{ demo_module_enry */
zend_module_entry demo_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"demo",
demo_functions,
NULL,
NULL,
NULL,
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
"1.0.0",
#en
STANDARD_MODULE_PROPERTIES
}
/* }}} */
]]>
</programlisting>
</example>
<para>
In his example you can see the above discussed elements and the module
structure, if you don't remember his strucure go back to
<xref linkend="internals2.structure.modstruct"/>.
</para>
<para>
The first part of this extension is the actual implementation. As a
convention each exported function is preceded by a two line comment
describing the function's userland prototype and a short one line comment.
</para>
<para>
For providing source code compatibility between different versions of PHP
the functions declaration is wrapped into the <code>PHP_FUNCTION</code>
macro. The compiler's preprocessor will, when using PHP 5.3 transform this
into a function like this:
</para>
<screen>
<![CDATA[
void zif_hello_world(int ht, zval *return_value, zval **return_value_ptr,
zval *this_ptr, int return_value_used TSRMLS_DC)
{
}
]]>
</screen>
<para>
For preventing a naming conflict between a function exported to userland
and another C function with the same name the C symbol of the exported
function is prefixed with the prefix <code>zif_</code>. You can also see
that this prototype doesn't reference the argument stack. Accessing
parameters passed from PHP is described below. The following table lists
the C level parameters which are also defined in the
<code>INTERNAL_FUNCTION_PARAMETERS</code> macro. Mind that these parameters
might change between PHP versions and you should use the provide macros.
</para>
<table xml:id="internals2.funcs.index.internal_func_params">
<title>INTERNAL_FUNCTION_PARAMETERS</title>
<tgroup cols="3">
<thead>
<row>
<entry>Name and type</entry>
<entry>Description</entry>
<entry>Access macros</entry>
</row>
</thead>
<tbody>
<row>
<entry><code>int ht</code></entry>
<entry>Number of actual parameters passed by user</entry>
<entry><code>ZEND_NUM_ARGS()</code></entry>
</row>
<row>
<entry><code>zval *return_value</code></entry>
<entry>
Pointer to a PHP variable that can be filled with the return value
passed to the user. The default type is <code>IS_NULL</code>.
</entry>
<entry><code>RETVAL_*</code>, <code>RETURN_*</code></entry>
</row>
<row>
<entry><code>zval **return_value_ptr</code></entry>
<entry>When returning references to PHP set this to a pointer to
your variable. It is not suggested to return references.</entry>
<entry/>
</row>
<row>
<entry><code>zval *this_ptr</code></entry>
<entry>
In case this is a method call this points to the PHP variable
holding the <code>$this</code> object.
</entry>
<entry><code>getThis()</code></entry>
</row>
<row>
<entry><code>int return_value_used</code></entry>
<entry>
Flag indicating whether the returned value will be used by the
caller.
</entry>
<entry/>
</row>
</tbody>
</tgroup>
</table>
<para>
As said the function shown above will simply return NULL to the user and do
nothing else. The function can be called, from the PHP userland, with an
arbitrary number of parameters. A more useful function consists of four
parts in general:
</para>
<orderedlist>
<listitem>
<para>
Declaration of local variables. In C we have to declare locale vars, so
we do this at the functions top.
</para>
</listitem>
<listitem>
<para>
Parameter parsing. PHP passes parameters on a special stack, we have to
read them from there, verify the types, cast them if needed and bail out
in case something goes wrong.
</para>
</listitem>
<listitem>
<para>
Actual logic. Do whatever is needed.
</para>
</listitem>
<listitem>
<para>
Set the return value, cleanup, return.
</para>
</listitem>
</orderedlist>
<para>
In some cases the exact order of these steps may change. Especially the
last two steps are often mixed up, but it's a good idea to stay in that
order.
</para>
<example xml:id="internals2.funcs.index.simplefunc">
<title>A simple function</title>
<programlisting role="c">
<![CDATA[
/* {{{ proto void hello_world(string name)
Greets a user */
PHP_FUNCTION(hello_world)
{
char *name;
int name_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
return;
}
php_printf("Hello %s!", name);
RETURN_TRUE;
}
/* }}} */
]]>
</programlisting>
</example>
<para>
The function above shows these parts. Let's start the analysis with the
last lines: <code>php_printf</code> is - as one can guess easily - PHP's
version of standard C's <code>printf</code>. Unlike <code>printf</code> it
doesn't print to the process's <code>STDOUT</code> channel but to the
current output stream, which might be buffered by the user. It should be
noted that <code>php_printf</code>, unlike most parts of PHP's API is not
binary safe, so if <code>name</code> contains a NULL-byte the rest would be
ignored. For a binary safe output <code>PHPWRITE</code> should be used.
</para>
<note>
<simpara>
In general it is considered to directly print data to the output stream,
it is suggested to return data as string to the user instead so the user
can decide what to do. Exceptions to this rule might be functions dealing
with binary data like images while your API should provide a way to access
the data even there.
</simpara>
</note>
<para>
The <code>RETURN_TRUE</code> macro in the last line does three things: It
sets the type of the variable we got as <code>return_value</code> pointer to
<code>IS_BOOLEAN</code> and the value to a <code>true</code> value. Then it
returns from the C function. So when using this macro you're housekeeping
for memory and other resources has to be completed as further code of
this function won't be executed.
</para>
<para>
The <code>zend_parse_parameters()</code> function is responsible for reading
the parameters passed by the user from the argument stack and putting it
with proper casting in local C variables. If the user doesn't pass the wrong
number of parameters or types that can't be casted the function will emit a
verbose error and return <code>FAILURE</code>. In that case we simply return
from the function - by not changing the <code>return_value</code> the
default return value of <code>NULL</code> will be returned to the user.
</para>
<note>
<simpara>
Please mind that <code>FAILURE</code> is represented by a <code>-1</code>
and <code>SUCCESS</code> by <code>0</code>. To make the code clear you
should always use the named constants for comparing the value.
</simpara>
</note>
<para>
The first parameter to <code>zend_parse_parameters()</code> is the number of
parameters that have actually been passed to the function by the user. The
number is passed as <code>ht</code> parameter to our function but, as
discussed above, this should be considered as an implementation detail and
<code>ZEND_NUM_ARGS()</code> should be used.
</para>
<para>
For compatibility with PHP's thread-isolation, the thread-safe resource
manager, we also have to pass the thread context using
<code>TSRMLS_CC</code>. Unlike in other functions this can't be the last
parameter as <code>zend_parse_parameters</code> expects a variable number of
parameters - depending on the number of user-parameters to read.
</para>
<para>
Following the thread context the expected parameters are declared. Each
parameter is represented by a character in the string describing the type.
In our case we expect one parameter as a string so our type specification
is simply <code>"s"</code>.
</para>
<para>
Lastly one has to pass one or more pointers to C variables which should be
filled with the variable's value or provide more details. In the case of a
string we get the actual string, which will always be NULL-terminated, as a
<code>char*</code> and its length, excluding the NULL-byte, as
<code>int</code>.
</para>
<para>
A documentation of all type specifiers and the corresponding additional C
types can be found in the file <code>README.PARAMETER_PARSING_API</code>
which is part of the source distribution. The most important types can be
found in the table below.
</para>
<table xml:id="internals2.funcs.index.zend_pars_params_types">
<title>zend_parse_parameters() type specifiers</title>
<tgroup cols="3">
<thead>
<row>
<entry>Modifier</entry>
<entry>Additional parameter types</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><code>b</code></entry>
<entry><code>zend_bool</code></entry>
<entry>Boolean value</entry>
</row>
<row>
<entry><code>l</code></entry>
<entry><code>long</code></entry>
<entry>A integer (long) value</entry>
</row>
<row>
<entry><code>d</code></entry>
<entry><code>double</code></entry>
<entry>A float (double) value</entry>
</row>
<row>
<entry><code>s</code></entry>
<entry><code>char*, int</code></entry>
<entry>A binary safe string</entry>
</row>
<row>
<entry><code>h</code></entry>
<entry><code>HashTable*</code></entry>
<entry>An array's hash table</entry>
</row>
</tbody>
</tgroup>
</table>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->
|