PHP扩展开发

1 创建扩展

(1) 创建扩展骨架

##本例用的是php7.1.1

cd ext

./ext_skel --extname=helloworld

 

(2)修改config.m4

把下面几行注释打开,config.m4中 dnl 为注释的意思

##动态编译选项,通过.so的方式链接,去掉dnl注释:

 

PHP_ARG_WITH(helloworld, for helloworld support,

Make sure that the comment is aligned:

[ --with-helloworld Include helloworld support])

 

##静态编译选项,通过enable来启用,去掉dnl注释:

 

PHP_ARG_ENABLE(helloworld, whether to enable helloworld support,

Make sure that the comment is aligned:

[ --enable-helloworld Enable helloworld support])

 

修改完成编译下

phpize

#这里用自己的php-config文件位置

./configure --with-php-config=./configure --with-php-config=/Applications/MAMP/bin/php/php7.1.1/bin/php-config

make && make install

make test #测试

#编辑php.ini,加上helloworld扩展

extension=helloworld.so

 

 

(3)重启扩展并使用里面的函数

 

 

 

2 全局资源

 

如果扩展中需要使用全局资源来实现函数之间的数据共享,则可以像内核中的EG,CG那样,向TSRM 注册全局资源,扩展中,通常定义一个全局资源结构体.然后把这个节后提注册到TSRM,这个结构可以使用ZEND_BEGIN_MODULE_GLOBALS(extension_name) ,ZEND_END_MODULE_GLOBALES(extension_name)两个宏定义,这两个宏必须成对出现,中间定义扩展用到的"全局变量" 即可 ,比如

 

1 定义全局变量

ZEND_BEGIN_MODULE_GLOBALS(mytest)

    zend_long open_cache;

    HashTable class_table;

ZEND_END_MODULE_GLOBALS(mytest)

 

展开后是个结构体

typedef struct _zend_mytest_globals{

    zend_long open_cache;

    HashTable class_table;

}zend_mytest_globals;

 

2 注册全局变量: ZEND_DECLARE_MODULE_GLOBALS(mytest)

3 最后,定义一个像EG,CG 那样的宏用于访问扩展的全局资源结构体,这一步将使用ZEND_MODULE_GLOBALS_ACCESSOR(module_name,v)  来完成

 

一般定义一个宏来做这个事情

#define MYTEST_G(v)  ZEND_MODULE_GLOBALS_ACCESSOR(mytest,v)

 

这样就可以直接使用 MYTEST_G(open_cache) 对结构体进行读写了

 

 

3 函数参数解析

    用户自动以函数在编译时会为每个参数创建一个zend_arg_info 结构. 这个结构用来记录参数的名称,是否引用传参,是否可变参数等信息.在存储上函数参数

与函数内部的局部变量并没有差别,可以把参数当做局部变量来对待,他们分配在zend_execute_data上,调用函数时首先会进行参数传递,这个过程是按参数次序,

一次将参数的value从函数调用空间传递到被调函数的zend_execute_data,函数内部像访问普通局部变量一样通过存储位置访问参数.

    内部函数和用户自定义函数最大的不同在于:内部函数就是一个C语言定义的函数,它的局部变量是C语言中的变量,并不会分配到zend_execute_data上,因此

除函数参数以外在zend_execute_data上没有其他变量的分配,函数参数是从PHP用户空间传到内部函数中的,他们与用户自定义函数完全相同,包括参数的分配

方式,传参过程,也是按照参数次序一次分配在zend_execute_data上,在内部函数中,可以按照参数顺序从zend_execute_data 上读取到对应的参数,当然,实际

使用时不需要我们自己去取,PHP提供了一个方法将zend_execute_data上的参数机械到指定变量上

    zend_parse_parameters(int num_args,const char *type_sepc)

下面是用法 用来获取第一个是字符串 第二个是整型的参数

 

zend_string *strg;

if(zend_parse_parameters(ZEND_NUM_ARGS(),"sl",&arg,&arg_len,&l_value) == FAILURE){

return;

}

//字符串连接函数

strg = strpprintf(0, "the module is %s,the num of params is %d, and the str is %s,the int value is %d.", "myext", arg_size,arg,l_value);

 

        RETURN_STR(strg);

 

 

 

引用传参

 

如果函数需要使用引用类型的参数或者返回引用,就需要创建函数的参数数组,这个数组通过ZEND_BEGIN_ARG_INFO() 或者 ZEND_BEGIN_ARG_INFO_EX(),

ZEND_END_ARG_INFO()宏定义.

 

//name 参数数组名,注册函数PHP_FE(function,arg_info) 会用到

//unused 保留值,暂时没用

// return_reference 返回值是否为引用,一般很少会用到

// required_num_args: required 参数数

ZEND_BEGIN_ARG_INFO_EX(arginfo_my_func_1, 0, 0, 1)

ZEND_ARG_INFO(1,a)

ZEND_END_ARG_INFO()

//定义完后这个数组后就需要把这个数组告诉函数

PHP_FE(my_func_1,arginfo_my_func_1) /* 测试内部修改返回的参数 */

//引用参数通过zend_parse_parameters()解析时只能使用z解析,不能直接解析为zend_value,否则引用将失效

PHP_FUNCTION(my_func_1){

    zval *lval; //必须为zval 定义成zend_long 也能解析出,但不是引用

    if(zend_parse_parameters(ZEND_NUM_ARGS(),"z",&lval) == FAILURE){

        return;

    }

    zval *real_val = Z_REFVAL_P(lval); //lval 的类型为IS_REFERENCE ,获取实际用用的zval 地址: &(lval.value->ref.val)

    Z_LVAL_P(real_val) = 100; //设置实际引用的类型

 

    php_printf("you are in");

}

 

 

 

函数返回值

 

PHP_FUNCTION(test_return_arr){

        zval *arr;

        if(zend_parse_parameters(ZEND_NUM_ARGS(),"a",&arr) == FAILURE){

            RETURN_FALSE;

        }

 

        Z_ADDREF_P(arr); //增加用一用计数

        ZVAL_ARR(return_value,Z_ARR_P(arr));

}

 

此函数接收一个数组,然后直接返回该数组,数组多了一个来自返回值得引用,因此需要增加数组的引用计数.

虽然可以直接设置return_value,但实际使用时并不建议这么做,PHP提供了很多专门用于设置返回值的宏,这些宏定义在zend_API.h中.

 

RETURN_BOOL()  //返回bool值

RETURN_NULL()

RETURN_LONG(l)  //zend_long

RETURN_DOUBLE(d) //double 

RETURN_STR(s) //zend_string

RETURN_INTERNED_STR(s) // 返回内部字符串,这种变量将不会被回收

RETURN_NEW_STR(s) //返回普通字符串

RETURN_STR_COPY(s)

RETURN_STRING(s)

RETURN_STRINGL(s,l)

RETURN_EMPTY_STRING()

RETURN_RES(r)

RETURN_ARR(r)

RETURN_OBJ(r)

RETURN_ZVAL(zv,copy,dtor)

RETURN_FALSE()

RETURN_TRUE()

 

函数调用(调用其他扩展的函数或者用户自定义的函数)

 

 

/* 调用自定义函数*/

PHP_FUNCTION(use_user_func){

    zend_long i;

    zval call_func_name,call_func_ret,call_func_params[1];

    uint32_t call_func_param_cnt = 1;

    zend_string *call_func_str;

    char *func_name = "mySum";

    

    //获取传进来的参数

    if(zend_parse_parameters(ZEND_NUM_ARGS(),"l",&i) == FAILURE){

        RETURN_FALSE;

    }

 

    //分配 zend_string : 调用完需要释放

    call_func_str = zend_string_init(func_name,strlen(func_name),0);

 

    //设置 zval

    ZVAL_STR(&call_func_name,call_func_str);

    //设置参数

    ZVAL_LONG(&call_func_params[0],i);

    //调用

    if(SUCCESS != call_user_function(EG(function_table),

    NULL, //调用成员方法的对象

    &call_func_name, // 调用的函数名名称

    &call_func_ret, // 函数返回值地址

    call_func_param_cnt, //实际传参数量

    call_func_params //参数数组

    )){

        zend_string_release(call_func_str);

        RETURN_FALSE;

    }

 

    zend_string_release(call_func_str);

    RETURN_LONG(Z_LVAL(call_func_ret));

}

 

 

函数调用(调用系统函数)

 

/* 调用系统函数 */

PHP_FUNCTION(use_system_func){

    zend_array *arr1,*arr2;

    zval call_func_name,call_func_ret,call_func_params[2];

    uint64_t call_func_param_cnt = 2;

    zend_string *call_func_str;

    char *func_name = "array_merge";

 

    if(zend_parse_parameters(ZEND_NUM_ARGS(),"hh",&arr1,&arr2)==FAILURE){

        RETURN_FALSE;

    }

    

    call_func_str = zend_string_init(func_name,strlen(func_name),0);

 

    ZVAL_STR(&call_func_name,call_func_str);

    ZVAL_ARR(&call_func_params[0],arr1);

    ZVAL_ARR(&call_func_params[1],arr2);

 

    if(call_user_function(EG(function_table),NULL,&call_func_name,&call_func_ret,call_func_param_cnt,

    call_func_params) != SUCCESS){

        zend_string_release(call_func_str);

        RETURN_FALSE;

    }

 

    zend_string_release(call_func_str);

    RETURN_ARR(Z_ARRVAL(call_func_ret));

 

}

 

 

 

ZVAL操作

 

1 ZVAL 的创建和获取

PHP7将变量的引用计数转移到了具体的value上,所以zval更多的是作为统一的传输格式,很多情况下只是临时性的使用,比如函数调用时的传参,最终需要的数据是zval写到的zend_value,

函数从zval取得zend_value后就不在关心zval了,这种就可以直接再栈上分配zval,分配完zval需要设置其类型,value等信息,php中定义的zval_xxx()系列的宏就是用来设置不同类型的zval

的,这些宏的第一个参数z均为要设置的zval的指针,后面为要设置的zend_value,对应的,内核提供了Z_XXX(zval),Z_XXX_P(zval_p) 系列的宏,用于获取不同类型zval 的value.

    zval的类型可以通过Z_TYPE(zval),Z_TYPE_P(zval_p)两个宏来获取,这个类型实际获取的就是zval.u1.v.type,但是设置时不能只修改这个type,而要设置typeinfo.因为zval还有其他的

表示需要社会,比如是否使用引用计数,是否可以被垃圾回收,是否可以被复制等.

 

 

(1) 获取 zval 的类型  Z_TYPE(zval) Z_TYPE_P(zval_p)

(2) UNDEF 和 NULL. 通过ZVAL_UNDEF(z) 将zval 设置为 UNDEF,ZVAL_NULL(z) 将设置为NULL. 

       Z_ISUNDEF(zval) ,Z_ISUNDEF_P(zval_p) ,判断zval是否为 UNDEF,

       Z_ISNULL(zval),Z_ISNULL_P(zval) 判断zval是否为NULL

(3)布尔值

    ZVAL_BOOL(z,b) b为 IS_TRUE,IS_FALSE 判读对应的bool值

    ZVAL_FALSE(z) 设置为false,ZVAL_TRUE(z) 设置为true

(4)整型,浮点型

    设置 : ZVAL_LONG(z,l),ZVAL_DOUBLE(z,d) 设置  l,d 为zend_long,zend_double 

    获取 : Z_LVAL(zval) / Z_LVAL_P(zval_p), Z_DVAL(zval)/Z_DVAL_P(zval_p) 来获取对应的值

(5)字符串

    ZVAL_STR(z,s) ,s为zend_string 类型的地址,这里不会讲s的字符串复制一份,只是会将s地址设置到新的zval,s的引用计数不会自动增加,如果需要增加引用计数,需要抖动增加,这个宏支持

                            内部字符串,如果s为内部字符串则只会设置zval 的type类型

 

    ZVAL_NEW_STR(z,s),除了不支持内部字符串,与ZVAL_STR(z,s) 完全一样.这里说的不支持内部字符串是指:如果把内部支付穿赋给zval,这个宏会把该字符串当做不同字符串对待.

    ZVAL_STR_COPY(z,s),与ZVAL_STR 操作一直,只是会根据s的类型决定是否需要增加引用计数器

   

    Z_STR(zval) ,Z_STR_P(zval_p) 获取zend_string 的地址

    Z_STRVAL(zval),Z_STRVAL_P(zval_p)  获取的是char 字符串即 zend_string->val

    Z_STRLEN(zval) ,Z_STRLEN_P(zval_p) 获取字符串长度

    Z_STRHASH(zval),Z_STRHASH_P(zval_p) 获取字符串的哈希值 即 zend_string->h

   

 

//字符串范例

PHP_FUNCTION(test_zval){

    zval test_str;

    zend_string *test_zend_string;

    char *test = "I used another way to init zend string";

    // test_zend_string = zend_string_init(test,strlen(test),0);

    test_zend_string = strpprintf(0,"I used another way to init zend string");

    ZVAL_STR(&test_str,test_zend_string); //赋值给zval

    php_printf("the value is %s\n",Z_STRVAL(test_str));

    php_printf("the value len is %d\n",Z_STRLEN(test_str));

    php_printf("the value hash is %d\n",Z_STRHASH(test_str));

    // php_printf("the value by p is %s\n",Z_STRVAL_P(&test_str));

 

    RETURN_STR(test_zend_string);

}   

 

(6) 数组

    如果已经分配了zend_array 结构,则可以通过ZVAL_ARR(z,a) 赋给 zval,a 就是zend_arry地址,该宏不会增加a的引用计数;如果是新分配一个空数组,则可以通过ZVAL_NEW_ARR(z) 完成,

需要注意的是,这个宏这是分配了zend_array 内存,但是并没有进行数组的初始化,也就是说此时数组还不能使用,关于数组初始化的操作接下来会单独说明

  可以通过Z_ARR(zval),Z_ARR_P(zval_p) 来获取zend_arry 的地址. Z_ARRVAL(zval) ,Z_ARRVAL_P(zval_p) 获取数组的值

 

(7)对象

对象可以通过ZVAL_OBJ(z,o) 宏设置,o 为zend_object 的地址,该宏不会增加o的引用计数,获取对象的操作比较多 

可以通过

Z_OBJ(zval) ,Z_OBJ_P(zval_p) 取到,另外以下几个宏用于获取对象的其他信息:

Z_OBJ_HT(zval)/ZEND_OBJ_HT_P(zval_p)  //获取对象的zend_object_handlers 即 zend_object -> handlers

Z_OBJ_HANDLER(zval)ZEND_OBJ_HANDLER_P(zval_p)  //获取对象对应操作的handler 的指针 write_property,read_property 等.注意这个宏取到的值为只读,不要试图修改这个值

Z_OBJ_HANDLE(zval)/ZEND_OBJ_HANDLE_P(zval_p) //获取对象打得handle 即 zend_objcect->handle

Z_OBJECT(zval)/Z_OBJECT_P(zval_p)  //获取对象实例化的类,即zend_object->ce

Z_OBJPROP(zval)/Z_OBJPROP_P(zval_p) //获取对象的成员数组,返回的是hashtable 的地址

 

(8) 资源

资源可以通过ZVAL_RES(z,r) 宏设置,r为zend_resource 地址,该宏不会增加r的引用计数,对应的,可以通过Z_RES(zval),Z_RES_P(zval_p) 宏来获取zend_resource地址,除了这俩宏

还可以通过Z_RES_HANDLE(zval),Z_RES_HANDLE_P(zval_p) 来获取资源的handle,即zend_resource->handle

 

(9)引用

如果已经有zend_reference 结构了,则可以通过ZVAL_REF(z,r) 宏直接设置zval,r为zend_reference 的地址,该宏不会增加r的引用计数;

如果没有zend_reference结构可以通过ZVAL_NEW_REF(z,r) 创建一个引用,r为也要引用的值

 

可以通过Z_REF(zval),Z_REF_P(zval_p)获取zend_reference 的地址,如果要获取引用实际value 可以使用Z_REFVAL(zval),Z_REFVAL_P(zval_p) 获取

 

除了上面这些与PHP变量类型相关的宏,还有一些内核自己使用类型的宏,因为zval是一个通用的结构,可以用它保存任意类型,比如类,函数等

 

Z_INDIRECT(zval) / Z_INDIRECT_P(zval_p)  // 获取indirect 的zval,指向另外一个zval

Z_CE(zval)/Z_CE_P(zval_p)                           //获取zval 的zend_class_entry

Z_FUNC(zval)/Z_FUNC_P(zval_p) //获取zval的zend_function

Z_PTR(zval)/Z_PTR_P(zval_p) //获取zval的ptr

 

 

变量赋值

 

扩展中经常会用到将一个变量复制到另外一个变量的场景,这个操作可以通过ZVAL_COPY(z,v) ,z为目标zval,复制后这个宏会增加对用value 的引用计数,

除这个宏之外,还有一个功能与之相同但不会主动增加引用计数的宏:ZVAL_COPY_VAL(z,v).

 

引用计数

 

在扩展中操作PHP用户控件相关变量时,需要谨慎考虑是否需要引用计数相关操作.比如下面的例子

function test($arr){

    return $arr;

}

 

$a = [1,2];

$b = test($a);

 

如果把函数test()用内部函数实现,这个函数接受了一个PHP用户控件传入的数组参数,然后又返回并赋值给了PHP用户空间的另外一个变量,这个时候就需要增加

传入数组的refcount.数组由PHP用户控件分配,函数调用前refcount = 1 ,传到内部函数时相当于赋值给了函数的参数,因此refcount 变成2,这次增加在函数执行

完释放参数是会减掉,等返回并赋值给$b后,此时共有两个变量指向这个数组,所以内部函数需要增加refcount,增加的引用是给返回值的.test()翻译成内部函数如下.

 

 

PHP_FUNCTION(test){

     zval *arr;

    if(zend_parse_parameters(ZEND_NUM_ARGS(),"a",&arr) == FAILURE){

        RETURN_FALSE;

    }

        

    Z_ADDREF_P(arr); //增加引用计数 如果注释掉下面这个将导致core dumped(核心错误)

    RETURN_ARR(Z_ARR_P(arr))

}

 

那么在哪些情况下需要考虑设置引用计数呢?一个关键条件就是:操作的PHP用户空间的相关变量,包括对用户控件变量的修改,赋值,要明确的一点是引用计数是用来

解决多个变量指向同一个value问题的,所以在PHP中来回传递zval的时候需要考虑是不是要修改引用计数.下面总结PHP中常见的会对引用计数进行操作的情况.

 

(1) 变量赋值:变量赋值是最常见的情况,一个引用计数的变量类型在初始赋值时其refcount=1,如果后面把此变量又赋值给了其它变量,那么久需要增加引用计数.

 

(2)数组操作:如果把一个变量插入数组中,那么久需要增加这个变量的引用计数,如果要删除一个数组元素,要相应的减少其引用.

 

(3)函数调用:传参世界可以当做普通的变量赋值,将调用空间的变量赋值给被调用空间的变量,函数返回时会销毁函数空间的变量,这是有会减少传参的引用,

   这两个过程由内核完成,不需要扩展自己处理

 

(4)成员属性:当把一个变量赋值给对象的成员属性的时候需要增加引用计数

 

PHP 中定义了以下宏用于引用计数的操作,包括获取变量的引用计数以及增加,减少变量的引用计数:

//pz 类型为zval* 

Z_REFCOUNT_P(pz)                   //获取引用数

Z_SET_REFCOUNT_P(pz)            //设置引用数

Z_ADDREF_P(pz)                       //增加引用数

Z_DELREF_P(pz)                        //减少引用数

 

Z_REFCOUNT(z)                   //获取引用数

Z_SET_REFCOUNT(z)            //设置引用数

Z_ADDREF(z)                       //增加引用数

Z_DELREF(z)                        //减少引用数

 

只对使用了引用计数的变量类型增加/减少引用建议使用下面的

Z_TRY_ADDREF_P(pz)/ Z_TRY_ADDREF(z)

Z_TRY_DELREF_P(pz) / Z_TRY_DELREF(z)

 

上面的这些宏的类型都是zval 或zval* 如果需要操作具体value的引用计数可以使用一下宏.

 

直接获取zend_value 的引用,可以直接通过这个宏修改value的refcount

GC_REFCOUNT(p)  //暂时还不知道如何使用

 

Z_REFCOUNTED(zval) / Z_REFCOUNTED_P(pz)  //判断是否用到引用计数机制

Z_COUNTED(zval) / Z_COUNTED_P(pz)   //根据zval 获取value 的zend_refcounted 头部

 

 

字符串操作

 

zend_string 是PHP自己封装的一个用于表示字符串的结构,也是使用比较频繁的一种类型

 

PHP扩展开发_第1张图片

 

 

PHP扩展开发_第2张图片

 

 

数组操作

 

(1)创建数组

    创建一个新的hashtable 分为两步:首先分配zend_array 内存,这个可以通过ZVAL_NEW_ARR()宏分配,也可以自己直接分配;然后初始化数组,通过zend_hash_init()

宏完成,如果不进行初始化操作将无法使用.

 

(2)插入更新元素

   zend_hash_add() //如果键值不存在则插入,存在的话则不管

   zend_hahs_update() //如果键值存在的话进行覆盖,不存在的话插入

   zend_hash_str_update //插入键值为普通字符串的 键值存在会进行覆盖

   zend_hash_str_add //插入键值为普通字符串的 如果键值存在则不操作

  zend_hash_next_index_insert //使用能自动索引

 

下面范例包含数组的增删改查 和遍历

 

/* 数组测试 */

PHP_FUNCTION(arr_test){

    zval array;

    zval first_value,second_value,third_value;

    zval *find_key;

    // zend_string *key_str;

    uint32_t size;

    HashTable *ht;

    ZVAL_NEW_ARR(&array); //分配内存

    zend_hash_init(Z_ARRVAL(array),size,NULL,ZVAL_PTR_DTOR,0); //进行初始化

    // key_str = strpprintf(0,"first");

    ZVAL_STR(&first_value,strpprintf(0,"I am first value 呵呵")); //zend_string字符串

    ZVAL_DOUBLE(&second_value,99.99); //设置为浮点型

    ZVAL_LONG(&third_value,1000); //设置为整型

    ht = Z_ARRVAL(array); //hashtable

    /*---------------------------插入元素----------------------------------------------- */

    // zend_hash_add(Z_ARRVAL(array),strpprintf(0,"first"),&first_value); // 如果键值存在则不操作

    zend_hash_update(ht,strpprintf(0,"first"),&first_value); // 如果键值存在进行覆盖

    // zend_hash_update(Z_ARRVAL(array),strpprintf(0,"first"),&second_value); // 数组插入

    zend_hash_update(ht,strpprintf(0,"second"),&second_value); // 数组插入

    zend_hash_str_update(ht,"third",5,&third_value); // 数组插入 针对键值为字符串的

    zend_hash_index_update(ht,6,&third_value); // 数组插入 针对键值为数值的

    zend_hash_next_index_insert(ht,&third_value); // 数组插入 针对键值为数值的

    zend_hash_next_index_insert(ht,&third_value); // 数组插入 使用自动索引

    zend_hash_next_index_insert(ht,&third_value); // 数组插入 使用自动索引

    zend_hash_update(ht,strpprintf(0,"last"),&second_value); // 数组插入

    /*---------------------------/插入元素----------------------------------------------- */

    /*---------------------------查找元素----------------------------------------------- */

    // find_key = zend_hash_find(ht,strpprintf(0,"first")); //根据zend_string 查找

    // find_key = zend_hash_str_find(ht,"third",5); //根据普通字符串查找

    find_key = zend_hash_index_find(ht,6); //根据数值元素来查找

    php_printf("the value I found is %d \n",Z_LVAL_P(find_key));

    

    // zend_bool key_exist = zend_hash_exists(ht,strpprintf(0,"first")); //根据zend_string字符串来查找

    // zend_bool key_exist = zend_hash_str_exists(ht,"third",5); //根据普通字符串来查找

    zend_bool key_exist = zend_hash_index_exists(ht,100); //根据索引来查找

    

    if(key_exist==TRUE){

        php_printf("I found the key\n");

    }else{

        php_printf("I did not find the key\n");

    }

    /*---------------------------/查找元素----------------------------------------------- */

    php_printf("the numer of arr is %d \n",zend_array_count(ht)); //获取数组元素个数

 

    /*---------------------------删除元素----------------------------------------------- */

    // zend_hash_del() // 根据zend_String 来删除

    // zend_hash_str_del() // 根据普通字符串来删除

    // zend_hash_index_del() // 根据数字索引来删除

    /*---------------------------/删除元素----------------------------------------------- */

    

    /*---------------------------遍历数组----------------------------------------------- */

    zval *val;

    //遍历过程中把数组元素赋值给val,除了上面的这个宏

    ZEND_HASH_FOREACH_VAL(ht,val){

        zend_long value_type = Z_TYPE_P(val);

        switch (value_type)

        {

            case IS_LONG:

                php_printf("the value is %d \n",Z_LVAL_P(val));

                break;

            case IS_DOUBLE:

                php_printf("the value is %f\n",Z_DVAL_P(val));

                break;

            case IS_STRING:

                php_printf("the value is %s\n",Z_STRVAL_P(val));

            default:

                break;

        }

    }ZEND_HASH_FOREACH_END();

    

    //遍历获得所有的数值索引

    zend_ulong h;

    ZEND_HASH_FOREACH_NUM_KEY(ht,h){

        php_printf("the ZEND_HASH_FOREACH_NUM_KEY KEY is %d \n",h);

    }ZEND_HASH_FOREACH_END();

    

    //遍历获得所有的zend_String索引

    zend_string *str_key;

    ZEND_HASH_FOREACH_STR_KEY(ht,str_key){

        if(str_key){

            //只有这样赋值了才能打印出来 或者使用 RETURN_STR(str_key)

            zval call_func_name;

            ZVAL_STR(&call_func_name,str_key);

            php_printf("the ZEND_HASH_FOREACH_STR_KEY KEY IS %s \n",Z_STRVAL(call_func_name));

 

        }

    }ZEND_HASH_FOREACH_END();

    

    //遍历获得所有的 数字 和 string key

    zend_string *str_key2;

    zend_ulong index;

    ZEND_HASH_FOREACH_KEY(ht,index,str_key2){

        if(str_key2){

            zval call_func_name;

            ZVAL_STR(&call_func_name,str_key2);

            php_printf("the ZEND_HASH_FOREACH_KEY KEY is %s \n",Z_STRVAL(call_func_name));

        }else if(index){

            php_printf("the ZEND_HASH_FOREACH_KEY KEY is %d \n",index);

        }

    }ZEND_HASH_FOREACH_END();

    

    //遍历key 和 value

    zend_string *str_key3;

    zend_ulong index3;

    zval *val3,key_val3;

    ZEND_HASH_FOREACH_KEY_VAL(ht,index3,str_key3,val3){

        

        if(str_key3){ //一定要字符串在前才行

            ZVAL_STR(&key_val3,str_key3);

            php_printf("the ZEND_HASH_FOREACH_KEY_VAL KEY is (%s)",Z_STRVAL(key_val3));

        }else if(index3){

            php_printf("the ZEND_HASH_FOREACH_KEY_VAL KEY is (%d)",index3);

        }

        

        //根据类型打印value的值

        zend_long value_type = Z_TYPE_P(val3);

        switch (value_type)

        {

            case IS_LONG:

                php_printf("and the value is %d \n",Z_LVAL_P(val3));

                break;

            case IS_DOUBLE:

                php_printf("and the value is %f\n",Z_DVAL_P(val3));

                break;

            case IS_STRING:

                php_printf("and the value is %s\n",Z_STRVAL_P(val3));

            default:

                break;

        }

 

    }ZEND_HASH_FOREACH_END();

    /*---------------------------/遍历数组----------------------------------------------- */

    // zend_array_destroy(ht); //销毁数组

 

    RETURN_ARR(ht); //返回数组

}

 

 

常量定义

 

PHP_MINIT_FUNCTION(myext)

{

    /* If you have INI entries, uncomment these lines

    REGISTER_INI_ENTRIES();

    */

    /* 常量定义 一般在 MINIT function 中定义 */

    REGISTER_STRING_CONSTANT("MY_EXT_STRING_CONSTANT","Hello World",CONST_CS | CONST_PERSISTENT);

    REGISTER_NULL_CONSTANT("MY_EXT_NULL_CONSTANT",CONST_CS | CONST_PERSISTENT);

    REGISTER_BOOL_CONSTANT("MY_EXT_BOOL_CONSTANT",TRUE,CONST_CS | CONST_PERSISTENT);

    REGISTER_LONG_CONSTANT("MY_EXT_LONG_CONSTANT",100,CONST_CS | CONST_PERSISTENT);

    REGISTER_DOUBLE_CONSTANT("MY_EXT_DOUBLE_CONSTANT",100.99,CONST_CS | CONST_PERSISTENT);

    REGISTER_STRINGL_CONSTANT("MY_EXT_STRLEN_CONSTANT","Hello World",5,CONST_CS | CONST_PERSISTENT);

    

    return SUCCESS;

}

 

 

面向对象

 

面向对象是PHP重要的特性之一,通过扩展可以对类实现更灵活的,更抢到的控制,同时与PHP脚本中的类相比.通过扩展实现的类不需要编译,有更高的性能.

 

内部类注册

    在扩展中定义一个内部类的方式和函数类似,函数最终注册到EG(function_table) ,而类则最终注册到EG(class_table) 符号表中.注册的过程首先是魏类创建一个

zend_class_entry结构.然后把这个结构插入EG(class_table),当然这个过程不需要我们手动操作,PHP提供了现成的方法和宏帮我们对zend_class_entry 进行初始化

和注册,通常情况下会把内部类的注册放到 module startup阶段. 也就是定义在扩展PHP_MINIT_FUNCTION()中.一个简单的类的注册只需要下面几行.

 

//类注册额

    zend_class_entry ce; //分配一个zend_class_entry 这个结构只在注册的时候使用,所以分配在栈上即可

    INIT_CLASS_ENTRY(ce,"MyClass",NULL);//对 zend_class_entry 进行初始化

    zend_register_internal_class(&ce); //注册

PHP_CODE

    $obj = new MyClass();    

    var_dump($obj);

 

上面那样就成功定义了一个内部类,类名为MyClass 只是这个类还没有任何的成员属性,成员方法.

 

成员属性

1 .声明成员属性

    zval myname;

    // ZVAL_STR(&myname,strpprintf(0,"see you again222")); //zend_string字符串

    ZVAL_DOUBLE(&myname,20.00);

    // ZEND_ACC_PUBLIC ZEND_ACC_PROTECTED ZEND_ACC_PRIVATE

    zend_declare_property(myclass_ce,"myname",6,&myname,ZEND_ACC_PUBLIC); //声明一个常规属性

    // php_printf("the len is %d ",ZEND_STRL("str"));

    zend_declare_property_string(myclass_ce,ZEND_STRL("str"),"Hello World222",ZEND_ACC_PUBLIC); //声明一个常规属性

     // 等价下面这一条 相当于展开了

    // = zend_declare_property_string(myclass_ce,"str",sizeof("str")-1,"Hello World222",ZEND_ACC_PUBLIC); //声明一个常规属性

 

内部声明的属性不能是数组,对象,资源. 如果是这几种类型在声明的时候将会报错,单着这并不是指内部类不能使用这些类型,只是默认是值不能是这些类型,所以实际使用时通常会把他们

定义为NULL,运行时在通过对象将其修改为其他类型.

 

2.修改成员属性

修改成员属性需要在类的成员方法中修改

//使用成员方法修改成员属性

ZEND_METHOD(MyClass, updatemyname)

{

    zval myname;

    ZVAL_DOUBLE(&myname,50.00);

    zend_update_property(myclass_ce,getThis(),ZEND_STRL("myname"),&myname);

    RETURN_TRUE;

}

 

3.获取成员属性

 

//读取属性

ZEND_METHOD(MyClass, getmyname)

{

    zval myname,*res;

    zend_bool nowtrue = 1;

    //不知道 myname 这个参数有啥用....

    res = zend_read_property(myclass_ce,getThis(),ZEND_STRL("myname"),nowtrue,&myname);

    //使用这种方法可以返回任何类型 不错

    ZVAL_DEREF(res);

    ZVAL_COPY(return_value, res);

}

 

成员方法

//1 定义要使用的方法集

zend_function_entry myclass_methods[]={

    PHP_ME(MyClass,updatemyname,NULL,ZEND_ACC_PUBLIC)

    PHP_ME(MyClass,getmyname,NULL,ZEND_ACC_PUBLIC)

    PHP_ME(MyClass,getConstant,NULL,ZEND_ACC_PUBLIC)

    PHP_FE_END

};

//INIT 的时候加入上面定义的

//对 zend_class_entry 进行初始化 后面测参数是类的方法

INIT_CLASS_ENTRY(ce,"MyClass",myclass_methods);

//2 在.h中像普通方法一样进行声明

ZEND_METHOD(MyClass, updatemyname);

ZEND_METHOD(MyClass, getmyname);

ZEND_METHOD(MyClass, getConstant);

 

//3具体实现

ZEND_METHOD(MyClass, getmyname)

{

    zval myname,*res;

    zend_bool nowtrue = 1;

    //不知道 myname 这个参数有啥用....

    res = zend_read_property(myclass_ce,getThis(),ZEND_STRL("myname"),nowtrue,&myname);

    //使用这种方法可以返回任何类型 不错

    ZVAL_DEREF(res);

    ZVAL_COPY(return_value, res);

}

 

 

常量

1 声明常量

zend_declare_class_constant_string(myclass_ce,ZEND_STRL("CONSTANT_VALUE"),"WORLD22");

    zend_declare_class_constant(myclass_ce,ZEND_STRL("CONSTANT_VAL"),&next_myname);

 

2获取常量(目前调试还有些问题) ,鉴于日常开发中 使用得非常少,先不管

zend_get_constant_ex(strpprintf(0, "CONSTANT_VAL"),myclass_ce,ZEND_ACC_PUBLIC);

 

类的实例化

 

/* 返回类实例*/

PHP_FUNCTION(return_myclass){

    object_init_ex(return_value, myclass_ce);

}

 

这样就可以通过外部方法返回实例了.

注意: 如果自定义了create_object 方法,则需要考虑是否定义对象销毁的handler:zend_object->handlers->free_obj. 默认情况下,当销毁一个对象时会调用默认的方法处理

如果你的create_object 方法里处理zend_object 外还分配了其他内存,则需要同时自定义free_obj,否则就会导致 zend_object 之外的内存得不到释放.自定义对象的handlers

