请确定你已阅读《PHP7扩展开发教程[6] – 如何调用PHP函数?》。
小憩一下
近些天连续的编写教程倍感疲惫,好在重要的内容已经讲过去大半,剩下的内容会陆陆续续写完。
在我的计划之中,整个教程应该在10篇左右,从本章开始的剩余的内容将会简单的多,希望可以尽快完成,呈现一个完整的教程给大家。
遥想从前,对于PHP扩展开发也是敬而远之的态度,主要是因为没有相关联的工作需求,后来是因为工作中需要真正用到扩展开发,所以才懵懵懂懂的走上了这条路。
虽然坎坎坷坷开发完了扩展,实际上对Zend API的认识仍旧非常模糊,以至于写出来的扩展只是”能用”而已。好在我本身对这个技术很有兴趣,同时也正是因为网上关于PHP扩展开发的博客少之又少,遇到问题几乎无法解决,只能求助源码,更别提PHP7已经大变样了。
正是如此,所以我决定从头学起,也就有了这篇教程。
正式开始
本章代码:https://github.com/owenliang/php7-extension-explore/tree/master/course7-how-to-create-object。
我的目标是在一个全局函数中,调用类的静态方法,然后创建类对象并调用它的非静态方法,让我们开始吧。
首先,我为myext类增加了一个static方法叫做print_author,它将输出一段信息:
1 2 3 4 5 6 7 8 9 10 |
zend_function_entry funcs[] = { // fname,handler,arg_info,,num_args,flags {"__construct", zim_myext_constructor, NULL, 0, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR}, {"version", zim_myext_version, NULL, 0, ZEND_ACC_PUBLIC}, {"strtolower", zim_myext_strtolower, myext_strtolwer_arginfo, 1, ZEND_ACC_PUBLIC/*method flag*/}, {"strtoupper", zim_myext_strtoupper, myext_strtoupper_arginfo, 1, ZEND_ACC_PUBLIC}, {"strcase_convert", zim_myext_strcase_convert, NULL, 2, ZEND_ACC_PRIVATE}, {"print_author", zim_myext_print_author, NULL, 0, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC}, {NULL, NULL, NULL, 0, 0}, }; |
可以看一下这个方法的实现zim_myext_print_author,非常简单:
1 2 3 4 5 6 |
// Myext's static method void zim_myext_print_author(zend_execute_data *execute_data, zval *return_value) { TRACE("zim_myext_print_author"); php_output_write("author=owenliang\n", sizeof("author=owenliang\n") - 1); ZVAL_BOOL(return_value, 1); } |
这里需要关注的是php_output_write函数,它相当于PHP里的echo方法,相关定义如下:
1 2 |
PHPAPI size_t php_output_write_unbuffered(const char *str, size_t len) PHPAPI size_t php_output_write(const char *str, size_t len) |
一般我们用第2个就可以,因为带缓存的输出性能会更好点。
接下来,我定义一个全局函数zif_myext_test_object,在导出扩展时注册它:
1 2 3 4 5 |
zend_function_entry global_funcs[] = { // fname,handler,arg_info,,num_args,flags {"test_object", zif_myext_test_object, NULL, 0, 0}, {NULL, NULL, NULL, 0, 0}, }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
zend_module_entry module = { STANDARD_MODULE_HEADER_EX, // size,zend_api,zend_debug,zts NULL, // ini_entry NULL, // deps "myext", //name global_funcs, // functions extension_startup, // module_startup_func extension_shutdown, // module_shutdown_func extension_before_request, // request_startup_func extension_after_request, // request_shutdown_func extension_info, // info_func "1.0", // version // globals_size,globals_ptr,globals_ctor,globals_dtor,post_deactivate_func,module_started,type, // handle,module_number,build_id STANDARD_MODULE_PROPERTIES, }; |
现在,我们具体来看一下这个全局函数的实现,代码将分段讲解。
1 2 3 4 5 6 7 8 9 |
// global function void zif_myext_test_object(zend_execute_data *execute_data, zval *return_value) { TRACE("zif_myext_test_object"); // call myext's static method: print_author zend_string *myext_classname = zend_string_init("myext", sizeof("myext") - 1, 0); zend_class_entry *myext_handle = zend_lookup_class(myext_classname); zend_string_release(myext_classname); assert(myext_handle == myext_class_handle); |
首先,我创建了一个string类型的zval,其值是类名myext。将类名传给zend_lookup_class方法,可以返回对应类的class句柄。通过断言我可以确定,zend_lookup_class返回的class句柄,与我注册myext类得到的class句柄是同一个对象。最后释放myext_classname的内存,因为不再使用。
在这里我们完全可以直接使用注册myext类时返回的class句柄,然而如果该类不是我们自己注册的类,那么就只能通过zend_lookup_class去获得了。
有了class句柄,接下来我要调用myext的静态方法print_author,因此我开始准备zend_call_function的第1个调用参数:
1 2 3 4 5 6 7 8 9 10 11 12 |
zval retval; zend_fcall_info fci = { size: sizeof(zend_fcall_info), retval: &retval, params: NULL, object: NULL, no_separation: 1, param_count: 0, }; ZVAL_UNDEF(&fci.function_name); zval *print_author_func = zend_hash_str_find(&(myext_handle->function_table), "print_author", sizeof("print_author") - 1); |
与上一章相比,object设置为了NULL,因为我们调用的是静态方法并没有对象,其他字段并没有什么区别。
接下来,我在myext的句柄的function_table里找出print_author方法,它底层数据是一个zend_function,前一章我们已经见过了。
1 2 3 4 5 6 7 8 9 10 |
zend_fcall_info_cache fcic = { initialized: 1, function_handler: print_author_func->value.func, calling_scope: myext_handle, called_scope: NULL, object: NULL, }; assert(zend_call_function(&fci, &fcic) == SUCCESS); assert(Z_TYPE_P(&retval) == IS_TRUE); |
接下来,我们定义第2个参数,function_handler指向print_author方法。calling_scope代表被调用函数所属的class句柄,所以填myext_handle。called_scope表示当前所处的类,我们不在类内部,所以传NULL。最后的object填NULL就不必多说了。
现在调用zend_call_function可以成功调用到myext类的静态方法print_author,打印出一段话在屏幕上,断言确认了返回值是True类型。
现在,我们尝试创建一个myext对象:
1 2 3 |
// new a myext object zval myext_obj; assert(object_init_ex(&myext_obj, myext_handle) == SUCCESS); |
这里主要用到了object_init_ex方法,它第1个参数是一个未经初始化的zval,第2个参数是要创建的类对应的class句柄。
我们也可以像PHP里new Object()一样,创建一个默认类型的对象,其方法名是object_init,相关定义如下:
1 2 |
ZEND_API int _object_init(zval *arg ZEND_FILE_LINE_DC) ZEND_API int _object_init_ex(zval *arg, zend_class_entry *class_type ZEND_FILE_LINE_DC) |
在创建对象的时候,Zend并不会帮我们调用构造函数,需要我们自己显式的在object上调用__construct方法:
1 2 3 4 5 6 |
// call object's __construct zval ctor_name; zval ctor_retval; ZVAL_STR(&ctor_name, zend_string_init("__construct", sizeof("__construct") - 1, 0)); assert(call_user_function(&EG(function_table), &myext_obj, &ctor_name, &ctor_retval, 0, NULL) == SUCCESS); zval_ptr_dtor(&ctor_name); |
先创建函数名ctor_name,然后通过便捷函数call_user_function调用即可。
类似的,接下来调用一下obj的strtolower方法,整个过程我们已经很熟悉了:
1 2 3 4 5 6 7 8 9 10 11 |
// call object's method zval func_name; ZVAL_STR(&func_name, zend_string_init("strtolower", sizeof("strtolower") - 1, 0)); zval param; ZVAL_STR(¶m, zend_string_init("OWENLIANG", sizeof("OWENLIANG") - 1, 0)); zval retval2; assert(call_user_function(&EG(function_table), &myext_obj, &func_name, &retval2, 1, ¶m) == SUCCESS); TRACE("$myext_obj->strtolower(OWENLIANG)=%.*s", retval2.value.str->len, retval2.value.str->val); zval_ptr_dtor(&func_name); zval_ptr_dtor(¶m); zval_ptr_dtor(&retval2); |
先创建函数名func_name,再准备调用参数param以及返回值容器retval2,最后调用call_user_function完成调用。最后不要忘记释放资源,函数名,参数,返回值。
到这里还没有结束!如果你的object不再使用,也请释放它:
1 2 |
// free object zval_ptr_dtor(&myext_obj); |
结语
本章你应该掌握:
- 获取任意类的class句柄。
- 调用类的静态方法。
- 创建类对象。
下一章,我将展示如何定义全局常量,以及获取全局变量($_GET,$_POST)。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

Pingback引用通告: PHP7扩展开发教程[8] – 如何访问超级全局变量? | 鱼儿的博客
Owen https://360.cn/
Stevens