查看文章 |
用C++扩展PHP
2008-05-11 2:21 A.M.
用C++扩展PHP - (1)
这个文档简单介绍了怎么使用C++为PHP编写一个扩展模块。我已经被问及这个问题无数次了,所以我决定为后来者写一个简单的HOWTO。这篇文章只会说明一些要点和关键之处,如果你希望学习C++或PHP,请查阅其它的文档。
顺便,在本文中我们也会看一下在PHP5中怎么把C++类影射到PHP中。文章中的例子可以在PHP4和PHP5的环境下使用,但是有一个小小的问题:扩展的PHP类只能在PHP5中使用。:P
基本上,我们会介绍怎么样为C++类创建一个可以在PHP4和PHP5下使用的结构化接口,及可以在PHP5下使用的对象化接口。
这个HOWTO是参考了一些实现得很好的PHP5扩展模块 后写成的。如:Sqlite和SimpleXML扩展就是教导大家怎样在PHP5中扩展类的很好例子。但我的那些作品除外,只有cryptopp-php 使用了很多特性。(还没有发布的PHP5将可以支持cryptopp-php 0.0.14。)
注意:PHP5目前仍在预发布状态,它可能还会做一些改动,尽管文档中的代码我已经在最新CVS版本的PHP 5环境下测试过,但在你读到这篇文章的时候可能还要做一些改动才能正常工作。如果文档中的代码在最新PHP 5中不能使用,请知会我,我会做相应的修正。
第1节. 开始之前
开始前,我要说明:这篇文章所描述的主要是在UNIX的PHP环境上的。当然,我会提及一些在Windows上的开发。但是我大部分的编码都是在UNIX系统上的,所以我会更多的介绍一下我所了解的那一部分。
另外一点我要说明的是:文中所介绍的方法在PHP 4.3.x和PHP 5下都是可行的。尽管我们在开始的时候会基于PHP5来介绍,但是你会发现这些方法在PHP 4.3.x中也是可行的。
我在本文中有一些约定...
$PHP_HOME 是指你的PHP源代码的位置,如:你解开的PHP源代码包所放的位置。在我的系统中指的是:/home/jay/setup/php/php-x.x.x.
我们用来做例子的模块叫做php5cpp.
第2节.安装
在你用C++编写PHP扩展前,你先要搭建一个基本的扩展模块的架构。在UNIX下,你可以运行一个在 $PHP_HOME/ext 下叫做ext_skel 的shell脚本。先切换到 $PHP_HOME/ext 目录和执行那个shell脚本,并用 --extname 参数为你的扩展模块命名。
jay@monty ~ $ cd setup/php/php-5.x.x/ext
jay@month ext $ ./ext_skel --extname php5cpp
在Windows系统,目前PHP CVS代码中,可以使用位于 $PHP_HOME/ext 的 ext_skel_win32.php 的PHP脚本是。也许它会成为PHP5的一部分及被PHP 4.x的分支包含。但这只是我大胆的猜想,我并不知道会不会实现...
这样,在$PHP_HOME/ext/php5cpp下,我们已经有了一个基本的PHP扩展模块架构。唯一的问题是,它是为C搭建的,而不是为C++。
第3节.修改config.m4
现在我们要修改那个扩展模块的config.m4 文件以支持C++。
你不需要做太多的改动,要做的只是告诉编译PHP的系统,你的模块是使用C++的,而且需要连接C++标准库。下边是一个删去自动生成的注释后,php5cpp 扩展模块的config.m4文件的例子:
PHP_ARG_ENABLE(php5cpp, for php5cpp support,
[ --enable-php5cpp Enable php5cpp support])
if test "$PHP_php5cpp" != "no" ; then
PHP_REQUIRE_CXX()
PHP_NEW_EXTENSION(php5cpp, php5cpp.cpp, $ext_shared)
fi
注意其中的PHP_REQUIRE_CXX(),和php5cpp.c 已经变成了 php5cpp.cpp 了。
第4节.编写代码
修改完config.m4 后,你可以编写代码了。记住把php5cpp.c 修改成C++文件的名字。根据前边 config.m4的修改,在这里我们把它改成 php5cpp.cpp.
现在你可以开始编写你的代码了。但是你如果现在编译这个扩展代码的话,可能会生一个so,并且不会产生任何编译错误,但是并不能在PHP中使用。如果你把它静态编译进PHP,则会产生连接错误。这是因为C和C++的变量空间的不一致引起的 (PHP是使用C来编写,你的扩展使用C++来编写) 。
修改的方法就是,你要告诉你的扩展模块,将把一些PHP API函数当成C函数来对待(他们是用C来写的),而不是当成C++。
你需要把一些代码用 BEGIN/END_EXTERN_C()包起来。你的php5cpp.cpp 可能要写成像下边的样子:
extern "C" {
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
}
.
.
.
#ifdef COMPILE_DL_PHP5CPP
BEGIN_EXTERN_C()
ZEND_GET_MODULE(php5cpp)
END_EXTERN_C()
#endif
一般地,我们是用 BEGIN/END_EXTERN_C() 来包起头文件的那些内容,如对 ZEND_GET_MODULE(php5cpp)那样。但是在引用声明 BEGIN/END_EXTERN_C() 的 zend.h文件前,可以通过使用extern "C" 来达到相同的作用。
在Windows系统,可以使用Visual C++编译你的扩展模块。这也需要在的扩展模块的头部加上类似的声明:
#ifdef PHP_WIN32
#include
#include
#endif
这样你可以使得你的代码保持跨平台的特性。
第5节.编译扩展模块
现在可以去编译扩展模块了。如果你想把它编译成静态模块(把它做为PHP的一个部分编译进PHP中去),去到PHP的根目录$PHP_HOME,删去 configure 文件和运行buildconf (译:需要libtool的支持)。
然后用你平常用的参数运行 configure 并加上--enable-php5cpp 项。运行 make clean, make, make installApache。 ,并完成其它的一些必要操作,如:重新编译
如果你想用动态链接库的方式编译扩展模块,到你的模块的目录下,运行phpize 命令(假设你已经安装了PEAR),它会为你的模块创建一个 configure 脚本。然后运行configure, make 和 make install。如果你想让你的模块自动加载,你要修改php.ini 以加载正确的文件。如:加上类似的一行:extension=php5cpp.so 。
现在你的PHP扩展模块已经编译好了。试着运行一下在模块目录下自动生成的php5cpp.php ,看看是不是一切正常?:)
第6节.将C++类影射到PHP中
目录
类MyClass
宏、函数及其它
封装代码
PHP 5的类支持很多新的特性。如:权限(protected, public, private),异常,interfaces,等等。在这个简单的介绍中,我们只做最基本的事情:使PHP可以影射到C++的类。这样你可以用PHP中 使用你的类,之后的事情将会变得很简单的。在看下面的介绍之前,你可以参考一下Sqlite, SimpleXML 及 cryptopp-php 模块的代码。
这里介绍一下。我们要用一个C++类做为例子,就叫做 MyClass吧。在PHP术语中,把它叫做一个resource(资源)。PHP常使用这类的东西,如数据库的连接就是resource,它也可能是一 个指向实际resource的struct(结构)。在C++中,class 实际上是struct的一个近义词(struct默认为public,classe默认为private --仅这个区别而已)。
在结构化的接口中,我们会用类似以下的PHP代码来使用resources:
$m = myclass_new();
myclass_set_string($m, 'hello world!');
var_dump(myclass_get_string($m));
?>
在面向对象式的接口中,一样可以使用PHP resources,不过已经被封装在一个PHP对象中了,如:
$m = new MyClass;
$m->setString('hello world!');
var_dump($m->getString());
?>
我们不需要关心被封装的实际的代码做了些什么。当我们有一个叫MyClass的C++类。我们可以把这个C++类当成resources并把C++类里的方法封装成PHP的函数。然后我们也可以把它封装成一个PHP的对象,使得可以像一般的C++那样使用。
在看本文时,记住我们的目的就是:把C++类封装成PHP可以使用的结构化的函数或对象化的类。也许在一开始有些东西会令你迷惑,但读下去后就会慢慢明白的了。中间会有很多的宏定义,但当你看明白后,会觉得所有东西都很清淅很容易了。
1 类MyClass
首先我们需要一个类。下边是一个只有一个私有属性和几个公有方法的简单的类。
以下是这个类的声明头文件 myclass.h:
#ifndef __MYCLASS_H__
#define __MYCLASS_H__
#include
using namespace std;
class MyClass {
private:
string itsString;
public:
MyClass(string s = "default");
~MyClass();
string getString() const;
bool setString(const string s);
};
#endif
下边是定义代码myclass.cpp:
#include "myclass.h"
MyClass::MyClass(string s)
{
itsString = s;
}
MyClass::~MyClass()
{
}
string MyClass::getString() const
{
return itsString;
}
bool MyClass::setString(const string s)
{
itsString = s;
return true;
}
这只是一个做为例子的类。
然后我们要让构建系统知道和可以编译这些文件。把config.m4文件做以下修改:
PHP_NEW_EXTENSION(php5cpp, php5cpp.cpp, $ext_shared)
becomes...
PHP_NEW_EXTENSION(php5cpp, php5cpp.cpp myclass.cpp, $ext_shared)
在php5cpp.cpp 文件中增加#include “myclass.h” :
extern "C" {
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
}
#include "php_php5cpp.h"
#include "myclass.h"
不要把include php_php5cpp.h 和 myclass.h 的语句放在 extern "C" 中,否则会出现错误。
2 宏、函数及其它
为了让这个模块可以同时在PHP 4和PHP 5使用,我们需要使用一些宏去声明是依赖于PHP 4或是PHP 5的。因为PHP 4和PHP 5的执行文件是不兼容的,所以你需要为这两个PHP的版本分别编译不同的版本。
通常我会把这些宏的声明放在一个单独的文件中。在这个例子里,就放在objects.h 中吧。
objects.h 中要写一些PHP 5需要的函数,如:
#ifndef __PHP5CPP_OBJECTS_H__
#define __PHP5CPP_OBJECTS_H__
#if PHP_MAJOR_VERSION == 5
zend_class_entry *php5cpp_ce_myclass;
static zend_object_handlers php5cpp_object_handlers_myclass;
function_entry php5cpp_myclass_methods[] = {
ZEND_ME_MAPPING(MyClass, myclass_new, NULL)
ZEND_ME_MAPPING(setString, myclass_set_string, NULL)
ZEND_ME_MAPPING(getString, myclass_get_string, NULL)
{NULL, NULL, NULL}
};
typedef enum {
is_myclass
} php5cpp_obj_type;
struct php5cpp_obj {
zend_object std;
int rsrc_id;
php5cpp_obj_type type;
union {
MyClass *myclass;
} u;
};
你可以发现,为了保持一致,每个声明都加上了php5cpp_ 前缀。这是习惯上的一种约定。
另外,#if PHP_MAJOR_VERSION == 5表明在下边的那些宏、函数等都只是在PHP 5下才生效,当我们在PHP 4下编译时它们会被预处理忽略掉。
php5cpp_ce_myclass 是类 MyClass 的入口。 php5cpp_object_handlers_myclass 是类的内部处理handler(句柄)。 |
最近读者:

