Home  >  Article  >  Backend Development  >  PHP extension and embedding--Extension development of c++ classes_PHP tutorial

PHP extension and embedding--Extension development of c++ classes_PHP tutorial

WBOY
WBOYOriginal
2016-07-13 17:18:45974browse

Today I spent almost a day studying related C++ extensions of PHP. When I first came into contact with them, I was not familiar with many places and encountered many pitfalls. The whole process is described here as follows. The reference articles are mainly http://devzone .zend.com/1435/wrapping-c-classes-in-a-php-extension/:

Now a Car class is defined, which has some member functions. The files included in the entire extension are as follows:

  • config.m4 Extended configuration file
  • php_vehicles.h Extended header file
  • vehicles.cc Extended source file
  • car.h Class header file
  • car.cc Class source file Next, each part of this extension will be described separately in the order of the files: Configuration file: 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

    The sixth line requires the use of a c++ compiler The seventh line indicates that the extension will appear in the form of a dynamic link library The eighth line of the table adds a C++ library, which means that things like string and std can be used. In the ninth line, be sure to include all source files. Class header file 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 */ 
    This is exactly the same as the C++ header file declaration. The source file of the class car.cc The source file is also a class definition of 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 }

    What comes next is the key point: PHP extended header file 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 */
    First, use a macro to determine whether the header file has been included. Then give the extension an alias on the fourth line. The version number is given on the fifth line. Note that lines 11 to 13 are included with extern "C". This is because PHP is written in C, so it must be declared when developing a C++ extension. Line 15 declares the entry of the entire extension module. In this entry function, startup functions such as MINITRINIT and shutdown functions such as MSHUTDOWN RSHUTDOWN will be defined. The source file of php extension vehicles.cc: There is quite a lot of content in this file, because it carries the task of how to connect the C++ classes we want with the PHP kernel. At the same time, the member functions in the class also need to be mapped accordingly in this file for convenience PHP can be called directly. These functions will be explained one by one in the source code below: In the first stage of the code, we will not cover the class-related parts first, but proceed step by step. The code here first gives some operations that need to be performed in the conventional PHP extension source code:
    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


    The first line introduces the header file. Lines 2 to 5 define the entry function of the module. No operations are performed here. Generally, the global variables of the module can be initialized here. Lines 6 to 21 give the connection between the extension and the Zend engine through zend_module_entry. Because only the MINT function is defined here, other positions are NULL. Lines 22 to 24 are regular items, which must be added in the extension. Note that special processing of extern "C" is done here for C++. After completing this step, you can now perform an extended compilation and verify whether your previous program has any errors. The process is as follows and will not be repeated later.
    • phpize
    • ./configure --enable-vehicles
    • make
    • sudo make install
    • Add extension=vehicles.so to php.ini (only needed once)
    • Restart apache, If it is a service, sudo /etc/init.d/httpd restart
    • Then check in info.php whether the vehicles extension is already available.
    • If you find it troublesome to log in every time, you can also simply Write a shell script to complete these tasks. After completing the basic initialization, it is time to consider the connection between the PHP user space and the C++ class we defined. This part of the code is to expose the functions in the class to the PHP user space script.
      • First, you need to define a php class with the same name as Car.
      • Then you need to define a set of zend_function_entry tables to explain which methods in this class you want to introduce into the php user space.
      • Need to pay attention to What's interesting is that the methods in the PHP user space do not have to have the same name as the methods in the C++ class. You can also add or delete methods in the C++ class according to your own needs. This is very free.

        Change vehicles.h as follows:
        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


        First, a zend_class_entry is defined in the second line. This entry will be initialized accordingly during MINIT. Lines 3 to 13 give the version of the PHP method converted from the member function of the C++ class, and the corresponding implementation will be added later. Lines 15 to 23 define the function entry zend_function_entry, where the PHP method is defined. You can also declare a set of self-defined aliases here. (How to define and how to reflect it?) 24~30 gives the new module initialization MINIT function:
        • The INIT_CLASS_ENTRY function connects the entry of the class with the previous method of the class in zend_function_entry, which belongs to the initialization of the class
        • and car_ce = zend_register_internal_class(&ce TSRMLS_CC), registers the class and adds the class to the Class Table. Lines 31 to 51 are no different from the previous module entry.

          Now that a set of php functions with the same name as the C++ class member functions have been declared, the next thing that needs to be done is to connect the two: Each C++ class instance must correspond to a PHP class instance. One way to achieve this is to use a structure to track existing C++ and PHP class instances.而为了做到这一点,就需要写出自己的对象处理器,在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代码就可以进行测试了:
            <pre class="code">/ 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扩展,第一次接触的时候很多地方不太熟悉,也碰到了不少坑,这里把整个过程叙述如下,参考的文...
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn