Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
PHP Extension Develop Resource Reference...

參考文件

devzone.zend.com

风雪之隅

其他

官方文件

理論上,extension相關的文件應該在(PHP at the Core: A Hacker's Guide to the Zend Engine)...很不幸,這個文件缺很大XD...底下列出的是裡面有內容而且有用的資訊

原始碼

  • Zend/zend.h
  • Zend/zend_API.h
  • Zend/zend_types.h (typedef struct _zval_struct zval;)

在Windows編譯

PHP

Apache httpd

產生模組框架

使用ext_skel

  1. 通常放在php原始碼的ext目錄中
  2. 使用--extname=modulename來指定模組名稱,模組框架會產生在同名的目錄中
  3. 使用--proto=prototype檔案來指定要使用的prototype檔案以產生模組檔頭與源碼,也可以指定要包裝進模組的.h檔案,但是效果不好,會產生許多不必要的函數及空函數
  4. 編輯config.m4來指定一些環境相關的東西,例如依賴的Library、檔頭檢查等
  5. 使用phpize來產生autoconf所需要的東西
  6. 執行./configure
  7. 執行make
  8. 編譯好的模組檔案會放在module目錄下

使用PECL_CodeGen

  1. 使用--extname=modulename來指定模組名稱,模組框架會產生在同名的目錄中
  2. --proto參數有問題,無法使用
  3. 可以使用複雜的xml檔案建立模組結構,參閱 CodeGen_PECL - the PHP extension generator

模組結構

標頭檔

  • 用ZEND_FUNCTION巨集宣告要輸出(在php中使用)的函數

源碼(.c)

  • 使用zend_function_entry結構宣告Zend函數區塊:
    zend_function_entry modulename_functions[] =
    {
        ZEND_FE(function1, NULL)
        ZEND_FE(function2, NULL)
        ZEND_FE(function3, NULL)
        {NULL, NULL, NULL}
    };
  • 使用zend_module_entry結構宣告Zend模組區塊:
    zend_module_entry modulename_module_entry = {
    #if ZEND_MODULE_API_NO >= 20010901
            STANDARD_MODULE_HEADER,
    #endif
            "modulename",
            modulename_functions,
            PHP_MINIT(modulename),         /* Replace with NULL if there's nothing to do at module load */
            PHP_MSHUTDOWN(modulename),     /* Replace with NULL if there's nothing to do at module unload */
            PHP_RINIT(modulename),         /* Replace with NULL if there's nothing to do at request start */
            PHP_RSHUTDOWN(modulename),     /* Replace with NULL if there's nothing to do at request end */
            PHP_MINFO(modulename), 
    #if ZEND_MODULE_API_NO >= 20010901
            "0.1", /* Replace with version number for your extension */
    #endif
            STANDARD_MODULE_PROPERTIES
    };
  • 使用ZEND_GET_MODULE巨集產生get_module函數,用來在動態載入模組時使用
    #ifdef COMPILE_DL_TOKYOCABINET
    ZEND_GET_MODULE(modulename)
    #endif
  • 視需要實作PHP_MINIT、PHP_MSHUTDOWN、PHP_RINIT、PHP_RSHUTDOWN、ZEND_GET_MODULE。PHP_MINIT、PHP_MSHUTDOWN、PHP_RINIT、PHP_RSHUTDOWN等如果不需要,可以直接在Zend模組區塊中對應的位置中填入NULL
  • 實作輸出函數,像這樣(這個是ext_skel產生的,用來確認框架編譯成功的函數,開發時要拿掉):
    PHP_FUNCTION(confirm_modulename_compiled)
    {
        char *arg = NULL;
        int arg_len, len;
        char string[256];
    
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
                return;
        }
    
        len = sprintf(string, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "modulename", arg);
        RETURN_STRINGL(string, len, 1);
    }

函數實作細節

參數

參數處理

  • 用ZEND_NUM_ARGS()取得傳入參數數量
  • 用WRONG_PARAM_COUNT巨集產生參數數量不正確的錯誤並返回
  • 用zend_parse_parameters函數取得參數,其中參數指定如下:
    1. int num_args TSRMLS_DC
    2. char *type_spec
    3. &arg1, &arg2...依此類推
  • num_args可用ZEND_NUM_ARGS()來取得,後面必須加上TSRMLS_DC巨集
  • 可以使用的type_spec如下:
    1. 'l': long
    2. 'd': double
    3. 's': string
    4. 'b': boolean
    5. 'r': resource (存放在zval*)
    6. 'a': array (存放在zval*)
    7. 'o': object(任意類別的) (存放在zval*)
    8. 'O': object(經由class entry指定的類別的) (存放在zval*)
    9. 'z': the actual zval*
    10. '|': 代表之後的參數是optional的
    11. '/': 代表之前的這個參數要用zend_parse_parameters()處理以指派一個副本給它,否則會指派一個reference
    12. '!': 代表之前的這個參數可為NULL
  • 如果需要傳入可變的參數數量及類型,可用zend_get_parameters_array_ex(),他第一個參數是一個flag,可用來抑制錯誤,並用if結構來使用數個zend_get_parameters_array_ex()對於輸入參數做匹配,但是需要在無法匹配參數的狀況時,自己處理錯誤訊息

參數型別轉換

可以做參數型別轉換的巨集(預期輸入的參數型別是**zval):

  • convert_to_boolean_ex() *convert_to_long_ex()
  • convert_to_double_ex()
  • convert_to_string_ex()
  • convert_to_array_ex(value)
  • convert_to_object_ex(value)
  • convert_to_null_ex(value)

傳參

如果要用pass by reference的方式傳入參數,參數必須是zval*,使用zend_parse_parameters()來取得參數,並且用PZVAL_IS_REF()來做檢查是否是reference

變數

產生變數

一般的方法

  1. 使用zval* varname;宣告
  2. 使用MAKE_STD_ZVAL(varname);巨集配置並初始化容器
  3. 使用ZVAL_型別;巨集來賦值(以下會詳細說明各個型別的巨集)
  4. 使用ZEND_SET_SYMBOL(EG(active_symbol_table), "varname", varname); 巨集將變數引入到active symbol table成為區域變數
  5. 或使用ZEND_SET_SYMBOL(&EG(symbol_table), "varname", varname); 巨集將變數引入到symbol table成為整體變數
  6. 或使用ZEND_SET_GLOBAL_VAR("varname", varname);巨集,與上面同義
  7. 以上三個巨集,底層是使用zend_hash_update(hashtable,key,key_length,var,var_length,destination)這個巨集
  8. 使用zend_hash_del(hashtable,key,keylength)巨集來刪除變數(unset)

看起來,所有的變數、函數參考、類別參考都是存放在不同的hash table裡面,透過hash table來操作。

Longs(integers)

一般方法

    zval *new_long;
    MAKE_STD_ZVAL(new_long);
    new_long->type = IS_LONG;
    new_long->value.lval = 10;

另一種方法

    zval *new_long;
    MAKE_STD_ZVAL(new_long);
    ZVAL_LONG(new_long, 10);

Doubles(Floats)

一般方法

    zval *new_double;
    MAKE_STD_ZVAL(new_double);
    new_double->type = IS_DOUBLE;
    new_double->value.dval = 3.45;

另一種方法

    zval *new_double;
    MAKE_STD_ZVAL(new_double);
    ZVAL_DOUBLE(new_double, 3.45);

Strings

一般方法

    zval *new_string;
    char *string_contents = "This is a new string variable";
    MAKE_STD_ZVAL(new_string);
    new_string->type = IS_STRING;
    new_string->value.str.len = strlen(string_contents);
    new_string->value.str.val = estrdup(string_contents);

另一種方法

    zval *new_string;
    char *string_contents = "This is a new string variable";
    MAKE_STD_ZVAL(new_string);
    ZVAL_STRING(new_string, string_contents, 1);

Booleans

一般方法

    zval *new_bool;
    MAKE_STD_ZVAL(new_bool);
    new_bool->type = IS_BOOL;
    new_bool->value.lval = 1;

Arrays

一般方法

    zval *new_array;
    MAKE_STD_ZVAL(new_array);
    array_init(new_array);

陣列用的API

  • 關係陣列
    • add_assoc_long(zval *array, char *key, long n);()
    • add_assoc_unset(zval *array, char *key);()
    • add_assoc_bool(zval *array, char *key, int b);()
    • add_assoc_resource(zval *array, char *key, int r);()
    • add_assoc_double(zval *array, char *key, double d);()
    • add_assoc_string(zval *array, char *key, char *str, int duplicate);()
    • add_assoc_stringl(zval *array, char *key, char *str, uint length, int duplicate); ()
    • add_assoc_zval(zval *array, char *key, zval *value);()
  • 索引陣列part1
    • add_index_long(zval *array, uint idx, long n);()
    • add_index_unset(zval *array, uint idx);()
    • add_index_bool(zval *array, uint idx, int b);()
    • add_index_resource(zval *array, uint idx, int r);()
    • add_index_double(zval *array, uint idx, double d);()
    • add_index_string(zval *array, uint idx, char *str, int duplicate);()
    • add_index_stringl(zval *array, uint idx, char *str, uint length, int duplicate);()
    • add_index_zval(zval *array, uint idx, zval *value);()
  • 索引陣列part2
    • add_next_index_long(zval *array, long n);()
    • add_next_index_unset(zval *array);()
    • add_next_index_bool(zval *array, int b);()
    • add_next_index_resource(zval *array, int r);()
    • add_next_index_double(zval *array, double d);()
    • add_next_index_string(zval *array, char *str, int duplicate);()
    • add_next_index_stringl(zval *array, char *str, uint length, int duplicate);()
    • add_next_index_zval(zval *array, zval *value);()

Objects

一般方法

    zval *new_object;
    MAKE_STD_ZVAL(new_object);
    if(object_init(new_object) != SUCCESS)
    {
        // do error handling here
    }

產生物件的API

  • add_property_long(zval *object, char *key, long l);()
  • add_property_unset(zval *object, char *key);()
  • add_property_bool(zval *object, char *key, int b);()
  • add_property_resource(zval *object, char *key, long r);()
  • add_property_double(zval *object, char *key, double d);()
  • add_property_string(zval *object, char *key, char *str, int duplicate);()
  • add_property_stringl(zval *object, char *key, char *str, uint length, int duplicate);()
  • add_property_zval(zval *obect, char *key, zval *container):()

Resources

(Resources的使用比較複雜,獨立一節來整理)

最常見的Resource,就是一個struct。以下是我自己在tokyocabinet中使用的例子,會在下面幾節中間說明:

  ......
  typedef struct {                         /* type of structure for an abstract database */
    int omode;                             /* open mode */
    TCMDB *mdb;                            /* on-memory hash database object */
    TCNDB *ndb;                            /* on-memory tree database object */
    TCHDB *hdb;                            /* hash database object */
    TCBDB *bdb;                            /* B+ tree database object */
    TCFDB *fdb;                            /* fixed-length databae object */
    TCTDB *tdb;                            /* table database object */
    int64_t capnum;                        /* capacity number of records */
    int64_t capsiz;                        /* capacity size of using memory */
    uint32_t capcnt;                       /* count for capacity check */
    BDBCUR *cur;                           /* cursor of B+ tree */
    void *skel;                            /* skeleton database */
  } TCADB;
  ......

(in tcadb.h)

  ......
  #define PHP_TCADB_RES_NAME "TokyoCabinet Abstract DB Data"
  ......

