Symisc PH7 Engine
An Embeddable PHP5 Engine

Foreign Function Implementation Guide.

The following quick guide is what you do to start experimenting with the PH7 foreign function mechanism without having to do a lot of tedious reading.


Foreign functions are used to add PHP functions or to redefine the behavior of existing PHP functions from the outside environment (see below) to the underlying virtual machine. This mechanism is know as “In-process extending”. After a successful call to the ph7_create_function() interface, the installed function is available immediately and can be called from the target PHP code.


A foreign function is simply a host application defined function typically implemented in C which is responsible of performing the desired computation. There are many reasons to implement foreign functions, one of them is to perform system calls and gain access to low level resources that is impossible to do using standard PHP functions.

Another reason to implement foreign functions is performance. That is, a foreign function implemented in C will typically run 10 times faster than if it was implemented in PHP, this is why most of the built-in PH7 functions (Over 470 as of this release) such as implode(), explode(), array_merge(), chdir(), file(), fopen() and many more are implemented in C rather than PHP.


The foreign function signature is as follows:

int (*xFunc)(ph7_context *pCtx,int argc,ph7_value **argv)

As you can see, the foreign function must accept three parameters. The first parameter is a pointer to a ph7_context structure. This is the context in which the foreign function executes. In other words, this parameter is the intermediate between the foreign function and the underlying virtual machine.

The application-defined foreign function implementation will pass this pointer through into calls to dozens of interfaces, these includes:

ph7_context_output(), ph7_context_throw_error(), ph7_context_new_scalar(), ph7_context_user_data(), ph7_context_alloc_chunk() and many more.


The second parameter is the total number of arguments passed to the foreign function. If the total number of arguments is not the expected one, the foreign functions can throw an error via ph7_context_throw_error() as follow: ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Unexpected number of arguments");


The last parameter is an array of pointers to ph7_value which represents function arguments. The implementation of the foreign functions can extract their contents via one of these interfaces:

ph7_value_to_int()

ph7_value_to_bool()

ph7_value_to_int64()

ph7_value_to_double()

ph7_value_to_string()

ph7_value_to_resource()

ph7_value_compare()

The computation result (I.e: the return value) of the foreign function can be set via one of these interfaces:

ph7_result_int()

ph7_result_int64()

ph7_result_bool()

ph7_result_double()

ph7_result_null()

ph7_result_string()

ph7_result_value()

ph7_result_resource()

ph7_result_string_format()

If no call is made to one of these interfaces, then a NULL return value is assumed.


The implementation of the foreign function must return PH7_OK on success, but if the callbacks wishes to abort processing (to stop program execution) and thus to emulate the exit() or die() PHP constructs, it must return PH7_ABORT instead.

Typical Implementation Of A Foreign Function

A typical implementation of a foreign function would perform the following operations in order:

  1. Check if the given arguments are of the expected numbers and expected types. If the foreign accepts arguments, this is the first operation it must perform before doing any serious work, for that, a set of interfaces are available for checking arguments types:

    ph7_value_is_int

    ph7_value_is_float

    ph7_value_is_bool

    ph7_value_is_string

    ph7_value_is_null

    ph7_value_is_numeric

    ph7_value_is_callable

    ph7_value_is_array

    ph7_value_is_scalar

    ph7_value_is_object

    ph7_value_is_resource

    ph7_value_is_empty

    To check if the given arguments are of the expected number, simply compare the expected number with the argc parameter (Second parameter the foreign function takes).

  2. If the foreign function accepts arguments, it may need to extract their contents via the following set of interfaces:

    ph7_value_to_int()

    ph7_value_to_bool()

    ph7_value_to_int64()

    ph7_value_to_double()

    ph7_value_to_string()

    ph7_value_to_resource()

    ph7_value_compare()

  3. Perform the desired computation.

  4. Allocate additional ph7_value via the ph7_context_new_scalar() and/or ph7_context_new_array() interfaces especially if the foreign function works with arrays (see below for a working example).

  5. If something goes wrong while processing the input or performing the computation, the foreign function can throw an error via the ph7_context_throw_error() or ph7_context_throw_error_format() interfaces.

  6. If the foreign function wishes to output a message and thus to emulate the echo() or the print() PHP constructs, it may call ph7_context_output() or ph7_context_output_format().

  7. When done, the computation result (return value) of the foreign function can be set via one of the these interfaces:

    ph7_result_int()

    ph7_result_int64()

    ph7_result_bool()

    ph7_result_double()

    ph7_result_null()

    ph7_result_string()

    ph7_result_value()

    ph7_result_resource()

    ph7_result_string_format()

  8. And finally, return PH7_OK when done. Note that you do not need to release any allocated resource manually via the ph7_context_release_value() interface, the virtual machine will release any allocated resources for you automatically.

