Calling Back into PHP
In addition to loading external scripts, as you saw in the last chapter, your PHP embedding application can also execute smaller snippets of arbitrary code using the underlying function that implements the familiar userspace eval() command.
int zend_eval_string(char *str, zval *retval_ptr, char *string_name TSRMLS_DC)
Here, str is the actual PHP script code to be executed, whereas string_name is an arbitrary description to associate with the execution. If an error occurs, PHP will report this description as the "filename" in the error output. retval_ptr, as you might guess, will be populated with any return value generated by the passed code. Try it out by creating a new project from Listing 20.1.
Listing 20.1. embed2.cRunning Arbitrary PHP Code
#include int main(int argc, char *argv[]) { PHP_EMBED_START_BLOCK(argc, argv) zend_eval_string("echo 'Hello World!';", NULL, "Simple Hello World App" TSRMLS_CC); PHP_EMBED_END_BLOCK() return 0; } |
Now build this using the command or Makefile shown in Chapter 19, "Setting Up a Host Environment," with embed1 replaced by embed2.
Alternatives to Script File Inclusion
Predictably, this makes compiling and executing external script files far easier than the method given previously because your application can simply replace its more complicated sequence of open/prepare/execute with this simpler, more functional design:
#include int main(int argc, char *argv[]) { char *filename; if (argc <= 1) { fprintf(stderr, "Usage: embed1 filename.php "); return -1; } filename = argv[1]; /* Ignore argv[0] when passing to PHP */ argc; argv++; PHP_EMBED_START_BLOCK(argc,argv) char *include_script; spprintf(&include_script, 0, "include '%s';", filename); zend_eval_string(include_script, NULL, filename TSRMLS_CC); efree(include_script); PHP_EMBED_END_BLOCK() return 0; }
Note
This particular method suffers from the disadvantage that if the filename contains a single quote, a parse error will resultat best. Fortunately this can be solved by using the php_addslashes() API call found in ext/standard/php_string.h. Take some time to look through this file and the API reference in the appendices as you'll find many features that can save you from reinventing the wheel later on.
Calling Userspace Functions
As you saw with loading and executing script files, there are two ways to call a userspace function from internals. The most obvious at this point would probably be to reuse zend_eval_string(), combining the function name and all its parameters into one monolithic string, and then collecting the return value:
PHP_EMBED_START_BLOCK(argc,argv) char *command; zval retval; spprintf(&command, 0, "return nl2br('%s';);", paramin); zend_eval_string(command, &retval, "nl2br() execution"); efree(command); paramout = Z_STRVAL(retval); PHP_EMBED_END_BLOCK()
Just like the include variant a moment ago, this method has a fatal flaw: If bad data is given by paramin, the function will fail at best, or cause unexpected results at worst. The solution is to avoid compiling a runtime snippet of code at all, and call the function directly using the call_user_function() API method instead:
int call_user_function(HashTable *function_table, zval **object_pp, zval *function_name, zval *retval_ptr, zend_uint param_count, zval *params[] TSRMLS_DC);
In practice, function_table will always be EG(function_table) when called from outside the engine. If calling an object or class method, object_pp can be an IS_OJBECT zval for calling an instance method, or an IS_STRING value for making a static class call. function_name is typically an IS_STRING value containing the name of the function to be called, but can be an IS_ARRAY containing an object or classname in element 0, and a method name in element 1.
The result of the function call will be populated into the zval pointer passed in retval_ptr. param_count and params act like the functions argc/argv data. That is, params[0] contains the first parameter to pass, and params[param_count-1] contains the last parameter to be passed.
This method can now be used to replace the prior example:
PHP_EMBED_START_BLOCK(argc, argv) zval *args[1]; zval retval, str, funcname; ZVAL_STRING(&funcname, "nl2br", 0); args[0] = &str; ZVAL_STRINGL(args[0], paramin, paramin_len, 0); call_user_function(EG(function_table), NULL, &funcname, &retval, 1, args TSRMLS_CC); paramout = Z_STRVAL(retval); PHP_EMBED_END_BLOCK()
Although the code listing here has actually become longer, the work being done has decreased dramatically because no intermediate code has to be compiled, the data being passed doesn't need to be duplicated, and each argument is already in a Zend-compatible structure. Also, remember that the original example was prone to potential errors if a string containing a quote was used. This version has no such drawback.