IT技术博客大学习 共学习 共进步

PHP内核介绍及扩展开发指南―Extensions 的编写

搜索研发部官方博客 2011-05-31 13:59:00 浏览 5,423 次

Extensions 的编写

    理解了这些运行机制以后,本章着手介绍Extensions 的编写,但凡写程序的人都知道hello world,那好,就从hello world开始。

1.1Hello World

    这是摘自《PHP手册》的示例程序:

/* include standard header */
#include "php.h"

/* declaration of functions to be exported */
ZEND_FUNCTION(first_module);

/* compiled function list so Zend knows what\'s in this module */
zend_function_entry firstmod_functions[] =
{
   ZEND_FE(first_module, NULL)
   {NULL, NULL, NULL}
};

/* compiled module information */
zend_module_entry firstmod_module_entry =
{
   STANDARD_MODULE_HEADER,
   "First Module",
   firstmod_functions,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NO_VERSION_YET,
   STANDARD_MODULE_PROPERTIES
};

/* implement standard "stub" routine to introduce ourselves to Zend */
#if COMPILE_DL_FIRST_MODULE
ZEND_GET_MODULE(firstmod)
#endif

/* implement function that is meant to be made available to PHP */
ZEND_FUNCTION(first_module)
{
   long parameter;
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", Pmeter)
== FAILURE)
return;
RETURN_LONG(parameter);
}

    这段代码实现了一个简单的extension,首先它包含了“php.h”,这是所有extensions都需要包含的头文件,它定义、声明了我们可以访问的所有Zend数据结构、常量和API等。下面对剩余的步骤进行解释。

1.1.1 声明导出函数

 ZEND_FUNCTION(first_module);

    ZEND_FUNCTION宏用于声明一个可在PHP代码中调用的函数,其参数即成为PHP函数名,因此,这一句声明了一个名为first_module的PHP函数,将其展开如下:

void zif_first_module (INTERNAL_FUNCTION_PARAMETERS);

// 最终展开得:
void zif_first_module (
int ht,
zval * return_value,
zval **return_value_ptr,
zval * this_ptr,
int return_value_used
);

    可见,ZEND_FUNCTION就是简单的声明了一个名为zif_ first_module的C函数,zif可能是”Zend Internal Function”的缩写。函数的原型满足Zend引擎对PHP函数的调用约定,关于其参数将在后面章节进行解释。

1.1.2 声明导出函数块

    声明C函数后,Zend并不知道如何调用,我们需要使用如下的语句来完成C函数到PHP函数的映射:

zend_function_entry firstmod_functions[] =
{
ZEND_FE(first_module, NULL)
{NULL, NULL, NULL}
};

    这创建了一个zend_function_entry数组,zend_function_entry存储了关于如何调用该PHP函数的信息,通过它Zend引擎就能够理解和调用我们的函数。

    其定义如下:

typedef struct _zend_function_entry {
	char *fname;
	void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
	struct _zend_arg_info *arg_info;
	zend_uint num_args;
	zend_uint flags;
} zend_function_entry;

    fname是PHP函数名,是PHP代码能够通过它来调用我们的函数;handler是指向我们在前面声明的C函数的函数指针。这两个参数已经足以完成从C函数到PHP函数的映射。剩余的参数用于告诉Zend该PHP函数对于函数参数的要求,arg_info是个数组,它的每一项都描述了对应下标的参数,num_args是参数的个数,具体将在后面的章节介绍。

    我们可以手动填充一个zend_function_entry,但更好的办法是使用Zend提供的宏ZEND_FE,因为Zend并不保证这个结构以后不会变。ZEND_FE使用第一个参数作为PHP函数名,并且在添加了zif前缀后作为C函数名;第二个参数用于填充arg_info,通常使用NULL。上面的代码将得到这样一个zend_function_entry结构:{” first_module,”, zif_first_module, NULL, 0, 0}。当然,这并不是说PHP函数名必须和C函数名有什么关系,也可以通过宏ZEND_NAMED_FE来手动指定PHP函数名,不过这并不是个好主意。

    我们必须为希望导出的每一个C函数都创建一个zend_function_entry结构,并将其放到一个数组中以备后用,数组最后一项的成员必须全部为NULL,这用于标记数组的结束。

1.1.3 填写模块信息

    下一步需要将我们的模块介绍给Zend,主要包括我们的模块名和导出的函数,这通过填充一个zend_module_entry结构来完成。

zend_module_entry firstmod_module_entry =
{
   STANDARD_MODULE_HEADER,
   "First Module",
   firstmod_functions,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NO_VERSION_YET,
   STANDARD_MODULE_PROPERTIES
};

    STANDARD_MODULE_HEADER和STANDARD_MODULE_

     PROPERTIES宏填充了该结构的首尾部分,具体填充了什么并不是我们需要关心的,并且为了兼容后续版本也最好不要手工修改。

    第二、三项是模块名称和导出函数,名称可以任意填写,导出函数就是我们在前面准备好的zend_function_entry数组。

    接下来的五个参数是函数指针,其用法在后面介绍,这里只用NULL填充。

    下面的参数是一个C字符串,用于表示模块版本,如果没有则使用NO_VERSION_YET,其实就是NULL。

    填写完毕后,需要把这个结构传给Zend引擎,这通过下面的语句完成:

#if COMPILE_DL_FIRST_MODULE
ZEND_GET_MODULE(firstmod)
#endif

    宏开关用于判断是否是动态链接的,动态链接时才会执行下面的语句,本文仅介绍动态链接的模块,并不关心静态链接时如何与Zend交流信息,因此,可以认为条件总为真。

     ZEND_GET_MODULE(firstmod)最后展开得到名为get_module的一个函数:

zend_module_entry *get_module(void)
{
return &firstmod_module_entry;
}

    这个函数就是简单的返回我们填充的zend_module_entry结构,这里需要注意的是结构的名称必须是xxx_module_entry,xxx是传递给ZEND_GET_MODULE的参数。当Zend加载我们的模块时,它首先会解析并调用名为get_module的函数,这样就可以得到我们的zend_module_entry,于是,PHP代码就可以调用模块导出的函数了。

1.1.4 实现导出函数

    代码最后一部分实现了我们导出的函数:

ZEND_FUNCTION(first_module)
{
long parameter;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l",
Pmeter) == FAILURE)
return;
RETURN_LONG(parameter);
}

    这里依然要用ZEND_FUNCTION来声明函数原型,函数体通过Zend API和宏,访问了函数参数并返回一个long值――这些都将在后面的章节进行详细介绍。

1.2使用参数

    函数的一个重要部分就是访问参数,但由于extension的特殊性,我们无法像通常的函数那样来访问参数。

    先来看导出C函数的原型:

void zif_first_module (
int ht,
zval * return_value,
zval **return_value_ptr,
zval * this_ptr,
int return_value_used
);

    ht是用户传入参数的数目,但一般不应直接读取,而是通过宏ZEND_NUM_ARGS()来获取,这通常用于判断用户是否传入了规定数目的参数。下面介绍如何在我们的C函数中访问这些参数。

1.2.1 标准方法

    常用的方法是使用下面这个函数,其使用方法类似于scanf,采用格式化字符串和变长参数列表的方式:

int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...);

    num_args指出我希望获取的参数数目,通常使用ZEND_NUM_ARGS(),因为我们一般会先用ZEND_NUM_ARGS()判断用户是否传入了规定数目的参数。TSRMLS_DC宏用于线程安全,define和declare时必须这样填写,在调用时应该改用TSRMLS_CC。

    type_spec是格式化字符串,其每个字符代表期望的当前参数的类型,之后应传递相应类型变量的指针来接收值,就像scanf那样,可用的字符如下:

格式字符 PHP参数类型 接收变量类型
l long long
d double double
s string char*和int
b boolean zend_bool
r resource zval*
a array zval*
z zval zval*
o/O/C 类,不予讨论 N/A

    这里面,string是个特例,它需要两个参数,分别获取字符串指针和长度,这是因为PHP没有采用C串,不能根据0来判断字符串结尾。下面是个示例程序:

// 获取一个long、一个string和一个resource
long l;
char *s;		// 字符串地址
int s_len;		// 字符串长度
zval *res;

