PHP扩展开发完整教程(中)

本文详细解析PHP内核中函数参数处理、数组操作及资源类型管理原理,深入探讨面向对象特性,包括类定义、属性、方法、接口与继承的实现机制。

第七章 函数的参数

  1. 最简单的获取函数调用者传递过来的参数便是使用zend_parse_parameters()函数,第一个参数是ZEND_NUM_ARGS() TSRMLS_CC代表参数个数,下一个参数是一个用于格式化的字符串,就像printf的第一个参数一样
Type Specifiers
Spec	Type	Locals
a	array	zval*
A	array or object		zval*
b	boolean	zend_bool
C	class	zend_class_entry*
d	double	double
f	function	zend_fcall_info*, zend_fcall_info_cache*
h	array	HashTable*
H	array or object	HashTable*
l	long	long
L	long (limits out-of-range LONG_MAX/LONG_MIN)	long
o	object	zval*
O	object (of specified zend_class_entry)	zval*, zend_class_entry*
p	string (a valid path)	char*, int
P	string (a valid path)	zend_string*
r	resource	zval*
s	string	char*, int
S   string	zend_string*
z	mixed	zval*
Z	mixed	zval**

例子如下:

ZEND_FUNCTION(sample_getlong) {

    long foo;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"l", &foo) == FAILURE)
    {
        RETURN_NULL();
    }
    php_printf("The integer value of the parameter is: %ld\n", foo);
    RETURN_TRUE;
}

所有的PHP语言中的复合类型参数都需要zval*类型来作为载体,因为它们都是内核自定义的一些数据结构

  1. 如果传递给函数的参数数量小于zend_parse_parameters()要接收的参数数量,它便会执行失败,并返回FAILURE。现改写如下函数
<?php
function sample_hello_world($name) {
    echo "Hello $name!\n";
}

内核写法

ZEND_FUNCTION(sample_hello_world) {
	char *name;
	int name_len;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",&name, &name_len) == FAILURE)
	{
		RETURN_NULL();
    }
	php_printf("Hello ");
	PHPWRITE(name, name_len);
	php_printf("!\n");
}

如果我们需要接收多个参数,可以直接在zend_parse_paramenters()的参数里罗列接收载体便可以了

ZEND_FUNCTION(sample_hello_world) { 
	char *name; 
	int name_len; 
	char *greeting; 
	int greeting_len; 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&name, &name_len, &greeting, &greeting_len) == FAILURE) 
	{ 
		RETURN_NULL(); 
	} 
	php_printf("Hello "); 
	PHPWRITE(greeting, greeting_len); 
	php_printf(" "); 
	PHPWRITE(name, name_len); 
	php_printf("!\n"); 
}

除了上面定义的参数,还有其它的三个参数来增强我们接收参数的能力,如下

|		它之前的参数都是必须的,之后的都是非必须的,也就是有默认值的。
!		如果接收了一个PHP语言里的null变量,
		则直接把其转成C语言里的NULL,而不是封装成IS_NULL类型的zval。
/		如果传递过来的变量与别的变量共用一个zval,而且不是引用,
		则进行强制分离,新的zval的is_ref__gc==0, and refcount__gc==1.
*01个或多个可变参数
+1个以上的可变参数
  1. 可变参数列表的取参数的用法与char*参数的获取方法类似
zval *args;
int num_args;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &num_args) == FAILURE) {
			return;
}
zval *args;
int num_args;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &args, &num_args) == FAILURE) {
			return;
}
  1. 函数参数的默认值
<?php
function sample_hello_world($name, $greeting='Mr./Ms.') {
    echo "Hello $greeting $name!\n";
}
sample_hello_world('Ginger Rogers','Ms.');
sample_hello_world('Fred Astaire');

内核写法

ZEND_FUNCTION(sample_hello_world) {
    char *name;
    int name_len;
    char *greeting = "Mr./Mrs.";
    int greeting_len = sizeof("Mr./Mrs.") - 1;


    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",
      &name, &name_len, &greeting, &greeting_len) == FAILURE) {
        RETURN_NULL();
    }
    php_printf("Hello ");
    PHPWRITE(greeting, greeting_len);
    php_printf(" ");
    PHPWRITE(name, name_len);
    php_printf("!\n");
}
  1. 如果你想让你的扩展能够兼容老版本的PHP,或者你只想以zval为载体来接收参数,便可以考虑使用zend_get_parameters()函数来接收参数
ZEND_FUNCTION(sample_onearg) {
    zval *firstarg;
    if (zend_get_parameters(ZEND_NUM_ARGS(), 1, &firstarg)== FAILURE) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING,"Expected at least 1 parameter.");
        RETURN_NULL();
    }
    /* Do something with firstarg... */
}

zend_get_parameters()在接收失败的时候,并不会自己抛出错误,它也不能方便的处理具有默认值的参数,它会自动的把所有符合copy-on-write的zval进行强制分离,生成一个崭新的copy送到函数内部
为了不对copy-on-write的变量进行分离操作,zend_get_parameters_ex()的参数是zval**类型的,而不是zval*

ZEND_FUNCTION(sample_onearg) {
    zval **firstarg;
    if (zend_get_parameters_ex(1, &firstarg) == FAILURE) {
    //WRONG_PARAM_COUNT宏,它的功能是抛出一个E_WARNING级别的错误信息,并自动return
        WRONG_PARAM_COUNT;
    }
    /* Do something with firstarg... */
}
  1. 有两种其它的zend_get_parameter_**函数,专门用来解决参数很多或者无法提前知道参数数目的问题
