php 5.x 扩展开发要点

来源:互联网 发布:淘宝朋友代付退款 编辑:程序博客网 时间:2024/05/17 03:29
最近因项目需要开发了一个windows dll形式的php扩展,实现访问soap webservice。现在简单记录一下要点,以备查看。
开发环境是visual studio 2010(VC10)。测试用了 xampp-win32-5.6.28-1-VC11-installer。

为什么不直接用php soap扩展
php确实有一个php_soap的官方扩展,对soap webservice操作进行了封装。在本案例中,此前博主已经用gSOAP对某个web service功能进行了封装,发布了两个DLL访问ws接口,实现数据查询和加载。php扩展是在gSOAP开发的DLL基础上,做进一步的封装。
除此之外,还可以借助gSOAP封装的DLL,开发其他语言的扩展,例如python module。良好的功能划分可以提高复用性,减少工作重复。

安装XAMPP
安装的时候,至少选择安装apache和php。

基本代码结构
php以extension的形式提供扩展。位于扩展功能底层核心的是zend引擎。

windows需要包含头文件
#include "zend_config.w32.h"
/* include standard header */
#include "php.h"

PHP扩展的开发,主要通过一组宏定义,完成扩展的框架构建。例如
PHP_MINIT_FUNCTION(CustomExt); // module加载。通常是apache启动的时候
PHP_MSHUTDOWN_FUNCTION(CustomExt); //module卸载。通常是apache关闭的时候。
PHP_RINIT_FUNCTION(CustomExt); //一般对应一个php脚本启动的时候。
PHP_RSHUTDOWN_FUNCTION(CustomExt); // 一般对应一个php脚本退出的时候。

而PHP扩展中的函数,通过PHP_FUNCTION定义。例如
PHP_FUNCTION(paradb_wsquery_new);
PHP_FUNCTION(paradb_wsquery_prepare);
PHP_FUNCTION(paradb_wsquery_query);
PHP_FUNCTION(paradb_anytype_print);
PHP_FUNCTION(paradb_wsload_new);
PHP_FUNCTION(paradb_wsload_prepare);
PHP_FUNCTION(paradb_wsload_load);

核心数据结构
zend_module_entry CustomExtModule_module_entry = {
STANDARD_MODULE_HEADER,
"CustomExt Module",
CustomExtModule_functions,
PHP_MINIT(CustomExt),
PHP_MSHUTDOWN(CustomExt),
PHP_RINIT(CustomExt),
PHP_RSHUTDOWN(CustomExt),
NULL,
NO_VERSION_YET, STANDARD_MODULE_PROPERTIES
};
这个结构引用了扩展需要的所有东西。PHP核心引擎通过这个结构找到扩展,调用相关的函数。

内存管理
基本原则是在哪个层次申请的,就在哪个层次释放。
在PHP层面,不要用malloc()函数,用php提供的emalloc()或者pemalloc()。这种方法申请的内存在php扩展代码中,不必显式释放。php框架对这些内存进行了统一的管理。PHP核心可以确保托管内存不会发生内存泄露而危及平台的运行稳定。
php扩展可以调用第三方DLL中定义的函数,返回一个新的类实例。那么这个类实例被创建和被析构的地方,都应该位于第三方DLL,例如不要在php层面用emalloc()为第三方DLL的对象申请内存。
如果使用EG(persistent_list)导致空指针访问违例,博主建议在MINIT函数中自定义一个hashtable。

资源如何定义和返回
为了封装c++结构,在PHP中使用自己定义的c++类,需要在php扩展中,定义资源resource。每一种资源类型对应着一个唯一的int。例如
int le_paradb_wsquery, le_paradb_xsdany;
#define PHP_PARADB_WSQUERY_RES_NAME "paradb wsquery"
#define PHP_GSOAP_XSD_ANYTYPE_RES_NAME "gsoap xsdany"
#define PHP_PARADB_WSLOAD_RES_NAME "paradb wsload"

在MINIT函数中,对资源进行定义。主要是定义了对应的析构函数。例如
le_paradb_wsquery = zend_register_list_destructors_ex(php_paradb_wsquery_dtor, NULL, PHP_PARADB_WSQUERY_RES_NAME, module_number);

PHP_FUNCTION(paradb_wsquery_new)
{
WsQuery *q;
char *name;
int name_len;
zval* p;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
RETURN_FALSE;
}
if (name_len < 1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "No endpoint given, WsQuery resource not created.");
RETURN_FALSE;
}
paradbc_wsquery_init2(&q, name);
php_printf("[paradb_wsquery_new %p]<br/>", q);
ZEND_REGISTER_RESOURCE(return_value, q, le_paradb_wsquery);
}

通过ZEND_REGISTER_RESOURCE宏,一个结构被返回到PHP脚本。实际是一个指针,其内容是完全透明的。

返回的这个资源,PHP脚本在后续可以使用。
PHP_FUNCTION(paradb_wsquery_query2)
{
WsQuery *q;
zval *zq;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zq) == FAILURE) {
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(q, WsQuery*, &zq, -1, PHP_PARADB_WSQUERY_RES_NAME, le_paradb_wsquery);
......
}
通过ZEND_FETCH_RESOURCE宏,PHP扩展代码得到这个resource,从zval得到实际的结构实例。

其他事项
解决http 80端口被占用的问题
windows system后台进程可能占用了本地80端口,到XAMPP控制面板,config,修改httpd.conf,改成 Listen 8080,监听8080端口。

PHP源码下载
下载对应当前版本的PHP源码。例如
php-5.6.28.tar.gz
http://php.net/distributions/php-5.6.28.tar.gz
解压为 C:\xampp\php-5.6.28

下载依赖包
需要下载bison。可将相关文件拷贝到windows系统目录。
http://gnuwin32.sourceforge.net/packages/bison.htm

生成需要的头文件
利用VC命令行环境,在PHP源码目录做一次configure操作。
Setting environment for using Microsoft Visual Studio 2010 x86 tools.
C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC>cd /d C:\xampp\php-5.6.28
C:\xampp\php-5.6.28>buildconf.bat
C:\xampp\php-5.6.28>set PATH=C:\Program Files\bison\bin;%PATH%
C:\xampp\php-5.6.28>configure.bat
nmake可以不做。这里主要是为了生成构建PHP扩展所需的头文件。
注意:建议使用与xampp完全相同的VC版本,否则需要修改config.w32.h中的PHP_COMPILER_ID与xampp的完全一致。例如apache error.log报告错误
PHP Warning: PHP Startup: CustomExt Module: Unable to initialize module
Module compiled with build ID=API20131226,TS,VC10
PHP compiled with build ID=API20131226,TS,VC11
These options need to match
解决办法是手动修改 main/config.w32.h
#define PHP_COMPILER_ID "VC11"

-END-