· 作者:laruence(http://www.laruence.com/)
· 本文地址: http://www.laruence.com/2008/04/16/19.html
· 轉載請註明出處
還是那個關於開發安全簽名的PHP模塊, 今天將它包裝成一個PHP的CLASS,也同樣,網上的資料少之甚少,於是我想將經驗寫一篇,關於如何在Extension Module中創建一個可以被PHP訪問的對象的文章。 和大家分享。
首先,讓我們達成幾個共識:
1.我的模塊叫做getCookie. 使用C++編寫,源文件是getCookie.cc,現在我要給它添加一個可以爲PHP使用的對象;
2.DOC_ROOT 是你的擴展開發目錄,比如我的擴展名字叫getCookie,那麼目錄就在/home/share/php/getCookie下;
3.我要添加的類叫做XCSecure,爲什麼要叫XC,因爲是我名字的縮寫:)
4.我的XCSecure完成一個功能,構造這個對象的時候,傳入一個字符串, 返回一個以這個字符串爲key,和cookie經過MD5後的字符串;
5.我假設你已經對基本的如何在linux下開發一個PHP Module很熟悉了,如果不熟悉 ,那麼你可以去在網上查閱《深入PHP內核》,翻譯的很不錯;
現在,我們已經有一定的共識了,那麼開始吧:
在PHP中,模塊和PHP腳本交換數據,都是通過Zval的,我們要使用自己的C++類,就必須把我們的C++類註冊爲一個資源,然後每次NEW的時候,就註冊一個資源實例,然後記錄它的句柄到Zval中。
接下來,我們一步一步來做把。
1. 首先你要在Module中定義你自己的C++ Class。 如下:
#ifndef _XCSECURE_H_
#define _XCSECURE_H_
#include <string>
using namespace std;
class XCSecure{
private:
string _sec;
public:
XCSecure(char * s);
~XCSecure();
char * genSec();
};
#endif
然後在XCSecure.cc中定義類函數,並在getCookie.cc中也包含XCSecure.h;
2. 修改config.m4,
PHP_NEW_EXTENSION(my_module, 'my_module.cc' 'XCSecure.cc', $ext_shared);
此處要注意的是,'my_module.cc'和'XCSecure.cc'之間使用空格分割;
然後在DOC_ROOT下運行phpize,使得爲我們生成configure;
然後在DOC_ROOT下運行./configure 使得爲我們生成Makefile;
到這裏,我們的前期工作就做好了, 你現在也可以運行make ; make install了,但只會生成一個空的module,接下來就可以填充完善它了。
3. 修改getCookie.cc,爲容納XCSecure做一些工作:
首先,我們要定義一個全局的zend_class_entry * ;
static zend_class_entry * php_xcsecure_ptr;
然後,我們要定義一個全局句柄(INT型), 我們要把我們的C++類註冊成一個PHP中的資源(Resource), 在PHP中,資源是個很寬泛的概念,比如,鏈接Mysql的句柄,打開一個文件的句柄等等。
static int de_xcsecure;
4. 定義我們XCSecure的成員函數:
PHP_FALIAS(XCSecure, XCSecure_new, NULL)
PHP_FALIAS(genSec, XCSecure_genSec, NULL)
{NULL, NULL, NULL}
};
當然,你還需要自己定義這些函數;
5. 我選擇在PHP_MINIT階段來初始化我的類;
zend_class_entry php5_secure_entry;
de_php5_secure = zend_register_list_destructors_ex(_de_php5_secure, NULL, "Signature Generate Type", module_number); //爲我們的C++類的對象創建析構函數,並獲得它的資源類型句柄
INIT_CLASS_ENTRY(php5_secure_entry, "XCSecure", php5_secure_method);
php5_secure_entry_ptr = zend_register_internal_class(&php5_secure_entry);//註冊我們的類,這樣在PHP腳本中就可以使用了。
REGISTER_LONG_CONSTANT("XCSECURE_LOAD", 1, CONST_CS|CONST_PERSISTENT);
return SUCCESS;
}
首先我們創建了一個zend_class_entry, 根據變量所表達出來的意思 ,這是一個zend中的對於一個對象的操作句柄類型
然後,我們註冊了我們這個對象的清理函數, 這個是因爲,當在PHP腳本中unset我們的變量的時候,zend內核必須要知道如何清理我們的對象,釋放我們佔用的內存;
然後,我們初始化了我們的類的申明,
INIT_CLASS_ENTRY(php5_secure_entry, "XCSecure", php5_secure_method);
參數2是我們的類型名字,在PHP中var_dump的時候 ,就會顯示出來,如:
object(XCSecure)#1 (0) { }
參數3是我們已經定義過的我們的類的成員函數;
然後我們向zend內核註冊了我們的類,
並將它返回的指針付給了我們的全局變量(這個我自己另有用處)
zend_register_internal_class(&php5_secure_entry);
這個時候,我們的對象就算註冊完成了,現在已經可以在腳本中使用了;
$xcSecure = new XCSecure('laruence');
6. 當在PHP腳本中new一個我們的對象的時候,Zend就會自動調用我們的構造函數,接下來,完成我們的構造函數:
PHP_FUNCTION(XCSecure_new)......{
XCSecure * instance;
char* s;
int len = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &s, &len) == FAILURE) ......{
RETURN_FALSE;
}
instance = new XCSecure(s);
zval * res_id;
MAKE_STD_ZVAL(res_id);
int id = ZEND_REGISTER_RESOURCE(NULL, instance, le_xc_secure);
ZVAL_LONG(res_id, id);
zend_hash_update(Z_OBJPROP_P(getThis()), Hash_Key, sizeof(Hash_Key), &res_id, sizeof(res_id), NULL);
}
首先我們取得用戶 new XCSecure($para)的參數,它是個字符串,然後,new了一個C++對象, 並註冊它爲一個資源實例,注意ZEND_REGISTER_RESOURCE的最後一個參數,是我們當時定義我們的資源析構函數的時候 ,返回的資源類型句柄。
然後我們把註冊資源實例以後返回的資源句柄,id 保存在一個Zval中。
當用戶在PHP中創建一個我們的類的實例的時候,Zend會調用一個zend_objects_new來創建一個標準的Zend對象,並把它存入zend_objects_store, 它是一個Bucket的數組,然後把我們的對象在數組中的索引(Zend中稱爲handler),存入一個zend_object_value結構,然後把這個zend_object_value存入一個zval結構,之後,zend會再調用我們的構造函數,並且this指針指向這個zval, 所以,在我們的構造函數中,就可以通過getThis(),來獲得這個zval,當然,也可以直接使用this_ptr;
註冊完資源後,構造函數就把得到的資源句柄(其實也是一個list的索引),存入this指向的zval的object的properties屬性中(這是一個哈希表),以後當用戶通過我們的對象調用類函數的時候,我們就可以通過this獲得這個對象,然後再通過對象的屬性中的資源句柄,獲得我們的C++對象。比如:
PHP_FUNCTION(XCSecure_genSec){
zval ** rsc;
XCSecure * secure;
if(zend_hash_find(Z_OBJPROP_P(getThis()), Hash_Key, sizeof(Hash_Key), (void **)&rsc) == SUCCESS){
secure = (XCSecure *)zend_fetch_resource(NULL, Z_LVAL_PP(rsc), NULL, NULL, 1, le_xc_secure);
}
ZVAL_STRING(return_value, secure->genSec(), 1);
}
首先我們通過getThis(),取得this指針, 然後通過Z_OBJPROP_P宏,來取得this指向的zval(一個對象object)的對象的propertis屬性,然後再通過zend_hash_find取得構造函數的時候保存的資源句柄。
之後,通過zend_fetch_resource取得構造函數創建的C++對象,之後你就可以象在C++中一樣,隨便使用這個C++對象了
7. 最後,我定義了個整型常量,這個也是另有它用;現在也可以在腳本中,訪問這個常量了 ,其中的CONST_SC表明我們的這個常量是大小寫敏感的,CONST_PERSISTENT顧名思義了。。
恩,到現在,我們的對象就已經加入到我們的模塊中了,你現在就可以簡單的make ; make install;然後來測試了 :)
最後:歡迎來信交流
附錄,關於本文涉及的Zend宏的定義:
typedef _zval_struct zval
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount;
zend_uchar type; /* active type */
zend_uchar is_ref;
};
zend_object
typedef struct _zend_object {
zend_class_entry *ce;
HashTable *properties;
HashTable *guards; /* protects from __get/__set ... recursion */
} zend_object;
zend_class_entry
struct _zend_class_entry {
char type;
char *name;
zend_uint name_length;
struct _zend_class_entry *parent;
int refcount;
zend_bool constants_updated;
zend_uint ce_flags;
HashTable function_table;
HashTable default_properties;
HashTable properties_info;
HashTable default_static_members;
HashTable *static_members;
HashTable constants_table;
struct _zend_function_entry *builtin_functions;
union _zend_function *constructor;
union _zend_function *destructor;
union _zend_function *clone;
union _zend_function *__get;
union _zend_function *__set;
union _zend_function *__unset;
union _zend_function *__isset;
union _zend_function *__call;
union _zend_function *__tostring;
union _zend_function *serialize_func;
union _zend_function *unserialize_func;
zend_class_iterator_funcs iterator_funcs;
/* handlers */
zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);
zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC);
int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* a class implements this interface */
/* serializer callbacks */
int (*serialize)(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC);
int (*unserialize)(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC);
zend_class_entry **interfaces;
zend_uint num_interfaces;
char *filename;
zend_uint line_start;
zend_uint line_end;
char *doc_comment;
zend_uint doc_comment_len;
struct _zend_module_entry *module;
};
zend_types.h
typedef struct _zend_object_value {
zend_object_handle handle;
zend_object_handlers *handlers;
} zend_object_value;
typedef unsigned int zend_object_handle;
zend_object_handlers.h
struct _zend_object_handlers {
/* general object functions */
zend_object_add_ref_t add_ref;
zend_object_del_ref_t del_ref;
zend_object_clone_obj_t clone_obj;
/* individual object functions */
zend_object_read_property_t read_property;
zend_object_write_property_t write_property;
zend_object_read_dimension_t read_dimension;
zend_object_write_dimension_t write_dimension;
zend_object_get_property_ptr_ptr_t get_property_ptr_ptr;
zend_object_get_t get;
zend_object_set_t set;
zend_object_has_property_t has_property;
zend_object_unset_property_t unset_property;
zend_object_has_dimension_t has_dimension;
zend_object_unset_dimension_t unset_dimension;
zend_object_get_properties_t get_properties;
zend_object_get_method_t get_method;
zend_object_call_method_t call_method;
zend_object_get_constructor_t get_constructor;
zend_object_get_class_entry_t get_class_entry;
zend_object_get_class_name_t get_class_name;
zend_object_compare_t compare_objects;
zend_object_cast_t cast_object;
zend_object_count_elements_t count_elements;
};
#define INIT_CLASS_ENTRY(class_container, class_name, functions) INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, func
tions, NULL, NULL, NULL)
#define INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset)
INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset, NULL,
NULL)
#define INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset, h
andle_propunset, handle_propisset)
{
class_container.name = strdup(class_name);
class_container.name_length = sizeof(class_name) - 1;
class_container.builtin_functions = functions;
class_container.constructor = NULL;
class_container.destructor = NULL;
class_container.clone = NULL;
class_container.serialize = NULL;
class_container.unserialize = NULL;
class_container.create_object = NULL;
class_container.interface_gets_implemented = NULL;
class_container.__call = handle_fcall;
class_container.__tostring = NULL;
class_container.__get = handle_propget;
class_container.__set = handle_propset;
class_container.__unset = handle_propunset;
class_container.__isset = handle_propisset;
class_container.serialize_func = NULL;
class_container.unserialize_func = NULL;
class_container.serialize = NULL;
class_container.unserialize = NULL;
class_container.parent = NULL;
class_container.num_interfaces = 0;
class_container.interfaces = NULL;
class_container.get_iterator = NULL;
class_container.iterator_funcs.funcs = NULL;
class_container.module = NULL;
}
ZEND_API zend_class_entry *zend_register_internal_class(zend_class_entry *orig_class_entry TSRMLS_DC)
{
return do_register_internal_class(orig_class_entry, 0 TSRMLS_CC);
}
static zend_class_entry *do_register_internal_class(zend_class_entry *orig_class_entry, zend_uint ce_flags TSRMLS_DC)
{
zend_class_entry *class_entry = malloc(sizeof(zend_class_entry));
char *lowercase_name = malloc(orig_class_entry->name_length + 1);
*class_entry = *orig_class_entry;
class_entry->type = ZEND_INTERNAL_CLASS;
zend_initialize_class_data(class_entry, 0 TSRMLS_CC);
class_entry->ce_flags = ce_flags;
class_entry->module = EG(current_module);
if (class_entry->builtin_functions) {
zend_register_functions(class_entry, class_entry->builtin_functions, &class_entry->function_table, MODULE_PERS
ISTENT TSRMLS_CC);
}
zend_str_tolower_copy(lowercase_name, orig_class_entry->name, class_entry->name_length);
zend_hash_update(CG(class_table), lowercase_name, class_entry->name_length+1, &class_entry, sizeof(zend_class_entry *)
, NULL);
free(lowercase_name);
return class_entry;
}
#define ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type)
zend_register_resource(rsrc_result, rsrc_pointer, rsrc_type);
ZEND_API int zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type)
{
int rsrc_id;
rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type);
if (rsrc_result) {
rsrc_result->value.lval = rsrc_id;
rsrc_result->type = IS_RESOURCE;
}
return rsrc_id;
}
ZEND_API int zend_list_insert(void *ptr, int type)
{
int index;
zend_rsrc_list_entry le;
TSRMLS_FETCH();
le.ptr=ptr;
le.type=type;
le.refcount=1;
index = zend_hash_next_free_element(&EG(regular_list));
zend_hash_index_update(&EG(regular_list), index, (void *) &le, sizeof(zend_rsrc_list_entry), NULL);
return index;
}
ZEND_API void *zend_object_store_get_object(zval *zobject TSRMLS_DC)
{
zend_object_handle handle = Z_OBJ_HANDLE_P(zobject);
return EG(objects_store).object_buckets[handle].bucket.obj.object;
}
zend_operators.h:#define Z_OBJ_HANDLE_P(zval_p) Z_OBJ_HANDLE(*zval_p)
zend_operators.h:#define Z_OBJVAL(zval) (zval).value.obj
zend_operators.h:#define Z_OBJ_HANDLE(zval) Z_OBJVAL(zval).handle
zend_globals_macros.h:# define EG(v) TSRMG(executor_globals_id, zend_executor_globals *, v)
TSRM.h:#define TSRMG(id, type, element) (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)
#define Z_OBJPROP(zval) Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC)