首页 > PHP扩展开发

PHP扩展开发

2014-03-13   hisenKing

在自己工作中,身边的同事很少会写PHP扩展。也许是PHPER只要熟悉基本的PHP语法,差不多就可以应对工作中的任务需求。这样导致很少人会主动的深入学习PHP内部的知识。这也是个人认为PHP基本上都在写业务逻辑一层,挺尴尬的一种情况不前不后
编写PHP扩展之前我们得了解php的整个生命周期,如下图

1.Call each extension'MINIT
这个过程扩展被载入时调用。

PHP_MINIT_FUNCTION(hisen)
{
        // 注册常量或者类等初始化操作
        return SUCCESS;
}

2.Request test.php
请求test.php。当请求到达后,PHP会初始化执行脚本的基本环境,例如创建一个执行环境,包括保存PHP运行过程中的变量名称和变量值内容的符号表,以及当前所有的函数以及类等信息的符号表。然后PHP会调用所有模块RINIT函数,在这个阶段各个模块也可以执行一些相关操作

PHP_RINIT_FUNCTION(hisen)
{
        // 例如记录请求开始时间,与之后记录请求结束时间计算整个请求的耗时
        return SUCCESS;
}

3.Execute test.php
执行test.php阶段,主要是把PHP文件编译成Opcode,然后再PHP虚拟机下执行

4.Call each extension's RSHUTDOWN
顺利的走到了自己文件的最后, 也可能是出师未捷,半道被用户给die或者exit了, 这时候PHP便会启动回收程序,收拾这个请求留下的烂摊子。 它这次会执行所有已加载扩展的RSHUTDOWN(俗称Request Shutdown)方法, 这时候扩展可以抓紧利用内核中的变量表之类的做一些事情, 因为一旦PHP把所有扩展的RSHUTDOWN方法执行完, 便会释放掉这次请求使用过的所有东西, 包括变量表的所有变量、所有在这次请求中申请的内存等等。

PHP_RSHUTDOWN_FUNCTION(hisen)
{
        // 记录请求的结束时间,计算耗时
        return SUCCESS;
}

5.Call each extension's MSHUTDOWN
前面该启动的也启动了,该结束的也结束了,现在该Apache老人家歇歇的时候,当Apache通知PHP自己要Stop的时候,PHP便进入MSHUTDOWN(俗称Module Shutdown)阶段。这时候PHP便会给所有扩展下最后通牒,如果哪个扩展还有未了的心愿,就放在自己MSHUTDOWN方法里,这可是最后的机会了,一旦PHP把扩展的MSHUTDOWN执行完,便会进入自毁程序,这里一定要把自己擅自申请的内存给释放掉,否则就杯具了。

要注意的是,只有在服务器没有请求的情况下才会执行“Call each extension'MINIT”和“Call each extension's MSHUTDOWN”。

以下是整个开发过程

安装phpize

sudo apt-get install php5-dev

查看是否安装成功

phpize - version

输出

Configuring for:
PHP Api Version:         20090626
Zend Module Api No:      20090626
Zend Extension Api No:   220090626

下载php源码包,并解压之后,进入以下目录

cd /php-5.5.9/ext/
ext_skel --extname=hisen

修改config.m4
查看以下几行

dnl If your extension references something external, use with:

dnl PHP_ARG_WITH(hisen, for hisen support,
dnl Make sure that the comment is aligned:
dnl [  --with-hisen             Include hisen support])

dnl Otherwise use enable:

dnl PHP_ARG_ENABLE(hisen, whether to enable hisen support,
dnl Make sure that the comment is aligned:
dnl [  --enable-hisen           Enable hisen support])

dnl是行注释。看if的说明 如果你的扩展引用了其他一些组件则...否则...
所以这里我们就简单的创建扩展,将Otherwise下面的三行dnl去掉

dnl Otherwise use enable:

PHP_ARG_ENABLE(hisen, whether to enable hisen support,
Make sure that the comment is aligned:
[  --enable-hisen           Enable hisen support])

config.m4的说明
http://www.php.net/manual/zh/internals2.buildsys.configunix.php

编写核心代码

sudo vim hisen.c

查找zend_function_entry hisen_functions在其前面加上一行

PHP_FUNCTION(say_hello_func);

Zend引擎通过zend_function_entry结构数组把声明的导出函数引入内部,方法如下

zend_function_entry hisen_functions[] = {
        PHP_FE(say_hello_func,        NULL)           // For testing, remove later
        PHP_FE_END      // Must be the last line in hisen_functions[]
};

Zend引擎在载入扩展时,自动把则个数组中的所有函数引入到函数表中,这样PHP脚本就可以调用这个函数了。
最后的PHP_FE_END(php5.3是{NULL, NULL, NULL})是必须的,Zend引擎就是靠这个判断导出函数列表是否完毕。

使用PHP_FUNCTON定义函数体

PHP_FUNCTION(say_hello_func)
{
        char *arg = NULL;
        int arg_len, len;
        char *strg;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sb", &arg, &arg_len, &b) == FAILURE) {
                return;
        }

        len = spprintf(&strg, 0, "hello %.78s", arg);
        RETURN_STRINGL(strg, len, 0);
}

因为PHP是支持可变参数的,所以参数的个数在没有被调用之前是不确定的,可以使用ZEND_NUM_ARGS()宏实现。

zend_parse_parameters() 函数负责读取用户从参数堆栈传递来参数,并将其适当地转换后放入局部 C 语言变量。如果用户传递的参数个数有误或类型不可被转换, 函数会发出一个冗长的错误信息,并返回 FAILURE。 当从函数简单返回的情况下——不修改 return_value,将默认的返回值 NULL 返回给用户。
注意,FAILURE 表示为 -1,SUCCESS 表示为 0。为了使代码清晰,应总是使用已命名的常量而不是其值。

传递给 zend_parse_parameters() 的第一个参数是用户实际传递到函数的参数数量。此数值做为 ht 参数传给函数,但就像上面讨论的那样,应使用做为实现细节的 ZEND_NUM_ARGS()。

为了与 PHP 的线程隔离、线程安全资源管理器兼容,还要用 TSRMLS_CC 传递线程上下文,没有启动线程安全时,这个宏辉被忽略。

如上面的代码"sb"指定传递一个字符串参数和一个布尔值的参数,其中如果是字符串时,除了接受字符串地址之外,还要接收字符串的长度。如上例的&arg, &arg_len

zend_parse_parameters() 类型说明符

修饰符   附加参数的类型   描述
b   zend_bool   Boolean 值
l   long        integer (long) 值
d   double      float (double) 值
s   char*, int  二进制的安全串
h   HashTable*  数组的哈希表

创建全局变量

PHP_FUNCTION(showyear)
{
        zval *new_var;
        MAKE_STD_ZVAL(new_var);
        ZVAL_LONG(new_var, 10);
        ZEND_SET_GLOBAL_VAR("new_var", new_var);
}

测试文件test.php

showyear();
echo $new_var;

编写phpinfo()回调函数

PHP_MINFO_FUNCTION(hisen)
{
        php_info_print_table_start();
        php_info_print_table_header(2, "hisen support", "enabled");
        php_info_print_table_row(2, "author", "hisen");
        php_info_print_table_end();
}

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

phpize
./configure --with-php-config=/usr/local/php/bin/php-config
sudo make
sudo make install
sudo vim /etc/php.d/php.ini // 添加一行extension=hisen.so
sudo /etc/init.d/httpd -k restart

参考资料:
《PHP核心技术与最佳实践》
《PHP扩展开发及内核应用》
http://blog.codinglabs.org/articles/php-extension-dev-guide.html

好久没更新了,需要除除草