写在最前面:
        无论是用什么编程语言编写应用程序,都会涉及到函数调用之间的问题。而调用过程可以分为两种,一种是主动请求调用,一种是被动等待调用。这也就是我们常说的调用与回调。下面我将说明DotNet(C#)与ISO C++关于函数回调的实现分析。

一、DotNet(C#)函数回调。
        在DotNet中实现函数调用是通过委托(delegate)实现的,首先你要声明委托原型:
    
深入探索面向对象事件(Delegate)机制delegate void Notify( int newValue );

      这样就声明了一个委托,那到底什么是委托呢?其实委托就是一个回调函数(更确切的说委托是一个安全的函数指针)。当需要回调的时候。可以调用委托的成员函数 Invoke 就可以实现调用你设置的回调函数。这时Invoke会自动根据你声明的委托形式进行调用。在这里我们举一个例子,压力计、报警器的例子:

当压力计的压力指数变化的时候,报警器会报警,并打印出变化的压力值。代码如下
 1深入探索面向对象事件(Delegate)机制using System;
 2深入探索面向对象事件(Delegate)机制using System.Collections.Generic;
 3深入探索面向对象事件(Delegate)机制using System.Text;
 4深入探索面向对象事件(Delegate)机制
 5深入探索面向对象事件(Delegate)机制namespace ConsoleApplication2
 6

根据上面的代码我们可以实现自己的自定义事件。(感叹:DotNet框架真是太便利了,声明委托之后,委托的调用方法会自动变成声明的形式。C++就不支持这种操作。下面我会讲一下C++的实现方法。)
在这里我们用Reflector反编译一下Delegate类,该类是委托类型的基类。然而,只有系统和编译器可以显式地从 Delegate 类或 MulticastDelegate 类派生。此外,还不允许从委托类型派生新类型。Delegate 类不是委托类型,该类用于派生委托类型。而我们实现的都是MulticastDelegate派生类型,这样就可以产生一个委托多播的类型。最基本的实现是

1深入探索面向对象事件(Delegate)机制protected virtual object DynamicInvokeImpl(object[] args)
2

当委托被调用时会产生一个运行时方法对象。并通过运行时对象调用

1深入探索面向对象事件(Delegate)机制[MethodImpl(MethodImplOptions.InternalCall), DebuggerStepThrough, DebuggerHidden]
2深入探索面向对象事件(Delegate)机制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深入探索面向对象事件(Delegate)机制#ifndef __EVENT_HPP
  2深入探索面向对象事件(Delegate)机制#define __EVENT_HPP
  3深入探索面向对象事件(Delegate)机制
  4深入探索面向对象事件(Delegate)机制#include <vector>
  5深入探索面向对象事件(Delegate)机制
  6深入探索面向对象事件(Delegate)机制using std::vector;
  7深入探索面向对象事件(Delegate)机制
  8

 

example.cpp

 1深入探索面向对象事件(Delegate)机制// EventTest.cpp : 定义控制台应用程序的入口点。
 2深入探索面向对象事件(Delegate)机制//
 3深入探索面向对象事件(Delegate)机制
 4深入探索面向对象事件(Delegate)机制#include "stdafx.h"
 5深入探索面向对象事件(Delegate)机制#include "event.hpp"
 6深入探索面向对象事件(Delegate)机制
 7深入探索面向对象事件(Delegate)机制using std::cout;
 8深入探索面向对象事件(Delegate)机制using std::endl;
 9深入探索面向对象事件(Delegate)机制
10深入探索面向对象事件(Delegate)机制// 声明事件代理模型(类成员函数指针)
11深入探索面向对象事件(Delegate)机制typedef void ( EventDelegater::*NumberChanged )( int );
12深入探索面向对象事件(Delegate)机制
13深入探索面向对象事件(Delegate)机制// 压力计
14深入探索面向对象事件(Delegate)机制class Piezometer
15


上面的做法完全是按照面向对象设计的,这种函数回调方法并不是静态函数回调,而是对象方法回调。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事件机制方便(一个线程要通知另一个线程,或是进程间通信无法实现,因为其始终在一个线程执行)。

相关文章: