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

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