【原創】PHP擴展開發入門




PHP擴展開發入門php

做者:wf (360電商技術組)web

 

 

    在咱們編寫本身的第一個php擴展以前,先了解一下php的整體架構和執行機制。apache


    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 。post

    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開源技術相關信息。歡迎關注微信!

微信二維碼掃描高速關注本號碼:


相關文章
相關標籤/搜索