// 检查参数数目
if(ZEND_NUM_ARGS() != 3)
WRONG_PARAM_COUNT; // 该宏输出相应错误信息并退出当前函数

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
                         "lsr", &l, &s, &s_len, &res) == FAILURE)
 return;

    由于PHP语法不能规定函数原型,因此用户可以传递任意类型的参数,对此,zend_parse_parameters自动进行了类型检查和转换:在内置标量类型,即long、double、boolean和string之间,Zend会自动进行类型转换,我们总能成功取得参数;resource和array则不进行转换,用户传入的参数必须具有指定类型,否则返回错误;zval作为通用结构,可以用于任何参数类型,Zend只需要简单的将其写入本地的接收变量。

    除了类型格式符外,该函数还支持另外3个控制符:

格式字符 意义
| 后面的参数是可选的,如果用户没有传递相应的参数,则本地接收变量保持不变,这用于支持默认参数;
! 前面的那个参数可以是NULL,仅用于razoOC,如果用户传递的是NULL,则本地的接收zval*被设为NULL;
/ 如果前面那个参数不是引用传递的,则不直接使用传入的zval,而是执行Copy-On-Write。这一点将在后面解释。

    最后,关于参数的数目也是有要求的。如果没有采用默认参数,即’|’格式符,则ZEND_NUM_ARGS()、num_args和格式串指出的参数数目这三者间必须完全匹配,否则zend_parse_parameters返回错误;如果使用了默认参数,则ZEND_NUM_ARGS()应和num_args相等,并且应该落在格式串指出的参数数目区间内。

1.2.2 底层方法

    大部分情况下,使用标准方法就可以了,但有些函数可能需要处理变参,标准方法对此无能为力(*)。此时,只有使用更加原始的方法――直接获取zval。Zend提供了如下的API:

int zend_get_parameters_array_ex(
int param_count,
zval ***argument_array
TSRMLS_DC);

    param_count是希望获取的参数数目,这个值不得大于ZEND_NUM_ARGS(),否则函数出错。argument_array是一个zval**类型的数组,用于接收参数。

     这个函数只是简单的返回zval,为了使用它们,我们需要自己访问其成员。首先是获取参数类型,这可以通过zval.type值来判断,可用的type见1.1.1节。之后是获取该type对应的值,我们可以直接访问zval的成员,比如zval.value.lval就是long值,但更方便的方法是使用Zend提供的宏:

展开
Z_LVAL(zval) (zval).value.lval
Z_DVAL(zval) (zval).value.dval
Z_STRVAL(zval) (zval).value.str.val
Z_STRLEN(zval) (zval).value.str.len
Z_ARRVAL(zval) (zval).value.ht
Z_RESVAL(zval) (zval).value.lval
Z_OBJVAL(zval) (zval).value.obj
Z_BVAL (zval) ((zend_bool)(zval).value.lval)
Z_TYPE(zval) (zval).type

    一个比较特殊的宏是Z_BVAL,它不是简单的返回值,而是进行了类型转换。另外,这些宏都有相应的xxx_P和xxx_PP版本,用于访问zval*和zval**。

    有时,用户传入参数的类型并不是我们期望的,这就需要手动进行类型转换了。为此,Zend提供了如下几个函数:

convert_to_boolean_ex()
convert_to_long_ex()
convert_to_double_ex()
convert_to_string_ex()
convert_to_array_ex()
convert_to_object_ex()
convert_to_null_ex()

    这些函数可将目标zval转换成指定类型,它接收zval**作为参数,为什么不用zval*呢?这是因为,这些函数有一个额外的步骤,它如果发现传入的zval不是引用类型的,并且需要执行类型转换,则会首先执行Copy-On-Write,并对副本施行转换,因此,为了返回副本必须使用zval**作为参数。如果zval是引用型的,则转换直接作用于目标zval结构。

    如果无法转换,这些函数就会将zval设置为目标类型的虚值,比如0、FALSE、空串等,因此函数总会成功返回。

    这些函数的非ex版本不执行zval分离,而是直接作用于原zval,因此参数类型是zval*。

