php擴充套件開發--快速入手
我們首先找到快速上手文章裡面關於函式定義的程式碼,以此說明然後開發PHP的函式
//php_myext.h
PHP_FUNCTION(myext_hello);//函式申明,所有在myext.c檔案定義的函式,都會在這個檔案裡有一個申明
//myext.c
static zend_function_entry myext_functions[] = {
PHP_FE(myext_hello, NULL)//每個函式一行,第一個引數與PHP_FUNCTION(name)的name一樣
{NULL, NULL, NULL}//固定語法
};
PHP_FUNCTION(myext_hello)
{
php_printf("hello you are success");
}
從上面的程式碼可以看出,開發一個php的函式需要3個步驟:
1,申明函式的原型
2,定義函式,書寫函式的程式碼
3,把函式註冊到本擴充套件模組裡
我們先來看2點。
1 PHP_FUNCTION(myext_hello)
2 {
3 php_printf("hello you are success");
4 }
5
6 #define PHP_FUNCTION ZEND_FUNCTION
7 #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
8 #define ZEND_FN(name) zif_##name
9 #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
10 #define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC
11
12 //根據Zend/zend.h 和 Zend/zend_API.h中的巨集定義,逐步展開
13
14 PHP_FUNCTION(myext_hello)
15 ZEND_FUNCTION(myext_hello)
16 ZEND_NAMED_FUNCTION(ZEND_FN(myext_hello))
17 ZEND_NAMED_FUNCTION(zif_myext_hello_name)
18 void zif_myext_hello_name(INTERNAL_FUNCTION_PARAMETERS)
19 void zif_myext_hello_name( int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)
INTERNAL_FUNCTION_PARAMETERS 代替的引數說明
名稱和型別 | 說明 | 使用方法 |
---|---|---|
int ht |
傳遞的引數數量 | ZEND_NUM_ARGS() |
zval *return_value |
函式的返回值,預設是IS_NULL . |
RETVAL_* ,RETURN_* |
zval **return_value_ptr |
當你需要返回引用的時候,把它賦值你要返回的變數,但是官方文件不建議返回引用. | |
zval *this_ptr |
如果當期函式是一個類方法,該指標指向對應的class,相當於PHP程式碼中的$this. | getThis() |
int return_value_used |
函式的呼叫者是否使用了返回值. |
通過編寫myext_example_args函式,瞭解了int ht , zval *return_value , int return_value_used三個引數的使用方法。
PHP_FUNCTION(myext_example_args);//php_myext.h
PHP_FE(myext_example_args, NULL)//static zend_function_entry myext_functions[] 在裡面增加一個註冊函式
//函式定義myext.c
PHP_FUNCTION(myext_example_args){
php_printf("zend_num_args() => %d,ht=>%d\n",ZEND_NUM_ARGS(),ht);//zend_num_args() => 3,ht=>3
if(return_value_used){
php_printf("返回值已經使用到了\n"); //$result = myext_example_args()
}else{
php_printf("返回值沒有被使用\n"); //myext_example_args()
}
RETURN_LONG(33);//返回值
}
我們來看函式註冊的程式碼,
PHP_FE(myext_hello, NULL)//第二個引數是用來對函式的傳參進行控制的,比如引數總數控制,是否傳遞引用,是否允許NULL,是否指定物件類,是否指定陣列型別等,傳遞NULL表示不做控制
//相關的巨集定義
#define PHP_FE ZEND_FE
#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags },
//arg_info的結構體
typedef struct _zend_arg_info {
const char *name;//引數名稱
zend_uint name_len;//名稱長度
const char *class_name;//引數類名稱
zend_uint class_name_len;//引數類名稱長度
zend_uchar type_hint;//不知道
zend_uchar pass_by_reference;//是否傳遞引用
zend_bool allow_null;//是否允許NULL值
zend_bool is_variadic;//不知道
} zend_arg_info;//每一個引數的具體規則#define ZEND_ARG_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, IS_OBJECT, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, sizeof(#name)-1, NULL, 0, IS_ARRAY, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, sizeof(#name)-1, NULL, 0, type_hint, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 1 },
//開始和結束的固定用法
//四個引數說明(變數名稱,沒有用到,是否返回引用,要求的引數數量)
#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \
static const zend_arg_info name[] = { \ { NULL, 0, NULL, required_num_args, 0, return_reference, 0, 0 }, #define ZEND_BEGIN_ARG_INFO(name, _unused) \ ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO() };
//我們來看一個demo,str_replace函式的引數定義
ZEND_BEGIN_ARG_INFO_EX(arginfo_str_replace, 0, 0, 3)
ZEND_ARG_INFO(0, search)
ZEND_ARG_INFO(0, replace)
ZEND_ARG_INFO(0, subject) ZEND_ARG_INFO(1, replace_count) ZEND_END_ARG_INFO()
我們編寫myext_example_arginfo函式,來演示一下arginfo變數的使用
PHP_FUNCTION(myext_example_arginfo);// php_myext.h
ZEND_BEGIN_ARG_INFO_EX(arginfo_arginfo, 0,0,1)//定義arginfo_arginfo
ZEND_ARG_INFO(1, str) //使用引用傳參
ZEND_END_ARG_INFO()
PHP_FE(myext_example_arginfo, arginfo_arginfo)//每個函式一行,第一個引數與PHP_FUNCTION(name)的name一樣
PHP_FUNCTION(myext_example_arginfo){
zval *z;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z) == FAILURE) {
return;
}
convert_to_string(z);
ZVAL_STRING(z,"引用引數的值已經改變",1);
return ;
}
/*php 程式碼*/
$ref = "我是初始值";
myext_example_arginfo($ref);
var_dump($ref);//string(30) "引用引數的值已經改變"
在剛才的例子裡,我們接受了一個php引數,我們來認真瞭解一個引數的接受
//有兩個主要的函式用於在擴充套件函式中結果傳遞過來的引數,兩者的區別就是ex結尾的多了一個flag引數
ZEND_API int zend_parse_parameters_ex(int flags, int num_args TSRMLS_DC, const char *type_spec, ...)
//flags 是一個標識位,當引數接收出錯的時候,是否抑制警告,目前只有一個常亮可以起作用,ZEND_PARSE_PARAMS_QUIET
//num_args 引數數量
//變數型別
ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, const char *type_spec, ...)
//num_args 引數數量
//變數型別
type_spec引數有點類似於printf的格式化字元引數
引數 | 型別 | 變數型別 |
---|---|---|
a | 陣列 |
zval* |
A | 陣列或物件 |
zval* |
b | 布林值 |
zend_bool |
C | 類 |
zend_class_entry* |
d | 浮點型 |
double |
f | 函式 |
zend_fcall_info* , zend_fcall_info_cache* |
h | 陣列 |
HashTable* |
H | 陣列或物件 |
HashTable* |
l | 長整型 | long |
L | 超出範圍的長整型 |
long |
o | 物件 |
zval* |
O | 物件或指定類的物件 |
zval* , zend_class_entry* |
p | 字串(一個有效的路徑) |
char* , int |
r | 資源型別 |
zval* |
s | 字串 |
char* , int |
z | zval*變數 |
zval* |
Z | zval**變數 | zval** |
在這些引數列表裡,大部分的引數都是對應著一個變數,也就是你前面寫了一個引數,後面對應的位置用一個變數去接受,有幾個特殊的(f,O,p,s),每一個引數對應兩個變數。
除了引數型別,還有幾個特殊用途的字元可以實現引數接收的特殊功能。
* 一共>=0個引數
+ 一共>=1個引數
| 在|之前的引數為必填引數,在|之後的引數為可選引數
/ 用來修飾前面一個引數,如果不是引用,則重新拷貝一個新的變數傳遞進來
! 用來修飾前面一個引數,如果傳遞過來的引數值為NULL,則直接轉化成C語言的NULL,而不是zval的IS_NULL變數,區別在於後者增加了計算和賦值,浪費了更多的資源
看到這裡,你很可能一頭霧水,完全不明白這些到底怎麼使用,沒有關係,接著來我們來看幾個例子,都是php標準擴充套件裡面用到的,結合你在php中使用的經驗,你應該一下子就會有所領悟。
//mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ZZZ|Z", &search, &replace, &subject, &zcount) == FAILURE)
//在str_replace中,核心用了前3個ZZZ來接受必要的三個引數,接收到的變數是zval**型別,然後有一個|,表示後面的引數是可選的,如果傳遞了,還是用一個Z(zval**)變數接受它
//int array_push ( array &$array , mixed $var [, mixed $... ] )
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a+", &stack, &args, &argc) == FAILURE)
//在array_push中,核心用了a(zval*)來接受第一個引數,然後用了一個+表示後面至少得有一個引數傳遞,或者更多。
//array range ( mixed $start , mixed $limit [, number $step = 1 ] )
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z/z/|z/", &zlow, &zhigh, &zstep) == FAILURE)
//在range中,一共出現了3個z/,每個z/表示一個引數,說明是用z(zval*)來接受變數,同時對非引用的變數做強制拷貝後再傳參
//mixed sscanf ( string $str , string $format [, mixed &$... ] )
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss*", &str, &str_len, &format, &format_len,&args, &num_args) == FAILURE)
//在sscanf中,每個s表示一個字串,得用兩個變數來接受,一個char*型別,指向字串,另一個是int型,等於字串的長度。&str, &str_len接受第一個s,&format, &format_len接受第二個s,最後一個*表示後面還可以有0到多個引數
看了以上這些,你應該能瞭解zend_parse_parameters函式的使用了,我們繼續通過幾個函式例子,讓你完整的理解引數的接受和使用的完整過程,由於*和+涉及到多引數的使用,會有一點點複雜,在這裡我們先跳過,日後再來進一步理解。
//一個類似strlen的函式,接受一個字串,返回字串的長度
PHP_FUNCTION(myext_example_strlen){
char *str;
int len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str,&len) == FAILURE) {
return;
}
RETURN_LONG(len);
}
/*
$str = "1234567";
var_dump(myext_example_strlen($str));
int(7)
*/
//輸入兩個引數,返回最大值
PHP_FUNCTION(myext_example_max_num){
long i1;
long i2;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &i1 ,&i2) == FAILURE) {
return;
}
if(i1 > i2){
RETURN_LONG(i1);
}else{
RETURN_LONG(i2);
}
}
/*
var_dump(myext_example_max_num(5,4));
var_dump(myext_example_max_num(4,5));
var_dump(myext_example_max_num("3","2"));//當輸入引數不是long型時,zend_parse_parameters會做自動轉換
var_dump(myext_example_max_num(1,false));
int(5)
int(5)
int(3)
int(1)
*/