# 5.4 編寫函數
# 5.4 編寫函數
前面我們已經生成好了一份擴展框架,但它是沒有什么實際作用的。一個擴展的作用可大了去了,既可以操作PHP中的變量、常量,還可以定義函數、類、方法、資源等。先讓我們從函數說起吧!
### ZEND\_FUNCTION()宏函數
ZEND\_FUNCTION()宏函數也可以寫成PHP\_FUNCTION(),但ZEND\_FUNCTION()更前衛、標準一些,但兩者是完全相同的。
```
#define PHP_FUNCTION ZEND_FUNCTION
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FN(name) zif_##name
```
其中,zif是zend internal function的意思,zif\_前綴是可供PHP語言調用的函數在C語言中的函數名稱前綴。
```
ZEND_FUNCTION(walu_hello)
{
php_printf("Hello World!\n");
}
```
上面定義了一個函數,在C語言中展開后應該是這樣的:
```
void zif_walu_hello(INTERNAL_FUNCTION_PARAMETERS)
{
php_printf("Hello World!\n");
}
```
上面的展開式僅供參考,絕不推薦在編程時使用,我們應該采用宏的形式,來提高程序的兼容性與可讀性。 上面的代碼定義了一個可供用戶在PHP語言中調用的函數實現,但現在用戶還不能在程序中調用,因為這個函數還沒有與用戶端建立聯系,也就是說雖然我們在C中完成了它的實現,但用戶端PHP語言還根本不知道它的存在呢。 現在我們回頭看一下5.1節中我們為擴展定義的zend\_module\_entry walu\_module\_entry(它是聯系C擴展與PHP語言的重要紐帶)中的“NULL, / *Functions* /”,當時我們為它賦予了NULL,是因為還沒有函數,現在我們已經為它編寫了函數了,便可以給它賦予一個新值了,這個值需要是zend\_function\_entry\[\]類型的,首先讓我們來構造這個重要數據。
```
static zend_function_entry walu_functions[] = {
ZEND_FE(walu_hello, NULL)
{ NULL, NULL, NULL }
};
/*
下面是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 },
ZEND_FE(walu_hello, NULL)展開后便是:
{"walu_hello",zif_walu_hello,NULL, (zend_uint) (sizeof(NULL)/sizeof(struct _zend_arg_info)-1), 0 },
*/
```
其中最后的{NULL,NULL,NULL}是固定不變的。ZEND\_FE()宏函數是對我們walu\_hello函數的一個聲明,如果我們有多個函數,可以直接以類似的形式添加到{NULL,NULL,NULL}之前,注意每個之間不需要加逗號。 其中的arg\_info我們現在先賦予NULL就行了,我們將在第7章討論這個參數。確保一切無誤后,我們替換掉zend\_module\_entry里的原有成員,現在應該是這樣的:
```
ZEND_FUNCTION(walu_hello)
{
php_printf("Hello World!\n");
}
static zend_function_entry walu_functions[] = {
ZEND_FE(walu_hello, NULL)
{ NULL, NULL, NULL }
};
zend_module_entry walu_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"walu", //這個地方是擴展名稱,往往我們會在這個地方使用一個宏。
walu_functions, /* Functions */
NULL, /* MINIT */
NULL, /* MSHUTDOWN */
NULL, /* RINIT */
NULL, /* RSHUTDOWN */
NULL, /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
"2.1", //這個地方是我們擴展的版本
#endif
STANDARD_MODULE_PROPERTIES
};
```
現在configure、make、make test,復制到extension dir。用下面這個命令來測試下,應該會輸出hello world了,如果沒有輸出,說明你哪個地方做錯了,查不出來的話可以給我發mail,看看是不是特例:-)
```
$ php -r 'walu_hello();'
```
## Zend Internal Functions
zif*前綴在前面我們已經說過了,代表著"Zend Internal Function",主要用來避免命名沖突,比如PHP語言中有個strlen()函數,而C語言中也有strlen()函數,所以PHP中的strlen在C中的實現不能是strlen,而應改是一個不同的名字。 但是有些時候盡管我們加了zif*前綴,還會出現一些沖突問題。比如函數名稱本身是一個宏名稱從而被編譯器替換掉了。在這種情況下,我們需要手動來為我們擴展中的函數命名,這一步操作通過ZEND\_NAMED\_FUNCTION(diy\_walu\_hello)來代替ZEND\_FUNCTION(hello\_hello)。前者由我們指定名稱,后者自己加上前綴。 如果我們在定義函數時使用了ZEND\_NAMED\_FUNCTION(),那么在walu\_functions\[\]里,我們需要用ZEND\_NAMED\_FE()宏來代替ZEND\_FE()宏。即:ZEND\_NAMED\_FE(walu\_hello,diy\_walu\_hello,NULL) 上面的技術在ext/standard/file.c用到了,我們可以看fopen()函數的定義:PHP\_NAMED\_FUNCTION(php\_if\_fopen)。但是用戶端不會感覺到任何變化,還是用fopen函數來使用,因為zend\_function\_entry中每一項的第一個值代表這此函數在PHP語言中的名稱。Internally, however, the function is protected from being mangled by preprocessor macros and over-helpful compilers.(原作者說的這個理由我也沒看明白,請知者指點)
## Function Aliases
去PHP手冊里查一下pos()函數,會得到這么一條信息:"This function is an alias of: current()";也就是說,它只是current的一個軟鏈接而已,類似linux中的ln -s命令,理解成win下的快捷方式也成。運行pos函數,其實就是在運行current函數,轉接了一下而已。這往往是因為版本升級引起的,新版本中的程序提供了某個功能的新的實現,先為原來的函數改個名,但還需要保留原來的函數名,所以這就用到了alias。這個功能可以在內核中通過ZEND\_NAMED\_FE宏來實現。
```
static zend_function_entry walu_functions[] = {
ZEND_FE(walu_hello, NULL)
ZEND_NAMED_FE(walu_hi, ZEND_FN(walu_hello), NULL)
{ NULL, NULL, NULL }
};
/*
ZEND_NAMED_FE也可以寫成PHP_NAMED_FE,但推薦用前者
#define ZEND_NAMED_FE(zend_name, name, arg_info) ZEND_FENTRY(zend_name, name, arg_info, 0)
*/
```
通過ZEND\_NAMED\_FE的展開式我們了解到,它只是把PHP語言中的兩個函數的名字對應到同一個C語言函數而已。 其實還有另外一種寫法:
```
static zend_function_entry walu_functions[] = {
ZEND_FE(walu_hello, NULL)
ZEND_FALIAS(walu_hi,walu_hello, NULL)
{ NULL, NULL, NULL }
};
/*
#define ZEND_FALIAS(name, alias, arg_info) ZEND_FENTRY(name, ZEND_FN(alias), arg_info, 0)
*/
```
展開式是一樣的,真不清楚官方鼓搗這么多同樣的宏干啥。
```
<?php
walu_hi();
walu_hello();
```
## links
- 5.3 [靜態編譯](5.3.html)
- 5.5 [第五章小結](5.5.html)
- 介紹
- 1 PHP的生命周期
- 1.1 讓我們從SAPI開始
- 1.2 PHP的啟動與終止
- 1.3 PHP的生命周期
- 1.4 線程安全
- 1.5 PHP的生命周期
- 2 PHP變量在內核中的實現
- 2.1 變量的類型
- 2.2 變量的值
- 2.3 創建PHP變量
- 2.4 變量的存儲方式
- 2.5 變量的檢索
- 2.6 類型轉換
- 2.7 小結
- 3 內存管理
- 3.1 內存管理
- 3.2 引用計數
- 3.3 內存管理
- 4 動手編譯PHP
- 4.1 動手編譯PHP
- 4.2 動手編譯PHP
- 4.3 Unix/Linux平臺下的編譯
- 4.4 在Win32平臺上編譯PHP
- 4.5 動手編譯PHP
- 5 Your First Extension
- 5.1 Your First Extension
- 5.2 編譯我們的擴展
- 5.3 靜態編譯
- 5.4 編寫函數
- 5.5 Your First Extension
- 6 函數返回值
- 6.1 函數返回值
- 6.2 引用與函數的執行結果
- 6.3 函數返回值
- 7 函數的參數
- 7.1 函數的參數
- 7.2 函數的參數
- 7.3 函數的參數
- 8 使用HashTable與{數組}
- 8.1 使用HashTable與{數組}
- 8.2 使用HashTable與{數組}
- 8.3 使用HashTable與{數組}
- 8.4 使用HashTable與{數組}
- 9 PHP中的資源類型
- 9.1 PHP中的資源類型
- 9.2 PHP中的資源類型
- 9.3 PHP中的資源類型
- 9.4 PHP中的資源類型
- 10 PHP中的面向對象(一)
- 10.1 PHP中的面向對象(一)
- 10.2 PHP中的面向對象(一)
- 10.3 PHP中的面向對象(一)
- 10.4 PHP中的面向對象(一)
- 10.5 PHP中的面向對象(一)
- 11 PHP中的面向對象(二)
- 11.1 PHP中的面向對象(二)
- 11.2 PHP中的面向對象(二)
- 11.3 PHP中的面向對象(二)
- 12 啟動與終止的那點事
- 12.1 關于生命周期
- 12.2 MINFO與phpinfo
- 12.3 常量
- 12.4 PHP擴展中的全局變量
- 12.5 PHP語言中的超級全局變量(Superglobals)
- 12.6 小結
- 13 INI設置
- 13.1 聲明和訪問INI設置
- 13.2 小結
- 14 流式訪問
- 14.1 流的概覽
- 14.2 訪問流
- 14.3 靜態資源操作
- 14.4 links
- 15 流的實現
- 15.1 php流的表象之下
- 15.2 包裝器操作
- 15.3 實現一個包裝器
- 15.4 操縱
- 15.5 檢查
- 15.6 小結
- 16 有趣的流
- 16.1 上下文
- 16.2 過濾器
- 16.3 小結
- 17 配置和鏈接
- 17.1 autoconf
- 17.2 庫的查找
- 17.3 強制模塊依賴
- 17.4 Windows方言
- 17.5 小結
- 18 擴展生成
- 18.1 ext_skel
- 18.2 PECL_Gen
- 18.3 小結
- 19 設置宿主環境
- 19.1 嵌入式SAPI
- 19.2 構建并編譯一個宿主應用
- 19.3 通過嵌入包裝重新創建cli
- 19.4 老技術新用
- 19.5 小結
- 20 高級嵌入式
- 20.1 回調到php中
- 20.2 錯誤處理
- 20.3 初始化php
- 20.4 覆寫INI_SYSTEM和INI_PERDIR選項
- 20.5 捕獲輸出
- 20.6 同時擴展和嵌入
- 20.7 小結