PHP扩展调用so动态链接库(2)
2015-07-15 14:50
555 查看
问题:so中的函数参数有数组。
比如我的so中的函数叫int test(double* a)
原来没想太多,直接就调了,果然崩了,报错类似于:
symbol lookup error: ./test: undefined symbol: ……
说白了就是从php调用模块中函数,数组参数到zend引擎中无法解析,这是为什么呢?
这要从PHP的内核说起了:
在PHP中,无论变量是数组型、布尔型,字符串型或者其他任何类型,其信息总会包含在一个zval联合体中。我们一般不直接存取zval,因为比较麻烦,zval中存数组的是个哈希表Hashtable,这个哈希表是一个双向链表来存值。zval的结构是:
typedef union _zval { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj; } zval;
这个东东不是多复杂,有点数据结构基础的人基本都可以看懂。
如果对PHP内核没有兴趣,又嫌直接操作有点麻烦,所以通过一些附加的宏来操作。就记住这几个简单粗暴的宏函数吧~
举个例子,从PHP脚本传入一个都是浮点数的数组,再传给装到本地的so中的C函数,计算后给脚本返回一个整数:
PHP_FUNCTION(hello) { int argc = ZEND_NUM_ARGS(); long count1,i,result; zval *hello1= NULL;//PHP调用hello传入的数组参数,到这就成zval类型了。 double data1[count1];//接php数组中值的c数组 if (zend_parse_parameters(argc TSRMLS_CC, "a", &hello1) == FAILURE)//让扩展把PHP脚本调用hello的参数内容读进来 return; zval **item1; count1 = zend_hash_num_elements(Z_ARRVAL_P(hello1)); zend_hash_internal_pointer_reset(Z_ARRVAL_P(hello1)); //循环读取zval中的值到c类型的双精度数组data1 for(i=0;i<count1;i++) { zend_hash_get_current_data(Z_ARRVAL_P(hello1),(void**)&item1); data1[i]=Z_DVAL_PP(item1); zend_hash_move_forward(Z_ARRVAL_P(hello1)); } result=test(data1);//调用本地so中的c函数 RETURN_LONG(result);//将计算结果返回给PHP脚本 }
接下来在你的PHP脚本中,写:
<?php $data=array(0.123,0.321,0.1312,0.1321); echo hello($data); ?>
你会看到正确结算结果的~
问题:扩展模块没有添加进去。
造成这个问题的原因比较多,但大多数都是版本问题。例如我在后台error_log中看到的:PHP Warning:PHP Startup:*:Unable to initilize module\nModule compiled with module API=20121212\n compiled with module API=20131226\nThese options need to match in Unknown on line 0
错误原因是现在PHP环境和这个模块版本不一致,解决方法:
用对应版本的phpize生成PHP模块,例如我的API=20131226的phpize在/usr/local/php5/bin/路径下,而不加路径直接phpize默认生成的版本是20121212,就导致了上述问题。
/usr/local/php5/bin/phpize
./configure –with-php-config=/usr/local/php5/bin/php-config
configure时本来也可以不用加路径,可是还是和你使用的phpize在一个路径中的好。
问题:从扩展模块中返回计算结果到脚本。
有时需要考虑PHP运行算法的效率问题,毕竟编译型的c语言运算效率还是高。所以,在模块中用c的特性计算完某个功能,经常需要将计算结果返回到脚本中。zend引擎准备了一个方法。它在每个zif函数声明里加了一个zval*类型的形参,名为return_value,专门来解决返回值问题。
还定义了一些宏来返回值:
RETURN_BOOL(bool) 设定返回值为指定的一个布尔值。 RETURN_NULL 设定返回值NULL RETURN_LONG(long) 设定返回值为指定的一个长整数。 RETURN_DOUBLE(double) 设定返回值为指定的一个双精度浮点数。 RETURN_STRING(string, duplicate) 设定返回值为指定的一个字符串,duplicate 含义同 RETURN_STRING。 RETURN_STRINGL(string, length, duplicate) 设定返回值为指定的一个定长的字符串。其余跟 RETURN_STRING 相同。这个宏速度更快而且是二进制安全的。 RETURN_EMPTY_STRING 设定返回值为空字符串。 RETURN_FALSE 设定返回值为布尔值假。 RETURN_TRUE 设定返回值为布尔值真。
Zend没有为我们提供返回数组的宏,so,就用到了前面提到的return_value。首先,在扩展中初始化一个数组
ZEND_FUNCTION(sample_array) { array_init(return_value); } /*return_value是zval*类型的,所以我们直接对它调用array_init()函数即可,即把它初始化成了一个空数组。*/
接下来往数组中添加数据,主要有三种方法:
在数组中指定的数字下标(arg[idx] = $value)处添加:
add_index_long(zval *arg, uint idx, long n) add_index_null(zval *arg, uint idx) add_index_bool(zval *arg, uint idx, int b) add_index_resource(zval *arg, uint idx, int r) add_index_double(zval *arg, uint idx, double d) add_index_string(zval *arg, uint idx, char *str, int duplicate) add_index_stringl(zval *a 4000 rg, uint idx, char *str, uint length, int duplicate) add_index_zval(zval *arg, uint index, zval *value)
在下一个数字下标(arg[]=value)处添加:
add_next_index_long(zval *arg, long n) add_next_index_null(zval *arg) add_next_index_bool(zval *, int b) add_next_index_resource(zval *arg, int r) add_next_index_double(zval *arg, double d) add_next_index_string(zval *arg, char *str, int duplicate) add_next_index_stringl(zval *arg, char *str, uint length, int duplicate) add_next_index_zval(zval *arg, zval *value)
在字符串型索引(arg[key] =$value)处添加:
add_assoc_long(zval *arg, char *key, long n) add_assoc_null(zval *arg, char *key) add_assoc_bool(zval *arg, char *key, int b) add_assoc_resource(zval *arg, char *key, int r) add_assoc_double(zval *arg, char *key, double d) add_assoc_string(zval *arg, char *key, char *str, int duplicate) add_assoc_stringl(zval *arg, char *key, char *str, uint length, int duplicate) add_assoc_zval(zval *arg, char *key, zval *value)
无论用哪种方法,只要讲初始化后的return_value当做arg参数即可,脚本调用该模块后,会自动将前端的数组值计算为调用so后的值。举个例子:
<?php $data={0.1,0.2,0.3} $n=1; $data1 = check($data,$n); ?>
计算每个data+1后,从php扩展中调用so中的c函数:check(double data,int n,double result),将计算结果都存在result数组中,再把result从扩展中返回到脚本。在扩展的.c文件中它是这样的:
PHP_FUNCTION(check){ int argc = ZEND_NUM_ARGS(); long n; zval *data = NULL; //以上为脚本传进来的参数 zval **item1; long i,count1; double result[3];//定义一个存运算结果的数组 if (zend_parse_parameters(argc TSRMLS_CC, "al", &data, &n) == FAILURE) return;//注意zend_parse_parameters函数的位置,它将读取脚本中的参数 count1 = zend_hash_num_elements(Z_ARRVAL_P(data));//得到参数长度 double data1[count1];//定义一个转接的c型数组,用于传参. // 下面的循环把php型数组转化成c型数组data1 zend_hash_internal_pointer_reset(Z_ARRVAL_P(data)); for(i=0;i<count1;i++){ zend_hash_get_current_data(Z_ARRVAL_P(data),(void**)&item1); data1[i]=Z_DVAL_PP(item1); zend_hash_move_forward(Z_ARRVAL_P(data)); }//转完了 //调用so,完成计算 check(data,n,result); //把return_value初始化为数组 array_init(return_value); //将result的值存入return_value中 add_index_double(return_value, 0, result[0]); for(i=1;i<2;i++) add_next_index_double(return_value, result[i]) }
这样,就完成了返回数组。其他类型:double,string不用这么复杂,只需要使用zend提供的那几个宏就OK了,也用不到return_value,直接把结果扔进去~
参考资料:
http://docstore.mik.ua/orelly/webprog/php/index.htm
http://www.walu.cc/phpbook/6.1.md
相关文章推荐
- 一个关于if else容易迷惑的问题
- Linux socket 初步
- 10 篇对初学者和专家都有用的 Linux 命令教程
- PHP5.2.*防止Hash冲突拒绝服务攻击的Patch
- 深入理解PHP之匿名函数
- Linux 与 Windows 对UNICODE 的处理方式
- Ubuntu12.04下QQ完美走起啊!走起啊!有木有啊!
- 解決Linux下Android开发真机调试设备不被识别问题
- 运维入门
- 运维提升
- Linux 自检和 SystemTap
- Ubuntu Linux使用体验
- JSP/PHP基于Ajax的分页功能实现
- c语言实现hashmap(转载)
- Linux 信号signal处理机制
- linux下mysql添加用户
- Scientific Linux 5.5 图形安装教程
- 关于PHP通过PDO用中文条件查询MySQL的问题。