摘要&引言
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源代码
要开发PHP扩展,第一步要下载PHP源代码,因为里面有开发扩展需要的工具。我下载的是PHP最新版本5.3.3,格式为tar.bz2压缩包。下载地址为:http://cn.php.net/get/php-5.3.3.tar.bz2/from/a/mirror。
下载后,将源代码移动到合适的目录并解压。解压命令为:
复制代码 代码如下:
tar -jxvf 源码包名称
复制代码 代码如下:
tar -zxvf 源码包名称
这时再用ls查看,会发现多了一个“say_hello”目录,进入这个目录,会发现ext_skel已经为我们建立好了say_hello的基本框架,如下图:
如果你懒得弄清楚PHP扩展包目录结构的全部内容,那么里面有三个文件你必须注意:
config.m4:这是Unix环境下的Build System配置文件,后面将会通过它生成配置和安装。
php_say_hello.h:这个文件是扩展模块的头文件。遵循C语言一贯的作风,这个里面可以放置一些自定义的结构体、全局变量等等。
say_hello.c:这个就是扩展模块的主程序文件了,最终的扩展模块各个函数入口都在这里。当然,你可以将所有程序代码都塞到这里面,也可以遵循模块化思想,将各个功能模块放到不同文件中。
下面的内容主要围绕这三个文件展开。
开发PHP扩展组件的第一步不是写实现代码,而是要先配置好Build System选项。由于我们是在Linux下开发,所以这里的配置主要与config.m4有关。
Build System の設定については、Unix システムのさまざまな事柄に関連しているので、詳しく書いても、私が興味を持って書いても、誰も読みたがらないでしょう。したがって、ここでは省略し、重要なポイントのみに焦点を当てます。config.m4 の詳細については、ここを参照してください。
生成された config.m4 ファイルを開きます。内容はおおよそ次のとおりです:
コードをコピーします コードは次のとおりです:
dnl $Id $
dnl config.m4 (拡張子 Say_hello 用)
dnl このファイル内のコメントは文字列 'dnl' で始まります。
dnl このファイルは必要に応じて削除してください。
dnl 拡張機能が外部のものを参照している場合は、次のオプションを使用します。
dnl PHP_ARG_WITH(say_hello、say_hello サポートの場合、
dnl) コメントが整列していることを確認してください:
dnl [ --with -say_hello Say_hello サポートを含めます])
dnl それ以外の場合は、enable を使用します:
dnl PHP_ARG_ENABLE(say_hello, Say_hello サポートを有効にするかどうか,
dnl コメントが整列していることを確認してください:
dnl [ --enable- Say_hello Say_hello サポートを有効にする])
if test "$PHP_SAY_HELLO" != "no"; then
dnl ここにテストの例を書きます...
dnl # --with-say_hello -> -path
dnl SEARCH_PATH ="/usr/local /usr" # この
dnl を変更する必要があるかもしれません SEARCH_FOR="/include/say_hello.h" # この
dnl を変更する可能性が高いのは次のような場合ですtest -r $PHP_SAY_HELLO/$ SEARCH_FOR; then # パラメータとして指定されたパス
dnl SAY_HELLO_DIR=$PHP_SAY_HELLO
dnl else # デフォルトのパスリストを検索
dnl AC_MSG_CHECKING([デフォルトパスのsay_helloファイルの場合])
dnl for i in $SEARCH_PATH ; do
dnl if test -r $i/$SEARCH_FOR; then
dnl SAY_HELLO_DIR=$i
dnl AC_MSG_RESULT(found in $i)
dnl fi
dnl 完了
dnl fi
dnl
dnl if test -z "$SAY_HELLO_DIR"; then
dnl AC_MSG_RESULT([not found])
dnl AC_MSG_ERROR([say_hello ディストリビューションを再インストールしてください] )
dnl fi
dnl # --with-say_hello -> インクルード パスを追加
dnl PHP_ADD_INCLUDE($SAY_HELLO_DIR/include)
dnl # --with-say_hello ->シンボルの存在
dnl LIBNAME =say_hello # この
dnl LIBSYMBOL=say_hello # 変更する可能性が高い
dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
dnl [
dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SAY_HELLO_DIR/lib, SAY_HELLO_SHARED_LIBADD)
dnl AC_DEFINE(HAVE_SAY_HELLOLIB,1,[ ])
dnl ],[
dnl AC_MSG_ERROR([間違ったsay_hello lib バージョンまたは lib が見つかりません] )
dnl ], [
dnl -L$SAY_HELLO_DIR/lib -lm
dnl ])
dnl
dnl PHP_SUBST(SAY_HELLO_SHARED_LIBADD)
PHP_NEW_EXTENSION(say_hello,say_hello.c, $ ext_shared)
fi
7 番目のフィールド「name」。このフィールドはこの PHP 拡張機能の名前で、この場合は「say_hello」です。
8 番目のフィールド「functions」には、この拡張機能で定義した関数への参照が保存されます。興味のある人は _zend_function_entry のソース コードを読むことができます。特定のコードを記述するときに、対応するマクロがここにあります。
9~12番目のフィールドはそれぞれ4つの関数ポインタであり、これら4つの関数はそれぞれ「拡張モジュールのロード時」、「拡張モジュールのアンロード時」、「各リクエストの開始時」のタイミングで呼び出されます。 」および「各リクエストの終了時」。これら 4 つの機能はインターセプト メカニズムとみなすことができ、主にリソースの割り当て、解放、および対応する時間のその他の関連操作に使用されます。
13番目のフィールド「info_func」も関数ポインタであり、phpinfo()の実行時にこのポインタが指す関数が呼び出され、カスタムモジュールの情報が表示されます。
14 番目のフィールド「version」はモジュールのバージョンです。
(zend_module_entry の詳細については、ここを参照してください)
上記のフィールドを導入した後、「say_hello.c」で自動的に生成された「say_hello_module_entry」フレームワーク コードを見てみましょう。
コードをコピー コードは次のとおりです:
/* {{{say_hello_module_entry
*/
zend_module_entry Say_hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
「こんにちは」 ,
say_hello_functions,
PHP_MINIT(say_hello),
PHP_MSHUTDOWN(say_hello),
PHP_RINIT(say_hello), /* リクエスト開始時に何もすることがない場合は NULL に置き換えます */
PHP_RSHUTDOWN( Say_hello), /* リクエスト終了時に何もすることがない場合は NULL に置き換えます */
PHP_MINFO(say_hello),
#if ZEND_MODULE_API_NO >= 20010901
"0.1", /* のバージョン番号に置き換えます拡張機能 */
#endif
STANDARD_MODULE_PROPERTIES
}
/* }}} */
まず、マクロ「STANDARD_MODULE_HEADER」は最初の 6 フィールドを生成し、「STANDARD_MODULE_PROPERTIES」は「version」以降のフィールドを生成するため、まだ心配する必要はありません。私たちが注目するいくつかのフィールドもマクロによって入力または生成されており、いくつかの関数のフレームワークも「say_hello.c」内の対応する場所に生成されています。ここで、いくつかのマクロのパラメータはすべて「say_hello」ですが、これはいくつかの関数の名前がすべて「say_hello」であることを意味するわけではなく、C 言語には関数名のオーバーロード機構がないことに注意してください。実際、PHP 拡張機能の開発プロセスでは、グローバル変数から関数定義、さらには戻り値に至るまで、Zend で事前に定義されたさまざまなマクロがほぼあらゆる場所で使用されます。 PHP のメカニズムは名前の競合などの問題を引き起こす可能性があり、これらのマクロは関数などの要素を内部名に変換しますが、これらはプログラマにとっては透過的です (マクロのコードを読まない限り)。私たちはさまざまなマクロやマクロを通じてプログラムします。私たちのために内部の多くのことを処理してください。
これを書いた後、私たちのタスクは明確です。まず、対応する時間に何かを処理する必要がある場合は、各インターセプト関数の内容を入力する必要があります。次に、say_hello の関数関数を記述し、への参照を追加します。 Say_hello_functions 。
say_hello 拡張機能はライフサイクルの各段階での操作を必要としないため、info_func の内容を記述するだけです。前述のように、この関数は拡張機能情報を表示するために phpinfo() が実行されるときに自動的に呼び出されます。この関数を記述するには 4 つの関数が使用されます:
php_info_print_table_start()—phpinfo テーブルを開始します。パラメータはありません。
php_info_print_table_header()—出力テーブルヘッダー。最初のパラメータはヘッダーの列数を示す整数で、後続のパラメータは表示するテキストを指定するために使用される列数に等しい (char*) タイプのパラメータです。
php_info_print_table_row()—テーブルの内容を出力します。最初のパラメータは、この行の列数を示す整数で、後続のパラメータは、表示するテキストを指定するために使用される列数に等しい (char*) タイプのパラメータです。
php_info_print_table_end()—phpinfo テーブルを終了します。パラメータはありません。