热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

php的扩展和嵌入--c++类的扩展开发_PHP教程

php的扩展和嵌入--c++类的扩展开发。今天花了几乎一天的时间研究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&#43;&#43;扩展的时候一定要声明一下. 第15行声明了整个扩展模块的入口,在这个入口函数中会定义诸如MINIT\RINIT这种startup函数 和 MSHUTDOWN RSHUTDOWN这种shutdown函数. php扩展的源文件vehicles.cc: 这个文件里面的内容相当多,因为它承载了如何把我们想要的c&#43;&#43;的类与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&#43;&#43;做了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&#43;&#43;类之间的联系了.这部分代码是为了把类中的函数都暴露给php的用户空间脚本,
      • 首先需要定义一个名字同样是Car的php类,
      • 然后还要定义一组zend_function_entry表,用来说明这个类中有哪些方法想要引入到php用户空间中.
      • 需要注意的是,在php用户空间中的方法不一定要跟c&#43;&#43;类中的方法同名,你同时还可以根据自己的需要增加或删减c&#43;&#43;类中的方法.这点非常的自由.

        按照如下的代码更改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&#43;&#43;类的成员函数所转换成的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&#43;&#43;类成员函数同名的php函数,再接下来需要做的就是把两者联系起来: 每个C&#43;&#43;的类实例都必须对应一个php的类实例,一种实现的方法是使用一个结构来追踪现有的C&#43;&#43;和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&#43;&#43;的实例,然后与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的&#20540;(这里还很不明晰)


            现在在php的类构造函数中,就要读取用户的参数,并且把它们传递给C&#43;&#43;的构造函数。一旦C&#43;&#43;的类实例被创建了,那就可以从zend对象store中抓取car_object指针,然后设定结构体中的car&#20540;。这样的话,就把zend对象实例和C&#43;&#43;的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&#43;&#43;类的一个实例。而下面的两个函数也是同样的道理:
            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.


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

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


推荐阅读
  • 本文由chszs撰写,详细介绍了Apache Mina框架的核心开发流程及自定义协议处理方法。文章涵盖从创建IoService实例到协议编解码的具体步骤,适合希望深入了解Mina框架应用的开发者。 ... [详细]
  • 搭建个人博客:WordPress安装详解
    计划建立个人博客来分享生活与工作的见解和经验,选择WordPress是因为它专为博客设计,功能强大且易于使用。 ... [详细]
  • Windows环境下Oracle数据库迁移实践
    本文详细记录了一次在Windows操作系统下将Oracle数据库的控制文件、数据文件及在线日志文件迁移至外部存储的过程,旨在为后续的集群环境部署做好准备。 ... [详细]
  • 本文探讨了当通过Nginx访问网站时出现504 Gateway Timeout错误的解决方案,特别是当请求处理时间超过30秒时的情况。文章提供了调整PHP-FPM配置的具体步骤,以延长请求超时时间。 ... [详细]
  • 本文介绍了一个来自AIZU ONLINE JUDGE平台的问题,即清洁机器人2.0。该问题来源于某次编程竞赛,涉及复杂的算法逻辑与实现技巧。 ... [详细]
  • 实现Win10与Linux服务器的SSH无密码登录
    本文介绍了如何在Windows 10环境下使用Git工具,通过配置SSH密钥对,实现与Linux服务器的无密码登录。主要步骤包括生成本地公钥、上传至服务器以及配置服务器端的信任关系。 ... [详细]
  • 本文介绍了使用Python和C语言编写程序来计算一个给定数值的平方根的方法。通过迭代算法,我们能够精确地得到所需的结果。 ... [详细]
  • 本文提供了一个关于AC自动机(Aho-Corasick Algorithm)的详细解析与实现方法,特别针对P3796题目进行了深入探讨。文章不仅涵盖了AC自动机的基本概念,还重点讲解了如何通过构建失败指针(fail pointer)来提高字符串匹配效率。 ... [详细]
  • 本报告记录了嵌入式软件设计课程中的第二次实验,主要探讨了使用KEIL V5开发环境和ST固件库进行GPIO控制及按键响应编程的方法。通过实际操作,加深了对嵌入式系统硬件接口编程的理解。 ... [详细]
  • 本文分享了作者在使用LaTeX过程中的几点心得,涵盖了从文档编辑、代码高亮、图形绘制到3D模型展示等多个方面的内容。适合希望深入了解LaTeX高级功能的用户。 ... [详细]
  • LeetCode 102 - 二叉树层次遍历详解
    本文详细解析了LeetCode第102题——二叉树的层次遍历问题,提供了C++语言的实现代码,并对算法的核心思想和具体步骤进行了深入讲解。 ... [详细]
  • 本文提供了一个详尽的前端开发资源列表,涵盖了从基础入门到高级应用的各个方面,包括HTML5、CSS3、JavaScript框架及库、移动开发、API接口、工具与插件等。 ... [详细]
  • JavaScript 中引号的多层嵌套使用技巧
    本文详细介绍了在 JavaScript 编程中如何处理引号的多级嵌套问题,包括双引号、单引号以及转义字符的正确使用方法。 ... [详细]
  • 最适合初学者的编程语言
    本文探讨了适合编程新手的最佳语言选择,包括Python、JavaScript等易于上手且功能强大的语言,以及如何通过有效的学习方法提高编程技能。 ... [详细]
  • HBase 数据复制与灾备同步策略
    本文探讨了HBase在企业级应用中的数据复制与灾备同步解决方案,包括存量数据迁移及增量数据实时同步的方法。 ... [详细]
author-avatar
乌鸦晕倒_767
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有