说明:此文章出自《深入浅出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 的东西(不管函数或变量)都并不和对象结合在一起,它们是类的一部分,不属于对象。

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

Callback 函数相关推荐

  1. keras中的fit函数参数_keras的fit_generator与callback函数

    fit_generator函数 fit_generator函数 callback类 每一个epoch结束(on_epoch_end)时,都要调用callback函数,callback函数(类)都要集成 ...

  2. javascript callback函数的理解与使用

    最近做的一个项目中用到了callback函数,于是就研究了下总结下我对javascript callback的理解 首先从callback的字面翻译"回调" 可以理解这是一个函数被 ...

  3. 008_效果和动画的Callback函数

    1. Callback函数在当前效果或动画100%完成之后执行. 2. jQuery动画的问题 2.1. 许多jQuery函数涉及动画.这些函数也许会将speed或duration作为可选参数. 2. ...

  4. Cython进阶--用Cython封装Callback函数

    2019独角兽企业重金招聘Python工程师标准>>> Cython封装Callback函数 1 说明: 回调函数,在C语言里是经常要用到的,但是,在Python里封装一个C的回调函 ...

  5. MATLAB GUI如何创建Callback函数

    本文以创建按钮的Callback函数为例介绍了在MATLAB如何在GUI中创建Callback函数 首先在MATLAB中输入guide,打开GUI文件,这里我随机打开一个我之前创建的GUI文件: 假设 ...

  6. java addcallback函数_java中怎么使用callback函数?

    UYOU 在很多场景,作为开发都会想到,在执行完毕一个任务的时候,能执行一个callback函数是多么好的事情.现在模拟一下这个情景:定义三个类.分别是主函数类.callback函数的接口类.业务处理 ...

  7. CallBack函数 回调函数

    CallBack函数 定义 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数.回调函数不是由该函数 ...

  8. callback函数_Nodejs 源码解析 util.promisify 如何将 Callback 转为 Promise

    Nodejs util 模块提供了很多工具函数.为了解决回调地狱问题,Nodejs v8.0.0 提供了 promisify 方法可以将 Callback 转为 Promise 对象. 工作中对于一些 ...

  9. 静态成员函数运用在CALLBACK函数和线程函数中《转载》

    CALLBACK函数要声明称 static或全局,而在静态的函数中药调用非静态成员还不能调用,必须把这些成员也设置成静态的,不怎么方便,有好的解决方法吗. 其原因是把CALLBACK函数封装成C++类 ...

  10. Python进程池apply_async的callback函数不执行的解决方案

    最近在用multiprocessing.Pool的apply_async方法做多进程,在写示例的时候发现callback居然没有执行,遂记录原因如下. 目录 apply_async的func传入lam ...

最新文章

  1. 2020年,人工智能如何走向高质量发展?
  2. SPI/I2S调试心得与经验总结
  3. secureCrT夜间模式
  4. ionic3 隐藏子页面tabs
  5. setTimeOut函数传参数
  6. oracle表参数,Oracle 表的创建 及相关参数
  7. Algs4-1.3.45栈的可生成性
  8. 原创:2016.4.25-2016.5.1 C# informal essay and tittle_tattle
  9. 手机安装python模块吗_1-Python-非root用户安装Python及Python模块
  10. ArcGIS建立拓扑并检查修改
  11. 2013 腾讯实习生招聘 武汉 一面
  12. PHP-SDK实现微信付款码支付
  13. 泰拉瑞亚正版大型服务器,泰拉瑞亚1.3.5.3物品大全-泰拉瑞亚1.3.5.3服务器版v1.3.5.3 安卓版-腾牛安卓网...
  14. 【软件工程】软工视频总结
  15. finalshell连接ubantu
  16. 计算机网络胡工程施工税率,弱电项目增值税6%、9%、13%税率怎样区分?项目经理必知...
  17. 段码液晶屏笔段电压范围_LCD段码(笔段)液晶显示屏和点阵液晶显示屏
  18. 关系抽取(二)远程监督方法总结
  19. 武汉理工大学博士生导师计算机,博士学位论文预答辩公告-武汉理工大学计算机学院.DOC...
  20. 教你用UltraISO制作启动光盘

热门文章

  1. 征服C指针---1.如何理解C指针的值和地址
  2. 世界杯:冰岛队竟然是强队
  3. JS 垃圾回收机制以及垃圾回收策略
  4. php shell地址,运用Shell 命令行获得本机IP地址
  5. 【yolov5+deepsort+tensorRT+QT+opencv临时启动内置跟踪器c++部署jetson-nx】
  6. 分布式存储Swift原理分析
  7. yansongda3支付宝APP支付返回空的问题
  8. VMware ESXi 8.0U1 Unlocker OEM BIOS 集成网卡驱动和 NVMe 驱动 (集成驱动版)
  9. 【Spring Boot 源码研究 】- 自动化装配条件化配置Conditional剖析
  10. 站长杂谈:这样的内容制作者真的很可爱