上一次是寫的c擴展調用c的標准函數,但是只能調用頭文件中申明的函數,今天來說下c擴展調用實現php函數的c函數,比方說,c擴展要用到php中ip2long這個函數,但是c不可能去php中調用,肯定是去調用實現php函數的c函數。那么c擴展如何調用c內核對php的API呢?
這里要用到一個函數:ZEND_API int call_user_function_ex(HashTable *function_table, zval **object_pp, zval *function_name, zval **retval_ptr_ptr, zend_uint param_count, zval **params[], int no_separation, HashTable *symbol_table TSRMLS_DC);
第一個參數是HashTable,Zend使用HashTable來存儲PHP函數,function_table用於指定從哪個HashTable中獲取函數。通常應該用CG(function_table),展開就是compiler_globals.function_table,compiler_globals是一個用來存儲編譯器數據的全局數據結構(與其對應的還有個EG宏,即executor_globals,它用來存儲執行器數據)。compiler_globals.function_table里面存儲了所有我們可以在PHP頁面里面調用的函數,包括Zend內建函數、PHP標准庫函數、模塊導出的函數以及用戶使用PHP代碼定義的函數。
object_pp是一個對象,當指定該值時,Zend會從對象的函數表中獲取函數,這里不予討論,總是設為NULL。
function_name必須是string型的zval,存儲我們希望調用的函數的名稱。為什么使用zval而不是直接用char*,是因為Zend考慮到大部分情況下,我們都是從用戶那獲得參數,然后再調用call_user_function_ex的,這樣就可以不作處理直接把用戶參數傳給該函數。當然,我們也可以手動創建一個string型zval傳給它。
retval_ptr_ptr用於獲取函數的返回值,Zend執行完指定的函數后,它就將返回值的指針填充到這里。這個容器的空間函數會自動幫你申請,所以我們無需手動申請,但在事后這個容器空間的銷毀釋放工作得由我們自己(使用 zval_dtor())來做。
param_count和params用於指定函數的參數,param_count是一個標識參數個數的整數,params[] 是一個包含具體參數的數組。
no_separation用於指定是否在必要時執行zval分離,這在寫入非引用zval時發生。應該總是將其設為0,表示執行zval分離,否則可能破壞數據。
symbol_table用於指定目標函數的active_symbol_table,通常應該使用NULL,這樣Zend會為目標函數生成一個空的符號表。下面來看一個具體例子:
我在c擴展中要使用php函數中的ip2long函數,那么調用方法
unsigned long ip2longs(const char* ip){
zval *funname,*ret_ptr = NULL,*args,**params[1],*args_2; //如果有多個參數,比方說兩個參數,就繼續定義變量,看紅色部分,如果繼續加參數,按紅色的步驟繼續,定義變量,賦值,放入數組,參數個數也要變
MAKE_STD_ZVAL(funname); //創建變量
ZVAL_STRING(funname, "ip2long", 1); //設置好zval的類型和值,第二個參數就是我們要調用的ip2long函數 ,這兩個方法不了解的可以查看http://www.cunmou.com/phpbook/2.3.md
MAKE_STD_ZVAL(args);
ZVAL_STRING(args,ip,1);
MAKE_STD_ZVAL(args_2);
ZVAL_LONG(args_2,123); //這是第二個參數,創建變量並賦值,整形只有兩個參數,別的類型可以上網查
params[0] = &args; //把參數放入數組中
params[1] = &args_2; // 放入數組
call_user_function_ex(EG(function_table), NULL, funname, &ret_ptr, 2, params, 0, EG(active_symbol_table)); //調用函數,第5個參數代表參數個數我們這是2
zval_ptr_dtor(&ret_ptr); //銷毀手動創建的空間
return ret_ptr->value.lval; //獲取返回的值,因為函數返回是一個long型的,所以取lval
}
//該函數是php可以直接調用的函數。功能是用來判斷某個ip是否在內網中
PHP_FUNCTION(is_intranet) {
char *ip;
int ip_length=0;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s",&ip,&ip_length) == FAILURE){
RETURN_NULL();
}
unsigned long ip_long = ip2longs(ip); //調用聲明的函數
if(ip_long == ip2longs("127.0.0.1") || (ip_long > ip2longs("10.0.0.0") && ip_long <= ip2longs("10.255.255.255")) ||
(ip_long >= ip2longs("172.16.0.0") && ip_long <= ip2longs("172.31.255.255")) ||
(ip_long >= ip2longs("192.168.0.0") && ip_long <= ip2longs("192.168.255.255"))
) {
RETURN_BOOL(1);
} else {
RETURN_BOOL(0);
}
}
這里在多說下zval的結構,這些是自己平時學習總結的,在此拿出來和大家分享:
unsigned char type;
unsigned char is_ref;
short refcount;
};
變量的實際值,具體來說是一個zvalue_value的聯合體(union):
long lval; /* long value */
double dval; /* double value */
struct { /* string */
char *val;
int len;
} str;
HashTable *ht; /* hash table value,used for array */
zend_object_value obj; /* object */
zvalue_value結構的說明如下:
dval 如果變量類型為 IS_DOUBLE 就用這個屬性值
str 如果變量類型為 IS_STRING 就訪問這個屬性值。它的字段 len 表示這個字符串的長度,字段 val 則指向該字符串。由於 Zend 使用的是 C 風格的字符串,因此字符串的長度就必須把字符串末尾的結束符 0×00 也計算在內
ht 如果變量類型為數組,那這個 ht 就指向數組的哈希表入口
&uservar) == FAILURE) {
RETURN_NULL();
IS_LONG 是一個(長)整數
IS_DOUBLE 是一個雙精度的浮點數
IS_STRING 是一個字符串
IS_ARRAY 是一個數組
IS_OBJECT 是一個對象
IS_BOOL 是一個布爾值
IS_RESOURCE 是一個資源(關於資源的討論,我們以后會在適當的時候討論到它)