1. 程式人生 > >PHP Extension元件開發完整例項(C/C++擴充套件PHP)

PHP Extension元件開發完整例項(C/C++擴充套件PHP)

PHP是當前應用非常廣泛的一門語言,從國外的Facebook、Twitter到國內的淘寶、騰訊、百度再到網際網路上林林總總的各種大中小型網站都能見到它的身影。PHP的成功,應該說很大程度上依賴於其開放的擴充套件API機制和豐富的擴充套件元件(PHP Extension),正是這些擴充套件元件使得PHP從各種資料庫操作到XML、JSON、加密、檔案處理、圖形處理、Socket等領域無所不能。有時候開發人員可能需要開發自己的PHP擴充套件,當前PHP5的擴充套件機制是基於Zend API的,Zend API提供了豐富的介面和巨集定義,加上一些實用工具,使得PHP擴充套件開發起來難度並不算特別大。本文將介紹關於PHP擴充套件元件開發的基本知識,並通過一個例項展示開發PHP擴充套件的基本過程。

PHP擴充套件元件的開發過程在Unix和Windows環境下有所不同,但基本是互通的,本文將基於Unix環境(具體使用Linux)。閱讀本文需要簡單瞭解Unix環境、PHP和C語言的一些基礎知識,只要簡單瞭解就行,我會盡量不涉及太過具體的作業系統和語言特性,並在必要的地方加以解釋,以便讀者閱讀。

本文的具體開發環境為Ubuntu 10.04 + PHP 5.3.3。

下載PHP原始碼

下載後,將原始碼移動到合適的目錄並解壓。解壓命令為:


若下載的是tar.gz壓縮包,解壓命令為


解壓後,在原始碼目錄中有個ext目錄,這裡便是和PHP擴充套件有關的目錄。進入目錄後用ls檢視,可以看到許多已經存在的擴充套件。下圖是在我的環境下檢視的結果:


其中藍色的均是擴充套件包目錄,其中可以看到我們很熟悉的mysql、iconv和gd等等。而ext_skel是Unix環境下用於自動生成PHP擴充套件框架的指令碼工具,後面我們馬上會用到,ext_skel_win32.php是windows下對應的指令碼。

開發自己的PHP擴充套件——say_hello

下面我們開發一個PHP擴充套件:say_hello。這個擴充套件很簡單,只是接受一個字串引數,然後輸出“Hello xxx!”。這個例子只是為了介紹PHP擴充套件元件的開發流程,不承擔實際功能。

生成擴充套件元件框架

PHP的擴充套件元件開發目錄和檔案是有固定組織結構的,你可以隨便進入一個已有擴充套件元件目錄,檢視其所有檔案,我想你一定眼花繚亂了。當然你可以選擇手工完成框架的搭建,不過我相信你更希望有什麼東西來幫你完成。上文提到的ext_skel指令碼就是用來自動構建擴充套件包框架的工具。ext_skel的完整命令為:

  1. ext_skel --extname=module[--proto=file][--stubs=file][--xml[=file]][--skel=dir][--full-xml][--no-help]

作為初學者,我們不必瞭解所有命令引數,實際上,大多數情況下只需要提供第一個引數就可以了,也就是擴充套件模組的名字。因此,我們在ext目錄中鍵入如下命令:

  1. ./ext_skel --extname=say_hello

(如果你希望詳細瞭解ext_skel的各項命令引數,請參考這裡

這時再用ls檢視,會發現多了一個“say_hello”目錄,進入這個目錄,會發現ext_skel已經為我們建立好了say_hello的基本框架,如下圖:

如果你懶得弄清楚PHP擴充套件包目錄結構的全部內容,那麼裡面有三個檔案你必須注意:

config.m4:這是Unix環境下的Build System配置檔案,後面將會通過它生成配置和安裝。

php_say_hello.h:這個檔案是擴充套件模組的標頭檔案。遵循C語言一貫的作風,這個裡面可以放置一些自定義的結構體、全域性變數等等。

say_hello.c:這個就是擴充套件模組的主程式檔案了,最終的擴充套件模組各個函式入口都在這裡。當然,你可以將所有程式程式碼都塞到這裡面,也可以遵循模組化思想,將各個功能模組放到不同檔案中。

下面的內容主要圍繞這三個檔案展開。

Unix Build System配置

開發PHP擴充套件元件的第一步不是寫實現程式碼,而是要先配置好Build System選項。由於我們是在Linux下開發,所以這裡的配置主要與config.m4有關。

關於Build System配置這一塊,要是寫起來能寫一大堆,而且與Unix系統很多東西相關,就算我有興趣寫估計大家也沒興趣看,所以這裡我們從略,只揀關鍵地方說一下,關於config.m4更多細節可以參考這裡

開啟生成的config.m4檔案,內容大致如下:

  1. dnl $Id$
  2. dnl config.m4 for extension say_hello
  3. dnl Commentsinthis file start with the string'dnl'.
  4. dnl Removewhere necessary.This file will not work
  5. dnl without editing.
  6. dnl If your extension references something external,usewith:
  7. dnl PHP_ARG_WITH(say_hello,for say_hello support,
  8. dnl Make sure that the comment is aligned:
  9. dnl [--with-say_hello Include say_hello support])
  10. dnl Otherwiseuse enable:
  11. dnl PHP_ARG_ENABLE(say_hello, whether to enable say_hello support,
  12. dnl Make sure that the comment is aligned:
  13. dnl [--enable-say_hello Enable say_hello support])
  14. if test "$PHP_SAY_HELLO"!="no";then
  15. dnl Write more examples of tests here...
  16. dnl # --with-say_hello -> check with-path
  17. dnl SEARCH_PATH="/usr/local /usr"# you might want to change this
  18. dnl SEARCH_FOR="/include/say_hello.h"# you most likely want to change this
  19. dnl if test -r $PHP_SAY_HELLO/$SEARCH_FOR;then# path given as parameter
  20. dnl SAY_HELLO_DIR=$PHP_SAY_HELLO
  21. dnl else# search default path list
  22. dnl AC_MSG_CHECKING([for say_hello files indefault path])
  23. dnl for i in $SEARCH_PATH ;do
  24. dnl if test -r $i/$SEARCH_FOR;then
  25. dnl SAY_HELLO_DIR=$i
  26. dnl AC_MSG_RESULT(found in $i)
  27. dnl fi
  28. dnl done
  29. dnl fi
  30. dnl
  31. dnl if test -z "$SAY_HELLO_DIR";then
  32. dnl AC_MSG_RESULT([not found])
  33. dnl AC_MSG_ERROR([Please reinstall the say_hello distribution])
  34. dnl fi
  35. dnl # --with-say_hello -> add include path
  36. dnl PHP_ADD_INCLUDE($SAY_HELLO_DIR/include)
  37. dnl # --with-say_hello -> check for lib and symbol presence
  38. dnl LIBNAME=say_hello # you may want to change this
  39. dnl LIBSYMBOL=say_hello # you most likely want to change this
  40. dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
  41. dnl [
  42. dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SAY_HELLO_DIR/lib, SAY_HELLO_SHARED_LIBADD)
  43. dnl AC_DEFINE(HAVE_SAY_HELLOLIB,1,[])
  44. dnl ],[
  45. dnl AC_MSG_ERROR([wrong say_hello lib version or lib not found])
  46. dnl ],[
  47. dnl -L$SAY_HELLO_DIR/lib -lm
  48. dnl ])
  49. dnl
  50. dnl PHP_SUBST(SAY_HELLO_SHARED_LIBADD)
  51. PHP_NEW_EXTENSION(say_hello, say_hello.c, $ext_shared)
  52. fi

不要看這麼多,因為所有以“dnl”開頭的全是註釋,所以真正起作用沒幾行。這裡需要配置的只有下面幾行:

  1. dnl If your extension references something external,usewith:
  2. dnl PHP_ARG_WITH(say_hello,for say_hello support,
  3. dnl Make sure that the comment is aligned:
  4. dnl [--with-say_hello Include say_hello support])
  5. dnl Otherwiseuse enable:
  6. dnl PHP_ARG_ENABLE(say_hello, whether to enable say_hello support,
  7. dnl Make sure that the comment is aligned:
  8. dnl [--enable-say_hello Enable say_hello support])

我想大家也都能看明白,意思就是“如果你的擴充套件引用了外部元件,使用…,否則使用…”。我們的say_hello擴充套件並沒有引用外部元件,所以將“Otherwise use enable”下面三行的“dnl”去掉,改為:

  1. dnl Otherwiseuse enable:
  2. PHP_ARG_ENABLE(say_hello, whether to enable say_hello support,
  3. Make sure that the comment is aligned:
  4. [--enable-say_hello Enable say_hello support])

儲存,這樣關於Build System配置就大功告成了。

PHP Extension及Zend_Module結構分析

以上可以看成是為開發PHP擴充套件而做的準備工作,下面就要編寫核心程式碼了。上文說過,編寫PHP擴充套件是基於Zend API和一些巨集的,所以如果要編寫核心程式碼,我們首先要弄清楚PHP Extension的結構。因為一個PHP Extension在C語言層面實際上就是一個zend_module_entry結構體,這點可以從“php_say_hello.h”中得到證實。開啟“php_say_hello.h”,會看到裡面有怎麼一行:

  1. extern zend_module_entry say_hello_module_entry;

say_hello_module_entry就是say_hello擴充套件的C語言對應元素,而關於其型別zend_module_entry的定義可以在PHP原始碼的“Zend/zend_modules.h”檔案裡找到,下面程式碼是zend_module_entry的定義:

  1. typedefstruct _zend_module_entry zend_module_entry;
  2. struct _zend_module_entry {
  3. unsignedshort size;
  4. unsignedint zend_api;
  5. unsignedchar zend_debug;
  6. unsignedchar zts;
  7. conststruct _zend_ini_entry *ini_entry;
  8. conststruct _zend_module_dep *deps;
  9. constchar*name;
  10. conststruct _zend_function_entry *functions;
  11. int(*module_startup_func)(INIT_FUNC_ARGS);
  12. int(*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
  13. int(*request_startup_func)(INIT_FUNC_ARGS);
  14. int(*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
  15. void(*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
  16. constchar*version;
  17. size_t globals_size;
  18. #ifdef ZTS
  19. ts_rsrc_id* globals_id_ptr;
  20. #else
  21. void* globals_ptr;
  22. #endif
  23. void(*globals_ctor)(void*global TSRMLS_DC);
  24. void(*globals_dtor)(void*global TSRMLS_DC);
  25. int(*post_deactivate_func)(void);
  26. int module_started;
  27. unsignedchar type;
  28. void*handle;
  29. int module_number;
  30. char*build_id;
  31. };

這個結構體可能看起來會讓人有點頭疼,不過我還是要解釋一下里面的內容。因為這就是PHP Extension的原型,如果不搞清楚,就沒法開發PHP Extension了。當然,我就不一一對每個欄位進行解釋了,只揀關鍵的、這篇文章會用到的欄位說,因為許多欄位並不需要我們手工填寫,而是可以使用某些預定義的巨集填充。

第7個欄位“name”,這個欄位是此PHP Extension的名字,在本例中就是“say_hello”。

第8個欄位“functions”,這個將存放我們在此擴充套件中定義的函式的引用,具體結構不再分析,有興趣的朋友可以閱讀_zend_function_entry的原始碼。具體編寫程式碼時這裡會有相應的巨集。

第9-12個欄位分別是四個函式指標,這四個函式會在相應時機被呼叫,分別是“擴充套件模組載入時”、“擴充套件模組解除安裝時”、“每個請求開始時”和“每個請求結束時”。這四個函式可以看成是一種攔截機制,主要用於相應時機的資源分配、釋放等相關操作。

第13個欄位“info_func”也是一個函式指標,這個指標指向的函式會在執行phpinfo()時被呼叫,用於顯示自定義模組資訊。

第14個欄位“version”是模組的版本。

(關於zend_module_entry更詳盡的介紹請參考這裡

介紹完以上欄位,我們可以看看“say_hello.c”中自動生成的“say_hello_module_entry”框架程式碼了。

  1. /* {{{ say_hello_module_entry
  2. */
  3. zend_module_entry say_hello_module_entry ={
  4. #if ZEND_MODULE_API_NO >= 20010901
  5. STANDARD_MODULE_HEADER,
  6. #endif
  7. "say_hello",
  8. say_hello_functions,
  9. PHP_MINIT(say_hello),
  10. PHP_MSHUTDOWN(say_hello),
  11. PHP_