写在最前面:
无论是用什么编程语言编写应用程序,都会涉及到函数调用之间的问题。而调用过程可以分为两种,一种是主动请求调用,一种是被动等待调用。这也就是我们常说的调用与回调。下面我将说明DotNet(C#)与ISO C++关于函数回调的实现分析。
一、DotNet(C#)函数回调。
在DotNet中实现函数调用是通过委托(delegate)实现的,首先你要声明委托原型:
delegate
void
Notify(
int
newValue );
这样就声明了一个委托,那到底什么是委托呢?其实委托就是一个回调函数(更确切的说委托是一个安全的函数指针)。当需要回调的时候。可以调用委托的成员函数 Invoke 就可以实现调用你设置的回调函数。这时Invoke会自动根据你声明的委托形式进行调用。在这里我们举一个例子,压力计、报警器的例子:
当压力计的压力指数变化的时候,报警器会报警,并打印出变化的压力值。代码如下
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
5
namespace
ConsoleApplication2
6
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
7
// 这里声明委托
8
public delegate void Notify( int newValue );
9![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
10
// 压力计
11
class Piezometer
12![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
13
// 压力值
14
private int m_PressureNumber;
15
public int PressureNumber
16![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
17
get
18![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
19
// 返回当前压力值
20
return this.m_PressureNumber;
21
}
22
set
23![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
24
// 设置新的压力值
25
this.m_PressureNumber = value;
26
// 判断是否有人注册该事件,如果有就调用并传入新的压力值。
27
if (OnPressureChanged != null)
28![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
29
// 这里就是DotNet框架实现的委托好处,它可以根据
30
// 声明的形式自动匹配调用的参数表和返回值。
31![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
32
// 调用回调事件,将新的压力值传入
33
OnPressureChanged.Invoke(value);
34
}
35
}
36
}
37![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
38
// 声明一个事件,当压力值变化的时候触发该事件
39
public event Notify OnPressureChanged;
40
}
41![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
42
// 报警器
43
class Alerter
44![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
45
// 设置监听的压力计
46
public void Listen(Piezometer piezometer)
47![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
48
// 注册压力计压力变化事件
49
piezometer.OnPressureChanged += new Notify(OnChanged);
50
}
51
// 这里就是压力计变化后调用的函数
52
public void OnChanged(int newValue)
53![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
54
// 打印出新的压力值
55
Console.WriteLine(string.Format("New PressureNumber is {0}.", newValue));
56
}
57
}
58![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
59
class Program
60![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
61
static void Main(string[] args)
62![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
63
Alerter alerter = new Alerter();
64
Piezometer piezometer = new Piezometer();
65![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
66
// 安装压力计,进行监听
67
alerter.Listen(piezometer);
68![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
69
// 设置新的压力值,报警器就会打印出新的压力值。
70
piezometer.PressureNumber = 10;
71
}
72
}
73
}
74
根据上面的代码我们可以实现自己的自定义事件。(感叹:DotNet框架真是太便利了,声明委托之后,委托的调用方法会自动变成声明的形式。C++就不支持这种操作。下面我会讲一下C++的实现方法。)
在这里我们用Reflector反编译一下Delegate类,该类是委托类型的基类。然而,只有系统和编译器可以显式地从 Delegate 类或 MulticastDelegate 类派生。此外,还不允许从委托类型派生新类型。Delegate 类不是委托类型,该类用于派生委托类型。而我们实现的都是MulticastDelegate派生类型,这样就可以产生一个委托多播的类型。最基本的实现是
1
protected
virtual
object
DynamicInvokeImpl(
object
[] args)
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
RuntimeMethodHandle methodHandle = new RuntimeMethodHandle(this.GetInvokeMethod());
4
RuntimeMethodInfo methodBase = (RuntimeMethodInfo) RuntimeType.GetMethodBase(Type.GetTypeHandle(this), methodHandle);
5
return methodBase.Invoke(this, BindingFlags.Default, null, args, null, true);
6
}
7
当委托被调用时会产生一个运行时方法对象。并通过运行时对象调用
1
[MethodImpl(MethodImplOptions.InternalCall), DebuggerStepThrough, DebuggerHidden]
2
private
extern
object
_InvokeMethodFast(
object
target,
object
[] arguments,
ref
SignatureStruct sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner);
通知VM进行系统执行方法体。而在多播委托里包含一个_invocationList实现了保存多个委托,使之内部可以循环调用进行广播。
二、ISO C++事件回调。
注意这里我指的是ISO C++实现的回调机制,他不依赖于任何操作系统。C++作为C语言的扩展(C++的Fans不要生气,毕竟C++在企业级快速开发比Java、DotNet要逊色一些),还是举上面的例子实现代码如下:
event.hpp(点击下载代码)
1
#ifndef __EVENT_HPP
2
#define
__EVENT_HPP
3
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
4
#include
<
vector
>
5
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
6
using
std::vector;
7
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
8
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
class
EmptyType
{}
;
9
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
10
template
<
typename EventFunctionPtrType
>
11
class
EventObject
12
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
13
public:
14
EventObject() :
15
ObjectPtr( NULL ),
16
EventFunctionPointerPtr( NULL )
17![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
18
}
19![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
20
public:
21
EmptyType* ObjectPtr;
22
EventFunctionPtrType* EventFunctionPointerPtr;
23
}
;
24
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
25
template
<
typename EventFunctionPtrType
>
26
class
Event
27
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
28
public:
29
typedef vector< EventObject< EventFunctionPtrType > > EventList;
30
typedef typename vector< EventObject< EventFunctionPtrType > >::iterator EventIterator;
31![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
32
public:
33
Event()
34![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
35
}
36![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
37
virtual ~Event()
38![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
39
EventIterator iter;
40
for( iter = m_EventList.begin();
41
iter != m_EventList.end();
42
++iter )
43![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
44
delete iter->EventFunctionPointerPtr;
45
}
46
}
47![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
48
template< typename ObjectType,
49
typename MemeberFunctionPtrType >
50
void Bind( ObjectType* pObj, MemeberFunctionPtrType memberFunctionPtr )
51![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
52
MemeberFunctionPtrType* pf = new MemeberFunctionPtrType;
53
*pf = memberFunctionPtr;
54
EventObject< EventFunctionPtrType > eventObj;
55
eventObj.ObjectPtr = (EmptyType*)pObj;
56
eventObj.EventFunctionPointerPtr = (EventFunctionPtrType*)pf;
57![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
58
bool hasTheEvent = false;
59
EventIterator iter;
60
for( iter = m_EventList.begin();
61
iter != m_EventList.end();
62
++iter )
63![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
64
if( iter->ObjectPtr == eventObj.ObjectPtr &&
65
*iter->EventFunctionPointerPtr == *eventObj.EventFunctionPointerPtr )
66![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
67
hasTheEvent = true;
68
break;
69
}
70
}
71
if( !hasTheEvent )
72
m_EventList.push_back( eventObj );
73
else
74
delete eventObj.EventFunctionPointerPtr;
75
}
76![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
77
template< typename ObjectType,
78
typename MemeberFunctionPtrType >
79
void UnBind( ObjectType* pObj, MemeberFunctionPtrType memberFunctionPtr )
80![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
81
MemeberFunctionPtrType* pf = new MemeberFunctionPtrType;
82
*pf = memberFunctionPtr;
83
EventObject< EventFunctionPtrType > eventObj;
84
eventObj.ObjectPtr = (EmptyType*)pObj;
85
eventObj.EventFunctionPointerPtr = (EventFunctionPtrType*)pf;
86![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
87
EventIterator iter;
88
for( iter = m_EventList.begin();
89
iter != m_EventList.end();
90
++iter )
91![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
92
if( iter->ObjectPtr == eventObj.ObjectPtr &&
93
*iter->EventFunctionPointerPtr == *eventObj.EventFunctionPointerPtr )
94![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
95
delete iter->EventFunctionPointerPtr;
96
m_EventList.erase( iter );
97
break;
98
}
99
}
100
delete eventObj.EventFunctionPointerPtr;
101
}
102![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
103
public:
104
EventList m_EventList;
105
}
;
106
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
107
typedef EmptyType EventDelegater;
108
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
109
#ifndef InvokeEvent
110
#define
InvokeEvent ((iter->ObjectPtr)->*(*iter->EventFunctionPointerPtr))
//
Invoke the Event
111
#endif
112
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
113
#endif
//
__EVENT_HPP
114
example.cpp
1
//
EventTest.cpp : 定义控制台应用程序的入口点。
2
//
3
4
#include
"
stdafx.h
"
5
#include
"
event.hpp
"
6
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
7
using
std::cout;
8
using
std::endl;
9
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
10
//
声明事件代理模型(类成员函数指针)
11
typedef
void
( EventDelegater::
*
NumberChanged )(
int
);
12
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
13
//
压力计
14
class
Piezometer
15
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
16
public:
17
Piezometer();
18![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
19
public:
20
void SetPressureNumber( int newVal ); // 设置压力值
21
int GetPressureNumber(); // 获取压力值
22![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
23
// 压力值变化事件
24
Event< NumberChanged > OnPressureChanged;
25![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
26
private:
27
int m_PressureNumber;
28
}
;
29
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
30
Piezometer::Piezometer() : m_PressureNumber(
0
)
31
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
32
}
33
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
34
void
Piezometer::SetPressureNumber(
int
newVal )
35
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
36
m_PressureNumber = newVal;
37![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
38
// 判断事件列表是否为空
39
if( !OnPressureChanged.m_EventList.empty() )
40![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
41
// 循环事件列表
42
Event< NumberChanged >::EventIterator iter;
43
for( iter = OnPressureChanged.m_EventList.begin();
44
iter != OnPressureChanged.m_EventList.end();
45
++iter )
46![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
47
// 调用事件
48
InvokeEvent( newVal );
49
}
50
}
51
}
52
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
53
int
Piezometer::GetPressureNumber()
54
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
55
return m_PressureNumber;
56
}
57
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
58
//
报警器
59
class
Alerter
60
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
61
public:
62
void Listen( Piezometer* );
63
void OnChanged( int newVal );
64
}
;
65
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
66
void
Alerter::Listen( Piezometer
*
pObj )
67
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
68
// 绑定成员函数到事件
69
pObj->OnPressureChanged.Bind( this, &Alerter::OnChanged );
70
}
71
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
72
void
Alerter::OnChanged(
int
newVal )
73
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
74
cout << "New Pressure Number is " << newVal << "." << endl;
75
}
76
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
77
int
_tmain(
int
argc, _TCHAR
*
argv[])
78
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
79
Piezometer piezometer;
80
Alerter alerter;
81![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
82
alerter.Listen( &piezometer );
83
piezometer.SetPressureNumber( 10 );
84![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
85
return 0;
86
}
87
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
88
上面的做法完全是按照面向对象设计的,这种函数回调方法并不是静态函数回调,而是对象方法回调。C++这种做法没有DotNet使用委托来的方便,因为C++回调类成员函数指针是不能转换为 void* ,必须转换成指针的指针,而且要引用一个EmptyType类,EmptyType类的更多应用可以参见《C++设计新思维》。
回调成员函数是C++实现事件机制的方法之一,实现调用是通过 ((iter->ObjectPtr)->*(*iter->EventFunctionPointerPtr))( newVal ); 语句实现的,我在这里讲一下大体思路 class Event 实现了事件列表以及内存指针释放的功能,这样就可以实现事件的多播。class EventObject 实现了对象指针与该对象实现的成员函数的配对(当然也可以用STL中的pair),我们是通过C++操作符.*或是->*实现调用成员函数指针。iter->ObjectPtr保存了对象指针,iter->EventFunctionPointerPtr保存了对象成员函数指针的指针。因为C++不支持将一个类成员函数的指针转换成另一个类的成员函数指针,所以我在这里实现一个EmptyType类用于委托声明和指针转换。
C++操作符.*或是->*实现调用成员函数指针,和回调静态函数有什么却别呢?
面向对象是将一组方法和方法相关的数据绑定起来,当类成员函数调用的时候可以访问该类的成员变量,而访问变量是通过向成员函数传入该类的this指针,当然这个不用我们去实现,C++内部已经实现。在成员方法被调用时,该类的this指针会传到寄存器ECX。这样成员函数就可以通过ECX访问到成员变量了:
push 参数
mov ecx, this
call 成员函数
这样就实现了成员函数的调用,而静态函数不涉及到成员函数,所以函数内部所用到的数据都是通过 push 参数实现的。
三、Windows实现事件机制。
使用Windows API实现事件机制主要是通过消息队列,通过GetMessage、PeekMessage创建消息循环,并调用DispatchMessage分发消息。这种实现事件机制,基本上是通过回调函数实现的,而不是通过回调成员函数实现的。具体方法可以参见MSDN。
写在最后:
我个人比较喜欢第二种做法,原因如下:不依赖于操作系统,与平台无关。而且面向对象,在面向对象项目中比静态回调函数更适合。效率高,当使用 Windows事件机制必须要创建窗体,使用消息循环,效率肯定比回调函数指针要低。但对于多线程没有Windows事件机制方便(一个线程要通知另一个线程,或是进程间通信无法实现,因为其始终在一个线程执行)。
转自
http://www.cnblogs.com/Aplo/archive/2007/09/07/886145.html