· 作者: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。 如下:
XCSecure.h
#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的成员函数:
function_entry php5_secure_method[]
=
...
{
PHP_FALIAS(XCSecure, XCSecure_new, NULL)
PHP_FALIAS(genSec, XCSecure_genSec, NULL)
...{NULL, NULL, NULL}
}
;
当然,你还需要自己定义这些函数;
5. 我选择在PHP_MINIT阶段来初始化我的类;
PHP_MINIT_FUNCTION(getCookie)...
...
{
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宏的定义:
zend.h
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)