Callback 函数

86 篇文章 0 订阅
48 篇文章 0 订阅

说明:此文章出自《深入浅出mfc》第6章的“Callback  函数”

Callback 函数

Hello的OnPaint在程序收到 WM_PAINT之后开始运作。为了让"Hello, MFC" 字样从天而降并有动画效果,程序采用LineDDA API 函数。我的目的一方面是为了示范消息的处理,一方面也为了示范  MFC 程序如何调用  Windows  API 函数。许多人可能不熟悉LineDDA,所以我也一并介绍这个有趣的函数。

首先介绍 LineDDA:

void WINAPI LineDDA(int, int, int, int, LINEDDAPROC, LPARAM);

这个函数用来做动画十分方便,你可以利用前四个参数指定屏幕上任意两点的(x,y) 座标,此函数将以  Bresenham 算法(注) 计算出通过两点之直线中的每一个屏幕图素座标;每计算出一个坐标,就通知由LineDDA 第五个参数所指定的  callback 函数。这个callback 函数的型式必须是:

typedef void (CALLBACK* LINEDDAPROC)(int, int, LPARAM);

通常我们在这个  callback 函数中设计绘图动作。玩过Windows的接龙游戏吗?接龙成功后扑克牌的跳动效果就可以利用LineDDA 完成。虽然扑克牌的跳动路径是一条曲线,但将曲线拆成数条直线并不困难。LineDDA 的第六个(最后一个)参数可以视应用程序的需要传递一个32位指针,本例中Hello传的是一个Device Context。

Bresenham 算法是计算机图学中为了「显示器(屏幕或打印机)系由图素构成」的这个特性而设计出来的算法,使得求直线各点的过程中全部以整数来运算,因而大幅提升计算速度。

你可以指定两个坐标点,LineDDA 将以Bresenham 算法计算出通过两点之直线中每一个
屏幕图素的坐标。每计算出一个坐标,就以该坐标为参数,调用你所指定的callback  函数。

图6-6  LineDDA函数说明

你可以指定两个坐标点,LineDDA将以Bresenham 算法计算出通过两点之直线中每一个屏幕图素的坐标。每计算出一个坐标,就以该坐标为参数,调用你所指定的callback函数。

LineDDA 并不属于任何一个MFC 类,因此调用它必须使用C++ 的 "scope operator" (也就是 ::):

void CMyFrameWnd::OnPaint()
{
CPaintDC dc(this);
CRect rect;
   GetClientRect(rect);
   dc.SetTextAlign(TA_BOTTOM | TA_CENTER);
   ::LineDDA(rect.right/2, 0, rect.right/2, rect.bottom/2,
       (LINEDDAPROC) LineDDACallback, (LPARAM) (LPVOID) &dc);
}

其中LineDDACallback是我们准备的callback 函数,必须在类中先有声明:

class CMyFrameWnd : public CFrameWnd
{
...
private:
   static VOID CALLBACK  LineDDACallback(int,int,LPARAM);
}; 

请注意,如果类的成员函数是一个callback 函数,你必须声明它为  "static",才能把C++ 编译器加诸于函数的一个隐藏参数this去掉(请看方块批注)。

以类的成员函数作为Windows callback函数

虽然现在来讲这个题目,对初学者而言恐怕是过于艰深,但我想毕竟还是个好机会--- 我可以在介绍如何使用callback 函数的场合,顺便介绍一些C++的重要观念。

首先我要很快地解释一下什么是callback 函数。凡是由你设计而却由  Windows系统调用的函数,统称为callback函数。这些函数都有一定的类型,以配合Windows的调用动作。

某些Windows API函数会要求以callback 函数作为其参数之一,这些API 例如SetTimer、LineDDA、EnumObjects。通常这种 API 会在进行某种行为之后或满足某种状态之时调用该 callback 函数。图 6-6 已解释过 LineDDA调用  callback 函数的时机;下面即将示范的 EnumObjects 则是在发现某个 Device Context 的 GDI object 符合我们的指定类型时,调用  callback 函数。

好,现在我们要讨论的是,什么函数有资格在  C++ 程序中做为 callback 函数?这个问题的背后是:C++ 程序中的  callback 函数有什么特别的吗?为什么要特别提出讨论?

是的,特别之处在于,C++ 编译器为类成员函数多准备了一个隐藏参数(程序代码中看不到),这使得函数类型与  Windows callback 函数的预设类型不符。

假设我们有一个CMyclass 如下:

class CMyclass {
  private :
    int nCount;
    int CALLBACK _export
        EnumObjectsProc(LPSTR lpLogObject, LPSTR lpData);
  public :
    void enumIt(CDC& dc);
}
void CMyclass::enumIt(CDC& dc)
{

//  注册callback  函数
  dc.EnumObjects(OBJ_BRUSH, EnumObjectsProc, NULL);
}

C++ 编译器针对CMyclass::enumIt实际做出来的代码相当于:

void CMyclass::enumIt(CDC& dc)
{
  CDC::EnumObjects(OBJ_BRUSH, EnumObjectsProc,
                     NULL, (CDC *)&dc);
}

你所看到的最后一个参数,(CDC *)&dc,其实就是this指针。类成员函数靠着this指针才得以抓到正确对象的数据。你要知道,内存中只会有一份类成员函数,但却可能有许多份类成员变量——每个对象拥有一份。

C++ 以隐晦的this指针指出正确的对象。当你这么做:

nCount = 0;

其实是:

this->nCount = 0;

基于相同的道理,上例中的  EnumObjectsProc 既然是一个成员函数,C++ 编译器也会为它多准备一个隐藏参数。

好,问题就出在这个隐藏参数。callback函数是给Windows调用用的,Windows 并不经由任何对象调用这个函数,也就无由传递this指针给callback 函数,于是导至堆栈中有一个随机变量会成为this指针,而其结果当然是程序的崩溃了。

要把某个函数用作callback函数,就必须告诉C++编译器,不要放this指针作为该函数的最后一个参数。两个方法可以做到这一点:

1. 不要使用类的成员函数(也就是说,要使用全局函数)做为callback 函数。

2. 使用static 成员函数。也就是在函数前面加上static修饰词。

第一种作法相当于在 C 语言中使用callback 函数。第二种作法比较接近 OO的精神。

我想更进一步提醒你的是,C++中的static成员函数特性是,即使对象还没有产生,static 成员也已经存在(函数或变量都如此)。换句话说对象还没有产生之前你已经可以调用类的static函数或使用类的static变量了。请参阅第二章。

也就是说,凡声明为static 的东西(不管函数或变量)都并不和对象结合在一起,它们是类的一部分,不属于对象。

 

获取更多帮主请关注小程序

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值