- Extension Writing Part I: Introduction to PHP and Zend
- Extension Writing Part II: Parameters, Arrays, and ZVALs
- Extension Writing Part II: Parameters, Arrays, and ZVALs (continued)
- Extension Writing Part III: Resources
- Wrapping C++ Classes in a PHP Extension
- 在PHP Module中获取$_GET/$_POST/$_COOKIE的方法研究
- 编写提供对象给PHP使用的Module
- 深入浅出PHP(Exploring PHP)
- 深入理解PHP原理之函数(Introspecting PHP Function)
- 实现PHP的编译执行分离(separating compilation and execution)
- 扩展PHP[Extending PHP(一)]
- 深入理解PHP原理之变量(Variables inside PHP)
- 深入理解PHP原理之变量作用域(Scope in PHP)
- 用C/C++扩展你的PHP
- 深入理解PHP原理之扩展载入过程
- 深入理解PHP原理之对象(一)
- 深入理解PHP原理之异常机制
- Zend Parameters Parser新增类型描述符介绍
- 关于做PHP扩展开发的一些资源
- (php.devel)Subject: Difference between sprintf, snprintf and spprintf
- Creating new test files(by PHP QA Team)
- php extension skeleton by c9s
- c9s on PHP Extension dev
理論上,extension相關的文件應該在(PHP at the Core: A Hacker's Guide to the Zend Engine)...很不幸,這個文件缺很大XD...底下列出的是裡面有內容而且有用的資訊
- http://www.php.net/manual/en/internals2.preface.php Preface (我不太相信This documentation is still under heavy development.)
- The "counter" Extension - A Continuing Example (底下的示範會使用這個extension)
- The PHP 5 build system
- Extension structure
- Memory management - Basic memory management
- Working with variables - Intro
- Writing functions
- Zend Engine 2 Opcodes
- http://www.php.net/manual/en/internals2.ze1.php Zend Engine 1 (很多東西是沿用的,所以還是可以參考)
- Zend/zend.h
- Zend/zend_API.h
- Zend/zend_types.h (typedef struct _zval_struct zval;)
- 通常放在php原始碼的ext目錄中
- 使用--extname=modulename來指定模組名稱,模組框架會產生在同名的目錄中
- 使用--proto=prototype檔案來指定要使用的prototype檔案以產生模組檔頭與源碼,也可以指定要包裝進模組的.h檔案,但是效果不好,會產生許多不必要的函數及空函數
- 編輯config.m4來指定一些環境相關的東西,例如依賴的Library、檔頭檢查等
- 使用phpize來產生autoconf所需要的東西
- 執行./configure
- 執行make
- 編譯好的模組檔案會放在module目錄下
- 使用--extname=modulename來指定模組名稱,模組框架會產生在同名的目錄中
- --proto參數有問題,無法使用
- 可以使用複雜的xml檔案建立模組結構,參閱 CodeGen_PECL - the PHP extension generator
- 用ZEND_FUNCTION巨集宣告要輸出(在php中使用)的函數
- 使用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函數取得參數,其中參數指定如下:
- int num_args TSRMLS_DC
- char *type_spec
- &arg1, &arg2...依此類推
- num_args可用ZEND_NUM_ARGS()來取得,後面必須加上TSRMLS_DC巨集
- 可以使用的type_spec如下:
- 'l': long
- 'd': double
- 's': string
- 'b': boolean
- 'r': resource (存放在zval*)
- 'a': array (存放在zval*)
- 'o': object(任意類別的) (存放在zval*)
- 'O': object(經由class entry指定的類別的) (存放在zval*)
- 'z': the actual zval*
- '|': 代表之後的參數是optional的
- '/': 代表之前的這個參數要用zend_parse_parameters()處理以指派一個副本給它,否則會指派一個reference
- '!': 代表之前的這個參數可為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
- 使用zval* varname;宣告
- 使用MAKE_STD_ZVAL(varname);巨集配置並初始化容器
- 使用ZVAL_型別;巨集來賦值(以下會詳細說明各個型別的巨集)
- 使用ZEND_SET_SYMBOL(EG(active_symbol_table), "varname", varname); 巨集將變數引入到active symbol table成為區域變數
- 或使用ZEND_SET_SYMBOL(&EG(symbol_table), "varname", varname); 巨集將變數引入到symbol table成為整體變數
- 或使用ZEND_SET_GLOBAL_VAR("varname", varname);巨集,與上面同義
- 以上三個巨集,底層是使用zend_hash_update(hashtable,key,key_length,var,var_length,destination)這個巨集
- 使用zend_hash_del(hashtable,key,keylength)巨集來刪除變數(unset)
看起來,所有的變數、函數參考、類別參考都是存放在不同的hash table裡面,透過hash table來操作。
一般方法
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);
一般方法
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);
一般方法
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);
一般方法
zval *new_bool;
MAKE_STD_ZVAL(new_bool);
new_bool->type = IS_BOOL;
new_bool->value.lval = 1;
一般方法
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);()
一般方法
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的使用比較複雜,獨立一節來整理)
最常見的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)
(假設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);)
php自己會呼叫之前用zend_register_list_destructor_ex()註冊的destructor來釋放資源(per request base)
直接執行巨集就可以返回,不必使用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