問題: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 *arg, 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