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();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值