ZEND_FUNCTION(var_dump) {
    int i, argc = ZEND_NUM_ARGS();
    zval ***args;

    args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0);
    if (ZEND_NUM_ARGS() == 0 || zend_get_parameters_array_ex(argc, args) == FAILURE) {
    	efree(args);
		WRONG_PARAM_COUNT;
    }
    for (i=0; i<argc; i++) {
		php_var_dump(args[i], 1 TSRMLS_CC);
    }
    efree(args);
}
  1. 每一个arg info结构的声明都是通过ZEND_BEGIN_ARG_INFO()或者ZEND_BEGIN_ARG_INFO_EX()宏函数开始的,然后紧跟着几行ZEND_ARG_*INFO()宏函数,最终以ZEND_END_ARG_INFO()宏函数结束
    这是php中的count函数
ZEND_FUNCTION(sample_count_array)
{
    zval *arr;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a",&arr) == FAILURE)
    {
        RETURN_NULL();
    }
    RETURN_LONG(zend_hash_num_elements(Z_ARRVAL_P(arr)));
}

zend_parse_parameters()本身可以保证传递过来的参数是一个数组。但是如果我们通过zend_get_parameter()函数来接收参数的话,需要我们自己进行类型校对

 ZEND_BEGIN_ARG_INFO(php_sample_array_arginfo, 0)
        ZEND_ARG_ARRAY_INFO(0, arr, 0)
    ZEND_END_ARG_INFO()

PHP_FE(sample_count_array, php_sample_array_arginfo)

我们同样可以对参数中的对象进行校验,限制其是继承自某个类或者实现了某个接口等等。

ZEND_BEGIN_ARG_INFO(php_sample_class_arginfo, 0)
	//此时第一个参数的值是数字1,代表着以引用的方式传递
	ZEND_ARG_OBJ_INFO(1, obj, stdClass, 0)
ZEND_END_ARG_INFO()

第八章 使用HashTable与{数组}

  1. 创建并初始化一个HashTable非常简单,只要使用zend_hash_init函数即可
int zend_hash_init(
	HashTable *ht,
	uint nSize,
	hash_func_t pHashFunction,
	dtor_func_t pDestructor,
	zend_bool persistent
);

*ht是指针,指向一个HashTable,我们既可以&一个已存在的HashTable变量, 也可以通过emalloc()、pemalloc()等函数来直接申请一块内存, 不过最常用的方法还是用ALLOC_HASHTABLE(ht)宏来让内核自动的替我们完成这项工作。 ALLOC_HASHTABLE(ht)所做的工作相当于ht = emalloc(sizeof(HashTable));

nSize代表着这个HashTable可以拥有的元素的最大数量(HashTable能够包含任意数量的元素, 这个值只是为了提前申请好内存,提高性能,省的不停的进行rehash操作)。 在我们添加新的元素时,这个值会根据情况决定是否自动增长,有趣的是, 这个值永远都是2的次方,如果你给它的值不是一个2的次方的形式, 那它将自动调整成大于它的最小的2的次方值。 它的计算方法就像这样:nSize = pow(2, ceil(log(nSize, 2)));
pHashFunction是早期的Zend Engine中的一个参数,为了兼容没有去掉它, 但它已经没有用处了,所以我们直接赋成NULL就可以了。在原来, 它其实是一个钩子,用来让用户自己hook一个散列函数,替换php默认的DJBX33A算法实现。
pDestructor也代表着一个回调函数,当我们删除或者修改HashTable中其中一个元素时候便会调用, 它的函数原型必须是这样的:void method_name(void pElement);这里的pElement是一个指针,指向HashTable中那么将要被删除或者修改的那个数据,而数据的类型往往也是个指针。
persistent是最后一个参数,它的含义非常简单。 如果它为true,那么这个HashTable将永远存在于内存中,而不会在RSHUTDOWN阶段自动被注销掉。 此时第一个参数ht所指向的地址必须是通过pemalloc()函数申请的。
2. 创建{数组}

ZEND_FUNCTION(sample_array)
{
	array_init(return_value);
}

return_value是zval*类型的,所以我们直接对它调用array_init()函数即可,即把它初始化成了一个空数组
3. 向数组添加元素

array_init(arrval);

add_assoc_long(zval *arrval, char *key, long lval);
add_index_long(zval *arrval, ulong idx, long lval);
//索引值是其自己计算出来的
add_next_index_long(zval *arrval, long lval);

//add_assoc_*系列函数:
add_assoc_null(zval *aval, char *key);
add_assoc_bool(zval *aval, char *key, zend_bool bval);
add_assoc_long(zval *aval, char *key, long lval);
add_assoc_double(zval *aval, char *key, double dval);
add_assoc_string(zval *aval, char *key, char *strval, int dup);
add_assoc_stringl(zval *aval, char *key,char *strval, uint strlen, int dup);
add_assoc_zval(zval *aval, char *key, zval *value);

//add_index_*系列函数:
ZEND_API int add_index_long		(zval *arg, ulong idx, long n);
ZEND_API int add_index_null		(zval *arg, ulong idx			);
ZEND_API int add_index_bool		(zval *arg, ulong idx, int b	);
ZEND_API int add_index_resource	(zval *arg, ulong idx, int r	);
ZEND_API int add_index_double	(zval *arg, ulong idx, double d);
ZEND_API int add_index_string	(zval *arg, ulong idx, const char *str, int duplicate);
ZEND_API int add_index_stringl	(zval *arg, ulong idx, const char *str, uint length, int duplicate);
ZEND_API int add_index_zval		(zval *arg, ulong index, zval *value);

//add_next_index_long函数:
ZEND_API int add_next_index_long		(zval *arg, long n	);
ZEND_API int add_next_index_null		(zval *arg			);
ZEND_API int add_next_index_bool		(zval *arg, int b	);
ZEND_API int add_next_index_resource	(zval *arg, int r	);
ZEND_API int add_next_index_double		(zval *arg, double d);
ZEND_API int add_next_index_string		(zval *arg, const char *str, int duplicate);
ZEND_API int add_next_index_stringl		(zval *arg, const char *str, uint length, int duplicate);
ZEND_API int add_next_index_zval		(zval *arg, zval *value);

例子

ZEND_FUNCTION(sample_array)
{
	zval *subarray;

	array_init(return_value);
	
	/* Add some scalars */
	add_assoc_long(return_value, "life", 42);
	add_index_bool(return_value, 123, 1);
	add_next_index_double(return_value, 3.1415926535);
	
	/* Toss in a static string, dup'd by PHP */
	add_next_index_string(return_value, "Foo", 1);
	
	/* Now a manually dup'd string */
	add_next_index_string(return_value, estrdup("Bar"), 0);

	/* Create a subarray */
	MAKE_STD_ZVAL(subarray);
	array_init(subarray);
	
	/* Populate it with some numbers */
	add_next_index_long(subarray, 1);
	add_next_index_long(subarray, 20);
	add_next_index_long(subarray, 300);
	
	/* Place the subarray in the parent */
	add_index_zval(return_value, 444, subarray);
}

第九章 PHP中的资源类型

  1. 先描述下{资源}类型在内核中的结构
//每一个资源都是通过它来实现的。
 typedef struct _zend_rsrc_list_entry {
  	void *ptr; 
  	int type; 
  	int refcount;
}zend_rsrc_list_entry;
  1. 为了区分不同类型的资源,比如一个是文件句柄,一个是mysql链接,我们需要为其赋予不同的分类名称。首先,我们需要先把这个分类添加到程序中去。这一步的操作可以在MINIT中来做
#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "山寨文件描述符"
static int le_sample_descriptor;
ZEND_MINIT_FUNCTION(sample)
{
//第一个回调函数会在脚本中的相应类型的资源变量被释放掉的时候触发,
//比如作用域结束了,或者被unset()掉了。
//第二个参数它将会在Web服务器进程终止时调用,相当于在MSHUTDOWN阶段被内核调用
	le_sample_descriptor = zend_register_list_destructors_ex(NULL, NULL, PHP_SAMPLE_DESCRIPTOR_RES_NAME,module_number);
	return SUCCESS;
}

接下来,我们把定义好的MINIT阶段的函数添加到扩展的module_entry里去

ZEND_MINIT(sample), /* MINIT */
  1. 创建资源
PHP_FUNCTION(sample_fopen)
{
	FILE *fp;
	char *filename, *mode;
	int filename_len, mode_len;
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&filename, &filename_len,&mode, &mode_len) == FAILURE)
    {
		RETURN_NULL();
	}
	if (!filename_len || !mode_len)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid filename or mode length");
			RETURN_FALSE;
	}
	fp = fopen(filename, mode);
	if (!fp)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING,"Unable to open %s using mode %s",filename, mode);
			RETURN_FALSE;
	}
	//将fp添加到资源池中去,并标记它为le_sample_descriptor类型的。
	ZEND_REGISTER_RESOURCE(return_value,fp,le_sample_descriptor);
}

我们先来定义第一种回调函数。

static void php_sample_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
    FILE *fp = (FILE*)rsrc->ptr;
    fclose(fp);
}
le_sample_descriptor = zend_register_list_destructors_ex(
        php_sample_descriptor_dtor,
        NULL,
        PHP_SAMPLE_DESCRIPTOR_RES_NAME,
        module_number);

现在,如果脚本中得到了一个上述类型的资源变量,当它被unset的时候,或者因为作用域执行完被内核释放掉的时候都会被内核调用底层的php_sample_descriptor_dtor来预处理它

<?php
  $fp = sample_fopen("/home/jdoe/notes.txt", "r");
  unset($fp);
  1. 对于资源变量,我们必须能够通过它找到相应的最终数据才行!
ZEND_FUNCTION(sample_fwrite)
{
	FILE *fp;
	zval *file_resource;
	char *data;
	int data_len;
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",&file_resource, &data, &data_len) == FAILURE )
	{
		RETURN_NULL();
	}
	/* Use the zval* to verify the resource type and
	 * retrieve its pointer from the lookup table */
	ZEND_FETCH_RESOURCE(fp,FILE*,&file_resource,-1,PHP_SAMPLE_DESCRIPTOR_RES_NAME,le_sample_descriptor);
	
	/* Write the data, and
	 * return the number of bytes which were
	 * successfully written to the file */
	RETURN_LONG(fwrite(data, 1, data_len, fp));
}

zend_parse_parameters()函数中的r占位符代表着接收资源类型的变量,它的载体是一个zval*
我们也可以通过另一种方法来获取我们最终想要的数据。

ZEND_FUNCTION(sample_fwrite)
{
    FILE *fp;
    zval *file_resource;
    char *data;
    int data_len, rsrc_type;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",&file_resource, &data, &data_len) == FAILURE ) {
        RETURN_NULL();
    }
    fp = (FILE*)zend_list_find(Z_RESVAL_P(file_resource),&rsrc_type);
    if (!fp || rsrc_type != le_sample_descriptor) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid resource provided");
        RETURN_FALSE;
    }
    RETURN_LONG(fwrite(data, 1, data_len, fp));
}

推荐使用ZEND_FETCH_RESOURCE()宏函数。
4. 关闭资源

PHP_FUNCTION(sample_fclose)
{
    FILE *fp;
    zval *file_resource;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r",&file_resource) == FAILURE ) {
        RETURN_NULL();
    }
    
    /* While it's not necessary to actually fetch the
     * FILE* resource, performing the fetch provides
     * an opportunity to verify that we are closing
     * the correct resource type. */
    ZEND_FETCH_RESOURCE(fp, FILE*, &file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor);
    
    /* Force the resource into self-destruct mode */
    zend_hash_index_del(&EG(regular_list),Z_RESVAL_P(file_resource));
    RETURN_TRUE;
}
  1. 。Mysql的长链接在PHP内核中其实就是一种持久{资源}。
  2. 我们删除一个{资源}的时候,其实是去EG(regular_list)中将其删掉,持久{资源},存储在另一个HashTable中:EG(persistent_list)。
  3. 当我们使用{资源},尤其是持久{资源}时,一定要保证获取出来的{资源}仍然是有效的、可以使用的。如果它失效了,我们必须将其从persistent list中移除。下面就是一个检测socket有效性的例子:
if (zend_hash_find(&EG(persistent_list), hash_key,hash_key_len + 1, (void**)&socket) == SUCCESS)
{
    if (php_sample_socket_is_alive(socket->ptr))
    {
        ZEND_REGISTER_RESOURCE(return_value,socket->ptr, le_sample_socket);
        return;
    }
    zend_hash_del(&EG(persistent_list),hash_key, hash_key_len + 1);
}

第十章 PHP中的面向对象

  1. 如果我们想获得一个名字为myclass的类该怎么做呢?首先我们定义一个zend_class_entry变量,并为它设置名字,最后注册到runtime中去
zend_class_entry *myclass_ce;


static zend_function_entry myclass_method[] = {
    { NULL, NULL, NULL }
};

ZEND_MINIT_FUNCTION(sample3)
{
    zend_class_entry ce;
    
    //"myclass"是这个类的名称。
    INIT_CLASS_ENTRY(ce, "myclass",myclass_method);
    myclass_ce = zend_register_internal_class(&ce TSRMLS_CC);//PHP7中取掉最后的TSRMLS_CC
    return SUCCESS;
}

这样我们便定义了一个类myclass,而且我们可以正常的在PHP语言中使用它,比如

<?php
$obj = new myclass();
  1. 我们正式的定义一个类。首先我给出PHP语言的实现
<?php
class myclass
{
	public $public_var;
	private $private_var;
	protected $protected_var;
	
	public static $static_var;
	
	public function __construct()
	{
		echo "我是__construct方法\n";
	}
	
	public function public_method()
	{
		echo "我是public类型的方法\n";
	}
	
	public function private_method()
	{
		echo "我是private类型的方法\n";
	}
	
	public function protected_method()
	{
		echo "我是protected类型的方法\n";
	}
	
	public static function static_var()
	{
		echo "我是static类型的方法\n";
	}
}

定义类的第一步,便是先定义好这个类的zend_class_entry,这一步操作是在MINIT阶段完成的

static zend_function_entry myclass_method[]=
{NULL,NULL,NULL};

PHP_MINIT_FUNCTION(test)
{
	zend_class_entry ce;
	INIT_CLASS_ENTRY(ce,"myclass",myclass_method);
	zend_register_internal_class(&ce TSRMLS_CC);
	return SUCCESS;
}

这就是最简单的一个类,没有属性没有方法,但是可以使用了
某个类的zend_class_entry会经常用到,所以我们一般会把它保存在一个变量里,供扩展中其它地方的程序使用,所以上述的代码组合一般是这样的

end_class_entry *myclass_ce;

static zend_function_entry myclass_method[]={
	{NULL,NULL,NULL}
};

ZEND_MINIT_FUNCTION(test)
{
	zend_class_entry ce;
	INIT_CLASS_ENTRY(ce,"myclass",myclass_method);
	myclass_ce = zend_register_internal_class(&ce TSRMLS_CC);
	return SUCCESS;
}
  1. 我们可以用zend_declare_property*系列函数来为类定义属性
    我们为上面的myclass类定义一个名为“public_var”的属性,默认值为null,访问权限为public
ZEND_MINIT_FUNCTION(test) { 
	zend_class_entry ce; 	
	INIT_CLASS_ENTRY(ce,"myclass",myclass_method);
	myclass_ce = zend_register_internal_class(&ce TSRMLS_CC);
	
	//定义属性
	zend_declare_property_null(myclass_ce, "public_var", strlen("public_var"), ZEND_ACC_PUBLIC TSRMLS_CC);
	return SUCCESS;
}

ZEND_ACC_PUBLIC是ZEND_ACC系列掩码中的一个,代表着public,其余的还有ZEND_ACC_PRIVATE,ZEND_ACC_PROTECTED等等
4. 为类定义方法比较繁琐一些,首先我们先回顾一下zend_function_entry结构, 首先,定义这个函数的C语言部分,不过这一次我们使用的是ZEND_METHOD(同PHP_METHOD源代码中多用PHP_METHOD)

ZEND_METHOD( myclass , public_method )
{
	php_printf("我是public类型的方法\n");
}

ZEND_METHOD( myclass , __construct )
{
	php_printf("我是__construct方法\n");
}

//然后,用PHP_METHOD声明public_method和__construct。

PHP_METHOD(myclass, public_method);
PHP_METHOD(myclass, __construct);

//再定义一个zend_function_entry(ZEND_ME同PHP_ME)
zend_function_entry myclass_method[]=
{
	ZEND_ME(myclass,	public_method,	NULL,	ZEND_ACC_PUBLIC)
	ZEND_ME(myclass,	__construct,	NULL,	ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
	{NULL,	NULL,	NULL}
}

//最后,在MINIT阶段register internal class的时候将它作为一个参数传递进去
ZEND_MINIT_FUNCTION(test)
{
	zend_class_entry ce;
	
	//这里使用了myclass_method这个zend_function_entry
	INIT_CLASS_ENTRY(ce,"myclass",myclass_method);
	
	myclass_ce = zend_register_internal_class(&ce TSRMLS_CC);
	return SUCCESS;
}

现在我们在PHP脚本中调用一下这个方法,看看输出结果:

<?php
$obj = new myclass();
$obj->public_method();

这里在定义__construct方法的时候,使用到了ZEND_ACC_CTOR,它的作用便是声明这个方法是此类的构造函数,而ZEND_ACC_PUBLIC|ZEND_ACC_CTOR是我们常见的掩码或运算
而定义static的属性与方法只是在掩码标志中加入ZEND_ACC_STATIC即可。 下面详细的罗列出了所有掩码,fn_flags代表可以在定义方法时使用,zend_property_info.flags代表可以在定义属性时使用

#define ZEND_ACC_STATIC                     0x01     /* fn_flags, zend_property_info.flags */
#define ZEND_ACC_ABSTRACT                   0x02     /* fn_flags */
#define ZEND_ACC_FINAL                      0x04     /* fn_flags */
#define ZEND_ACC_IMPLEMENTED_ABSTRACT       0x08     /* fn_flags */
#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS    0x10     /* ce_flags */
#define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS    0x20     /* ce_flags */
#define ZEND_ACC_FINAL_CLASS                0x40     /* ce_flags */
#define ZEND_ACC_INTERFACE                  0x80     /* ce_flags */
#define ZEND_ACC_INTERACTIVE                0x10     /* fn_flags */
#define ZEND_ACC_PUBLIC                     0x100    /* fn_flags, zend_property_info.flags */
#define ZEND_ACC_PROTECTED                  0x200    /* fn_flags, zend_property_info.flags */
#define ZEND_ACC_PRIVATE                    0x400    /* fn_flags, zend_property_info.flags */
#define ZEND_ACC_PPP_MASK	(ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
#define ZEND_ACC_CHANGED                    0x800    /* fn_flags, zend_property_info.flags */
#define ZEND_ACC_IMPLICIT_PUBLIC            0x1000   /* zend_property_info.flags; unused (1) */
#define ZEND_ACC_CTOR                       0x2000   /* fn_flags */
#define ZEND_ACC_DTOR                       0x4000   /* fn_flags */
#define ZEND_ACC_CLONE                      0x8000   /* fn_flags */
#define ZEND_ACC_ALLOW_STATIC               0x10000  /* fn_flags */
#define ZEND_ACC_SHADOW                     0x20000  /* fn_flags */
#define ZEND_ACC_DEPRECATED                 0x40000  /* fn_flags */
#define ZEND_ACC_CLOSURE                    0x100000 /* fn_flags */
#define ZEND_ACC_CALL_VIA_HANDLER           0x200000 /* fn_flags */
  1. 为类定义常量,只涉及到一组函数,可以查看Zend/zend_API.h
ZEND_API int zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value TSRMLS_DC);
ZEND_API int zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length TSRMLS_DC);
ZEND_API int zend_declare_class_constant_long(zend_class_entry *ce, const char *name, size_t name_length, long value TSRMLS_DC);
ZEND_API int zend_declare_class_constant_bool(zend_class_entry *ce, const char *name, size_t name_length, zend_bool value TSRMLS_DC);
ZEND_API int zend_declare_class_constant_double(zend_class_entry *ce, const char *name, size_t name_length, double value TSRMLS_DC);
ZEND_API int zend_declare_class_constant_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_length TSRMLS_DC);
ZEND_API int zend_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value TSRMLS_DC);

mongoclient类中的用法

zend_declare_class_constant_string(mongo_ce_MongoClient, "DEFAULT_HOST", strlen("DEFAULT_HOST"), "localhost" TSRMLS_CC);
  1. 定义一个接口还是很方便的,我先给出一个PHP语言中的形式
<?php
interface i_myinterface
{
	public function hello();
}

在扩展中是这样的

zend_class_entry *i_myinterface_ce;

static zend_function_entry i_myinterface_method[]={
	ZEND_ABSTRACT_ME(i_myinterface, hello, NULL) //注意这里的null指的是arginfo
	ZEND_FE_END
};

ZEND_MINIT_FUNCTION(test)
{	
	zend_class_entry ce;
	INIT_CLASS_ENTRY(ce, "i_myinterface", i_myinterface_method);

	i_myinterface_ce = zend_register_internal_interface(&ce TSRMLS_CC);
	return SUCCESS;
}

使用ZEND_ABSTRACT_ME()宏函数来为这个接口添加函数
下面我们在PHP语言中使用这个接口

<?php
class sample implements i_myinterface
{
	public $name = "hello world!";
	
	public function hello()
	{
		echo $this->name."\n";
	}
}

$obj = new sample();
$obj->hello();
  1. 在定义一个类时往往会使其继承某个父类或者实现某个接口,在扩展中实现这个功能非常方便。下面我先给出PHP语言中的代码。
<?php
interface i_myinterface
{
	public function hello();
}

class parent_class implements i_myinterface
{
	public function hello()
	{
		echo "Good Morning!\n";
	}
}

final class myclass extends parent_class
{
	public function call_hello()
	{
		$this->hello();
	}
}

在PHP扩展中的实现应该是这样的

//三个zend_class_entry
zend_class_entry *i_myinterface_ce,*parent_class_ce,*myclass_ce;

//parent_class的hello方法
ZEND_METHOD(parent_class,hello)
{
	php_printf("hello world!\n");
}

ZEND_METHOD(myclass,call_hello)
{
	//这里涉及到如何调用对象的方法,详细内容下一章叙述
	zval *this_zval;
	this_zval = getThis();
	zend_call_method_with_0_params(&this_zval,myclass_ce,NULL,"hello",NULL);
	//zend_call_method_with_0_params(this_zval,myclass_ce,NULL,"hello",NULL); //php7
}

//各自的zend_function_entry
static zend_function_entry i_myinterface_method[]={
	ZEND_ABSTRACT_ME(i_myinterface,	hello,	NULL)
	ZEND_FE_END
};

static zend_function_entry parent_class_method[]={
	ZEND_ME(parent_class,hello,NULL,ZEND_ACC_PUBLIC)
	ZEND_FE_END
};

static zend_function_entry myclass_method[]={
	ZEND_ME(myclass,call_hello,NULL,ZEND_ACC_PUBLIC)
	ZEND_FE_END
};


ZEND_MINIT_FUNCTION(test)
{
	zend_class_entry ce,p_ce,i_ce;
	INIT_CLASS_ENTRY(i_ce,"i_myinterface",i_myinterface_method);
	i_myinterface_ce = zend_register_internal_interface(&i_ce TSRMLS_CC);
	
	
	//定义父类,最后使用zend_class_implements函数声明它实现的接口
	INIT_CLASS_ENTRY(p_ce,"parent_class",parent_class_method);
	parent_class_ce = zend_register_internal_class(&p_ce TSRMLS_CC);
	zend_class_implements(parent_class_ce TSRMLS_CC,1,i_myinterface_ce);

	//定义子类,使用zend_register_internal_class_ex函数
	INIT_CLASS_ENTRY(ce,"myclass",myclass_method);
	
	//最后一个参数一般指定为NULL,否则如果指定最后一个参数时,如果找不到此类则此函数会返回NULL
	myclass_ce = zend_register_internal_class_ex(&ce,parent_class_ce,"parent_class" TSRMLS_CC);//php5
	
	//注意该函数在php7中只有两个参数
	myclass_ce = zend_register_internal_class_ex(&ce,parent_class_ce );//php7
	
	//注意:ZEND_ACC_FINAL是用来修饰方法的,而ZEND_ACC_FINAL_CLASS是用来修饰类的
	myclass_ce->ce_flags |= ZEND_ACC_FINAL_CLASS;
	return SUCCESS;
}

这样,当我们在PHP语言中进行如下操作时,便会得到预期的输出

<?php
	$obj = new myclass();
	$obj->hello();
如果用PHP不能再满足你的需求,最好的办法就是开发PHP扩展。这有一些好处: 1、增加自己的特殊功能。 2、保护自己的专利代码。 这是几年前的一篇英文文章,现在已被翻译成中文版的。 作者应该是hshq_cn。 链接是:http://bbs3.chinaunix.net/thread-1028798-1-1.html。 现我将此转变为PDF文件,仅有兴趣者参阅。同时非常感谢 原作者及hshq_cn,给我们带来的这么好的资料。里面还有一个幻灯片的,也是很有帮助的文档。另外,再提供一篇相关的文章(http://www.programbbs.com/doc/4083.htm): 编写php的extension实例的方法 所属类别:JSP 推荐指数:★★☆ 文档人气:161 本周人气:1 发布日期:2008-7-3 一、说明 前端时间因为客户的原因折腾了一下asp的扩展,在ATL的帮助下写一个asp的模块还是很容易的。不巧的时刚刚折腾完asp的COM就碰到另一个客户的问题。客户想给系统集成ICBC的接口,但是用ICBC的接口需要用他们的提供的库函数去 1. sign对发送的数据进行签名 2. getcertid获取用户证书的版本 3. verifySign对签名后的数据进行验证 问题是ICBC只给了现成的COM组件,意味在只能在Win的主机上使用。俺们公司只有linux的主机,在*nix上就要自己想办法调用ICBC给的静态库了。对此我们有两个想法 1.用ICBC的静态库做一个独立的执行文件,用PHP的系统调用函数来执行这个独立的执行文件 2.将ICBC的静态库做出一个PHP扩展 方法一应该比较简单,但是远不如方法二的灵活。搞成PHP扩展,只要服务器编译一次,服务器上的所有客户都可以用的。 有ASP的前科,俺觉得搞个PHP的也不是什么难事。操起google搜了一通,结果发现Zend已经写了一个如何编写php extension的教程: http://devzone.zend.com/article/1021-Extension-Writing-Part-I-Introduction-to-PHP-and-Zend 浏览完牛人的大作,更是信心十足,php扩展其实很简单,分七步走: 1. 制作编译配置文件:config.m4 2. 执行phpize生成扩展的框架 3. 在生成的php_xxx.h中声明自己写的函数 4. 在xxx.c中实现自己的函数 5. 编译自己的扩展 6. 将生成的xxx.so拷贝到php.ini中指定的extensions_dir 7. 在php.ini中打开xxx.so的调用 此问题问题唯一搞的地方就是在config.m4中折腾出正确的Makefile,因为Zend的教程中没有提到,俺自己也折腾了好久,才搞出来。 二、实际操作 1.建立工作环境 将php源码包解开,我的版本的php-4.4.4,转到源码包中的ext目录建立一个新的目录叫icbc,然后在icbc目录下touch三个文件config.m4、php_icbc.h、icbc.c 2.建立config.m4 内容如下: PHP_ARG_ENABLE(icbc, whether to enable ICBC support, [ --enable-icbc Enable ICBC support]) if test \"$PHP_ICBC\" = \"yes\"; then AC_DEFINE(HAVE_ICBC, 1, [Whether you have ICBC]) if test -f ./libicbcapi.a; then PHP_ADD_LIBRARY_WITH_PATH(icbcapi, ./, ICBCAPI_SHARED_LIBADD) PHP_SUBST(ICBCAPI_SHARED_LIBADD) AC_MSG_RESULT(checking for libicbcapi.a is OK) else AC_MSG_RESULT(libicbcapi.a not found) AC_MSG_ERROR(Please make sure the libicbcapi.a is in current directory) [Page] fi PHP_NEW_EXTENSION(icbc, icbc.c, $ext_shared) fi 第三行判断是否要启用icbc扩展, 第五行判断ICBC的静态库是否在当前目录(phpdir/ext/icbc)下 第六、七行将ICBC的静态库加入到编译环境中 3.在php_icbc.h中声明我们要导出的函数icbc_sign、icbc_vsign、icbc_getCertID #ifndef PHP_ICBC_H #define PHP_ICBC_H extern zend_module_entry icbc_module_entry; #define phpext_icbc_ptr &icbc_module_entry #ifdef PHP_WIN32 #define PHP_ICBC_API __declspec(dllexport) #else #define PHP_ICBC_API #endif #ifdef ZTS #include \"TSRM.h\" #endif PHP_MINIT_FUNCTION(icbc); PHP_MSHUTDOWN_FUNCTION(icbc); PHP_RINIT_FUNCTION(icbc); PHP_RSHUTDOWN_FUNCTION(icbc); PHP_MINFO_FUNCTION(icbc); /*Modify youself here*/ PHP_FUNCTION(icbc_sign); PHP_FUNCTION(icbc_vsign); PHP_FUNCTION(icbc_getCertID); /****End of Self control section***/ #ifdef ZTS #define ICBC_G(v) TSRMG(icbc_globals_id, zend_icbc_globals *, v) #else #define ICBC_G(v) (icbc_globals.v) #endif #endif /* PHP_ICBC_H */ 涉及到我们也就 PHP_FUNCTION(icbc_sign); PHP_FUNCTION(icbc_vsign); PHP_FUNCTION(icbc_getCertID); 其他的都是PHP各个状态的入口函数声明 4.编写这三个函数的实现: #ifdef HAVE_CONFIG_H #include \"config.h\" #endif #include \"php.h\" #include \"php_ini.h\" #include \"ext/standard/info.h\" #include \"php_icbc.h\" #include \"icbcapi.h\" static int le_icbc; zend_function_entry icbc_functions[] = { PHP_FE(icbc_sign,NULL) PHP_FE(icbc_vsign,NULL) PHP_FE(icbc_getCertID,NULL) {NULL, NULL, NULL} /* Must be the last line in icbc_functions[] */ }; zend_module_entry icbc_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif \"icbc\", icbc_functions, PHP_MINIT(icbc), PHP_MSHUTDOWN(icbc), PHP_RINIT(icbc), /* Replace with NULL if there’s nothing to do at request start */ PHP_RSHUTDOWN(icbc), /* Replace with NULL if there’s nothing to do at request end */ [Page] PHP_MINFO(icbc), #if ZEND_MODULE_API_NO >= 20010901 \"0.1\", /* Replace with version number for your extension */ #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_ICBC ZEND_GET_MODULE(icbc) #endif PHP_MINIT_FUNCTION(icbc) { return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(icbc) { return SUCCESS; } PHP_RINIT_FUNCTION(icbc) { return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(icbc) { return SUCCESS; } PHP_MINFO_FUNCTION(icbc) { php_info_print_table_start(); php_info_print_table_header(2, \"icbc support\", \"enabled\"); php_info_print_table_end(); } PHP_FUNCTION(icbc_sign) { char* src; int srclen; char* pkey; int keylen; char* keypass; int keypasslen; char* signedbuf; int signedbuflen; FILE* fp; char key[2000]; int rcc; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,\"sss\",&src,&srclen,&pkey,&keylen,&keypass,&keypasslen) == FAILURE){ return; } fp = fopen(pkey,\"rb\"); if(fp == NULL) { return; } fseek(fp,2,SEEK_SET); fread((void*)key,609,1,fp); fclose(fp); if(rcc = sign(src,srclen,key,607,keypass,&signedbuf,&signedbuflen) >= 0){ base64enc(signedbuf,signedbuflen,&signedbuf,&signedbuflen); src = estrndup(signedbuf,signedbuflen); if(signedbuf != NULL) infosec_free(signedbuf); RETURN_STRING(src,1); [Page] }else{ RETURN_LONG(rcc); } }PHP_FUNCTION(icbc_vsign) { char* src; int srclen; char* cert; int certlen; char* vsignedbuf; int vsignedbuflen; FILE* fp; char vcert[2000]; int rcc; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,\"sss\",&src,&srclen,&cert,&certlen,&vsignedbuf,&vsignedbuflen) == FAILURE){ return; } fp = fopen(cert,\"rb\"); if(fp == NULL) { return; } fread((void*)vcert,1525,1,fp); fclose(fp); base64dec(vsignedbuf,vsignedbuflen,&vsignedbuf,&vsignedbuflen); if(rcc = verifySign(src,srclen,vcert,1525,vsignedbuf,vsignedbuflen) >= 0){ if(vsignedbuf != NULL) infosec_free(vsignedbuf); RETURN_TRUE; }else{ if(vsignedbuf != NULL) infosec_free(vsignedbuf); RETURN_LONG(rcc); } } PHP_FUNCTION(icbc_getCertID) { char* arg; char* certid; int arg_len,certidlen; FILE* fp; char cert[2000]; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,\"s\", &arg,&arg_len) == FAILURE){ return; } fp = fopen(arg,\"rb\"); if(fp == NULL) { return; } fread((void*)cert,1525,1,fp); fclose(fp); [Page] if(!getCertID(cert,1525,&certid,&certidlen)) { arg = estrndup(certid,certidlen); if(certid != NULL) infosec_free(certid); RETURN_STRING(arg,1); }else{ return; } } 先在zend_function_entry icbc_functions[]数组中放入我们的要实现的函数名,然后是一堆php各个状态入口函数,详情请看Zend的教程。最后是在PHP_FUNCTION宏定义中放我们声明函数的具体实现。具体实现时难点也就是参数的传入和结果传出,还好PHP已经为我们做了很好的抽象。在Zend的教程中也有详尽的说明,俺就不啰嗦了。关键代码照搬icbc的test.c就行了。 5.编译安装我们的库 先将ICBC的头文件考到当前目录,重命名为icbcapi.php,将静态库也cp过来,重命名为*nix的标准形式libicbcapi.a,然后运行 phpize 生成configure,运行 ./configure --enable-icbc 生成Makefile,这里有一个很搞的地方,在生成的Makefile中最后一句中指定ICBC静态库的地方错了,正确的应该是(红色标记地方): $(LIBTOOL) --mode=link $(CC) $(COMMON_FLAGS) $(CFLAGS_CLEAN) $(EXTRA_CFLAGS) $(LDFLAGS) -o $@ -export-dynamic -avoid-version -prefer-pic -module -rpath $(phplibdir) $(EXTRA_LDFLAGS) $(shared_objects_icbc) $(ICBCAPI_SHARED_LIBADD) 改好Makefile后就可以执行 make 如果一切顺利的话会在modules中得到我们的icbc.so,将我们的icbc.so拷贝到/usr/local/lib/php/extensions目录下,然后在php.ini中确认extensions_dir的值是/usr/local/lib/php/extensions,然后加入这句话 extension=icbc.so 重启apache后,就可以在php中直接调用这三个函数了 6.测试程序,要将测试的证书和key文件放到php测试文件的当前目录 <?php $realpath = dirname(__FILE__); $key = $realpath.\"/user.key\"; $cert = $realpath.\"/user.crt\"; $src = \"zzz\"; $passwd = \"12345678\"; echo \"The Cert file information is \"; echo icbc_getCertID($cert); echo \"<br>\"; $b64sdata = icbc_sign($src,$key,$passwd); echo \"The string \".$src.\" encrypt by icbc api is \".$b64sdata.\"(base64 encoded)<br>\"; echo \"Now we check it weather is correct....<br>\"; if(icbc_vsign(\"zzz\",$cert,$b64sdata)){ echo \"The signtrue to \".$src.\" is right!!<br>\"; [Page] }else{ echo \"The signtrue to \".$src.\" is wrong!!<br>\"; exit(); } ?>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值