做为从事php开发的人来讲, 有些时候要自己写一些扩展来方便自己的应用。 网络上有很多的php开发的例程, 有的讲的还不错, 有的很简单,我把自己学习的过程记录下来。 开发php extension的过程基本可以分为如下几步: 1. 生成扩展框架 2. dsp配置 3. 编写核
做为从事php开发的人来讲, 有些时候要自己写一些扩展来方便自己的应用。
网络上有很多的php开发的例程, 有的讲的还不错, 有的很简单,我把自己学习的过程记录下来。
开发php extension的过程基本可以分为如下几步:
1. 生成扩展框架
2. dsp配置
3. 编写核心代码
4. 配置、编译
5. 配置php.ini
生成扩展框架下载php源代码,我使用的是php 5.2.5。进入php源代码目录可以看到有个ext目录,这里是和php extension有关可以看到很多已经存在的php extension,如pdo_mysql,json等
linux和windows会使用不同的方法来生成skeleton.
本文主要讲述在windows下的开发, 有两种方法
1. 安装cygwin,然后使用上图中的windows那个文件 php ext_skel_win32.php -extname=, 这个网上有很多 的例子, 本人不想装cygwin所以采用的下面的方法。
2. 直接修改skeleton文件的内容 。
首先将ext/skeleton拷贝一份, 将名字改为你想用的extname, 本文用hello world为例。
=>>>>
dsp 配置
再将php/dev目录下的php5t.lib拷贝到当前的目录下。
将上面4个文件中所有的extname 变成 hello_world, 将extname 变成hello_world, 注意大小写
这样就可以进行简单的编译生成 dsw工程文件。
分析php extension核心代码
为了下面更好的介绍,这里先简单的介绍php extension核心代码,打开hello_world.c文件
/* {{{ hello_world_module_entry */zend_module_entry hello_world_module_entry = {#if zend_module_api_no >= 20010901 standard_module_header,#endif hello_world, hello_world_functions, php_minit(hello_world), php_mshutdown(hello_world), php_rinit(hello_world), /* replace with null if there's nothing to do at request start */ php_rshutdown(hello_world), /* replace with null if there's nothing to do at request end */ php_minfo(hello_world),#if zend_module_api_no >= 20010901 0.1, /* replace with version number for your extension */#endif standard_module_properties};
这里初始化了一个c语言中的结构体,每个php extension其实就是一个zend_module_entry结构体,在该结构体中定义了每个扩展所需的字段,大家可以通过查看zend_module_entry源代码看到。就本例而言,我们简单的介绍一下上面的代码:
1. standard_module_header:c语言的宏,用来初始化zend_module_entry的前几个字段,包括结构体大小等
2. hello_world:指定了扩展的名字,对应结构体中的name字段
3. hello_world_functions:一个zend_function_entry类型的数组,指向扩展的函数表,所有需要暴露给用户的函数都需要在该函数表中注册
4. php_minit(hello_world):模块初始化回调函数,在扩展被加载时调用,minit = module initialization
5. php_mshutdown(hello_world):模块卸载回调函数,在扩展杯卸载时调用,mshutdown = module shutdown
6. php_rinit(hello_world):请求初始化回调函数,每个请求开始时调用,rinit = request initialization
7. php_rshutdown(hello_world):请求结束回调函数,每个请求结束时调用,rshutdown = request shutdown
8. php_minfo(hello_world):扩展信息函数,在phpinfo()函数中会调用,用于显示模块的自定义信息。
9. 0.1:指定了扩展的版本号,对应结构体中的version字段
10. standard_module_properties:c语言的宏,用来初始化zend_module_entry的后几个字段
大家可以看到,在4-8我们指定了4个回调函数,这四个函数可以说我们提供了一种注入机制,让我们能够在这几个关键点进行资源的初始化或者资源回收。另外,需要说明的一点是,所有的回调函数我们都是通过zend提供的宏定义的,主要是为了防止在php运行时的命名冲突问题,事实上不仅仅是函数,包括函数返回值、全局变量等我们都会使用这种方式。
编写phpinfo()回调函数
打开hello_world.c文件,在php_minfo_function里面编写如下代码:
php_minit_function(hello_world){ /* if you have ini entries, uncomment these lines register_ini_entries(); */// php_info_print_table_start();// php_info_print_table_header(2,hello_world support,enabled);// php_info_print_table_row(2,author,johnny);// php_info_print_table_row(2,version,1.0);// php_info_print_table_end(); return success;}
这里主要是phpinfo()函数调用时显示自定义信息,用到了四个函数:
1. php_info_print_table_start():定义phpinfo表格开始
2. php_info_print_table_header():定义phpinfo表格头,第一个参数指定列数,后面指定与第一个参数数量相等的自定义文字信息
3. php_info_print_table_row():定义phpinfo表格内容,第一个参数指定列数,后面指定与第一个参数数量相等的自定义文字信息
4. php_info_print_table_end():定义phpinfo表格结尾
在本例中我们定义了表格头,指定扩展是否可用;另外定义了两行内容,指定扩展的作者和版本。
编写核心代码接下来是时候编写我们的扩展核心代码了,打开php_hello_world.h文件,添加一行声明:
php_function(say_hello);
注意这里不是用“原生”的编写c语言函数的方式,而是通过php_function宏定义(具体原因前面讲过),say_hello是我们开发的扩展模块要暴露给用户的函数名称。
打开hello_world.c,在这里实现say_hello函数:
php_function(say_hello){ char *arg = null; int arg_len, len; char *strg; if (zend_parse_parameters(zend_num_args() tsrmls_cc, s, &arg, &arg_len) == failure) { return; } len = spprintf(&strg, 0, hello %s/n, arg); return_stringl(strg, len, 0); }
里面的具体实现很简单,接收到参数之后,返回“hello 参数”字符串,需要解释的是:
1. 参数接收:这里接收函数的参数需要通过zend_parse_parameter函数解析,第一个参数指定用户传入say_hello函数的参数个数,可以通过宏zend_num_args()生成,tsrmls_cc用来确保线程安全;第二个参数是一个字符串,每个字母代表一种类型,其中”s”代表char*或者int类型,“b”代表布尔类型,“l”代表long类型,完整的类型映射可以看这里;后面几个参数是我们定义的局部变量,用来接收传入的参数值
2. 函数返回值:不能使用c语言原生的return语句,而应该使用zend api里提供的宏定义,如return_stringl返回一个字符串;而return_true返回布尔类型true。
声明扩展函数参数信息,我们的函数原型为say_hello(name),声明参数方式:
zend_begin_arg_info(arg_say_hello, 0)zend_arg_info(0, name)zend_end_arg_info()
这里都是zend api提供的宏定义,在后面我会专门介绍扩展函数参数声明。实现完say_hello函数之后,我们再注册该函数到函数表 hello_world_functions(前面介绍过),第一个参数为函数名,第二个参数为函数参数数组信息,如下代码所示:
zend_function_entry hello_world_functions[] = { //php_fe(confirm_hello_world_compiled, null) /* for testing, remove later. */ php_fe(say_hello,arg_say_hello) /* __function_entries_here__ */ {null, null, null} /* must be the last line in hello_world_functions[] */};
注意最后一行{null, null, null}是必须的,只有注册到函数表中的函数才能暴露给用户使用。
配置、编译、安装
直接用vc6生成php_hello_world.dll, 将其拷贝到php/ext目录下, 配置php.ini如下
运行 上面的步骤完成后, 运行 php -i| findstr hello_world
编写一个简单的测试脚本,如下所示:
会得到 如下
总结本文通过一个简单的示例,介绍了如何使用zend api和c语言在windows下开发一个php extension。