首頁  >  文章  >  後端開發  >  php擴展開發

php擴展開發

不言
不言原創
2018-04-14 14:33:0016142瀏覽

這篇文章主要介紹了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原始碼

要開發PHP擴展,第一步要下載PHP原始碼,因為裡面有開發擴充所需的工具。我下載的是PHP最新版本5.3.3,格式為tar.bz2壓縮包。下載網址為:http://cn.php.net/get/php-5.3.3.tar.bz2/from/a/mirror。

下載後,將原始程式碼移至適當的目錄並解壓縮。解壓縮指令為:

<br/>
  1. tar -jxvf 源碼包名稱

  1. 若下載的是tar.gz壓縮包,解壓縮指令為
<br/>

tar

#-

zxvf

原始碼包名稱

解壓縮後,在原始碼目錄中有個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的完整指令為:###
ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]] [--skel=dir] [--full-xml] [--no-help]
###身為初學者,我們不必了解所有指令參數,實際上,大多數情況下只需要提供第一個參數就可以了,也就是擴充模組的名字。因此,我們在ext目錄中鍵入如下命令:###
./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文件,内容大致如下:

<br/>
  1. dnl $Id$
    dnl config.m4 for extension say_hello
    dnl Comments in this file start with the string &#39;dnl&#39;.
    dnl Remove where necessary. This file will not work
    dnl without editing.
     
    dnl If your extension references something external, use with:
    dnl PHP_ARG_WITH(say_hello, for say_hello support,
    dnl Make sure that the comment is aligned:
    dnl [ --with-say_hello Include say_hello support])
     
    dnl Otherwise use enable:
    dnl PHP_ARG_ENABLE(say_hello, whether to enable say_hello support,
    dnl Make sure that the comment is aligned:
    dnl [ --enable-say_hello Enable say_hello support])
     
    if test "$PHP_SAY_HELLO" != "no"; then
    dnl Write more examples of tests here...
    dnl # --with-say_hello -> check with-path
    dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
    dnl SEARCH_FOR="/include/say_hello.h" # you most likely want to change this
    dnl if test -r $PHP_SAY_HELLO/$SEARCH_FOR; then # path given as parameter
    dnl SAY_HELLO_DIR=$PHP_SAY_HELLO
    dnl else # search default path list
    dnl AC_MSG_CHECKING([for say_hello files in default path])
    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 done
    dnl fi
    dnl
    dnl if test -z "$SAY_HELLO_DIR"; then
    dnl AC_MSG_RESULT([not found])
    dnl AC_MSG_ERROR([Please reinstall the say_hello distribution])
    dnl fi
    dnl # --with-say_hello -> add include path
    dnl PHP_ADD_INCLUDE($SAY_HELLO_DIR/include)
    dnl # --with-say_hello -> check for lib and symbol presence
    dnl LIBNAME=say_hello # you may want to change this
    dnl LIBSYMBOL=say_hello # you most likely want to change this
    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([wrong say_hello lib version or lib not found])
    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

不要看这么多,因为所有以“dnl”开头的全是注释,所以真正起作用没几行。这里需要配置的只有下面几行:

<br/>
  1. dnl If your extension references something external, use with:
    dnl PHP_ARG_WITH(say_hello, for say_hello support,
    dnl Make sure that the comment is aligned:
    dnl [ --with-say_hello Include say_hello support])
     
    dnl Otherwise use enable:
    dnl PHP_ARG_ENABLE(say_hello, whether to enable say_hello support,
    dnl Make sure that the comment is aligned:
    dnl [ --enable-say_hello Enable say_hello support])

我想大家也都能看明白,意思就是“如果你的扩展引用了外部组件,使用…,否则使用…”。我们的say_hello扩展并没有引用外部组件,所以将“Otherwise use enable”下面三行的“dnl”去掉,改为:

<br/>
  1. dnl Otherwise use enable:
    PHP_ARG_ENABLE(say_hello, whether to enable say_hello support,
    Make sure that the comment is aligned:
    [ --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”,会看到里面有怎么一行:

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的定义:

<br/>
  1. typedef struct _zend_module_entry zend_module_entry;
     
    struct _zend_module_entry {
        unsigned short size;
        unsigned int zend_api;
        unsigned char zend_debug;
        unsigned char zts;
        const struct _zend_ini_entry *ini_entry;
        const struct _zend_module_dep *deps;
        const char *name;
        const struct _zend_function_entry *functions;
        int (*module_startup_func)(INIT_FUNC_ARGS);
        int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
        int (*request_startup_func)(INIT_FUNC_ARGS);
        int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
        void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
        const char *version;
        size_t globals_size;
     
        #ifdef ZTS
        ts_rsrc_id* globals_id_ptr;
        #else
        void* globals_ptr;
        #endif
     
        void (*globals_ctor)(void *global TSRMLS_DC);
        void (*globals_dtor)(void *global TSRMLS_DC);
        int (*post_deactivate_func)(void);
        int module_started;
        unsigned char type;
        void *handle;
        int module_number;
        char *build_id;
    };

这个结构体可能看起来会让人有点头疼,不过我还是要解释一下里面的内容。因为这就是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”框架代码了。

<br/>
  1. /* {{{ say_hello_module_entry
    */
    zend_module_entry say_hello_module_entry = {
        #if ZEND_MODULE_API_NO >= 20010901
        STANDARD_MODULE_HEADER,
        #endif
        "say_hello",
        say_hello_functions,
        PHP_MINIT(say_hello),
        PHP_MSHUTDOWN(say_hello),
        PHP_RINIT(say_hello), /* Replace with NULL if there&#39;s nothing to do at request start */
        PHP_RSHUTDOWN(say_hello), /* Replace with NULL if there&#39;s nothing to do at request end */
        PHP_MINFO(say_hello),
        #if ZEND_MODULE_API_NO >= 20010901
        "0.1", /* Replace with version number for your extension */
        #endif
        STANDARD_MODULE_PROPERTIES
    };
    /* }}} */

首先,宏“STANDARD_MODULE_HEADER”会生成前6个字段,“STANDARD_MODULE_PROPERTIES ”会生成“version”后的字段,所以现在我们还不用操心。而我们关心的几个字段,也都填写好或由宏生成好了,并且在“say_hello.c”的相应位置也生成了几个函数的框架。这里要注意,几个宏的参数均为“say_hello”,但这并不表示几个函数的名字全为“say_hello”,C语言中也不可能存在函数名重载机制。实际上,在开发PHP Extension的过程中,几乎处处都要用到Zend里预定义的各种宏,从全局变量到函数的定义甚至返回值,都不能按照“裸写”的方式来编写C语言,这是因为PHP的运行机制可能会导致命名冲突等问题,而这些宏会将函数等元素变换成一个内部名称,但这些对程序员都是透明的(除非你去阅读那些宏的代码),我们通过各种宏进行编程,而宏则为我们处理很多内部的东西。

写到这里,我们的任务就明了了:第一,如果需要在相应时机处理一些东西,那么需要填充各个拦截函数内容;第二,编写say_hello的功能函数,并将引用添加到say_hello_functions中。

编写phpinfo()回调函数

因为say_hello扩展在各个生命周期阶段并不需要做操作,所以我们只编写info_func的内容,上文说过,这个函数将在phpinfo()执行时被自动调用,用于显示扩展的信息。编写这个函数会用到四个函数:

php_info_print_table_start()——开始phpinfo表格。无参数。

php_info_print_table_header()——输出表格头。第一个参数是整形,指明头的列数,然后后面的参数是与列数等量的(char*)类型参数用于指定显示的文字。

php_info_print_table_row()——输出表格内容。第一个参数是整形,指明这一行的列数,然后后面的参数是与列数等量的(char*)类型参数用于指定显示的文字。

php_info_print_table_end()——结束phpinfo表格。无参数。

下面是“say_hello.c”中需要编写的info_func的具体代码:

<br/>
  1. /* {{{ PHP_MINFO_FUNCTION
    */
    PHP_MINFO_FUNCTION(say_hello)
    {
        php_info_print_table_start();
        php_info_print_table_header(2, "say_hello support", "enabled");
        php_info_print_table_row(2, "author", "Zhang Yang"); /* Replace with your name */
        php_info_print_table_end();
        /* Remove comments if you have entries in php.ini
        DISPLAY_INI_ENTRIES();
        */
    }
    /* }}} */

可以看到我们编写了两行内容、组件是否可用以及作者信息。

编写核心函数

编写核心函数,总共分为三步:1、使用宏PHP_FUNCTION定义函数体;2、使用宏ZEND_BEGIN_ARG_INFO和ZEND_END_ARG_INFO定义参数信息;3、使用宏PHP_FE将函数加入到say_hello_functions中。下面分步说明。

使用宏PHP_FUNCTION定义函数体

<br/>
  1. PHP_FUNCTION(say_hello_func)
    {
        char *name;
        int name_len;
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE)
        {
            return;
        }
        php_printf("Hello %s!", name);
     
        RETURN_TRUE;
    }

上文说过,编写PHP扩展时几乎所有东西都不能裸写,而是必须使用相应的宏。从上面代码可以清楚看到这一点。总体来说,核心函数代码一般由如下几部分构成:

定义函数,这一步通过宏PHP_FUNCTION实现,函数的外部名称就是宏后面括号里面的名称。

声明并定义局部变量。

解析参数,这一步通过zend_parse_parameters函数实现,这个函数的作用是从函数用户的输入栈中读取数据,然后转换成相应的函数参数填入变量以供后面核心功能代码使用。zend_parse_parameters的第一个参数是用户传入参数的个数,可以由宏“ZEND_NUM_ARGS() TSRMLS_CC”生成;第二个参数是一个字符串,其中每个字母代表一个变量类型,我们只有一个字符串型变量,所以第二个参数是“s”;最后各个参数需要一些必要的局部变量指针用于存储数据,下表给出了不同变量类型的字母代表及其所需要的局部变量指针。

参数解析完成后就是核心功能代码,我们这里只是输出一行字符,php_printf是Zend版本的printf。

最后的返回值也是通过宏实现的。RETURN_TRUE宏是返回布尔值“true”。

使用宏ZEND_BEGIN_ARG_INFO和ZEND_END_ARG_INFO定义参数信息

参数信息是函数所必要部分,这里不做深究,直接给出相应代码:

ZEND_BEGIN_ARG_INFO(arginfo_say_hello_func, 0) ZEND_END_ARG_INFO()

如需了解具体信息请阅读相关宏定义。

使用宏PHP_FE将函数加入到say_hello_functions中

最后,我们需要将刚才定义的函数和参数信息加入到say_hello_functions数组里,代码如下:

<br/>
  1. const zend_function_entry say_hello_functions[] = {
    PHP_FE(say_hello_func, arginfo_say_hello_func)
        {NULL, NULL, NULL}
    };

这一步就是通过PHP_EF宏实现,注意这个数组最后一行必须是{NULL, NULL, NULL} ,请不要删除。

下面是编写完成后的say_hello.c全部代码:

<br/>
  1. /*
    +----------------------------------------------------------------------+
    | PHP Version 5                                                        |
    +----------------------------------------------------------------------+
    | Copyright (c) 1997-2010 The PHP Group                                |
    +----------------------------------------------------------------------+
    | This source file is subject to version 3.01 of the PHP license,      |
    | that is bundled with this package in the file LICENSE, and is        |
    | available through the world-wide-web at the following url:           |
    | http://www.php.net/license/3_01.txt                                  |
    | If you did not receive a copy of the PHP license and are unable to   |
    | obtain it through the world-wide-web, please send a note to          |
    | license@php.net so we can mail you a copy immediately.               |
    +----------------------------------------------------------------------+
    | Author: ZhangYang                          |
    +----------------------------------------------------------------------+
    */
    /* $Id: header 297205 2010-03-30 21:09:07Z johannes $ */
    #ifdef HAVE_CONFIG_H
    #include "config.h"
    #endif
    #include "php.h"
    #include "php_ini.h"
    #include "ext/standard/info.h"
    #include "php_say_hello.h"
    /* If you declare any globals in php_say_hello.h uncomment this:
    ZEND_DECLARE_MODULE_GLOBALS(say_hello)
    */
    /* True global resources - no need for thread safety here */
    static int le_say_hello;
    /* {{{ PHP_FUNCTION
    */
    PHP_FUNCTION(say_hello_func)
    {
        char *name;
        int name_len;
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE)
        {
            return;
        }
        php_printf("Hello %s!", name);
        RETURN_TRUE;
    }
    ZEND_BEGIN_ARG_INFO(arginfo_say_hello_func, 0)
    ZEND_END_ARG_INFO()
    /* }}} */
    /* {{{ say_hello_functions[]
    *
    * Every user visible function must have an entry in say_hello_functions[].
    */
    const zend_function_entry say_hello_functions[] = {
        PHP_FE(say_hello_func, arginfo_say_hello_func)
        {NULL, NULL, NULL} /* Must be the last line in say_hello_functions[] */
    };
    /* }}} */
    /* {{{ say_hello_module_entry
    */
    zend_module_entry say_hello_module_entry = {
        #if ZEND_MODULE_API_NO >= 20010901
        STANDARD_MODULE_HEADER,
        #endif
        "say_hello",
        say_hello_functions,
        NULL,
        NULL,
        NULL,
        NULL,
        PHP_MINFO(say_hello),
        #if ZEND_MODULE_API_NO >= 20010901
        "0.1", /* Replace with version number for your extension */
        #endif
        STANDARD_MODULE_PROPERTIES
    };
    /* }}} */
    #ifdef COMPILE_DL_SAY_HELLO
    ZEND_GET_MODULE(say_hello)
    #endif
    /* {{{ PHP_MINFO_FUNCTION
    */
    PHP_MINFO_FUNCTION(say_hello)
    {
        php_info_print_table_start();
        php_info_print_table_header(2, "say_hello support", "enabled");
        php_info_print_table_row(2, "author", "Zhang Yang"); /* Replace with your name */
        php_info_print_table_end();
        /* Remove comments if you have entries in php.ini
        DISPLAY_INI_ENTRIES();
        */
    }
    /* }}} */

编译并安装扩展

在say_hello目录下输入下面命令:

<br/>
  1. /usr/bin/phpize
    ./configure
    make
    make install

这样就完成了say_hello扩展的安装(如果没有报错的话)。

这时如果你去放置php扩展的目录下,会发现多了一个say_hello.so的文件。如下图所示:

下面就是将其加入到php.ini配置中,然后重启Apache(如果需要的话)。这些都是PHP基本配置的内容,我就不详述了。

扩展测试

如果上面顺利完成,这时运行phpinfo(),应该能看到如下信息:

这说明扩展已经安装成功了。然后我们编写一个测试用PHP脚本:

<?php say_hello_func(&#39;Zhang Yang&#39;); ?>;

执行这个脚本,结果如下:

說明擴充功能已經正常運作了。

總結

這篇文章主要用範例方法介紹PHP Extension的開發基礎。在PHP的使用中,也許是因為需要支援新的元件(如新的資料庫),又或是業務需要或效能需要,幾乎都會遇到需要開發PHP擴充的地方。後續如果有機會,我會寫文章介紹一些關於擴展開發較為深入的東西,如擴展模組生命週期、INI使用以及編寫面向對象的擴展模組等等。

相關推薦:

PHP擴充開發教學總結

#

以上是php擴展開發的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn