php7扩展开发[9] zend_call_method和zend_call_function


场景:我们想要在php7扩展中调用用户自定的类中的方法,而且方法有多个参数,找到以下方
法,没有看到可以超过两个参数的方法。所以一直向下查找,发现zend_call_method调用的
zend_call_function,但是并非只能传两个参数。一直追下去。这样我们用zend_call_function来调用多个参数,实现我们要的目的。
函数原型:在 Zend/zend_interfaces.下:
#define zend_call_method_with_0_params(obj, obj_ce, fn_proxy, function_name, retval) \
    zend_call_method(obj, obj_ce, fn_proxy, function_name, sizeof(function_name)-1, retval, 0, NULL, NULL)

#define zend_call_method_with_1_params(obj, obj_ce, fn_proxy, function_name, retval, arg1) \
    zend_call_method(obj, obj_ce, fn_proxy, function_name, sizeof(function_name)-1, retval, 1, arg1, NULL)

#define zend_call_method_with_2_params(obj, obj_ce, fn_proxy, function_name, retval, arg1, arg2) \
    zend_call_method(obj, obj_ce, fn_proxy, function_name, sizeof(function_name)-1, retval, 2, arg1, arg2)


上面三个调用下面的zend_call_methodZEND_API zval* zend_call_method(zval *object, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, size_t function_name_len, zval *retval_ptr, int param_count, zval* arg1, zval* arg2)
{
    int result;
    zend_fcall_info fci;
    zval retval;
    HashTable *function_table;

    zval params[2];

    if (param_count > 0) {
        ZVAL_COPY_VALUE(&params[0], arg1);
    }
    if (param_count > 1) {
        ZVAL_COPY_VALUE(&params[1], arg2);
    }

    //看这里.原来可以传多个参数,只是这里做了限制
    //看这里.原来可以传多个参数,只是这里做了限制
    //看这里.原来可以传多个参数,只是这里做了限制

    fci.size = sizeof(fci);

    fci.object = (object && Z_TYPE_P(object) == IS_OBJECT) ? Z_OBJ_P(object) : NULL;
    ZVAL_STRINGL(&fci.function_name, function_name, function_name_len);
    fci.retval = retval_ptr ? retval_ptr : &retval;
    fci.param_count = param_count;
    fci.params = params;
    fci.no_separation = 1;
    fci.symbol_table = NULL;

    if (!fn_proxy && !obj_ce) {

        fci.function_table = !object ? EG(function_table) : NULL;

        //看这里.原来调用的是zend_call_function
        //看这里.原来调用的是zend_call_function
        //看这里.原来调用的是zend_call_function

        result = zend_call_function(&fci, NULL);
        zval_ptr_dtor(&fci.function_name);
    } else {
        zend_fcall_info_cache fcic;

        fcic.initialized = 1;
        if (!obj_ce) {
            obj_ce = object ? Z_OBJCE_P(object) : NULL;
        }
        if (obj_ce) {
            function_table = &obj_ce->function_table;
        } else {
            function_table = EG(function_table);
        }
        if (!fn_proxy || !*fn_proxy) {
            if ((fcic.function_handler = zend_hash_find_ptr(function_table, Z_STR(fci.function_name))) == NULL) {
                /* error at c-level */
                zend_error_noreturn(E_CORE_ERROR, "Couldn't find implementation for method %s%s%s", obj_ce ? ZSTR_VAL(obj_ce->name) : "", obj_ce ? "::" : "", function_name);
            }
            if (fn_proxy) {
                *fn_proxy = fcic.function_handler;
            }
        } else {
            fcic.function_handler = *fn_proxy;
        }
        fcic.calling_scope = obj_ce;
        if (object) {
            fcic.called_scope = Z_OBJCE_P(object);
        } else {
            zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));

            if (obj_ce &&
                (!called_scope ||
                 !instanceof_function(called_scope, obj_ce))) {
                fcic.called_scope = obj_ce;
            } else {
                fcic.called_scope = called_scope;
            }
        }
        fcic.object = object ? Z_OBJ_P(object) : NULL;

        //看这里.原来调用的是zend_call_function
        //看这里.原来调用的是zend_call_function
        //看这里.原来调用的是zend_call_function

        result = zend_call_function(&fci, &fcic);
        zval_ptr_dtor(&fci.function_name);
    }
    if (result == FAILURE) {
        /* error at c-level */
        if (!obj_ce) {
            obj_ce = object ? Z_OBJCE_P(object) : NULL;
        }
        if (!EG(exception)) {
            zend_error_noreturn(E_CORE_ERROR, "Couldn't execute method %s%s%s", obj_ce ? ZSTR_VAL(obj_ce->name) : "", obj_ce ? "::" : "", function_name);
        }
    }
    /* copy arguments back, they might be changed by references */
    if (param_count > 0 && Z_ISREF(params[0]) && !Z_ISREF_P(arg1)) {
        ZVAL_COPY_VALUE(arg1, &params[0]);
    }
    if (param_count > 1 && Z_ISREF(params[1]) && !Z_ISREF_P(arg2)) {
        ZVAL_COPY_VALUE(arg2, &params[1]);
    }
    if (!retval_ptr) {
        zval_ptr_dtor(&retval);
        return NULL;
    }
    return retval_ptr;
}


我们可以看到zend_call_method调用zend_call_function,函数体定义在Zend/zend_API.h下:
ZEND_API int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache);
我们可以看到要想使用zend_call_function 必须要了解zend_fcall_info结构体。

PHP7 中
typedef struct _zend_fcall_info {
    size_t size;
    HashTable *function_table;
    zval function_name;
    zend_array *symbol_table;
    zval *retval;
    zval *params;
    zend_object *object;
    zend_bool no_separation;
    uint32_t param_count;
} zend_fcall_info;

由于网上流传的代码都是PHP5的代码,我们也就再贴一下PHP5的代码,到改造代码时,可以有个借鉴。
PHP5 中
typedef struct _zend_fcall_info {
   size_t size;
   HashTable *function_table;
   zval *function_name;
   HashTable *symbol_table;
   zval **retval_ptr_ptr;
   zend_uint param_count;
   zval ***params;
   zval *object_ptr;
   zend_bool no_separation;
} zend_fcall_info;

其中有变化的参数:
zval *function_name;   ->  zval function_name;
zval **retval_ptr_ptr; ->  zval *retval;
zval *object_ptr;      ->  zend_object *object;
zval ***params;        ->  zval *params;


在PHP7的扩展使用PHP5的代码make时,会出现如下错误:
error: incompatible types when assigning to type ‘struct zval *’ from type ‘zval’
error: ‘zend_fcall_info’ has no member named ‘object’
error: ‘zend_fcall_info’ has no member named ‘retval’


我们搜一下php源码中zend_call_function中的使用情况,我们会发现自动加载中的代码如下:

static inline void spl_instantiate_arg_n(zend_class_entry *pce, zval *retval, int argc, zval *argv)
{
    zend_function *func = pce->constructor;
    zend_fcall_info fci;
    zend_fcall_info_cache fcc;
    zval dummy;

    spl_instantiate(pce, retval);

    fci.size = sizeof(zend_fcall_info);
    fci.function_table = &pce->function_table;
    ZVAL_STR(&fci.function_name, func->common.function_name);
    fci.object = Z_OBJ_P(retval);
    fci.symbol_table = NULL;
    fci.retval = &dummy;
    fci.param_count = argc;
    fci.params = argv;
    fci.no_separation = 1;

    fcc.initialized = 1;
    fcc.function_handler = func;
    fcc.calling_scope = EG(scope);
    fcc.called_scope = pce;
    fcc.object = Z_OBJ_P(retval);

    zend_call_function(&fci, &fcc);
}

OK,我们写一个可以调用用户定义类的方法的函数

int class_call_user_method(zval *retval, zend_class_entry *obj_ce, 
    zval *obj, zval func,  uint32_t params_count, zval params[]){ 


    HashTable *function_table; 

    if(obj) { 
                function_table = &Z_OBJCE_P(obj)->function_table;
        }else{
                function_table = (CG(function_table));
    }

    // 对象初始化内容,不能放在这里
    // if(!obj_ce){

    //      object_init(&obj);
    // }
    // else{

    //      object_init_ex(&obj, obj_ce);
    // }

    zend_fcall_info fci;  
    fci.size = sizeof(fci);  
    fci.function_table = function_table;  
    fci.object =  obj ? Z_OBJ_P(obj) : NULL;;
    fci.function_name = func;   
    fci.retval = retval;  
    fci.param_count = params_count;  
    fci.params = params;  
    fci.no_separation = 1;  
    fci.symbol_table = NULL;  


    /**/
    int result;
    result = zend_call_function(&fci, NULL TSRMLS_CC);         //函数调用结束。  


    if (result == FAILURE) {

        zend_printf("error");
    }

    /**/
    zend_printf("Success.\n");


}

使用方法如下:

ZEND_METHOD(udf,get){

    //call  __construct
    zval retval;

    zval obj;
    object_init_ex(&obj, adf_ce);

    zval function_name;
    ZVAL_STRING(&function_name,"__construct");

    //int class_call_user_method(zval *retval, zend_class_entry *obj_ce, 
    //zval *obj, zval func,  uint32_t params_count, zval params[])
    class_call_user_method(&retval, adf_ce, &obj, function_name, 0,NULL);


    //call  set
    zval retval2;

    zval obj2;
    object_init_ex(&obj2, adf_ce);


    zval function_name2;
    ZVAL_STRING(&function_name2,"set");


    zval params[2];
    ZVAL_STRING(&params[0],"root");
    ZVAL_STRING(&params[1],"tyyy");


    class_call_user_method(&retval2, adf_ce, &obj2, function_name2, 2, params);


    //call get
    zval retval3;

    zval obj3;
    object_init_ex(&obj3, adf_ce);

    zval function_name3;
    ZVAL_STRING(&function_name3,"get");


    class_call_user_method(&retval3, adf_ce, &obj3, function_name3, 0, NULL);

}
  • 请尊重本人劳动成功,可以随意转载但保留以下信息
  • 作者:岁月经年
  • 时间:2016年03月
  • 首发:http://www.djhull.com
posted @ 2016-04-07 23:23  岁月经年  阅读(2214)  评论(0编辑  收藏  举报