程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> php的擴展和嵌入--c++類的擴展開發

php的擴展和嵌入--c++類的擴展開發

編輯:關於PHP編程

今天花了幾乎一天的時間研究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-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.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.


            如果說輸出跟標識的一致的話,那麼整個過程就成功了,恭喜!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved