php定义一个名为Vehicles,php的扩展和嵌入--c++类的扩展开发

配置文件: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-vehicles

make

sudo 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.cc

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.

如果说输出跟标识的一致的话,那么整个过程就成功了,恭喜!

原文链接:原文

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值