您的位置:首页 > 编程语言 > PHP开发

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  php php扩展 linux