【原創】PHP擴展開發入門
PHP擴展開發入門
作者:wf (360電商技術組)
在我們編寫自己的第一個php擴展之前,先了解一下php的總體架構和執行機制。
php的架構如圖1所看到的。
當中一個重要的就是SAPI(server端應用編程端口),它使得PHP能夠和其它應用進行數據交互,把外部錯綜復雜的外部環境進行抽象化,為內部的php提供一套固定和統一的接口。使得php自身不受外部影響,保持一定的獨立性。常見的SAPI有CGI。FastCGI。Shell的CLI,apache的mod_php5,IIS的ISAPI。
另外一個非常重要就是ZendEngine。Zend Engine是官方提供的PHP實現的核心,提供了語言實現上的基礎設施,其它比較知名的還有facebook的hiphop實現。
比如PHP的語法實現。腳本的編譯執行環境。擴展機制以及內存管理等。
我們在後面編寫php擴展時,也將基於Zend Engine。
PHP3時代還是採用邊解釋邊執行的執行方式,這種方式執行效率非常受影響,其次代碼總體耦合度比較高。可擴展性也不夠好。因此隨著php在web應用開發中的普及,於是ZeevSuraski和Andi Gutmans決定重寫代碼以解決這兩個問題。終於他們倆把該項技術的核心引擎命名為Zend Engine 。
Zend Engine最基本的特性就是把PHP的邊解釋邊執行的執行方式改為先預編譯(Compile),再執行(Execute)。
這兩者的分開給 PHP 帶來了革命性的變化:執行效率大幅提高。因為實行了功能分離。減少了模塊間耦合度,可擴展性也大大增強。
眼下PHP的實現和Zend Engine之間的關系非常緊密。比如非常多PHP擴展都是使用的Zend API,而Zend正是PHP語言本身的實現,PHP僅僅是使用Zend這個內核來構建PHP語言的,而PHP擴展大都使用Zend API,這就導致PHP的非常多擴展和Zend引擎耦合在一起了,後來才有PHP核心開發人員就提出將這種耦合解開的建議。只是以下我們還以下在Zend Engine的基礎上開始編寫我們第一個簡單的php擴展。
1.配置文件
每個PHP擴展都至少須要一個配置文件和一個源文件。配置文件用來告訴編譯器應該編譯哪幾個文件。以及編譯本擴展是否須要的其它庫文件。
在php源代碼文件夾的ext文件夾下創建一個新的文件,擴展的名字取作myfirst。然後在這個文件夾下創建一個config.m4文件,並輸入以下內容:
PHP_ARG_ENABLE(
myfirst,
[Whether to enable the "myfirst" extension],
[enable-myfirst Enable"myfirst" extension support])
if test $PHP_Myfirst !="no"; then
PHP_SUBST(Myfirst_SHARED_LIBADD)
PHP_NEW_EXTENSION(myfirst, myfirst.c, $ext_shared)
fi
上面PHP_ARG_ENABLE函數有三個參數,第一個參數是我們的擴展名(註意不用加引號),第二個參數是當我們執行./configure腳本時顯示的內容。最後一個參數則是我們在調用./configure--help時顯示的幫助信息。PHP_SUBST函數僅僅是php官方對autoconf中AC_SUBST函數的一層封裝。
PHP_NEW_EXTENSION函數聲明了這個擴展的名稱、須要的源文件名稱、擴展的編譯形式。假設擴展使用了多個文件。能夠將文件名稱羅列在函數的參數裏,如:PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared)最後的$ext_shared參數用來聲明這個擴展為動態庫。在php執行時動態載入的。
2.源文件
在完畢了配置文件後。以下的就是完畢擴展主邏輯的頭文件和C文件。
頭文件
//php_myfirst.h
#ifndef Myfirst_H
#define Myfirst_H
//載入config.h。假設配置了的話
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
//載入php頭文件
#include "php.h"
#define phpext_myfirst_ptr &myfirst_module_entry
extern zend_module_entrymyfirst_module_entry;
#endif
C文件
//myfirst.c
#include "php_myfirst.h"
//module entry
zend_module_entrymyfirst_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"myfirst",//擴展名稱
NULL, /*Functions */
NULL, /*MINIT */
NULL, /*MSHUTDOWN */
NULL, /*RINIT */
NULL, /*RSHUTDOWN */
NULL, /*MINFO */
#if ZEND_MODULE_API_NO >= 20010901
"2.1",//擴展的版本號
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_Myfirst
ZEND_GET_MODULE(myfirst)
#endif
3.擴展編譯
準備好了擴展須要編譯的源文件。接下來須要的便是把它們編譯成目標文件了。
第一步:依據config.m4文件使用phpize生成一個configure腳本、Makefile等文件:
$ phpize
PHP Api Version: 20041225
Zend Module Api No: 20050617
Zend Extension Api No: 220050617
如今查看擴展所在的文件夾,會發現phpize程序依據config.m4裏的信息生成了很多編譯php擴展必須的文件,比方makefiles等。
第二部:執行./configure腳本。然後執行make; make test就可以。
假設沒有錯誤。那麽在module文件夾以下便會生成擴展的目標文件 myfirst.so,這裏因為之前我們在配置文件中寫申明的是動態擴展,所以會被編譯成動態庫。
如今,先讓我們執行一下PHP源代碼根文件夾下的./buildconf —force,再執行./configure --help命令。會發現myfirst擴展的信息已經出現了。
為了使PHP能夠找到須要的擴展文件,我們須要把編譯好的so文件拷貝到PHP的擴展文件夾下,並在php.ini中配置:
extension_dir=/usr/local/lib/php/modules/
extension=myfirst.so
這樣php就會在每次啟動的時候自己主動載入我們的擴展了。
4.擴展功能函數編寫
前面我們已經生成好了一份擴展框架,但它是沒有什麽實際作用的,我們還須要編寫詳細的功能函數。
#definePHP_FUNCTION ZEND_FUNCTION
#defineZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
#defineZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FN(name) zif_##name
當中zif是zend internal function的意思,zif前綴是可供PHP語言調用的函數在C語言中的函數名稱前綴。
ZEND_FUNCTION(myfirst_hello)
{
php_printf("HelloWorld!\n");
}
上面的函數在C語言中宏展開後是這種:
voidzif_myfirst_hello(INTERNAL_FUNCTION_PARAMETERS)
{
php_printf("HelloWorld!\n");
}
函數的功能已經實現了,可是還不能在程序中調用。因為這個函數還沒有在擴展模塊中註冊。
如今看下擴展中zend_module_entry
myfirst_module_entry(它是聯系C擴展與PHP語言的重要紐帶)中/*Functions*/的值為NULL。這是之前還沒有編寫函數。
如今我們能夠將編寫的函數賦值給它了。這個值須要是zend_function_entry[]類型:
static zend_function_entrymyfirst_functions[] = {
ZEND_FE(myfirst_hello, NULL)
{ NULL, NULL,NULL }
};
當中最後的{NULL,NULL,NULL}是固定不變的。ZEND_FE()宏函數是對myfirst_hello函數的一個聲明,假設有多個函數,能夠直接以相似的形式加入到{NULL,NULL,NULL}之前,註意每個之間不須要加逗號。確保一切無誤後,我們替換掉zend_module_entry裏的原有成員,如今應該是這種:
ZEND_FUNCTION(myfirst_hello)
{
php_printf("HelloWorld!\n");
}
static zend_function_entrymyfirst_functions[] = {
ZEND_FE(myfirst_hello, NULL)
{ NULL, NULL,NULL }
};
zend_module_entrymyfirst_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"myfirst",//擴展名稱。
myfirst_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。並復制.so文件到extension dir文件夾。
最後寫一個腳本在命令行測試,應該能夠輸出helloworld了。
<?php
myfirst_hello();
?>
-------------------------------------------------------------------------------------
黑夜路人,一個關註開源技術、樂於學習、喜歡分享的程序猿
博客:http://blog.csdn.net/heiyeshuwu
微博:http://weibo.com/heiyeluren
微信:heiyeluren2012
想獲取很多其它IT開源技術相關信息。歡迎關註微信!
微信二維碼掃描高速關註本號碼:
?
【原創】PHP擴展開發入門