PHP扩展调用so动态链接库(2)

问题: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()函数即可,即把它初始化成了一个空数组。*/

接下来往数组中添加数据,主要有三种方法:

  1. 在数组中指定的数字下标(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 *arg, uint idx, char *str, uint length, int duplicate)
    add_index_zval(zval *arg, uint index, zval *value)
    
  2. 在下一个数字下标(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)
    
  3. 在字符串型索引(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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章