Foreign Function Examples

We will start our examples with the simplest foreign function implemented in C which perform a simple right shift operation on a given number and return the new shifted value as it's computation result (I.e: return value), here is the C code:


  1. int shift_func(

  2.   ph7_context *pCtx, /* Call Context */

  3.   int argc, /* Total number of arguments passed to the function */

  4.   ph7_value **argv /* Array of function arguments */

  5. )

  6. {

  7.   int num;

  8.   /* Make sure there is at least one argument and is of the

  9.    * expected type [i.e: numeric].

  10.    */

  11.   if( argc < 1 || !ph7_value_is_numeric(argv[0]) ){

  12.    /*

  13.     * Missing/Invalid argument,throw a warning and return FALSE.

  14.     * Note that you do not need to log the function name,PH7 will

  15.     * automatically append the function name for you.

  16.     */

  17.    ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Missing numeric  argument");

  18.   /* Return false */

  19.    ph7_result_bool(pCtx,0);

  20.    return PH7_OK;

  21.   }

  22.   /* Extract the number */

  23.   num = ph7_value_to_int(argv[0]);

  24.   /* Shift by 1 */

  25.   num <<= 1;

  26.   /* Return the new value */

  27.   ph7_result_int(pCtx,num);

  28.   /* All done */

  29.   return PH7_OK;

  30. }


On line 11, we check if there is at least one argument and is numeric (integer or float or a string that looks like a number). If not, we throw an error on line 17 and we return a Boolean false (zero) on line 19 using the ph7_result_bool() interfaces.

We extract the function argument which is the number to shift on line 23 using the ph7_value_to_int() interface.

We perform the desired computation (right shift the given number by one) on line 25.
And finally we return the new value as our computation result one line 27 using a call to
ph7_result_int().

Now after installing this function (see below for a working example), it can be called from your PHP script as follows:


<?php

   echo shift_func(150); //you should see 300.

?>


Another simple foreign function named date_func(). This function does not take arguments and return the current system date in a string. A typical call to this function would return something like: '2012-23-09 13:53:30'. Here is the implementation


  1. int date_func(

  2.   ph7_context *pCtx, /* Call Context */

  3.   int argc, /* Total number of arguments passed to the function */

  4.   ph7_value **argv /* Array of function arguments*/

  5. ){

  6.   time_t tt;

  7.   struct tm *pNow;

  8.   /* Get the current time */

  9.   time(&tt);

  10.   pNow = localtime(&tt);

  11.   /*

  12.    * Return the current date.

  13.    */

  14.   ph7_result_string_format(pCtx,

  15.     "%04d-%02d-%02d %02d:%02d:%02d", /* printf() style format */

  16.      pNow->tm_year + 1900, /* Year */

  17.      pNow->tm_mday, /* Day of the month */

  18.      pNow->tm_mon + 1, /* Month number */

  19.      pNow->tm_hour, /* Hour */

  20.      pNow->tm_min, /* Minutes */

  21.      pNow->tm_sec /* Seconds */

  22.   );

  23.   /* All done */

  24.   return PH7_OK;

  25. }


We get the date on line 10 using the libc localtime() routine and we return it using the ph7_result_string_format() interface on 14. This function is a work-alike of the "printf()" family of functions from the standard C library which is used to append a formatted string.


Download & Compile this C file for a working version of the function defined above plus four others foreign functions.

Working With Arrays

Working with array is relatively easy and involves two or three additional call to PH7 interfaces. A typical implementation of a foreign function that works with arrays would perform the following operation:

  1. Call ph7_context_new_array() to allocate a fresh ph7_value of type array. The new array is empty and need to be populated.

  2. Call ph7_context_new_scalar() to allocate a new scalar ph7_value. This value is used to populate array entries with the desired value.

  3. Populate the array using one or more calls to ph7_array_add_elem() and/or it's wrappers.

  4. Finally when done, return the fresh array using the ph7_result_value() interface.

We will start our examples by a simple foreign function names array_time_func(). This function does not expects arguments and return the current time in a array. A typical output of this function would look like this:

<?php var_dump(array_time_func()) ?>

array(3) {

  [0] =>

   int(14)

  [1] =>

   int(53)

  [2] =>

   int(30)

}

