Today, I spent almost a day studying the c ++ extensions related to php. During my first contact, I was unfamiliar with many places and encountered many pitfalls. Here I will describe the entire process as follows, for more information, see http://devzone.zend.com/1435/wrapping-c-classes-in-a-php-extension /:
Now a Car class is defined, which has some member functions. The entire extension includes the following files:
- Config. m4 Extension Configuration File
- Php_vehicles.h extension header file
- Source file for vehicles. cc Extension
- Head file of the car. h class
- The source file of the car. cc class is described in the file order: 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 row requires the c ++ compiler to use the seventh row to indicate that the extension will be dynamically connected to the database. The eighth row table is a library with c ++ added, that is, it can be used like string or std. in the ninth line, be sure to include all the 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 header file declaration of c ++. The source file car. cc of the class is also the 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 }
The next step is the focus: php extension header file php_vehicles.h1 #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 #endif10 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 is included. then, expand an alias for the fourth row. the version number given by the fifth line. note that extern "C" is included in lines 11 to 13. This is because php is written in c, so you must declare it when developing c ++ extensions. row 3 declares the entrance of the entire extension module. In this function, the startup function such as MINIT \ RINIT and the shutdown function such as mshutdown rshutdown are defined. php extension source file vehicles. cc: This file contains a lot of content, because it carries the task of connecting the desired c ++ class with the php kernel. at the same time, you also need to map the member functions in the class in this file, so that php can directly call. these functions are described in the following source code: in the first stage of the Code, classes are not involved, but gradually, the code here first provides some operations that need to be performed in the general 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 >= 200109018 STANDARD_MODULE_HEADER,9 #endif10 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 >= 2001090118 PHP_VEHICLES_EXTVER,19 #endif20 STANDARD_MODULE_PROPERTIES21 };22 #ifdef COMPILE_DL_VEHICLES23 extern "C" {24 ZEND_GET_MODULE(vehicles)25}26 #endif
The first line introduces header files. 2 ~ Line 5 defines the module entry function. No operation is performed here. The global variables of the module can be initialized here. 6th ~ In line 21, zend_module_entry is used to show the relationship between the extension and the Zend engine. Here, because only the MINT function is defined, all other locations are NULL. 22nd ~ The 24 rows are regular projects and must be added to the extensions. Note that extern "C" is specially processed for C ++. after completing this step, you can perform an extension compilation to verify whether your previous program has any errors. the process is as follows.
- Phpize
- ./Configure -- enable-vehicles
- Make
- Sudo make install
- Add extension = vehicles. so to php. ini (only once required)
- Restart apache. If it is a service, sudo/etc/init. d/httpd restart
- Check whether the vehicles extension is available in info. php.
- If you think it is very troublesome, you can simply write a shell script to complete these tasks. after completing the basic initialization, we need to consider the connection between the php user space and the C ++ class we have defined. this code is used to expose all functions in the class to php user space scripts,
- First, define a php class with the same name as Car,
- Then define a group of zend_function_entry tables to describe the methods in this class to be introduced to the php user space.
- Note that the methods in the php user space do not have to be the same as those in the c ++ class. You can also add or delete methods in the c ++ class as needed. this is very free.
Change vehicles. h according to the following code: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 >= 2001090133 STANDARD_MODULE_HEADER,34 #endif35 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 >= 2001090143 PHP_VEHICLES_EXTVER,44 #endif45 STANDARD_MODULE_PROPERTIES46 };47 #ifdef COMPILE_DL_VEHICLES48 extern "C" {49 ZEND_GET_MODULE(vehicles)50 }51 #endif
First, define a zend_class_entry in the second line. This entry will be initialized during MINIT. 3 ~ Line 13 shows the version of the php method converted by the member functions of the C ++ class. The corresponding implementation will be added later ~ Line 23 defines the function entry zend_function_entry, where the php method is defined. Here we can also declare a set of custom aliases. (How can we define them ?) 24 ~ 30. The new module initializes the MINIT function:
- The INIT_CLASS_ENTRY function associates the class entry with the class method in zend_function_entry, and belongs to class initialization.
- While car_ce = zend_register_internal_class (& ce TSRMLS_CC), registration Class, add Class to Class Table, 31 ~ Line 51 is no different from the previous module entry.
Now we have declared a group of php functions with the same name as the C ++ class member functions. Next we need to associate the two functions: each C ++ class instance must correspond to a php class instance. One method is to use a structure to track existing C ++ and php class instances. To do this, you need to write your own object processor. In php5, an object is composed of a handle (so that the Zend engine can locate the id of your class) A function table and a group of processors. The processor can be rewritten at different stages of the object declaration cycle to implement different functions. Therefore, add an object processor based on the previous Code:
1 #include "car.h"2 zend_object_handlers car_object_handlers;3 struct car_object {4 zend_object std;5 Car *car;6 };
This car_object structure will be used to trace the C ++ instance and then associate it with zend_object. Before declaring the PHP_METHOD, add the following two methods:
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 }
Then, modify the module initialization function MINIT as follows:
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}
Here we can see row 30th. First, call car_create_handler to create a create_object processor. When calling car_create_handler, pay attention to a big pitfall, that is, line 1. Here $ type-> default_properties. In the new php version, properties_info is used. This compilation error is correct only after comparing the source code.
- 13 ~ Row 15 requests a space for a car_object and completes initialization. At the same time, the zend_class_object corresponding to obj is connected to the input type (), that is, the zend object initialized in MINIT is bound to the car_object structure.
- After binding ~ 19. Continue the copy process.
- 20 ~ In line 21, obj is added to the zend object, and the car_free_storage function is defined when the object is destroyed using the function pointer. At the same time, an object obj handle is generated.
- In line 3, the corresponding zend_object_handlers value is assigned to the processor handlers (this is not clear yet)
In php class constructor, we need to read USER Parameters and pass them to the C ++ constructor. Once the class instance of C ++ is created, you can capture the car_object pointer from the zend object store and set the car value in the struct. In this way, the zend object instance and the C ++ Car instance are bound.
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 }
You can call the zend_object_store_get_object function to obtain an instance of the C ++ class. The following two functions share the same principle:
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();}
Now, the entire framework has basically been ready. Don't forget to re-configure and compile it, and then use the following php code to test it:/ 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.
If the output is consistent with the identifier, the entire process is successful. Congratulations!