1.2.2 引用传递

    函数参数的传递也是采用的引用计数方式,函数栈中存放的只是zval**,它很可能和几个变量共享一个zval。

    显然,对于引用型的zval,我们可以直接进行写入操作;而对于非引用型的zval,并且其refcount大于1时,如果要进行写入操作,就必须执行zval分离(参见1.1.3)。refcount等于1的情况是因为Zend引擎已经执行了zval状态切换(参见1.1.4情况II),我们得到的是自己独占的zval,可以直接写入。

    关于传入的zval是否引用,可以通过zval.is_ref来判断,或者使用宏PZVAL_IS_REF(zval*)。对于zval分离,可以使用宏SEPARATE_ZVAL(zval**),它会自动判断refcount,并且将新zval的地址填充到参数里。

1.2.4 编译检查(TODO)

    上面几节介绍了如何在我们的函数中对参数进行检查,也就是运行时检查,这为函数的编写带来了一些负担,代码也不够简洁。为此,Zend提供了编译时检查机制,允许我们指定函数原型,如果用户不按规定调用,则会报错并且跳过该函数,因此,我们的函数总能得到期望的参数。

1.3返回值

    从C函数向PHP返回值,并不能使用通常的return语句,导出函数的原型也说明了这一点:

void zif_first_module (
int ht,
zval * return_value,
zval **return_value_ptr,
zval * this_ptr,
int return_value_used
);

    因此,Zend将返回值地址作为参数传给我们,return_value是Zend为我们预先创建的一个标准zval结构,相当于一个局部变量,用户获得返回值时就相当于对return_value进行赋值操作,我们只需填充它即可;return_value_used表明用户是否使用了返回值,0表明没有使用返回值,当函数结束后return_value的refcount将被减为0,并被销毁,因此,这种情况下完全可以不处理返回值;return_value_ptr用于返回引用,它需要和zend_function_entry.arg_info联合使用,通常都是NULL。

    Zend提供了一组宏用于填充return_value:

Macro Description
RETURN_RESOURCE(resource) resource
RETURN_BOOL(bool) boolean
RETURN_FALSE false
RETURN_TRUE true
RETURN_NULL() NULL
RETURN_LONG(long) long
RETURN_DOUBLE(double) double
RETURN_STRING(string, duplicate) 字符串。string必须是C串,因为Zend将调用strlen();duplicate表示是否将传入的C串复制一份再赋给zval,如果传入的C串不是用Zend例程分配的,应该指定该值
RETURN_STRINGL(string, length, duplicate) 指定字符串长度,而不是使用strlen()
RETURN_EMPTY_STRING() 空字符串

    这些宏将在填充完return_value后,执行return语句。如果不想return,可以改用相应RETURN_xxx宏的RETVAL_xxx版本。

1.3.1 返回引用

    默认情况下,return_value_ptr是NULL,而当指定返回引用后(参见2.2.4),zend将采用*return_value_ptr作为返回值。初始状态下,return_value 依然指向一个临时zval,同时 *return_value_ptr = return_value。

    通常应该把return_value销毁,并且将*return_value_ptr设为将要返回的zval*,注意要加加引用计数,因为这相当于将该zval赋值给一个用作返回值的临时变量,函数返回后,Zend会减减引用计数。

    示例程序:

ZEND_FUNCTION(str_reverse)
{
    if(ZEND_NUM_ARGS()!= 1)
        WRONG_PARAM_COUNT;
    zval **args;
if(zend_get_parameters_array_ex(ZEND_NUM_ARGS(), &args TSRMLS_CC)
 == FAILURE)
    {
        return;
    }
    convert_to_string(*args);
    char swap;
    char *head = Z_STRVAL_PP(args);
    char *end = head + Z_STRLEN_PP(args) - 1;
    for(; head < end; ++head, --end)     
    {        
      swap = *end;         
      *end = *head; 
      *head = swap; 
    } // 销毁临时zval 
    zval_ptr_dtor(return_value_ptr); // 返回传入的参数 
    *return_value_ptr = *args; // 增加引用计数 
    ++(*return_value_ptr)->refcount;
}

1.4启动和终止函数

    Zend允许模块在加载和卸载时收到通知,以进行初始化和清除工作,我们要做的就是把相应函数传递给Zend,它会在合适的时机自动调用。2.1.3节里留下的五个NULL就是用于这个目的,它们都是函数指针,最后一个用于配合phpinfo()来显示模块信息,在此忽略,只看其他四个。

    Zend提供了如下四个宏,分别用于声明对应的函数:

意义
ZEND_MODULE_STARTUP_D(module) 在加载模块时调用
ZEND_MODULE_SHUTDOWN_D(module) 在卸载模块时调用
ZEND_MODULE_ACTIVATE_D(module) 一个页面开始运行时调用
ZEND_MODULE_DEACTIVATE_D(module) 一个页面运行完毕时调用

    这些宏的用法和ZEND_FUNCTION宏一样(参见2.1.1),展开后就是声明了特定原型的函数,其参数module可以是任意的,但最好使用模块名称。这些函数的参数中,对我们有用的是int module_number,它是模块号,全局唯一,后面会提到其用处。

    在声明和实现相应函数时,都应该使用这些宏。最后,需要把这些函数填写到zend_module_entry里(参见2.1.3),可按顺序使用如下的宏,这些宏生成相应的函数名称:

ZEND_MODULE_STARTUP_N(module)
ZEND_MODULE_SHUTDOWN_N(module)
ZEND_MODULE_ACTIVATE_N(module)
ZEND_MODULE_DEACTIVATE_N(module)

1.5调用PHP函数

    有时我们需要在模块中调用用户指定的函数,比如我们实现了sort这样的函数,并且允许用户指定比较函数。这可以使用如下的Zend函数:

int call_user_function_ex(
HashTable *function_table,
zval **object_pp,
zval *function_name,
zval **retval_ptr_ptr,
zend_uint param_count,
zval **params[],
int no_separation,
HashTable *symbol_table
TSRMLS_DC)

    第一个参数是HashTable,在1.2.3节提到Zend使用HashTable来存储PHP函数,function_table用于指定从哪个HashTable中获取函数。通常应该用CG(function_table),展开就是compiler_globals.function_table,compiler_globals是一个用来存储编译器数据的全局数据结构(与其对应的还有个EG宏,即executor_globals,它用来存储执行器数据)。compiler_globals.function_table里面存储了所有我们可以在PHP页面里面调用的函数,包括Zend内建函数、PHP标准库函数、模块导出的函数以及用户使用PHP代码定义的函数。

    object_pp是一个对象,当指定该值时,Zend会从对象的函数表中获取函数,这里不予讨论,总是设为NULL。

    function_name必须是string型的zval,存储我们希望调用的函数的名称。为什么使用zval而不是直接用char*,是因为Zend考虑到大部分情况下,我们都是从用户那获得参数,然后再调用call_user_function_ex的,这样就可以不作处理直接把用户参数传给该函数。当然,我们也可以手动创建一个string型zval传给它。

    retval_ptr_ptr用于获取函数的返回值,Zend执行完指定的函数后,它就将返回值的指针填充到这里。

    param_count和params用于指定函数的参数,params是个zval **这点可能让人感到奇怪,但考虑到该函数的常见用法(见下面的示例)以及2.2.2节关于函数参数的介绍,就一点也不奇怪了。

    no_separation用于指定是否在必要时执行zval分离(参见1.1.3),这在写入非引用zval时发生。应该总是将其设为0,表示执行zval分离,否则可能破坏数据。

    symbol_table用于指定目标函数的active_symbol_table(参见1.2.3),通常应该使用NULL,这样Zend会为目标函数生成一个空的符号表。

    说了这么多,该动动手了,下面的程序片段简单实现了PHP API call_user_func的功能:

ZEND_FUNCTION(call)
{
    int num_args = ZEND_NUM_ARGS();
    if(num_args < 1)
        WRONG_PARAM_COUNT;
    zval ***args = (zval***)emalloc(sizeof(zval**)*num_args);
zval *ret_zval;
// 获取传入的参数
if(zend_get_parameters_array_ex(num_args, args TSRMLS_CC)
== FAILURE)
    {
        efree(args);
        return;
}
// 第一个参数作为函数名,后面的作为函数参数
if(call_user_function_ex(CG(function_table), NULL, **args,
 	&ret_zval, num_args - 1, args + 1, 0, NULL TSRMLS_CC)
== FAILURE)
    {
        efree(args);
        zend_error(E_ERROR, "Function call failed");
}
// 将函数返回值反馈给用户
    *return_value = *ret_zval;
    efree(args);
}

1.6访问PHP变量

1.6.1 设置

    1.2.3节提到Zend使用HashTable来存储全局和局部变量符号,因此访问PHP变量,其实就是操作HashTable。当然,我们不需要手工去做,Zend提供了一组宏完成这些工作。

    PHP变量的创建共有三步,首先需要创建一个zval结构,可使用如下的宏:

MAKE_STD_ZVAL(zval*)

    这个宏先调用emalloc分配一块zval,然后将其refcount设为1、is_ref设为0。

    之后就是设置zval的值,同样,我们不需要直接操作zval的成员,Zend已经提供了如下的宏:

Macro Description
ZVAL_RESOURCE(zval*, resource) resource
ZVAL_BOOL(zval*, bool) boolean
ZVAL_FALSE(zval*) false
ZVAL_TRUE(zval*) true
ZVAL_NULL(zval*) NULL
ZVAL_LONG(zval*, long) long
ZVAL_DOUBLE(zval*, double) double
ZVAL_STRING(zval*, string, duplicate) string必须是C串,因为Zend将调用strlen();duplicate表示是否将传入的C串复制一份再赋给zval,如果传入的C串不是用Zend例程分配的,应该指定该值
ZVAL_STRINGL(zval*, string, length, duplicate) 指定字符串长度,而不是使用strlen()
ZVAL_EMPTY_STRING(zval*) 空字符串

    可能你会发现,这个表格和2.3节里面的返回值宏表格很相似,不错,返回值宏就是直接调用的ZVAL_xxx。

    既然有了zval,下面把它添加到变量符号表里就可以了,可以使用如下的一组宏:

ZEND_SET_SYMBOL(symtable, name, var)
ZEND_SET_GLOBAL_VAR(name, var)

    symtable用来指定你想插入的符号表,一般使用EG(active_symbol_table),表示访问当前调用者的活动符号表。如果想强制访问全局符号表,可以用&EG(symbol_table),这也正是ZEND_SET_GLOBAL_VAR(name, var)所做的。这两个宏的最终效果和执行PHP赋值语句name = var完全一样。

    如果只是访问全局变量,可以使用单个宏代替上述三步:

SET_VAR_STRING(name, value)
SET_VAR_STRINGL(name, value, length)
SET_VAR_LONG(name, value)
SET_VAR_DOUBLE(name, value)

    上述宏分别用于创建全局的string、long和double变量,它们在内部执行了以上三步,当然,最后调用的是ZEND_SET_GLOBAL_VAR宏。

1.6.2 获取

    如果想获取已有的PHP变量,则只能直接访问HashTable,Zend并没有提供相应的操作:

int zend_hash_find(
HashTable *ht,
char *arKey, uint nKeyLength,
void **pData)

    这个函数从HashTable中查找元素,pData用于获取结果值,Bucket.pData将被放到这里(如果找到的话)。函数成功则返回SUCCESS,否则返回FAILURE。

    下面是个示例:

zval **ppzval; // Bucket.pData里存放的是zval**
if(zend_hash_find(EG(active_symbol_table),"var", 4,
(void**)&ppzval) == SUCCESS)
printf("var.refcount = %d\\n", (*p)->refcount);
	else
		printf("Not Found\\n");

    这段代码从活动符号表中查找名为var的变量,需要注意的是nKeyLength是4,必须包括结尾的0。

    获得变量后,拿来读是没有问题的,但是写操作就应该小心对待了。只有当refcount为1或者is_ref为1,才可以写入;否则应该进行zval分离,具体参见1.2.3节

1.6.3 常量

    PHP常量的内部定义如下:

typedef struct _zend_constant {
	zval value;
	int flags;
	char *name;
	uint name_len;
	int module_number;
} zend_constant;

    常量的值依然使用zval存储,但这里的zval是私有的,不会和其他变量或常量共享,其refcount和is_ref被忽略。module_number是模块号,在启动函数中可以获取该值(参见2.4),当模块被卸载时,Zend会使用模块号查找和删除所有该模块注册的常量。如果希望在模块被卸载后,常量依然有效,可以将module_number设为0。另一个注意点是,name_len需要包含结尾的0。

    flags值可以是如下两个,可以使用”|”联用:

flag 意义
CONST_CS 常量名大小写敏感
CONST_PERSISTENT 持久常量,在创建常量的页面执行结束后,常量依然有效(*)

    所有常量都被放在EG(zend_constants)这张HashTable里,其key是常量名称,value是zend_constant,注意不是zend_constant*,因此HashTable会复制一份zend_constant作为value。

    获取一个常量非常简单,只要传递常量名和接受常量值的zval:

int zend_get_constant(char *name, uint name_len, zval *result
	TSRMLS_DC);

    设置常量稍微复杂一点,需要先填写一个zend_constant结构,要注意的是,常量只能是long、double和string。然后使用如下函数将其加入常量表:

    同时,Zend也为我们提供了如下的宏,可以直接创建常量:

int zend_register_constant(zend_constant *c TSRMLS_DC);
REGISTER_LONG_CONSTANT(name, value, flags)REGISTER_MAIN_LONG_CONSTANT(name, value, flags)
REGISTER_DOUBLE_CONSTANT(name, value, flags)REGISTER_MAIN_DOUBLE_CONSTANT(name, value, flags)
REGISTER_STRING_CONSTANT(name, value, flags)REGISTER_MAIN_STRING_CONSTANT(name, value, flags)
REGISTER_STRINGL_CONSTANT(name, value, length, flags)REGISTER_MAIN_STRINGL_CONSTANT(name, value, length, flags)

    上述宏的MAIN版本用于创建module_number为0的宏,在模块被卸载后,常量依然有效。而非MAIN版本则假设存在一个名为module_number的int变量,并拿来给zend_constant.module_number赋值,可见这组宏原本就是为在模块启动函数里调用而设计的。另外,当创建string型常量时,Zend也会dup一份字符串,因此可以直接使用C串指定常量值。

    最后需要指出的是,上述函数和宏都无法改变已有的常量,如果发现已经存在同名常量,则函数失败。如果想修改的话,只能通过HashTable操作。

1.7输出信息

    Zend提供了两个函数用于向浏览器输出信息:

int zend_printf(const char *format, ...);
void zend_error(int type, const char *format, ...);

    zend_printf用法和C的printf一样;zend_error用于输出错误信息,type可以指定错误的性质,对于不同的错误,Zend将作不同处理:

错误码 处理
E_ERROR 严重错误,立即终止脚本运行。
E_WARNING 警告, 脚本继续执行。
E_PARSE 解析错误,解析器复位,脚本继续执行。
E_NOTICE 通知,脚本继续执行。该信息默认情况下不予输出,可以修改php.ini来启用。

    该函数会同时输出出错的文件和行号,类似这样:

Fatal error: no memory in /home/wiki/zdj/ext/test.php on line 6
 by zhangdongjin

建议继续学习

  1. linux内核研究笔记(一)内存管理 – page介绍 (阅读 10,323)
  2. Linux内核协议栈对于timewait状态的处理 (阅读 4,764)
  3. PHP内核介绍及扩展开发指南―基础知识 (阅读 4,702)
  4. 我的内核配置文件 (阅读 4,681)
  5. PHP内核介绍及扩展开发指南―高级主题 (阅读 4,583)
  6. Linux内核模块开发(笔记) (阅读 4,303)
  7. PHP内核介绍及扩展开发指南―类和对象 (阅读 4,006)
  8. 在Ubuntu上使用SystemTap (阅读 3,923)
  9. 在 Dell PowerEdge 1950 上安装 Linux 2.6.32-rc8 内核的问题与解决 (阅读 3,882)
  10. 内核编译升级失败了以后的处理方案 (阅读 3,864)