您的位置:首页 > 运维架构 > 网站架构

回调函数的面向对象改造

2015-12-14 18:00 651 查看
熟悉C编程的朋友对回调函数一定不陌生,它一般用于一个模块将自己的阶段性输出传递给另一个模块,由另一个模块进行更细致的处理。

整个处理过程,就像一条河流,从源头出发,到了某个点分出若干支流,支流又分出若干支流,每一段都会被人所用

在面向对象系统里,我们怎么对这个基于回调函数的处理过程进行描述?

我想用一个概念来描述,叫信息流的上下游,信息流上的每个节点都有一个上游,若干个下游,上游可以向下游发送消息,消息的内容就是上游生产的数据

而面向对象系统中,A向B发送消息,其实就是调用B的方法,同时消息内容作为方法的参数传递给了B

这样我们就把回调函数转换成了方法调用

用代码举例

语音识别引擎ifly生产文本,然后通知ROS节点publisher来消费该文本,所以ifly是上游,pub是下游

class IFlyRecognizer{
private:
char *recogSessionId;
int audStat;
int epStat;
int recStat;
bool lastSeg;
std::string recogTxt;
SpeechPublisher &publisher;
public:
void stop();//开始为退出识别循环做准备
int audioProc(const void *inputBuffer);
IFlyRecognizer(SpeechPublisher &pub);
~IFlyRecognizer();
};
注意看,上游ifly的构造函数只有一个入参,那就是下游pub的引用,这就是OO中的组合模式compsition,ifly生产string类型的消息,pub消费string类型的消息,以此实现回调函数的效果
注册回调的地方,就是发生组合compsition的地方

IFlyRecognizer::IFlyRecognizer(SpeechPublisher &p):recogSessionId(NULL),audStat(MSP_AUDIO_SAMPLE_CONTINUE),epStat(MSP_EP_LOOKING_FOR_SPEECH),recStat(MSP_REC_STATUS_SUCCESS),lastSeg(false),recogTxt(""),publisher(p)
{
}

发生消息调用的地方
int IFlyRecognizer::audioProc( const void *inputBuffer)
{
if (MSP_REC_STATUS_COMPLETE == recStat)
{
publisher.publishRecogTxt(recogTxt);
recogTxt = "";
}
}

回调函数绕不过去的情况怎么办

PortAudio是一个音频库,可以实现录音、播放等功能,简称PA,该库初始化时需要传入一个固定格式的回调函数,所以必须要定义一个回调函数,但我们可以将该函数做成一个wrapper,具体见下:

我将录音相关API封装成一个类。PA是上游,ifly是下游,PA生产音频数据,ifly消费音频数据,所以PA的构造函数也要传入一个引用参数——下游节点ifly

class PortAudioStream{
public:
PortAudioStream(IFlyRecognizer &r);
~PortAudioStream();
private:
IFlyRecognizer &recognizer;
static int record_Callback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData );
};


不过因为PA规定了回调函数接口的格式,所以需要一个wrapper函数record_Callback来牵线搭桥,这里选择将record_Callback做成静态成员函数,是为了避免this指针扩展造成的类型不匹配错误
不过,静态成员函数无法访问非静态成员,所以构造函数传入的ifly对象不能访问,幸好PA有void指针类型userData参数,可以用该参数来传递ifly对象

PortAudioStream::PortAudioStream(IFlyRecognizer &r):recognizer(r)
{
PaStreamParameters inputParameters;
PaError ret = paNoError;
PaStream *stream = NULL;
ret = Pa_OpenStream(
&stream,
&inputParameters,
NULL, /* &outputParameters, */
(double)AudioConfig::SAMPLE_RATE,
AudioConfig::FRAMES_PER_BUF,
paClipOff, /* we won't output out of range samples so don't bother clipping them */
record_Callback,
(void *)&recognizer); //将recog对象地址传给静态类方法
}

发送消息(调用回调函数)是这样进行的
int PortAudioStream::record_Callback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData )
{
int ret;
IFlyRecognizer *recog = (IFlyRecognizer *)userData;
ret = recog->audioProc(inputBuffer);
}

总结

1、在main函数里先实例化最下游对象,然后依次用下游对象作为初始化参数来构造上游,直到信息流的源头

2、信息流源头生产出数据后,给注册好的下游发送消息,实现数据处理
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息