(in php_tokyocabinet.h)

  ......
  #include "php.h"
  #include "php_ini.h"
  #include "ext/standard/info.h"
  #include "php_tokyocabinet.h"
  
  static int le_tcadb;
  ......
  static void php_tcadb_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
  {
          TCADB *tcadb = (TCADB*)rsrc->ptr;
  
          if (tcadb) {
                  if (tcadb->omode != ADBOVOID) {
                          tcadbclose(tcadb);
                  }
                  tcadbdel(tcadb);
          }
  }
  ......
  PHP_MINIT_FUNCTION(tokyocabinet)
  {
          le_tcadb = zend_register_list_destructors_ex(php_tcadb_dtor, NULL, PHP_TCADB_RES_NAME, module_number);
          /* If you have INI entries, uncomment these lines
          ZEND_INIT_MODULE_GLOBALS(tokyocabinet, php_tokyocabinet_init_globals, NULL);
          REGISTER_INI_ENTRIES();
          */
          return SUCCESS;
  }
  ......
  PHP_FUNCTION(tcadbnew)
  {
          if (ZEND_NUM_ARGS() != 0) {
                  WRONG_PARAM_COUNT;
          }
          TCADB *tcadb;
          tcadb = tcadbnew();
          ZEND_REGISTER_RESOURCE(return_value, tcadb, le_tcadb);
  }
  ......
  PHP_FUNCTION(tcadbclose)
  {
          if (ZEND_NUM_ARGS() != 1) {
                  WRONG_PARAM_COUNT;
          }
          zval *ztcadb;
          TCADB *tcadb;
          if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &ztcadb) == FAILURE) {
                  RETURN_FALSE;
          }
          ZEND_FETCH_RESOURCE(tcadb, TCADB*, &ztcadb, -1, PHP_TCADB_RES_NAME, le_tcadb);
          if (tcadbclose(tcadb)) {
                  RETURN_TRUE;
          }
          RETURN_FALSE;
  }
  ......

(in tokyocabinet.c)

Resources初始

(假設struct已定義在外部lib的標頭檔中,例如在範例中的tcadb.h中)

  • 在extension的標頭檔中定義一個以le_開頭命名的int變數,用來辨識此資源(例如範例中的le_tcadb)
  • 在extension的標頭檔中定義一個以"PHP_資源名稱_RES_NAME"命名的巨集,來定義此資源名稱字串,以便程式中重複使用(例如範例中的PHP_TCADB_RES_NAME)
  • 在extension的源碼中撰寫一個以"php_資源名稱_dtor"命名的destructor靜態函數,裡面的程式碼專門用來釋放此資源(例如範例中的php_tcadb_dtor)
  • 在extension的源碼中的PHP_MINIT_FUNCTION()函數中,使用zend_register_list_destructor_ex API函數來註冊此資源的destructor函數,返回值指派給用來辨識此資源的int變數(le_開頭)(例如範例中的le_tcadb = zend_register_list_destructors_ex(php_tcadb_dtor, NULL, PHP_TCADB_RES_NAME, module_number);)
  • 在產生此資源的函數中,使用ZEND_REGISTER_RESOURCE()函數來註冊此資源(例如範例中的ZEND_REGISTER_RESOURCE(return_value, tcadb, le_tcadb);)

Resources結束

php自己會呼叫之前用zend_register_list_destructor_ex()註冊的destructor來釋放資源(per request base)

Resources作為函數的參數

返回Resources

拷貝變數

常數

返回

用來返回值的巨集

直接執行巨集就可以返回,不必使用return。例如:

    RETURN_EMPTY_STRING();
  • RETURN_RESOURCE(resource_id)
  • RETURN_BOOL(bool)
  • RETURN_NULL()
  • RETURN_LONG(long)
  • RETURN_DOUBLE(double)
  • RETURN_STRING(string, duplicate)
  • RETURN_STRINGL(string, length, duplicate)
  • RETURN_EMPTY_STRING()
  • RETURN_FALSE
  • RETURN_TRUE

用來設定返回值的巨集

用return來返回巨集設定好的值,例如:

    return RETVAL_TRUE;
  • RETVAL_RESOURCE(resource)
  • RETVAL_BOOL(bool)
  • RETVAL_NULL
  • RETVAL_LONG(long)
  • RETVAL_DOUBLE(double)
  • RETVAL_STRING(string, duplicate)
  • RETVAL_STRINGL(string, length, duplicate)
  • RETVAL_EMPTY_STRING
  • RETVAL_FALSE
  • RETVAL_TRUE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.