3.10 代 理
代理是C++托管扩展中一个崭新的名词,也是一个从字面上不容易理解的名词,其英文是delegate。听起来比boxing、unboxing之类名词的神秘色彩又重了几分,那么到底什么是“代理”呢?www.goodsgy.com
提起delegate,开发人员可能不清楚,但是谈到callback,也就是回调函数,便不会感到陌生了。其实,从某种角度上来看,开发人员可以把delegate看作是传统编程中回调函数和函数指针的等价物。www.goodsgy.com
为了更好地理解delegate,我们首先回忆一下有关回调函数的内容。关于回调函数,对于经常使用Visual C++或SDK的开发人员来讲,应当是十分熟悉的。在标准Win32应用程序中,几乎处处都有回调函数的参与:处于核心位置的窗口过程WNDPROC;调用EnumWindows函数时传递的WNDENUMPROC函数;当我们调用SetWindowsHook给系统挂钩子时传递的HOOKPROC函数;当调用使用线程的APC(异步过程调用)时等等。www.goodsgy.com
对于回调函数概念,用一句简单的话加以概括就是:用户按照系统要求进行自定义的,最后在适当时间由系统来调用的函数。www.goodsgy.com
传统应用程序中,回调机制得以实现的实质是在对应的API函数被调用时,将回调函数本身的地址告诉系统。于是,当系统决定要调用回调函数时,就会根据这个地址来找到回调函数并调用之。但是,有过这方面开发经历的开发人员一定知道,回调函数对系统而言仅仅是一个函数地址,除此之外,没有任何的附加信息,这就存在着类型安全方面的隐患。例如,在进行窗口子类化开发中,必须通过调用SetWindowsLong函数来修改某个窗口过程的地址,并使用自定义窗口过程来处理感兴趣的消息。但是,开发人员最后却忘记了还原(原来被替换的窗口过程指针),这样,就会在应用程序运行过程中或者结束时,遇到可怕的内存访问错误。www.goodsgy.com
但是,对于具备类型安全的托管的应用程序来讲,回调机制(或更为广泛地讲,函数指针)如果单纯依赖地址是不足以实现类型安全的,还必须带有函数的参数个数和每个参数的类型以及函数的返回值类型的详细信息描述,才可以在CLR的帮助下完成引用安全。www.goodsgy.com
所以,delegate和标准的C++函数指针可以说是“形似而神不似”,虽然作用一样,但内部机制有许多区别。www.goodsgy.com
Delegate在.NET框架中作为抽象类得以实现。有两类delegate:single-cast和multi-cast。前者由System.Delegate实现,它仅仅可以把函数指针绑定到一个方法上;而后者是由Syetem.MutiCastDelegate来实现的,可以将函数指针绑定到一个或多个方法上。从前面的论述中,我们已经知道了,尽管_delegate关键字使用的语法和标准C++ typedref关键字类似,但是编译器却创建了一个托管的类的定义。这个托管的类具有以下性质:www.goodsgy.com
● 这个类是从System.Delegate或System.MuticastDelegate中继承下来的。www.goodsgy.com
● 这个类的构造函数有两个参数,一个是托管的引用或者干脆为空,另一个是被确定了类型的方法。www.goodsgy.com
● 这个类中有一个名叫Invoke的方法,对这个方法的调用将真正地激活绑定到delegate的方法。www.goodsgy.com
在C++托管扩展中,delegates类型的定义依靠_delegate关键字。这个关键字定义了一个single-cast delegate类型,并指定一个函数标识。这个关键字使用当中的一个变种:_delegate(multicast),用于定义一个muti cast delegate类型,这个类型同样有一个指定的方法标识。www.goodsgy.com
下面我们使用实际的例子来验证和熟悉我们所讲述的知识。这段源程序可以在配书光盘的\SourceList\_Chap03\delegate目录下找到。www.goodsgy.com
Delegate.cppwww.goodsgy.com
--------------------------------------------www.goodsgy.com
#using <mscorlib.dll>www.goodsgy.com
using namespace System;www.goodsgy.com
www.goodsgy.com
_delegate int GetDayOfWeek(); www.goodsgy.com
www.goodsgy.com
_gc class MyCalendar www.goodsgy.com
{www.goodsgy.com
public:www.goodsgy.com
MyCalendar() : m_nDayOfWeek(4) www.goodsgy.com
{www.goodsgy.com
}www.goodsgy.com
int MyGetDayOfWeek() www.goodsgy.com
{ www.goodsgy.com
return m_nDayOfWeek; www.goodsgy.com
}www.goodsgy.com
static int MyStaticGetDayOfWeek() www.goodsgy.com
{www.goodsgy.com
return 6; www.goodsgy.com
}www.goodsgy.com
private:www.goodsgy.com
int m_nDayOfWeek;www.goodsgy.com
};www.goodsgy.com
www.goodsgy.com
void main ()www.goodsgy.com
{www.goodsgy.com
// 定义了delegate类型www.goodsgy.com
GetDayOfWeek * pGetDayOfWeek; www.goodsgy.com
int nDayOfWeek;www.goodsgy.com
www.goodsgy.com
// 将delegate绑定到静态方法www.goodsgy.com
pGetDayOfWeek = new GetDayOfWeek(0, MyCalendar::MyStaticGetDayOfWeek);www.goodsgy.com
nDayOfWeek = pGetDayOfWeek->Invoke();www.goodsgy.com
Console::WriteLine(_box(nDayOfWeek)->ToString());www.goodsgy.com
www.goodsgy.com
// 绑定到实例方法www.goodsgy.com
MyCalendar * pcal = new MyCalendar();www.goodsgy.com
pGetDayOfWeek = new GetDayOfWeek(pcal, &MyCalendar::MyGetDayOfWeek);www.goodsgy.com
nDayOfWeek = pGetDayOfWeek->Invoke();www.goodsgy.com
Console::WriteLine(_box(nDayOfWeek)->ToString());www.goodsgy.com
}www.goodsgy.com
以上例子代码是一个很典型的delegate应用,首先使用_delegate关键字定义delegate类型,然后声明并定义一个类来实现静态方法和实例方法。在实际使用中,首先让delegate绑定到可以实现回调的方法,最后,在需要回调的时候调用delegate对象的Invoke方法来触发绑定函数的调用。www.goodsgy.com
另外,为了验证delegate是托管的应用程序的特色,请首先构造上面的例子程序,并使用ILDesam.exe来查看,如图3-10所示。www.goodsgy.com
www.goodsgy.com
图3-10 验证托管的程序中的delegatewww.goodsgy.com
请特别注意图3-10中被圈中区域的信息。我们可以从图中清楚地看出,delegate与传统的函数指针有很大的区别,特别是在类型安全方面。www.goodsgy.com