前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手撸PHP扩展 0x05: 协程创建(一)

手把手撸PHP扩展 0x05: 协程创建(一)

作者头像
桶哥
发布2019-07-17 16:02:23
4640
发布2019-07-17 16:02:23
举报
文章被收录于专栏:PHP饭米粒PHP饭米粒

协程相关结构定义

首先,我们需要一个PHP可用的协程,根据梳理一下架构这篇文章的内容,我们需要在study_coroutine.h里面来定义:

代码语言:javascript
复制
#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下面:

代码语言:javascript
复制
#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下面:

代码语言:javascript
复制
#ifndef CONTEXT_H
#define CONTEXT_H

typedef void (*coroutine_func_t)(void*);

#endif	/* CONTEXT_H */

它定义了一个指向函数的指针类型,返回值是void,参数是一个void *指针。

然后,我们在include/coroutine.h中引入这个context.h文件:

代码语言:javascript
复制
#include "context.h"

协程接口参数声明

OK,此时,我们需要为PHP脚本提供一个创建协程的接口,我们在文件study_coroutine_util.cc里面来完成。首先,我们需要确定一下这个接口的参数是什么,很显然,是一个PHP函数:

代码语言:javascript
复制
#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_EXZEND_END_ARG_INFO是一对宏,用来声明函数接受的参数。其中ZEND_BEGIN_ARG_INFO_EX展开如下:

代码语言:javascript
复制
#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展开如下:

代码语言:javascript
复制
#define ZEND_END_ARG_INFO()		};

因此,我们对这个参数声明展开后,会得到如下内容:

代码语言:javascript
复制
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)
};

所以,虽然我在

代码语言:javascript
复制
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_coroutine_create, 0, 0, 1)

中写下了单词arginfostudy_coroutine_create,似乎一定是要这样写,但是,我们把ZEND_BEGIN_ARG_INFO_EX宏展开之后会发现,arginfo_study_coroutine_create只是一个变量名而已。因此,我这里这样组合这个变量是为了可读性更好,并不是一定要这样声明这个参数,这一点大家需要去注意。

协程接口方法声明

然后,我们需要在文件study_coroutine_util.cc里面去声明这个方法:

代码语言:javascript
复制
static PHP_METHOD(study_coroutine_util, create);

PHP_METHOD展开之后的内容如下:

代码语言:javascript
复制
#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

所以,接口方法展开的内容如下:

代码语言:javascript
复制
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);相当于声明了如下函数:

代码语言:javascript
复制
void zim_study_coroutine_util_create(zend_execute_data *execute_data, zval *return_value);

(其中,zimzend internal method的缩写)

通过对接口方法的展开,我们发现,虽然接口命名是单词study_coroutine_utilcreate,似乎必须得是真正的类名加上方法名。其实不然,这里也只是为了可读性更好。

我们还可以对比一下PHP_FUNCTION这个宏,实际上,它和PHP_METHOD的一个区别就是少拼接了classname

协程接口实现

我们在study_coroutine_util.cc文件里面写下:

代码语言:javascript
复制
PHP_METHOD(study_coroutine_util, create)
{
    php_printf("success!\n");
}

因为文章篇幅的原因,我们这里简单实现。

协程接口收集

接着,我们需要对这个方法进行收集,放在变量study_coroutine_util_methods里面。在study_coroutine_util.cc写下:

代码语言:javascript
复制
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
};

这里我们使用到一个数据结构:

代码语言:javascript
复制
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是函数的名字,对应的是PHP_ME的第二个参数,Zend引擎将会创建一个包含函数名fnameinterned zend_string。在这里,是create。这个fname是我们可以在PHP脚本中使用的。而PHP_ME的第一个参数study_coroutine_util是为了拼凑出:

代码语言:javascript
复制
PHP_METHOD(study_coroutine_util, create)

声明的函数:

代码语言:javascript
复制
zim_study_coroutine_util_create

handler是一个函数指针,也就是该函数的主体。那么是什么样的函数指针呢?我们得看看前面的zif_handler

代码语言:javascript
复制
/* zend_internal_function_handler */
typedef void (ZEND_FASTCALL *zif_handler)(INTERNAL_FUNCTION_PARAMETERS);

可以发现,这是一个没有返回值,参数是INTERNAL_FUNCTION_PARAMETERS的函数。这个其实和PHP_METHOD这个宏展开得到的函数声明类型是一致的。在这里,这个handler存放的就是函数指针zim_study_coroutine_util_create

arg_info是这个接口方法对应的参数。可以发现,它实际上就是我们上面的那个参数展开后的类型。所以这里很明显,我们必须填写arginfo_study_coroutine_create,也就是我们参数展开后定义的那个变量。

num_args是接口方法的参数个数。可以发现,我们这里并没有填写参数的个数,实际上,这个参数的个数会通过宏ZEND_FENTRY来计算出来:

代码语言:javascript
复制
#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是标志。例如这个接口方法是publicstatic的。

协程类注册

然后,我们需要去注册我们的Study\Coroutine这个类。我们在MINIT这个阶段进行注册,代码如下:

代码语言:javascript
复制
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提供一个函数,我们在这个函数里面实现注册功能:

代码语言:javascript
复制
/**
 * 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里面来进行声明:

代码语言:javascript
复制
void study_coroutine_util_init();

然后,我们在MINIT中对这个函数进行调用,完成类的注册:

代码语言:javascript
复制
PHP_MINIT_FUNCTION(study)
{
	study_coroutine_util_init();
	return SUCCESS;
}

编译测试

代码语言:javascript
复制
~/codeDir/cppCode/study # ./make.sh

编写测试脚本:

代码语言:javascript
复制
<?php

Study\Coroutine::create();
代码语言:javascript
复制
~/codeDir/cppCode/study # php test.php
success!
~/codeDir/cppCode/study # 

OK,到这里,我们算是完成了协程创建接口的前期工作。

下一篇:协程创建(二)

----------伟大的分割线-----------

PHP饭米粒(phpfamily) 由一群靠谱的人建立,愿为PHPer带来一些值得细细品味的精神食粮!

饭米粒只发原创或授权发表的文章,不转载网上的文章

所发的文章,均可找到原作者进行沟通。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP饭米粒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 协程相关结构定义
    • 协程接口参数声明
      • 协程接口方法声明
        • 协程接口实现
          • 协程接口收集
            • 协程类注册
              • 编译测试
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档