我们要开发的扩展的php原型是:
<?php
// 调用一个可变参函数,输出函数名和参数,返回调用后的结果
function calltask(callable $func[, mixed $arg1[, ...]]);
// 原型可变参写法
function calltask(... $args) {
$func = array_shift($args);
if (!is_callable($func)) {
printf("the function is not callable:%s\n", $func);
return false;
}
// 输出
printf("you call func:%s\n", $func);
foreach ($args as $i=>$arg) {
printf("arg[%d] type=%d len=%d val=%s \n", $i, typeof($arg), strlen($arg), strval($arg));
}
return $func(... $args);
}
我的开发环境是:
系统: CentOS 7
PHP: 7.0+
gcc :4.8.4
PHP已经提供了工具用来创建扩展,并初始化代码:ext_skel
$ cd php-src/ext
$ ./ext_skel --extname=hello
工具会在当前目录生成 hello 文件夹。
修改配置文件
cd到hello,工具已经初始化了目录,打开配置文件 config.m4:
dnl If your extension references something external, use with:
dnl PHP_ARG_WITH(hello, for hello support,
dnl Make sure that the comment is aligned:
dnl [ --with-hello Include hello support])
dnl Otherwise use enable:
dnl PHP_ARG_ENABLE(hello, whether to enable hello support,
dnl Make sure that the comment is aligned:
dnl [ --enable-hello Enable hello support])
dnl 是注释符,表示当前行是注释。这段话是说如果此扩展依赖其他扩展,去掉PHP_ARG_WITH段的注释符;否则去掉PHP_ARG_ENABLE段的注释符。显然我们不依赖其他扩展或lib库,所以去掉PHP_ARG_ENABLE段的注释符:
PHP_ARG_ENABLE(hello, whether to enable hello support,
Make sure that the comment is aligned:
[ --enable-hello Enable hello support])
书写代码
工具生成的hello.c,写上我们的实现:
/* proto function calltask(callable $func[, mixed $arg1[, ...]]);*/
PHP_FUNCTION(calltask)
{
int i, status, argc = ZEND_NUM_ARGS();
zval *args = NULL;
zval retval;
// use FAST_ZPP get any count of args, args use original args pointer, no need to free here
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
RETURN_FALSE;
}
#else
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_VARIADIC('+', args, argc)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
#endif
if (argc < 1) {
WRONG_PARAM_COUNT;
}
// 第一个参数为函数名 取出来
zval *thefunc = &(args[0]);
php_printf("you will call: %s argc=%d is_callable=%d\n", Z_STRVAL(*thefunc), argc, zend_is_callable(thefunc, IS_CALLABLE_CHECK_NO_ACCESS, NULL));
if (!zend_is_callable(thefunc, IS_CALLABLE_CHECK_NO_ACCESS, NULL)) {
php_printf("the function is not callable:%s\n", Z_STRVAL(*thefunc));
RETURN_FALSE;
}
// 从第二个参数开始,把参数打印出来
for (i=1; i<argc; i++) {
convert_to_string_ex(&args[i]);
php_printf("arg[%d] type=%d len=%d val=%s \n", i, Z_TYPE(args[i]), Z_STRLEN(args[i]), Z_STRVAL(args[i]));
}
// 使用zend_API.h中的call_user_function_ex调用
status = call_user_function_ex(EG(function_table), NULL, thefunc, &retval, argc-1, (argc>1?args+1:NULL), 0, NULL);
// 判断返回值
if (status == SUCCESS && !Z_ISUNDEF(retval)) {
php_printf("the function success:%d\n", status);
//zval_ptr_dtor(return_value);
//ZVAL_COPY_VALUE(return_value, &retval);
} else {
php_printf("the function failed:%d\n", status);
//zval_ptr_dtor(return_value);
//ZVAL_NULL(return_value);
}
php_printf("the call over: %s argc=%d status=%d\n", Z_STRVAL(*thefunc), argc);
//efree(args);// Z_PARAM_VARIADIC 的args指针来自于复制函数原参数指针,不需要free
RETURN_ZVAL(&retval, 1, 1);//RETURN_TRUE;// 返回该返回值的类型
}
添加到编译列表里:
const zend_function_entry hello_functions[] = {
PHP_FE(hello, NULL) /*添加这行*/
PHP_FE(confirm_hello_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in hello_functions[] */
};
编译与安装
$ phpize
$ ./configure --with-php-config=/usr/local/php7/bin/php-config
$ make && make install
修改php.ini,开启扩展,若找不到可以用phpinfo()查看使用哪个配置文件.
make install会输出你的php扩展的路径:
Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20151012/
其实不用关心这个路径。相信来学习扩展开发的同学都知道哪里配置php.ini,如何重启对应的apache或者fpm,这里不多说。
extension=hello.so
写个脚本测试,此脚本在ext_skel会自动生成, hello.php:
<?php
$br = (php_sapi_name() == "cli")? "":"<br>";
if(!extension_loaded('hello')) {
dl('./module/hello.' . PHP_SHLIB_SUFFIX);
}
$module = 'hello';
/* 测试打印扩展中的函数列表
$functions = get_extension_funcs($module);
echo "Functions available in the test extension:$br\n";
foreach($functions as $func) {
echo $func."$br\n";
}
echo "$br\n";
$function = 'confirm_' . $module . '_compiled';
if (extension_loaded($module)) {
$str = $function($module);
} else {
$str = "Module $module is not compiled into PHP";
}
echo "called in script: $str\n";
*/
//$ret = calltask($function, 100, 2.2);
$func2 = function(... $args) {
$args[0] = "make a str:".$args[0];
return sprintf(... $args);
};
$func1 = 'printf';
//$ret = calltask($func1, 'FORMAT_FROM_PARAM:%s:%d\n', date('Y-m-d H:i:s'), __LINE__);
$ret = calltask($func2, 'FORMAT_FROM_PARAM:%s:%d\n', date('Y-m-d H:i:s'), __LINE__);
echo "the ret in php=";
var_dump($ret);
输出:
you will call: ??argc=4 is_callable=1
arg[1] type=6 len=25 val=FORMAT_FROM_PARAM:%s:%d\n
arg[2] type=6 len=19 val=2017-08-01 19:09:14
arg[3] type=6 len=2 val=28
the function success:0
the call over: ??argc=4 status=8
the ret in php=string(53) "make a str:FORMAT_FROM_PARAM:2017-08-01 19:09:14:28\n"
运行成功。
祝小伙伴们开发顺利。