Here is the C code.

  1. int array_time_func(ph7_context *pCtx,int argc,ph7_value **argv)

  2. {

  3.   ph7_value *pArray; /* Our Array */

  4.   ph7_value *pValue; /* Array entries value */

  5.   time_t tt;

  6.   struct tm *pNow;

  7.   /* Get the current time first */

  8.   time(&tt);

  9.   pNow = localtime(&tt);

  10.   /* Create a new array */

  11.   pArray = ph7_context_new_array(pCtx);

  12.   /* Create a worker scalar value */

  13.   pValue = ph7_context_new_scalar(pCtx);

  14.   if( pArray == 0 || pValue == 0 ){

  15.    /*

  16.     * If the supplied memory subsystem is so sick that we are unable

  17.     * to allocate a tiny chunk of memory,there is no much we can do here.

  18.     * Abort immediately.

  19.     */

  20.     ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Fatal,PH7 is running out of memory");

  21.     /* emulate the die() construct */

  22.     return PH7_ABORT; /* die('Fatal,PH7 is running out of memory'); */

  23.   }

  24.   /* Populate the array.

  25.    * Note that we will use the same worker scalar value (pValue) here rather than

  26.    * allocating a new value for each array entry. This is due to the fact

  27.    * that the populated array will make it's own private copy of the inserted

  28.    * key(if available) and it's associated value.

  29.    */


  30.   ph7_value_int(pValue,pNow->tm_hour); /* Hour */

  31.   /* Insert the hour at the first available index */

  32.   ph7_array_add_elem(pArray,0/* NULL: Assign an automatic index*/,pValue /* Will make it's own copy */);


  33.   /* Overwrite the previous value */

  34.   ph7_value_int(pValue,pNow->tm_min); /* Minutes */

  35.   /* Insert minutes */

  36.   ph7_array_add_elem(pArray,0/* NULL: Assign an automatic index*/,pValue /* Will make it's own copy */);


  37.   /* Overwrite the previous value */

  38.   ph7_value_int(pValue,pNow->tm_sec); /* Seconds */

  39.   /* Insert seconds */

  40.   ph7_array_add_elem(pArray,0/* NULL: Assign an automatic index*/,pValue /* Will make it's own copy */);


  41.   /* Return the array as the function return value */

  42.   ph7_result_value(pCtx,pArray);


  43.   /* All done. Don't worry about freeing memory here,every

  44.    * allocated resource will be released automatically by the engine

  45.    * as soon we return from this foreign function.

  46.    */

  47.   return PH7_OK;

  48. }

Download the C file

We allocate a fresh empty array on line 11 using a call to ph7_context_new_array(). Also we allocate a fresh scalar value one line 13 using a call to ph7_context_new_scalar().


We populate the scalar ph7_value with the desired value, here the current hour on line 31 using a call to ph7_value_int() and we insert it in the new array using the ph7_array_add_elem() interface on line 33.

This process is repeated again on line 36,38,41 and 43. Note that we use the same scalar value to populate the array with the three entries. This is due to the fact that a call to ph7_array_add_elem() and it's wrappers will make a private copy of the inserted value and so it's safe to use the same scalar value for other insertions.


Finally we return our array as our computation result one line 46 using the ph7_result_value() interface.

Test The Implementation

Now we have implemented our foreign functions, it's time to test them. For that, we will create a simple PHP program that call each of the defined functions and output their return values. Here is the program:

<?php

  echo 'shift_func(150) = '.shift_func(150).PHP_EOL;

  echo 'sum_func(7,8,9,10) = '.sum_func(7,8,9,10).PHP_EOL;

  echo 'date_func(5) = '.date_func().PHP_EOL;

  echo 'array_time_func() ='.PHP_EOL;

  var_dump(array_time_func());

  echo 'array_date_func(5) ='.PHP_EOL;

  var_dump(array_date_func(5));

  echo 'array_str_split(\'Hello\') ='.PHP_EOL;

  var_dump(array_str_split('Hello'));

?>

When running this program, you should see something like that:

shift_func(150) = 300

sum_func(7,8,9,10) = 34

date_func(5) = 2012-23-09 03:53:30

array_time_func() =

array(3) {

[0] =>

int(3)

[1] =>

int(53)

[2] =>

int(30)

}

array_date_func(5) =

array(6) {

[tm_year] =>

int(2012)

[tm_mon] =>

int(9)

[tm_mday] =>

int(23)

[tm_hour] =>

int(3)

[tm_min] =>

int(53)

[tm_sec] =>

int(30)

}

array_str_split('Hello') =

array(5) {

[0] =>

string(1 'H')

[1] =>

string(1 'e')

[2] =>

string(1 'l')

[3] =>

string(1 'l')

[4] =>

string(1 'o')

}

