### 7.6.1 內部函數注冊
通過擴展可以將C語言實現的函數提供給PHP腳本使用,如同大量PHP內置函數一樣,這些函數統稱為內部函數(internal function),與PHP腳本中定義的用戶函數不同,它們無需經歷用戶函數的編譯過程,同時執行時也不像用戶函數那樣每一個指令都調用一次C語言編寫的handler函數,因此,內部函數的執行效率更高。除了性能上的優勢,內部函數還可以擁有更高的控制權限,可發揮的作用也更大,能夠完成很多用戶函數無法實現的功能。
前面介紹PHP函數的編譯時曾經詳細介紹過PHP函數的實現,函數通過`zend_function`來表示,這是一個聯合體,用戶函數使用`zend_function.op_array`,內部函數使用`zend_function.internal_function`,兩者具有相同的頭部用來記錄函數的基本信息。不管是用戶函數還是內部函數,其最終都被注冊到EG(function_table)中,函數被調用時根據函數名稱向這個符號表中查找。從內部函數的注冊、使用過程可以看出,其定義實際非常簡單,我們只需要定義一個`zend_internal_function`結構,然后注冊到EG(function_table)中即可,接下來再重新看下內部函數的結構:
```c
typedef struct _zend_internal_function {
/* Common elements */
zend_uchar type;
zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags;
zend_string* function_name;
zend_class_entry *scope;
zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_internal_arg_info *arg_info;
/* END of common elements */
void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //函數指針,展開:void (*handler)(zend_execute_data *execute_data, zval *return_value)
struct _zend_module_entry *module;
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_internal_function;
```
Common elements就是與用戶函數相同的頭部,用來記錄函數的基本信息:函數類型、參數信息、函數名等,handler是此內部函數的具體實現,PHP提供了一個宏用于此handler的定義:`PHP_FUNCTION(function_name)`或`ZEND_FUNCTION()`,展開后:
```c
void *zif_function_name(zend_execute_data *execute_data, zval *return_value)
{
...
}
```
PHP為函數名加了"zif_"前綴,gdb調試時記得加上這個前綴;另外內部函數定義了兩個參數:execute_data、return_value,execute_data不用再說了,return_value是函數的返回值,這兩個值在擴展中會經常用到。
比如要在擴展中定義兩個函數:my_func_1()、my_func_2(),首先是編寫函數:
```c
PHP_FUNCTION(my_func_1)
{
printf("Hello, I'm my_func_1\n");
}
PHP_FUNCTION(my_func_2)
{
printf("Hello, I'm my_func_2\n");
}
```
函數定義完了就需要向PHP注冊了,這里并不需要擴展自己注冊,PHP提供了一個內部函數注冊結構:zend_function_entry,擴展只需要為每個內部函數生成這樣一個結構,然后把它們保存到擴展`zend_module_entry.functions`即可,在加載擴展中會自動向EG(function_table)注冊。
```c
typedef struct _zend_function_entry {
const char *fname; //函數名稱
void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //handler實現
const struct _zend_internal_arg_info *arg_info;//參數信息
uint32_t num_args; //參數數目
uint32_t flags;
} zend_function_entry;
```
zend_function_entry結構可以通過`PHP_FE()`或`ZEND_FE()`定義:
```c
const zend_function_entry mytest_functions[] = {
PHP_FE(my_func_1, NULL)
PHP_FE(my_func_2, NULL)
PHP_FE_END //末尾必須加這個
};
```
這幾個宏的定義為:
```c
#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, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags },
#define ZEND_FN(name) zif_##name
```
最后將`zend_module_entry->functions`設置為`mytest_functions`即可:
```c
zend_module_entry mytest_module_entry = {
STANDARD_MODULE_HEADER,
"mytest",
mytest_functions, //functions
NULL, //PHP_MINIT(mytest),
NULL, //PHP_MSHUTDOWN(mytest),
NULL, //PHP_RINIT(mytest),
NULL, //PHP_RSHUTDOWN(mytest),
NULL, //PHP_MINFO(mytest),
"1.0.0",
STANDARD_MODULE_PROPERTIES
};
```
下面來測試下這兩個函數能否使用,編譯安裝后在PHP腳本中調用這兩個函數:
```php
//test.php
my_func_1();
my_func_2();
```
cli模式下執行`php test.php`將輸出:
```
Hello, I'm my_func_1
Hello, I'm my_func_2
```
大功告成,函數已經能夠正常工作了,后續的工作就是不斷完善handler實現擴展自己的功能了。
- 前言
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本實現
- 1.3.3 FPM的初始化
- 1.3.4 請求處理
- 1.3.5 進程管理
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 第3章 Zend虛擬機
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 3.2 函數實現
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.3.2 執行流程
- 3.3.3 函數的執行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向對象實現
- 3.4.1 類
- 3.4.2 對象
- 3.4.3 繼承
- 3.4.4 動態屬性
- 3.4.5 魔術方法
- 3.4.6 類的自動加載
- 3.5 運行時緩存
- 3.6 Opcache
- 3.6.1 opcode緩存
- 3.6.2 opcode優化
- 3.6.3 JIT
- 第4章 PHP基礎語法實現
- 4.1 類型轉換
- 4.2 選擇結構
- 4.3 循環結構
- 4.4 中斷及跳轉
- 4.5 include/require
- 4.6 異常處理
- 第5章 內存管理
- 5.1 Zend內存池
- 5.2 垃圾回收
- 第6章 線程安全
- 6.1 什么是線程安全
- 6.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.3.1 擴展的構成
- 7.3.2 編譯工具
- 7.3.3 編寫擴展的基本步驟
- 7.3.4 config.m4
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.5.1 全局變量
- 7.5.2 ini配置
- 7.6 函數
- 7.6.1 內部函數注冊
- 7.6.2 函數參數解析
- 7.6.3 引用傳參
- 7.6.4 函數返回值
- 7.6.5 函數調用
- 7.7 zval的操作
- 7.7.1 新生成各類型zval
- 7.7.2 獲取zval的值及類型
- 7.7.3 類型轉換
- 7.7.4 引用計數
- 7.7.5 字符串操作
- 7.7.6 數組操作
- 7.8 常量
- 7.9 面向對象
- 7.9.1 內部類注冊
- 7.9.2 定義成員屬性
- 7.9.3 定義成員方法
- 7.9.4 定義常量
- 7.9.5 類的實例化
- 7.10 資源類型
- 7.11 經典擴展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空間
- 8.1 概述
- 8.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- break/continue按標簽中斷語法實現
- defer推遲函數調用語法的實現
- 一起線上事故引發的對PHP超時控制的思考