php扩展 创建类 给外部调用
原文地址:
?
terrylee.me/blog/post/2011/02/13/php-extension-part2.aspx
網頁打不開 從快照獲取的 ?
PHP擴展開發(1):快速入門
發布者:TerryLee?|?分類:PHP開發一. 摘要
PHP Extension是擴展PHP的主要手段,如數據庫訪問,序列化,或者遠程過程調用,使用過PHP的人,其實都使用過PHP Extension,PHP里面很多的函數也是通過擴展實現的,而在PHP源碼中包含了幾十個擴展,PECL倉庫中也提供了上百個擴展,所以只要使用PHP開發,就不可避免的要開發PHP Extension。本文會用圖文并茂的方式一步一步為大家介紹如何開發一個PHP Extension。
開發環境:Ubuntu 10.10,PHP 5.3.5,有一個可運行PHP的Web服務器(Nginx或Apache)
要求:了解C語言基礎,了解PHP編程
需求:開發一個名為fetion_echo的Extension,只有一個簡單的say_goodbye()函數,輸入一個字符串,該函數返回"Goodbye xxx"。
開發PHP Extension的過程基本可以分為如下幾步:?
1. 生成擴展框架?
2. Unix Build System配置?
3. 編寫phpinfo()回調函數?
4. 編寫核心代碼?
5. 配置、編譯?
6. 配置php.ini
二. 生成擴展框架
下載PHP源代碼,我使用的是PHP 5.3.5。進入PHP源代碼目錄可以看到有個ext目錄,這里是和PHP Extension有關,使用ls命令查看,可以看到很多已經存在的PHP Extension,如pdo_mysql,json等,如圖1所示:
注意在該目錄下有一個ext_skel腳本文件,接下來我們將會用它來生成Linux環境下PHP Extension代碼框架,而ext_skel_win32.php是windows下的生成腳本。ext_skel完整的命令格式為:
./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]] [--skel=dir] [--full-xml] [--no-help]對其中的參數解釋如下:?
--extname:參數指定了Extension的名字?
--no-help:指定在生成的代碼框架中不加入注釋等,除非我們對于開發PHP Extension非常有經驗,否則還是不要指定該參數。其他的參數我們暫時可以不用考慮。
接下來在ext目錄下輸入如下命令:
$ ./ext_skel --extname=fetion_echo再次使用ls命令查看ext目錄,可以看到多了一個名為fetion_echo的目錄,進入該目錄可以看到ext_skel已經為我們建立好了PHP Extension的基本框架,如圖2所示:
在這里有幾個比較重要的文件,我們需要介紹一下:?
config.m4:Linux下的Build System配置文件,會使用它來生成configure文件和makefile。?
php_fetion_echo.h:擴展模塊的頭文件。?
fetion_echo.c:擴展模塊的主程序文件,如果我們的擴展模塊中有多個函數,最終所有的函數入口都在該文件中。
三. Unix Build System配置
config.m4文件告訴Unix Build System我們的擴展支持什么configure選項,使用Emacs或者vim打開該文件,大家可以看到一堆不認識的配置,不過不用擔心,因為該文件中以“dnl”開頭的都是注釋,暫時不用考慮。我們能用到只有下面幾行:
dnl If your extension references something external, use with:dnl PHP_ARG_WITH(fetion_echo, for fetion_echo support, dnl Make sure that the comment is aligned: dnl [ --with-fetion_echo Include fetion_echo support])dnl Otherwise use enable:dnl PHP_ARG_ENABLE(fetion_echo, whether to enable fetion_echo support, dnl Make sure that the comment is aligned: dnl [ --enable-fetion_echo Enable fetion_echo support])其實大家都能看明白,如果擴展使用了一些外部的引用,就使用下面的三行,否則使用最后面的三行,由于我們只是開發一個簡單的echo擴展,沒有使用任何外部引用,只要取消掉最后三行的注釋就行:
PHP_ARG_ENABLE(fetion_echo, whether to enable fetion_echo support, Make sure that the comment is aligned: [ --enable-fetion_echo Enable fetion_echo support])保存退出,至此配置文件就完成了。
四. 分析PHP Extension核心代碼
為了下面更好的介紹,這里先簡單的介紹PHP Extension核心代碼,打開fetion_echo.c文件,可以看到如下一段代碼:
/* {{{ fetion_echo_module_entry */ zend_module_entry fetion_echo_module_entry = { #if ZEND_MODULE_API_NO >= 20010901STANDARD_MODULE_HEADER, #endif"fetion_echo",fetion_echo_functions,PHP_MINIT(fetion_echo),PHP_MSHUTDOWN(fetion_echo),PHP_RINIT(fetion_echo), PHP_RSHUTDOWN(fetion_echo), PHP_MINFO(fetion_echo), #if ZEND_MODULE_API_NO >= 20010901"0.1", #endifSTANDARD_MODULE_PROPERTIES };這里初始化了一個C語言中的結構體,每個PHP Extension其實就是一個zend_module_entry結構體,在該結構體中定義了每個擴展所需的字段,大家可以通過查看zend_module_entry源代碼看到。就本例而言,我們簡單的介紹一下上面的代碼:
1. STANDARD_MODULE_HEADER:C語言的宏,用來初始化zend_module_entry的前幾個字段,包括結構體大小等?
2. fetion_echo:指定了擴展的名字,對應結構體中的name字段?
3. fetion_echo_functions:一個zend_function_entry類型的數組,指向擴展的函數表,所有需要暴露給用戶的函數都需要在該函數表中注冊?
4. PHP_MINIT(fetion_echo):模塊初始化回調函數,在擴展被加載時調用,MINIT = Module Initialization?
5. PHP_MSHUTDOWN(fetion_echo):模塊卸載回調函數,在擴展杯卸載時調用,MSHUTDOWN = Module Shutdown?
6. PHP_RINIT(fetion_echo):請求初始化回調函數,每個請求開始時調用,RINIT = Request Initialization?
7. PHP_RSHUTDOWN(fetion_echo):請求結束回調函數,每個請求結束時調用,RSHUTDOWN = Request Shutdown?
8. PHP_MINFO(fetion_echo):擴展信息函數,在phpinfo()函數中會調用,用于顯示模塊的自定義信息。?
9. 0.1:指定了擴展的版本號,對應結構體中的version字段?
10. STANDARD_MODULE_PROPERTIES:C語言的宏,用來初始化zend_module_entry的后幾個字段
大家可以看到,在4-8我們指定了4個回調函數,這四個函數可以說我們提供了一種注入機制,讓我們能夠在這幾個關鍵點進行資源的初始化或者資源回收。另外,需要說明的一點是,所有的回調函數我們都是通過Zend提供的宏定義的,主要是為了防止在PHP運行時的命名沖突問題,事實上不僅僅是函數,包括函數返回值、全局變量等我們都會使用這種方式。
五. 編寫phpinfo()回調函數?
打開fetion_echo.c文件,在PHP_MINFO_FUNCTION里面編寫如下代碼:
PHP_MINFO_FUNCTION(fetion_echo) { php_info_print_table_start(); php_info_print_table_header(2, "fetion_echo support", "enabled"); php_info_print_table_row(2, "author", "TerryLee"); php_info_print_table_row(2, "version", "0.1"); php_info_print_table_end();/* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */ }這里主要是phpinfo()函數調用時顯示自定義信息,用到了四個函數:
1. php_info_print_table_start():定義phpinfo表格開始?
2. php_info_print_table_header():定義phpinfo表格頭,第一個參數指定列數,后面指定與第一個參數數量相等的自定義文字信息?
3. php_info_print_table_row():定義phpinfo表格內容,第一個參數指定列數,后面指定與第一個參數數量相等的自定義文字信息?
4. php_info_print_table_end():定義phpinfo表格結尾
在本例中我們定義了表格頭,指定擴展是否可用;另外定義了兩行內容,指定擴展的作者和版本。
六. 編寫核心代碼
接下來是時候編寫我們的擴展核心代碼了,打開php_fetion_echo.h文件,添加一行聲明:
PHP_FUNCTION(say_goodbye);注意這里不是用“原生”的編寫C語言函數的方式,而是通過PHP_FUNCTION宏定義(具體原因前面講過),say_goodbye是我們開發的擴展模塊要暴露給用戶的函數名稱。
打開fetion_echo.c,在這里實現say_goodbye函數:
PHP_FUNCTION(say_goodbye) { char *arg = NULL; int arg_len, len; char *strg;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",&arg, &arg_len) == FAILURE) { return; }len = spprintf(&strg, 0, "Goodbye %s\n", arg); RETURN_STRINGL(strg, len, 0); }里面的具體實現很簡單,接收到參數之后,返回“Goodybye 參數”字符串,需要解釋的是:
1. 參數接收:這里接收函數的參數需要通過zend_parse_parameter函數解析,第一個參數指定用戶傳入say_goodbye函數的參數個數,可以通過宏ZEND_NUM_ARGS()生成,TSRMLS_CC用來確保線程安全;第二個參數是一個字符串,每個字母代表一種類型,其中”s”代表char*或者int類型,“b”代表布爾類型,“l”代表long類型,完整的類型映射可以看這里;后面幾個參數是我們定義的局部變量,用來接收傳入的參數值
2. 函數返回值:不能使用C語言原生的return語句,而應該使用Zend API里提供的宏定義,如RETURN_STRINGL返回一個字符串;而RETURN_TRUE返回布爾類型true。
聲明擴展函數參數信息,我們的函數原型為say_goodbye(name),聲明參數方式:
ZEND_BEGIN_ARG_INFO(arg_say_goodbye, 0) ZEND_ARG_INFO(0, name) ZEND_END_ARG_INFO()這里都是Zend API提供的宏定義,在后面我會專門介紹擴展函數參數聲明。實現完say_goodbye函數之后,我們再注冊該函數到函數表fetion_echo_functions(前面介紹過),第一個參數為函數名,第二個參數為函數參數數組信息,如下代碼所示:
const zend_function_entry fetion_echo_functions[] = {PHP_FE(say_goodbye, arg_say_goodbye){NULL, NULL, NULL} };注意最后一行{NULL, NULL, NULL}是必須的,只有注冊到函數表中的函數才能暴露給用戶使用。
七. 配置、編譯、安裝?
在fetion_echo目錄下輸入如下命令,具體路徑請根據自己安裝的PHP路徑設置:
/usr/local/php5/bin/phpize注意如果沒有安裝過m4和autoconf,請先使用下列命令安裝 :
sudo apt-get install m4 sudo apt-get install autoconf運行phpize之后,再用ls命令可以看到在fetion_echo目錄生成了很多的文件,包括configure文件:
下面就可以安裝該擴展了,使用Linux下面的“標準”三步安裝模式:
./configure make make install在安裝完成后會提示具體的擴展安裝路徑,然后就可以把該擴展加入到php.ini配置中,注意extension_dir的設置,重啟Web服務器。
八. 運行?
上面步驟都完成后,運行phpinfo()應該可以看到:
編寫一個簡單的測試腳本,如下所示:
<?php echo say_goodbye("Foo"); ?>在瀏覽器里面查看:
如果您能夠看到該界面,說明擴展已經工作正常了。
九. 總結
本文通過一個簡單的示例,為大家介紹了如何使用Zend API和C語言在Linux下開發一個PHP Extension。下一篇我們看一下如何開發一個簡單的類擴展。
參考資料:?
1.?http://www.php.net/manual/en/internals2.buildsys.configunix.php?
2.?http://www.php.net/manual/en/internals2.buildsys.skeleton.php
?
PHP擴展開發(2):實現類擴展
發布者:TerryLee?|?分類:PHP開發
在第一篇文章中,我們所開發的擴展是單個函數,本篇文章看一下如何開發一個類擴展。假設我們要用PHP擴展實現一個類Person,它有一個private的成員變量$_name和兩個public的實例方法getName()和setName(),可以用PHP代碼表示如下:
<?php class Person {private $_name;public function getName() {return $this->_name;}public function setName($name) {$this->_name = $name;} }1. 聲明方法:還使用第一篇文章里面用過的示例,首先在頭文件php_fetion_echo.h里加入方法聲明。
PHP_METHOD(Person, __construct); PHP_METHOD(Person, __destruct); PHP_METHOD(Person, getName); PHP_METHOD(Person, setName);前面的擴展在聲明函數時使用PHP_FUNCTION宏,而在實現類擴展時我們使用PHP_METHOD宏,第一個參數指定類名,第二個參數指定方法名。
2. 方法實現:在fetion_echo.c文件中實現這幾個方法,構造函數和析構函數中只是輸出一些文本。
PHP_METHOD(Person, __construct) {php_printf("__construct called.<br/>"); }PHP_METHOD(Person, __destruct) {php_printf("__destruct called.<br/>"); }PHP_METHOD(Person, getName) {zval *self, *name;self = getThis();name = zend_read_property(Z_OBJCE_P(self), self, ZEND_STRL("_name"), 0 TSRMLS_CC);RETURN_STRING(Z_STRVAL_P(name), 0); }PHP_METHOD(Person, setName) {char *arg = NULL;int arg_len;zval *value, *self;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {WRONG_PARAM_COUNT;}self = getThis();MAKE_STD_ZVAL(value);ZVAL_STRINGL(value, arg, arg_len, 0);SEPARATE_ZVAL_TO_MAKE_IS_REF(&value);zend_update_property(Z_OBJCE_P(self), self, ZEND_STRL("_name"), value TSRMLS_CC);RETURN_TRUE;}對上面的代碼做一些解釋:
A. 獲取方法的參數信息,仍然使用zend_parse_parameters函數,與之前我們介紹過的一樣;
B. 獲取this指針(相對于PHP代碼而言,在PHP擴展中仍然使用zval結構表示)使用getThis()函數;
C. 使用MAKE_STD_ZVAL宏申請并初始化一個zval結構,在PHP擴展中,所有的數據類型其實都是用zval結構來表示的,在本系列文章中我會單獨寫一篇來介紹zval。
D. 獲取屬性值使用zend_read_property()函數,使用zend_update_property()函數更新屬性值。
3. 初始化類:在擴展初始化函數中,注冊并初始化類。
zend_class_entry *person_ce;PHP_MINIT_FUNCTION(fetion_echo) { zend_class_entry person;INIT_CLASS_ENTRY(person, "Person", fetion_echo_functions);person_ce = zend_register_internal_class_ex(&person, NULL, NULL TSRMLS_CC);zend_declare_property_null(person_ce, ZEND_STRL("_name"), ZEND_ACC_PRIVATE TSRMLS_CC);return SUCCESS; }使用INIT_CLASS_ENTRY宏初始化類,第二個參數指定類名,第三個參數是函數表。
4. 注冊到函數:聲明方法的參數,并注冊到函數表中。
ZEND_BEGIN_ARG_INFO(arg_person_setname, 0)ZEND_ARG_INFO(0, name) ZEND_END_ARG_INFO()const zend_function_entry fetion_echo_functions[] = {PHP_ME(Person, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)PHP_ME(Person, __destruct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)PHP_ME(Person, getName, NULL, ZEND_ACC_PUBLIC)PHP_ME(Person, setName, arg_person_setname, ZEND_ACC_PUBLIC){NULL, NULL, NULL} /* Must be the last line in fetion_echo_functions[] */ };類方法參數的聲明與之前我們函數參數聲明方式一致,在注冊類方法到函數表中時使用PHP_ME宏,而不是之前使用的PHP_FE宏。
ZEND_ACC_PUBLIC:指定方法的訪問修飾符
ZEND_ACC_CTOR:指定該方法為構造函數
ZEND_ACC_DTOR:指定該方法為析構函數
5. 運行測試:編譯安裝擴展后,編寫一段簡單的測試腳本:
<?php $person = new Person();$person->setName("TerryLee");echo $person->getName().'<br/>';運行后可以看到如下輸出,說明擴展工作正常:
__construct called. TerryLee __destruct called.在后面的文章,我會介紹更詳細介紹PHP?Extension開發內容,如INI設置,全局變量,參數傳遞等。
轉載于:https://blog.51cto.com/shendongming/626416
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的php扩展 创建类 给外部调用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SSIS包如何动态指定文件路径
- 下一篇: poj1039