仓库地址
协程相关结构定义
首先,我们需要一个PHP
可用的协程,根据梳理一下架构这篇文章的内容,我们需要在study_coroutine.h
里面来定义:
#include "php_study.h"
namespace Study
{
class PHPCoroutine
{
static long create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv);
};
}
然后,我们还需要一个与PHP
无关的协程数据结构,我们定义在include/coroutine.h
下面:
#ifndef COROUTINE_H
#define COROUTINE_H
namespace Study
{
class Coroutine
{
static long create(coroutine_func_t fn, void* args = nullptr);
};
}
#endif /* COROUTINE_H */
coroutine_func_t
是协程需要跑的函数,我们可以定义在include/context.h
下面:
#ifndef CONTEXT_H
#define CONTEXT_H
typedef void (*coroutine_func_t)(void*);
#endif /* CONTEXT_H */
它定义了一个指向函数的指针类型,返回值是void
,参数是一个void *
指针。
然后,我们在include/coroutine.h
中引入这个context.h
文件:
#include "context.h"
协程接口参数声明
OK,此时,我们需要为PHP
脚本提供一个创建协程的接口,我们在文件study_coroutine_util.cc
里面来完成。首先,我们需要确定一下这个接口的参数是什么,很显然,是一个PHP
函数:
#include "study_coroutine.h"
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_coroutine_create, 0, 0, 1)
ZEND_ARG_CALLABLE_INFO(0, func, 0)
ZEND_END_ARG_INFO()
其中ZEND_BEGIN_ARG_INFO_EX
和ZEND_END_ARG_INFO
是一对宏,用来声明函数接受的参数。其中ZEND_BEGIN_ARG_INFO_EX
展开如下:
#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
static const zend_internal_arg_info name[] = {
{ (const char*)(zend_uintptr_t)(required_num_args), 0, return_reference, 0 },
name
是参数的的名字。
_unused
我们不用管,因为ZEND_BEGIN_ARG_INFO_EX
展开之后,并没有用到_unused
。
return_reference
表示是否返回引用。
required_num_args
表示这个函数最少需要传递的参数个数。
ZEND_ARG_CALLABLE_INFO
用来显式声明参数为callable
,将检查函数、成员方法是否可调用。
ZEND_END_ARG_INFO
展开如下:
#define ZEND_END_ARG_INFO() };
因此,我们对这个参数声明展开后,会得到如下内容:
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_coroutine_create, 0, 0, 1)
ZEND_ARG_CALLABLE_INFO(0, func, 0)
ZEND_END_ARG_INFO()
static const zend_internal_arg_info arginfo_study_coroutine_create[] = {
{ (const char*)(zend_uintptr_t)(1), 0, 0, 0 },
ZEND_ARG_CALLABLE_INFO(0, func, 0)
};
所以,虽然我在
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_coroutine_create, 0, 0, 1)
中写下了单词arginfo
、study_coroutine_create
,似乎一定是要这样写,但是,我们把ZEND_BEGIN_ARG_INFO_EX
宏展开之后会发现,arginfo_study_coroutine_create
只是一个变量名而已。因此,我这里这样组合这个变量是为了可读性更好,并不是一定要这样声明这个参数,这一点大家需要去注意。
协程接口方法声明
然后,我们需要在文件study_coroutine_util.cc
里面去声明这个方法:
static PHP_METHOD(study_coroutine_util, create);
PHP_METHOD
展开之后的内容如下:
#define PHP_METHOD ZEND_METHOD
#define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))
#define ZEND_MN(name) zim_##name
#define ZEND_NAMED_FUNCTION(name) void ZEND_FASTCALL name(INTERNAL_FUNCTION_PARAMETERS)#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value
所以,接口方法展开的内容如下:
PHP_METHOD(study_coroutine_util, create);
ZEND_METHOD(study_coroutine_util, create);
ZEND_NAMED_FUNCTION(ZEND_MN(study_coroutine_util##_##create));
ZEND_NAMED_FUNCTION(zim_##study_coroutine_util##_##create);
void ZEND_FASTCALL zim_##study_coroutine_util##_##create(INTERNAL_FUNCTION_PARAMETERS);
void ZEND_FASTCALL zim_##study_coroutine_util##_##create(zend_execute_data *execute_data, zval *return_value);
static PHP_METHOD(study_coroutine_util, create);
相当于声明了如下函数:
void zim_study_coroutine_util_create(zend_execute_data *execute_data, zval *return_value);
通过对接口方法的展开,我们发现,虽然接口命名是单词study_coroutine_util
和create
,似乎必须得是真正的类名加上方法名。其实不然,这里也只是为了可读性更好。
我们还可以对比一下PHP_FUNCTION
这个宏,实际上,它和PHP_METHOD
的一个区别就是少拼接了classname
。
协程接口实现
我们在study_coroutine_util.cc
文件里面写下:
PHP_METHOD(study_coroutine_util, create)
{
php_printf("success!n");
}
因为文章篇幅的原因,我们这里简单实现。
协程接口收集
接着,我们需要对这个方法进行收集,放在变量study_coroutine_util_methods
里面。在study_coroutine_util.cc
写下:
const zend_function_entry study_coroutine_util_methods[] =
{
PHP_ME(study_coroutine_util, create, arginfo_study_coroutine_create, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_FE_END
};
这里我们使用到一个数据结构:
typedef struct _zend_function_entry {
const char *fname;
zif_handler handler;
const struct _zend_internal_arg_info *arg_info;
uint32_t num_args;
uint32_t flags;
} zend_function_entry;
fname
是函数的名字。
handler
是一个函数指针,也就是该函数的主体。那么是什么样的函数指针呢?我们得看看前面的zif_handler
:
/* zend_internal_function_handler */
typedef void (ZEND_FASTCALL *zif_handler)(INTERNAL_FUNCTION_PARAMETERS);
可以发现,这是一个没有返回值,参数是INTERNAL_FUNCTION_PARAMETERS
的函数。这个其实和PHP_METHOD
这个宏展开得到的函数声明类型是一致的。
arg_info
是这个接口方法对应的参数。可以发现,它实际上就是我们上面的那个参数展开后的类型。所以这里很明显,我们必须填写arginfo_study_coroutine_create
,也就是我们参数展开后定义的那个变量。
num_args
是接口方法的参数个数。可以发现,我们这里并没有填写参数的个数,实际上,这个参数的个数会通过宏ZEND_FENTRY
来计算出来:
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags },
flags
是标志。例如这个接口方法是public
、static
的。
协程类注册
然后,我们需要去注册我们的StudyCoroutine
这个类。我们在MINIT
这个阶段进行注册,代码如下:
zend_class_entry study_coroutine_ce;
zend_class_entry *study_coroutine_ce_ptr;
PHP_MINIT_FUNCTION(study)
{
INIT_NS_CLASS_ENTRY(study_coroutine_ce, "Study", "Coroutine", study_coroutine_util_methods);
return SUCCESS;
}
但是,考虑到以后我们会有许多的类,我们不在MINIT
里面直接写注册的代码,而是让study_coroutine_util.cc
提供一个函数,我们在这个函数里面实现注册功能:
/**
* Define zend class entry
*/
zend_class_entry study_coroutine_ce;
zend_class_entry *study_coroutine_ce_ptr;
void study_coroutine_util_init()
{
INIT_NS_CLASS_ENTRY(study_coroutine_ce, "Study", "Coroutine", study_coroutine_util_methods);
study_coroutine_ce_ptr = zend_register_internal_class(&study_coroutine_ce TSRMLS_CC); // Registered in the Zend Engine
}
然后,我们在php_study.h
里面来进行声明:
void study_coroutine_util_init();
然后,我们在MINIT
中对这个函数进行调用,完成类的注册:
PHP_MINIT_FUNCTION(study)
{
study_coroutine_util_init();
return SUCCESS;
}
编译测试
~/codeDir/cppCode/study # ./make.sh
编写测试脚本:
<?php
StudyCoroutine::create();
~/codeDir/cppCode/study # php test.php
success!
~/codeDir/cppCode/study #
OK,到这里,我们算是完成了协程创建接口的前期工作。