Embedding the PHP Interpreter

Few weeks ago I had to use iDealer PHP library from C++. I did that and now the code has been tested and is working fine.

I had embedded Python before, so I knew what I had to do: initialize the PHP interpreter, create some variables, execute PHP code, read some PHP variables and shutdown the PHP interpreter. The first and the last step will be executed only once for whole server lifetime (performance considerations) while steps 2-4 will be executed for every request. To keep it simple, I will use global PHP variables and global symbol table known as EG(symbol_table) in C code and $GLOBALS in PHP code. To keep it even more simple, I will use only string PHP variables and will create a correct type (array for example) in PHP code.

I can’t assure that everything written below is 100% correct, since PHP documentation was really poor, especially compared to Python. I found a part of solutions in Zend source code, so there might be a better and cleaner solutions. I also spent some time using gdb (The GNU Debugger) and valgrind and chasing bugs.


First you need to install PHP. I compiled version 5.0.4 from sources:

env CC=gcc ./configure --enable-embed --prefix=/home/aivarsk/php && make && make install

The most important here is --enable-embed flag that will compile PHP library (libphp5.so) because it’s not compiled by default. At the same time it compiles sapi/embed module, that in theory should let you execute PHP in C, but in practice it sounds a bit different. Anyway, you may look at that code for some ideas.

1. Initialization of the PHP interpreter

That’s not so easy, so you should use php_embed_init() from sapi/embed module. I’m using some piece of it as I don’t understand everything it does.

static char *argv[2] = {"myname", NULL};
if (php_embed_init(1, argv PTSRMLS_CC) == FAILURE) {
    /* Uhhh? */

You should implement following 3 functions that are used for output. I, for example, forwarded all output to logfiles. Following lines are ripped from Irssi presentation:

static int ub_write(const char *str, unsigned int str_length TSRMLS_DC)
    /* php-irssi line-buffers output and then renders line-by-line
     * to one of the console windows. */
static void log_message(char *message)
    /* catch default output for log_errors; these are the messages
     * that end up in your apache error log for example */
static void sapi_error(int type, const char *fmt, ...)
    /* Catch some low-level SAPI errors */

There is a global structure php_embed_module where you should override default PHP functions with your own.

php_embed_module.ub_write    = ub_write;
php_embed_module.log_message = log_message;
php_embed_module.sapi_error  = sapi_error;

2. Creating variables

PHP documentation suggests using SET_VAR_STRING, but I found out a problem: it does not create a copy of value and PHP code unset($var) or PHP shutdown with php_request_shutdown() leads to Segmentation fault. I replaced this macro with following code:

zval  *var;
ALLOC_ZVAL(var); */
/* ZVAL_STRING(var, value, 0); is wrong*/
ZVAL_STRING(var, value, 1);

Zend documentation says that instead of ZEND_SET_GLOBAL_VAR() you can use a more optimal code, but you will have to use also MAKE_STD_ZVAL() to update reference counter.

zval  *var;
MAKE_STD_ZVAL(var); */
ZVAL_STRING(var, value, 1);
(void)zend_hash_update(&EG(symbol_table), name, strlen(name) + 1, &var, sizeof(zval *), NULL);

3. Execution of PHP code

You can find some information in Zend documentation, but that was not useful for me. Google found some information about zend_eval_string() and I’m using it:

zend_first_try {
    if (zend_eval_string("echo 'foobar';", NULL, __func__) == FAILURE) {
        /* Syntax error */
} zend_catch {
} zend_end_try();

Current function name __func__ is passed for nicer stack trace in case of error.
The main problem here is that zend_eval_string() returns an error only because of invalid PHP code syntax and returns success even when an exception occurs. Even more, unhandled exceptions will cause next zend_eval_string() call end with Segmentation fault. You can solve that by wrapping PHP code with try/catch and assigning result code to a global variable (done in this case):

$done = false;
try {
    call_some_function($arg); /* Throws exception on error */
    $done = true;
} catch (Exception $e) {
    /* Error handling */

4. Reading variables

Zend documentation is silent about this. Still, after experience with creation of variables it is obvious that you can find them in global symbol table. Since symbol table is just a Zend HashTable after short grep-ing I found zend_hash_find(). Somewhere before I’ve seen convert_to_string() that converts any type to string.

zval **data = NULL;
if (zend_hash_find(&EG(symbol_table), name, strlen(name) + 1, (void **)&data) == FAILURE) {
    /* Name not found in $GLOBALS */
if (data == NULL) {
    /* Value is NULL (not possible for symbol_table?) */
convert_to_string(*data); /* We need string value */
std::string value(Z_STRVAL(**data), Z_STRLEN(**data));

Since I’m using the same environment for executing every request, I have to cleanup unused variables. I’m sure that I could find some zend_hash_ function that does that. But I’m lazy, so I just call zend_eval_string() again and delete global variables in PHP code:


5. Shutdown of the PHP interpreter

It’s safe to use php_embed_shutdown() function from sapi/embed:


That’s all! Oh, and pardon my english.

Leave a Reply

Your email address will not be published.