為什么要用C擴展
C是靜態編譯的,執行效率比PHP代碼高很多。同樣的運算代碼,使用C來開發,性能會比PHP要提升數百倍。
另外C擴展是在進程啟動時加載的,PHP代碼只能操作Request生命周期的數據,C擴展可操作的范圍更廣。
下載PHP7.1.1擴展
1.下載地址: http://php.net/get/php-7.1.1.tar.bz2/from/a/mirror
2.下載后進行解壓
創建擴展骨架
##本例用的是php7.1.1 cd ext ./ext_skel --extname=helloworld
修改config.m4
把下面幾行注釋打開,config.m4中 dnl 為注釋的意思
##動態編譯選項,通過.so的方式鏈接,去掉dnl注釋: PHP_ARG_WITH(helloworld, for helloworld support, Make sure that the comment is aligned: [ --with-helloworld Include helloworld support]) ##靜態編譯選項,通過enable來啟用,去掉dnl注釋: PHP_ARG_ENABLE(helloworld, whether to enable helloworld support, Make sure that the comment is aligned: [ --enable-helloworld Enable helloworld support])
修改完成編譯下
phpize #這里用自己的php-config文件位置 ./configure --with-php-config=./configure --with-php-config=/Applications/MAMP/bin/php/php7.1.1/bin/php-config make && make install make test #測試 #編輯php.ini,加上helloworld擴展 extension=helloworld.so
在myfun有個php的測試腳本,執行測試下
php -d enable_dl=On myfile.php
輸出結果:
Functions available in the test extension: confirm_myfun_compiled Congratulations! You have successfully modified ext/myfun/config.m4. Module myfun is now compiled into PHP.
其實confirm_myfun_compiled是構建工具幫我們生成的測試函數
創建helloWorld函數
現在我們來創建屬於自己的函數 helloWorld()
,功能就是輸出 Hello World!
vim myfun/php_myfun.h
##在PHP_FUNCTION(confirm_myfun_compiled); 下追加一行
PHP_FUNCTION(helloWorld);
實現helloworld實體
vim myfun/myfun.c
##zend_function_entry myfun_functions 補充要實現的函數 const zend_function_entry myfun_functions[] = { PHP_FE(confirm_myfun_compiled, NULL) /* For testing, remove later. */ PHP_FE(helloWorld, NULL) /*這是補充的一行,末尾沒有逗號*/ {NULL, NULL, NULL} /* Must be the last line in myfun_functions[] */ }; ##在末尾實現helloworld的內容 PHP_FUNCTION(helloWorld) { php_printf("Hello World!\n"); RETURN_TRUE; }
重新編譯
./configure && make && make install
測試
php -d enable_dl=On -r "dl('myfun.so');helloWorld();" Hello World! php -d enable_dl=On -r "dl('myfun.so');print confirm_myfun_compiled('helloWorld');" Congratulations! You have successfully modified ext/myfun/config.m4. Module helloWorld is now compiled into PHP.
頭文件分析
#ifndef PHP_MYFUN_H #define PHP_MYFUN_H extern zend_module_entry myfun_module_entry; #define phpext_myfun_ptr &myfun_module_entry #ifdef PHP_WIN32 # define PHP_MYFUN_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 # define PHP_MYFUN_API __attribute__ ((visibility("default"))) #else # define PHP_MYFUN_API #endif #ifdef ZTS #include "TSRM.h" #endif PHP_MINIT_FUNCTION(myfun); /*當PHP被裝載時,模塊啟動函數即被引擎調用。這使得引擎做一些例如資源類型,注冊INI變量等的一次初始化。*/ PHP_MSHUTDOWN_FUNCTION(myfun); /*當PHP完全關閉時,模塊關閉函數即被引擎調用。通常用於注銷INI條目*/ PHP_RINIT_FUNCTION(myfun); /*在每次PHP請求開始,請求前啟動函數被調用。通常用於管理請求前邏輯。*/ PHP_RSHUTDOWN_FUNCTION(myfun); /*在每次PHP請求結束后,請求前關閉函數被調用。經常應用在清理請求前啟動函數的邏輯。*/ PHP_MINFO_FUNCTION(myfun); /*調用phpinfo()時模塊信息函數被呼叫,從而打印出模塊信息。*/ PHP_FUNCTION(confirm_myfun_compiled); /* For testing, remove later. */ PHP_FUNCTION(helloWorld); #ifdef ZTS #define MYFUN_G(v) TSRMG(myfun_globals_id, zend_myfun_globals *, v) #else #define MYFUN_G(v) (myfun_globals.v) #endif #endif /* PHP_MYFUN_H */
confirm_myfun_compiled分析
PHP_FUNCTION(confirm_myfun_compiled) //使用了宏PHP_FUNCTION(),該宏可以生成一個適合於Zend引擎的函數原型 { char *arg = NULL; int arg_len, len; char *strg; //獲得函數傳遞的參數 //第一個參數是傳遞給函數的參數個數。通常的做法是傳給它ZEND_NUM_ARGS()。這是一個表示傳遞給函數參數總個數的宏。 //第二個參數是為了線程安全,總是傳遞TSRMLS_CC宏。 //第三個參數是一個字符串,指定了函數期望的參數類型,后面緊跟着需要隨參數值更新的變量列表。因為PHP采用松散的變量定義和動態的類型判斷,這樣做就使得把不同類型的參數轉化為期望的類型成為可能。例如,如果用戶傳遞一個整數變量,可函數需要一個浮點數,那么zend_parse_parameters()就會自動地把整數轉換為相應的浮點數。如果實際值無法轉換成期望類型(比如整形到數組形),會觸發一個警告。 /* 類型指示符 l long 符號整數 d double 浮點數 s char *, int 二進制字符串,長度 b zend_bool 邏輯型(1或0) r zval * 資源(文件指針,數據庫連接等) a zval * 聯合數組 o zval * 任何類型的對象 O zval * 指定類型的對象。需要提供目標對象的類類型 z zval * 無任何操作的zval */ //第四個參數為傳遞的參數數據的引用 //第五個參數為傳遞的參數個數 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { //獲得函數傳遞的參數 return; } len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "myfun", arg); /* 通過設置RETURN_type()的方式,將返回控制到PHP。下表解釋了大多數存在的宏。 RETVAL_LONG(l) 整數 RETVAL_BOOL(b) 布爾數(1或0) RETVAL_NULL() NULL RETVAL_DOUBLE(d) 浮點數 RETVAL_STRING(s, dup) 字符串。如果dup為1,引擎會調用estrdup()重復s,使用拷貝。如果dup為0,就使用s RETVAL_STRINGL(s, l, dup) 長度為l的字符串值。與上一個宏一樣,但因為s的長度被指定,所以速度更快。 RETVAL_TRUE 返回布爾值true。注意到這個宏沒有括號。 RETVAL_FALSE 返回布爾值false。注意到這個宏沒有括號。 RETVAL_RESOURCE(r) 資源句柄。 */ RETURN_STRINGL(strg, len, 0); }