Now, the main program.(You can get a working version of this program here)

  1. /*

  2. * Container for the foreign functions defined above.

  3. * These functions will be registered later using a call

  4. * to [ph7_create_function()].

  5. */

  6. static const struct foreign_func {

  7. const char *zName; /* Name of the foreign function*/

  8. int (*xProc)(ph7_context *,int,ph7_value **); /* Pointer to the C function performing the computation*/

  9. }aFunc[] = {

  10. {"shift_func",shift_func},

  11. {"date_func", date_func},

  12. {"sum_func", sum_func },

  13. {"array_time_func", array_time_func},

  14. {"array_str_split", array_string_split_func},

  15. {"array_date_func", array_date_func}

  16. };

  17. /*

  18. * Main program: Register the foreign functions defined above,compile and execute

  19. * our PHP test program.

  20. */

  21. int main(void)

  22. {

  23.   ph7 *pEngine; /* PH7 engine */

  24.   ph7_vm *pVm; /* Compiled PHP program */

  25.   int rc;

  26.   int i;

  27.   /* Allocate a new PH7 engine instance */

  28.   rc = ph7_init(&pEngine);

  29.   if( rc != PH7_OK ){

  30.    /*

  31.     * If the supplied memory subsystem is so sick that we are unable

  32.     * to allocate a tiny chunk of memory,there is no much we can do here.

  33.     */

  34.     Fatal("Error while allocating a new PH7 engine instance");

  35.   }

  36.   /* Compile the PHP test program defined above */

  37.   rc = ph7_compile_v2(

  38.          pEngine, /* PH7 engine */

  39.          PHP_PROG, /* PHP test program */

  40.          -1 /* Compute input length automatically*/,

  41.          &pVm, /* OUT: Compiled PHP program */

  42.          0 /* IN: Compile flags */

  43.        );

  44.  if( rc != PH7_OK ){

  45.    if( rc == PH7_COMPILE_ERR ){

  46.      const char *zErrLog;

  47.      int nLen;

  48.      /* Extract error log */

  49.      ph7_config(pEngine,

  50.          PH7_CONFIG_ERR_LOG,

  51.          &zErrLog,

  52.          &nLen

  53.         );

  54.      if( nLen > 0 ){

  55.         /* zErrLog is null terminated */

  56.         puts(zErrLog);

  57.       }

  58.   }

  59.   /* Exit */

  60.   Fatal("Compile error");

  61. }

  62.   /* Now we have our program compiled,it's time to register

  63.    * our foreign functions.

  64.    */

  65.   for( i = 0 ; i < (int)sizeof(aFunc)/sizeof(aFunc[0]) ; ++i ){

  66.      /* Install the foreign function */

  67.      rc = ph7_create_function(pVm,aFunc[i].zName,aFunc[i].xProc,0 /* NULL: No private data */);

  68.      if( rc != PH7_OK ){

  69.        Fatal("Error while registering foreign functions");

  70.      }

  71.   }

  72.   /*

  73.    * Configure our VM:

  74.    * Install the VM output consumer callback defined above.

  75.    */

  76.    rc = ph7_vm_config(pVm,

  77.         PH7_VM_CONFIG_OUTPUT,

  78.         Output_Consumer, /* Output Consumer callback */

  79.         0 /* Callback private data */

  80.       );

  81.   if( rc != PH7_OK ){

  82.      Fatal("Error while installing the VM output consumer callback");

  83.    }

  84.   /*

  85.    * Report run-time errors such as unexpected numbers of arguments and so on.

  86.    */

  87.    ph7_vm_config(pVm,PH7_VM_CONFIG_ERR_REPORT);

  88.    /*

  89.     * And finally,execute our program. Note that your output (STDOUT in our case)

  90.     * should display the result.

  91.     */

  92.    ph7_vm_exec(pVm,0);

  93.    /* All done,cleanup the mess left behind.

  94.     */

  95.    ph7_vm_release(pVm);

  96.    ph7_release(pEngine);

  97.    return 0;

  98. }

Download the C file

We create a new PH7 engine instance using a call to ph7_init() on line 28. This is often the first PH7 API call that an application makes and is a prerequisite in order to compile PHP code using one of the compile interfaces.

We compile our PHP test program on line 37 using the ph7_compile_v2() interface.

We register our implemented foreign functions on line 67 using the ph7_create_function() interface.

We configure our Virtual Machine on line 76 by setting a VM output consumer callback named Output_Consumer() (Download the C file to see the implementation) which redirect the VM output to STDOUT.

And finally we execute our PHP program on line 92 using a call to ph7_vm_exec().

Clean-up is done on line 95 and 96 respectively via calls to ph7_vm_release() and ph7_release().

Other useful links

As you can see, in-process extending under PH7 is extremely powerful yet simple and involves only a single call to ph7_create_function().

Check out the Introduction To The PH7 C/C++ Interface for an introductory overview and roadmap to the dozens of PH7 interface functions.

A separate document, The PH7 C/C++ Interface, provides detailed specifications for all of the various C/C++ APIs for PH7. Once the reader understands the basic principles of operation for PH7, that document should be used as a reference guide.

Any questions, check the Frequently Asked Questions page or visit the Support Page for additional information.


Symisc Systems
Copyright © Symisc Systems