php的扩展和嵌入–c++类的扩展开发

今天花了几乎一天的时间研究php的相关c++扩展,第一次接触的时候很多地方不太熟悉,也碰到了不少坑,这里把整个过程叙述如下,参考的文章主要是http://devzone.zend.com/1435/wrapping-c-classes-in-a-php-extension/:

现在定义了一个car类,它有一些成员函数,整个扩展包括的文件如下:

config.m4 扩展的配置文件php_vehicles.h 扩展的头文件vehicles.cc 扩展的源文件car.h 类的头文件car.cc 类的源文件
接下来就按照文件的顺序对这个扩展的每个部分分别进行叙述:
配置文件:config.m4
1 php_arg_enable(vehicles,
2 [whether to enable the “vehicles” extension],
3 [ –enable-vehicles enable “vehicles” extension support])
4
5 if test $php_vehicles != “no”; then
6 php_require_cxx()
7 php_subst(vehicles_shared_libadd)
8 php_add_library(stdc++, 1, vehicles_shared_libadd)
9 php_new_extension(vehicles, vehicles.cc car.cc, $ext_shared)
10 fi
第六行是要求使用c++的编译器
第七行表示扩展会以动态连接库的形式出现
第八行表是增加了c++的库,也就是类似与string和std这种都可以用了.
第九行里面注意要把所有的源文件都包括进来.
类的头文件car.h
#ifndef vehicles_car_h
2 #define vehicles_car_h
3
4 // a very simple car class
5 class car {
6 public:
7 car(int maxgear);
8 void shift(int gear);
9 void accelerate();
10 void brake();
11 int getcurrentspeed();
12 int getcurrentgear();
13 private:
14 int maxgear;
15 int currentgear;
16 int speed;
17 };
18
19 #endif /* vehicles_car_h */
这个跟c++的头文件声明是完全一样的.
类的源文件car.cc
源文件也是,属于c++的类定义
2 #include “car.h”
3 car::car(int maxgear) {
4 this->maxgear = maxgear;
5 this->currentgear = 1;
6 this->speed = 0;
7 }
9 void car::shift(int gear) {
10 if (gear < 1 || gear > maxgear) {
11 return;
12 }
13 currentgear = gear;
14 }
16 void car::accelerate() {
17 speed += (5 * this->getcurrentgear());
18 }
20 void car::brake() {
21 speed -= (5 * this->getcurrentgear());
22 }
24 int car::getcurrentspeed() {
25 return speed;
26 }
接下来才是重点:
php扩展的头文件php_vehicles.h
1 #ifndef php_vehicles_h
2 #define php_vehicles_h
4 #define php_vehicles_extname “vehicles”
5 #define php_vehicles_extver “0.1”
7 #ifdef have_config_h
8 #include “config.h”
9 #endif
10
11 extern “c” {
12 #include “php.h”
13 }
14
15 extern zend_module_entry vehicles_module_entry;
16 #define phpext_vehicles_ptr &vehicles_module_entry;
17
18 #endif /* php_vehicles_h */
首先用宏判断这个头文件是不是已经包含了.然后在第四行给这个扩展一个别名.第五行给定版本号.
注意在11到13行用extern “c”包含了起来,这是因为php是用c写的,所以在开发c++扩展的时候一定要声明一下.
第15行声明了整个扩展模块的入口,在这个入口函数中会定义诸如minit\rinit这种startup函数 和 mshutdown rshutdown这种shutdown函数.
php扩展的源文件vehicles.cc:
这个文件里面的内容相当多,因为它承载了如何把我们想要的c++的类与php的内核联系起来的任务.同时在这个文件中还需要把类中的成员函数进行相应的mapping,以方便php可以直接调用.这些功能会在下面的源码中一一加以说明:
在第一阶段的代码里,先不涉及类相关的部分,而是循序渐进,这里的代码先给出常规php扩展源码中需要进行的一些操作:
1 #include “php_vehicles.h”
2 php_minit_function(vehicles)
3 {
4 return success;
5 }
6 zend_module_entry vehicles_module_entry = {
7 #if zend_module_api_no >= 20010901
8 standard_module_header,
9 #endif
10 php_vehicles_extname,
11 null, /* functions */
12 php_minit(vehicles),
13 null, /* mshutdown */
14 null, /* rinit */
15 null, /* rshutdown */
16 null, /* minfo */
17 #if zend_module_api_no >= 20010901
18 php_vehicles_extver,
19 #endif
20 standard_module_properties
21 };
22 #ifdef compile_dl_vehicles
23 extern “c” {
24 zend_get_module(vehicles)
25}
26 #endif

第一行引入头文件.2~5行定义了模块的入口函数,这里先不进行任何操作.这里一般可以初始化模块的全局变量.
第6~21行通过zend_module_entry给出了扩展和zend引擎之间的联系. 在这里因为只有mint函数被定义了,所以其他位置都是null.
第22~24行则是常规项目,在扩展中都要加上,注意这里为了c++做了extern “c”的特殊处理.
在完成了这一步之后,已经可以进行一次扩展编译了,可以验证一下自己之前的程序有没有错误.过程如下,之后就不重复了.

phpize./configure –enable-vehiclesmakesudo make install在php.ini中加入extension=vehicles.so(只需要一次)重启apache,如果是服务的话 sudo /etc/init.d/httpd restart然后在info.php中查看是否已经有了vehicles这一项扩展.如果觉得每次打都很麻烦,也可以简单的写一个shell脚本来完成这些工作.
在完成了基本的初始化之后,就要开始考虑php用户空间与我们定义的c++类之间的联系了.这部分代码是为了把类中的函数都暴露给php的用户空间脚本,

首先需要定义一个名字同样是car的php类,然后还要定义一组zend_function_entry表,用来说明这个类中有哪些方法想要引入到php用户空间中.需要注意的是,在php用户空间中的方法不一定要跟c++类中的方法同名,你同时还可以根据自己的需要增加或删减c++类中的方法.这点非常的自由.

按照如下的代码更改vehicles.h
1 #include “php_vehicles.h”
2 zend_class_entry *car_ce;
3 php_method(car, __construct){}
5 php_method(car, shift) {}
7 php_method(car, accelerate) {}
9 php_method(car, brake) {}
11 php_method(car, getcurrentspeed){}
13 php_method(car, getcurrentgear){}
15 zend_function_entry car_methods[] = {
16 php_me(car, __construct, null, zend_acc_public | zend_acc_ctor)
17 php_me(car, shift, null, zend_acc_public)
18 php_me(car, accelerate, null, zend_acc_public)
19 php_me(car, brake, null, zend_acc_public)
20 php_me(car, getcurrentspeed, null, zend_acc_public)
21 php_me(car, getcurrentgear, null, zend_acc_public)
22 {null, null, null}
23 };
24 php_minit_function(vehicles)
25 {
26 zend_class_entry ce;
27 init_class_entry(ce, “car”, car_methods);
28 car_ce = zend_register_internal_class(&ce tsrmls_cc);
29 return success;
30 }
31 zend_module_entry vehicles_module_entry = {
32 #if zend_module_api_no >= 20010901
33 standard_module_header,
34 #endif
35 php_vehicles_extname,
36 null, /* functions */
37 php_minit(vehicles), /* minit */
38 null, /* mshutdown */
39 null, /* rinit */
40 null, /* rshutdown */
41 null, /* minfo */
42 #if zend_module_api_no >= 20010901
43 php_vehicles_extver,
44 #endif
45 standard_module_properties
46 };
47 #ifdef compile_dl_vehicles
48 extern “c” {
49 zend_get_module(vehicles)
50 }
51 #endif

首先是在第二行定义了一个zend_class_entry,这个入口会在minit的时候进行相应的初始化.
3~13行给出了c++类的成员函数所转换成的php方法的版本,之后会添加上相应的实现.
15~23行定义了函数入口zend_function_entry,php方法定义的地方.这里也可以声明一组自己定义的别名.(如何定义,怎么体现?)
24~30给出的是新的模块初始化minit函数:

init_class_entry函数把类的入口和之前在zend_function_entry中类的方法联系了起来,属于类的初始化而car_ce = zend_register_internal_class(&ce tsrmls_cc) ,注册类,把类加入到class table中,
31~51行跟之前的模块入口没什么差别。

现在已经声明了一组跟c++类成员函数同名的php函数,再接下来需要做的就是把两者联系起来:
每个c++的类实例都必须对应一个php的类实例,一种实现的方法是使用一个结构来追踪现有的c++和php的类实例。而为了做到这一点,就需要写出自己的对象处理器,在php5中,一个对象就是由一个句柄(使得zend引擎能够定位你的类的id)、一个函数表、和一组处理器组成的。在对象的声明周期的不同阶段都可以重写处理器以实现不同的功能。所以在之前的代码基础上,先增加一个对象处理器:

1 #include “car.h”
2 zend_object_handlers car_object_handlers;
3 struct car_object {
4 zend_object std;
5 car *car;
6 };

这个car_object结构会被用来追踪c++的实例,然后与zend_object联系起来。在php_method的声明之前,需要加上下面两个方法:

1 void car_free_storage(void *object tsrmls_dc)
2 {
3 car_object *obj = (car_object *)object;
4 delete obj->car;
5 zend_hash_destroy(obj->std.properties);
6 free_hashtable(obj->std.properties);
7 efree(obj);
8 }
9 zend_object_value car_create_handler(zend_class_entry *type tsrmls_dc)
10 {
11 zval *tmp;
12 zend_object_value retval;
13 car_object *obj = (car_object *)emalloc(sizeof(car_object));
14 memset(obj, 0, sizeof(car_object));
15 obj->std.ce = type;
16 alloc_hashtable(obj->std.properties);
17 zend_hash_init(obj->std.properties, 0, null, zval_ptr_dtor, 0);
18 zend_hash_copy(obj->std.properties, &type->default_properties,
19 (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *));
20 retval.handle = zend_objects_store_put(obj, null,
21 car_free_storage, null tsrmls_cc);
22 retval.handlers = &car_object_handlers;
23 return retval;
24 }

而后需要对模块初始化函数minit进行如下修改:

25 php_minit_function(vehicles)
26 {
27 zend_class_entry ce;
28 init_class_entry(ce, “car”, car_methods);
29 car_ce = zend_register_internal_class(&ce tsrmls_cc);
30 car_ce->create_object = car_create_handler;
31 memcpy(&car_object_handlers,
32 zend_get_std_object_handlers(), sizeof(zend_object_handlers));
33 car_object_handlers.clone_obj = null;
34 return success;
35}

这里我们看到第30行,首先调用car_create_handler创建一个create_object处理器。在调用car_create_handler的时候注意一个大坑那就是第18行这里$type->default_properties,php的新版本中这里是properties_info,这个编译错误在对比源码了之后才知道怎么改。

13~15行给一个car_object申请了空间,并完成了初始化。同时把obj对应的zend_class_object和输入的type()进行了连接,也就是把minit中初始化的zend对象绑定在了car_object这个结构中。在完成了绑定之后,16~19继续进行拷贝过程。20~21行把obj加入到了zend的对象中,并用函数指针的方式定义了销毁时候的函数car_free_storage,同时产生了一个对象obj的句柄第31行则给处理器handlers了相应的zend_object_handlers的值(这里还很不明晰)

现在在php的类构造函数中,就要读取用户的参数,并且把它们传递给c++的构造函数。一旦c++的类实例被创建了,那就可以从zend对象store中抓取car_object指针,然后设定结构体中的car值。这样的话,就把zend对象实例和c++的car实例绑定了起来。

1 php_method(car, __construct)
2 {
3 long maxgear;
4 car *car = null;
5 zval *object = getthis();
6 if (zend_parse_parameters(zend_num_args() tsrmls_cc, “l”, &maxgear) == failure) {
7 return_null();
8 }
9 car = new car(maxgear);
10 car_object *obj = (car_object *)zend_object_store_get_object(object tsrmls_cc);
11 obj->car = car;
12 }

通过调用zend_object_store_get_object函数就能够获得c++类的一个实例。而下面的两个函数也是同样的道理:

php_method(accelerate)
{
car *car;
car_object *obj = (car_object *)zend_object_store_get_object(
getthis() tsrmls_cc);
car = obj->car;
if (car != null) {
car->accelerate();
}
}
php_method(getcurrentspeed)
{
car *car;
car_object *obj = (car_object *)zend_object_store_get_object(
getthis() tsrmls_cc);
car = obj->car;
if (car != null) {
return_long(car->getcurrentspeed());
}
return_null();
}

好了,到现在为止基本上整个框架就搭好了。
不要忘了重新配置编译一下,然后用如下的php代码就可以进行测试了:
/ create a 5 gear car
$car = new car(5);
print $car->getcurrentspeed(); // prints ‘0’
$car->accelerate();
print $car->getcurrentspeed(); // prints ‘5’
if you can run this script, congratulations, you’ve just created a php extension that wraps a c++ class.
如果说输出跟标识的一致的话,那么整个过程就成功了,恭喜!

http://www.bkjia.com/phpjc/621614.htmlwww.bkjia.comtruehttp://www.bkjia.com/phpjc/621614.htmltecharticle今天花了几乎一天的时间研究php的相关c扩展,第一次接触的时候很多地方不太熟悉,也碰到了不少坑,这里把整个过程叙述如下,参考的文…

Posted in 未分类