方法比较简单,直接再创建对象时直接将handlers指定为自定义即可

 

资源

除了PHP中定义的那些布尔型,整型,浮点型数组等类型外,实际应用中还会经常用到socket 连接,文件句柄,那么这些类型在PHP中是如何实现的呢? 答案就是资源.资源是PHP中比较

特殊的一种类型,资源类型只能通过扩展,内核定义,无法在用户控件自定义资源类型,只能使用资源.资源也是PHP中最为灵活的一种类型,可以把任何数据定义为资源类型.

 

注册新的资源类型时,PHP内核并不关心资源的具体数据是什么,它只分配给资源一个唯一编号作为区分资源类型的标识,剩下的事情都需要扩展自己控制.

 

struct _zend_resource {

    zend_refcounted_h gc;

    int handle; //资源的序号

    int type; //资源的类型

    void *ptr; //资源的数据

};

 

创建资源有两种含义: 一种是创建实际的资源,并不是zend_resource结构,这种完全由资源注册方自己完成;另外一种就是创建zend_resource结构.比如我们创建的资源需要传递

到用户PHP用户空间,这个时候就需要创建一个zend_resource结构,然后把实际的资源保存到zend_resource->ptr.如果要创建一个资源类型的变量,即zend_resource,可以通过

zend_register_resource()方法申请,该方法只有两个参数:一个为资源的数据指针,另一个为注册资源时返回的资源类型,申请成功后返回 zend_resource地址.分配得所有资源变量

保存在EG(regular_list) 哈希表中.请求结束后清理这个哈希表.

 

 

ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type)

 

 

范例:

//定义资源结构

typedef struct{

    zend_string *filename;

    FILE *fp;

} my_file_t;

static int le_myfile;

//销毁资源的函数

void myfile_dtor(zend_resource *res){

    my_file_t *myresource;

    myresource = (my_file_t *)(res->ptr);

    //关闭文件

    fclose(myresource->fp);

    //释放资源

    efree(myresource);

}

PHP_MINIT_FUNCTION(myext)

{

    //注册资源

    le_myfile = zend_register_list_destructors_ex(myfile_dtor,NULL,"myfile",module_number);

}

//实现方法

//返回资源

PHP_FUNCTION(my_fopen){

    zend_string *path;

    FILE *fp;

    my_file_t *myresource;

    zend_resource *var_resource;

    //解析参数

    if(zend_parse_parameters(ZEND_NUM_ARGS(),"S",&path) == FAILURE){

        RETURN_FALSE;

    }

    //打开文件

    fp = fopen(ZSTR_VAL(path),"w+");

    //创建资源结构

    myresource = emalloc(sizeof(my_file_t));

    myresource->filename = path;

    myresource->fp = fp;

    var_resource = zend_register_resource(myresource,le_myfile);

    RETURN_RES(var_resource);

}

//写入数据

PHP_FUNCTION(my_fwrite){

    zval *res;

    zend_string *content;

    my_file_t *myresource;

    if(zend_parse_parameters(ZEND_NUM_ARGS(),"rS",&res,&content) == FAILURE){

        RETURN_FALSE;

    }

 

    myresource = Z_RES_VAL_P(res);

    fwrite(ZSTR_VAL(content),sizeof(char),content->len,myresource->fp);

}

PHPCODE

$myfp = my_fopen("/tmp/log.log");

my_fwrite($myfp,"Hello World");

 

上面的是一个资源的基本用法,从上面的例子我们看到,通过资源类型可以灵活的存储任意类型,在PHP扩展中.很多RPC 客户端是通过资源类型来存储socket 连接的.大家可以到ext目录搜索查看

 

 

销毁资源

PHP 的变量是自动回收的,资源类型不同于其他数据,因为资源不仅仅是释放内存,它还需要进行其他的一些清理,比如上面的示例,销毁资源时需要关闭文件,在比如保存socket连接的资源,在释放是也需要关闭连接,在注册资源类型时可以提供一个析构函数,当资源销毁时,资源管理器会回调该函数进行清理,这个函数类型如下

 

持久化资源

普通资源的生命周期在request 结束时也随之结束,持久化资源则可以使资源在request 结束时任然保留,下次请求时还可以继续使用,持久化资源通过EG(persistent_list) 保存.直接将

资源插入这个哈希表即可.持久化资源有着非常重要的意义.利用持久化资源可以实现长连接,从而提高系统的性能.持久化资源在module shutdown 阶段才被释放

 

总结

本章介绍了PHP扩展开发中用到的一些知识,主要是一些借口,宏的使用.内容更多的倾向于应用性,原理性的东西介绍的比较少.实际PHP扩展的开发并不复杂,尽管扩展开发并不需要对

ZEND_VM 有很深入的了解,但是如果对ZEND_VM 的实现比较熟悉,那么对扩展开发的帮助将非常大.你可以更容易想到从哪些地方入手实现扩展的功能.debug的时候也就更加得心应手.总之一句话,如果你想真正理解PHP 的内部实现,仅仅通过扩展是不可能完成的

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(PHP)