VC中回调函数的用法

来源:互联网 发布:网络视听许可证照租赁 编辑:程序博客网 时间:2024/05/22 04:46


回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就

是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可

以在回调函数里完成你要做的事。

 

  前些天写一个可编辑的ListCtrl类时,遇到这样一个问题,在ListCtrl的指定格中创建了一个Button,创建过程我写在

ListCtrlButtuon.cpp中,在对外提供的接中类CMMListCtrl中这样调用:

void SetButtonEx(int iColumn, int iRow, int iIndex, int (*)(CWnd *pWnd, int iItem, int iSubItem));

这样设计的思想是,当单击Button时,相应的消息处理在调用者自己的类中,不须要改动CListCtrlButton类来响应用户的具体处理,非常灵活.这一过程如上面所看到的,用到了回调函数.下面简单做下总结:

 1.在ListCtrlButton.h头部中定义回调函数指针:

   typedef int (*EditCallFunc)(CWnd *, int, int);

 2.在CListCtrlButton类中定义:

   EditCallFunc m_func;

 3.然后在OnClicked()消息响应中应用此函数:

   void CListCtrlButton::OnClicked()

   {  

    m_func(m_pParentList, m_iItem, m_iSubItem);

    OnEditEnd();

    }

  4.在我封装的MMListCtrl接口类中这样使用:

 // MMListCtrl.h中

  void SetButtonEx(int iColumn, int iRow, int iIndex, int (*)(CWnd *pWnd, int iItem, int iSubItem));

 

 //MMListCtrl.cpp中

/*********************************************************************

* 函数名称:SetButtonEx

* 说明:将指定方格格设为Button,有关点击Button后的相应操作应根据实际情况添加

* 入口参数:

* int iColumn       -- 指定格所在的列

* int iRow          -- 指定格所在的行

* int iIndex        -- 用于区分不同的Button (iIndex = 0, 1, 2,...)

* int (*fSetClickButton)(CWnd *, int , int)

                    --函数指针,指向实现函数

* 返回值:

* void              --

* 作者: Duanyx

* 时间: 2008-04-10 11:40:50

* 修改: 如果不用iIndex,采用二维数组存放如果行数超过一定值发生stack overflow

*********************************************************************/

 

void CMMListCtrl::SetButtonEx(int iColumn, int iRow, int iIndex, int (*fSetClickButton)(CWnd *, int, int))

{  

    if (m_ListCtrlButton[iIndex].m_hWnd == NULL)

    {

        m_ListCtrlButton[iIndex].CreateEx(this->GetParent(), this);

        m_ListCtrlButton[iIndex].Insert(iColumn, iRow);

        m_ListCtrlButton[iIndex].m_func = fSetClickButton;     

    }

    else

    {

        m_ListCtrlButton[iIndex].Insert(iColumn, iRow);

        m_ListCtrlButton[iIndex].m_func = fSetClickButton;

    }  

}

 

5.下面就是用户类中调用的使用,假设已经声明CMMListCtrl m_List;

在用户类EditListCtrlDlg.h的头部声明:

int SetMyDlg2(CWnd *pWnd, int iItem, int iSubItem);

//EditListCtrlDlg.cpp中

BOOL CEditListCtrlDlg::OnInitDialog()

{

    CDialog::OnInitDialog();

 

       //注意最后一个参数为函数指针

       m_List.SetButtonEx(0, 0, 1, SetMyDlg);

}

 

//最后是此函数的实现

int SetMyDlg2(CWnd *pWnd, int iItem, int iSubItem)

{

    CString strText;

    COleTest dlg;

 

    if (dlg.DoModal() == IDOK)

    {

        strText = "dyx1024@gmail.com";

    }

    ((CListCtrl *)pWnd)->SetItemText(iItem, iSubItem, strText);

    return 0;

}

 

------------------------------------------------------------------------------------------------------

在书写的过程中参考了网上好多资料,附于此处,以便读者能更好的理解回调函数的使用.

 

参考资料一:

 

回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就

是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可

以在回调函数里完成你要做的事。

 

capVideoStreamCallback 这个回调函数,我没有做过,看了一下Help,应该是通过发送消息

WM_CAP_SET_CALLBACK_VIDEOSTREAM,来设置的,或者调用宏capSetCallbackOnVideoStream

来完成的。这样设定之后,系统在进行图像捕捉的过程中,就会自动调用你写的回调函数。

 

这个回调函数的函数体需要你自已来写,然后在另一函数中调用,即是说:

LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr)

{

 ........

}

//在另一函数中调用它(即以capVideoStreamCallback的地址作为一参数)

Function(1,......,capVideoStreamCallback,.....);

这就好像我们用定时器一样,在设置定时器时需要为定时器设置一回调函数:

::SetTimer(m_hWnd,1,1000,(TIMERPROC)TMProc);这里的TMProc就是回调函数

 

 

模块A有一个函数foo,它向模块B传递foo的地址,然后在B里面发生某种事件(event)时,通过从A里面传递过来的foo的地址调用foo,通知A发生了什么事情,让A作出相应反应。

    那么我们就把foo称为回调函数。

 

    这个回调函数不是VFW.h中声明的么,

    ----那是声明了回调函数原型,是告诉你传递进来的回调函数必须和它定义的原型保持一致。

 

    为什么要自己写函数体呢?

    ----比如在上面模块B里面,它只知道当event发生时,向模块A发出通知,具体怎么回应这个事件,不是B所关心的,也不是B所能预料到的。

    你站在A的角度上思考,当然要你自己作出对event的反应,也就是你要自己写函数体。

 

    你如果明白了C++里面的函数指针,就很容易理解回调函数了。

 

"不知道系统调用后有什么结果,或者我怎么利用这个结果啊"

---如果你向系统传递一个回调函数地址,那么你的程序就相当于上面我说的模块A,系统就相当于模块B,系统只是调用你的函数,它根本不可能知道会有什么结果。

   你怎么利用这个结果,看你是怎么定义这个回调函数的。     

回调函数和回调机制是不同的概念,。,,函数是被调用的,但是回调机制在不同的语言中不都是以函数指针来实现的。。。。比如c#...一般的在windows api 中,会调都是使用函数指针实现的。。。

 

参考资料二:

回调函数是一个很有用,也很重要的概念。当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。回调函数在windows编程使用的场合很多,比如Hook回调函数:MouseProc,GetMsgProc以及EnumWindows,DrawState的回调函数等等,还有很多系统级的回调过程。本文不准备介绍这些函数和过程,而是谈谈实现自己的回调函数的一些经验。

 

之所以产生使用回调函数这个想法,是因为现在使用VC和Delphi混合编程,用VC写的一个DLL程序进行一些时间比较长的异步工作,工作完成之后,需要通知使用DLL的应用程序:某些事件已经完成,请处理事件的后续部分。开始想过使用同步对象,文件影射,消息等实现DLL函数到应用程序的通知,后来突然想到可不可以在应用程序端先写一个函数,等需要处理后续事宜的时候,在DLL里直接调用这个函数即可。

 

于是就动手,写了个回调函数的原形。在VC和 Delphi里都进行了测试

 

一:声明回调函数类型。

   VC 版   typedef int (WINAPI *PFCALLBACK)(int Param1,int Param2) ;

 

   Delph版 PFCALLBACK = function(Param1:integer;Param2:integer):integer;stdcall;

 

   实际上是声明了一个返回值为int,传入参数为两个int的指向函数的指针。

   由于C++和PASCAL编译器对参数入栈和函数返回的处理有可能不一致,把函数类型用WINAPI(WINAPI宏展开就是__stdcall)或stdcall统一修饰。

 

 

二:声明回调函数原形

    声明函数原形

      VC 版      int WINAPI CBFunc(int Param1,int Param2);

      Delphi 版  function CBFunc(Param1,Param2:integer):integer;stdcall;

 

  以上函数为全局函数,如果要使用一个类里的函数作为回调函数原形,把该类函数声明为静态函数即可。

 

 

三: 回调函数调用调用者

 

调用回调函数的函数我把它放到了DLL里,这是一个很简单的VC生成的WIN32 DLL.并使用DEF文件输出其函数名 TestCallBack。实现如下:

PFCALLBACK  gCallBack=0;

void WINAPI TestCallBack(PFCALLBACK Func)

{

  if(Func==NULL)return;

  gCallBack=Func;

  DWORD ThreadID=0;

  HANDLE hThread = CreateThread(

    NULL,

    NULL,

    Thread1,

    LPVOID(0),

    &ThreadID

  );

 

  return;

}

 

此函数的工作把传入的 PFCALLBACK Func参数保存起来等待使用,并且启动一个线程。声明了一个函数指针PFCALLBACK gCallBack保存传入的函数地址。

 

四:回调函数如何被使用:

TestCallBack函数被调用后,启动了一个线程,作为演示,线程人为的进行了延时处理,并且把线程运行的过程打印在屏幕上.

本段线程的代码也在DLL工程里实现

ULONG  WINAPI Thread1(LPVOID Param)

{

  TCHAR Buffer[256];

  HDC hDC = GetDC(HWND_DESKTOP);

  int Step=1;

  MSG Msg;

  DWORD StartTick;

  //一个延时循环

  for(;Step<200;Step++)

  {

    StartTick = GetTickCount();

    /*这一段为线程交出部分运行时间以让系统处理其他事务*/

    for(;GetTickCount()-StartTick<10;)

    {

      if(PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE) )

      {

        TranslateMessage(&Msg);

        DispatchMessage(&Msg);

      }

    }

    /*把运行情况打印到桌面,这是vcbear调试程序时最喜欢干的事情*/

    sprintf(Buffer,"Running %04d",Step);

    if(hDC!=NULL)

      TextOut(hDC,30,50,Buffer,strlen(Buffer));

  }

 

  /*延时一段时间后调用回调函数*/

  (*gCallback)(Step,1);

 

  /*结束*/

  ::ReleaseDC (HWND_DESKTOP,hDC);

  return 0;

}

 

五:万事具备

  使用vc和Delphi各建立了一个工程,编写回调函数的实现部分

  VC 版

   int WINAPI CBFunc(int Param1,int Param2)

   {

      int res= Param1+Param2;

      TCHAR Buffer[256]="";

      sprintf(Buffer,"callback result = %d",res);

      MessageBox(NULL,Buffer,"Testing",MB_OK);  //演示回调函数被调用

      return res;

   }

 

   Delphi版

    function CBFunc(Param1,Param2:integer):integer;

    begin

        result:= Param1+Param2;

        TForm1.Edit1.Text:=inttostr(result);    / /演示回调函数被调用

    end;

 

  使用静态连接的方法连接DLL里的出口函数 TestCallBack,在工程里添加 Button( 对于Delphi的工程,还需要在Form1上放一个Edit控件,默认名为Edit1)。

  响应ButtonClick事件调用 TestCallBack

 

  TestCallBack(CBFunc) //函数的参数CBFunc为回调函数的地址

  函数调用创建线程后立刻返回,应用程序可以同时干别的事情去了。现在可以看到屏幕上不停的显示字符串,表示dll里创建的线程运行正常。一会之后,线程延时部分结束结束,vc的应用程序弹出MessageBox,表示回调函数被调用并显示根据Param1,Param2运算的结果,Delphi的程序edit控件里的文本则被改写成Param1,Param2 的运算结果。

 

  可见使用回调函数的编程模式,可以根据不同的需求传递不同的回调函数地址,或者定义各种回调函数的原形(同时也需要改变使用回调函数的参数和返回值约定),实现多种回调事件处理,可以使程序的控制灵活多变,也是一种高效率的,清晰的程序模块之间的耦合方式。在一些异步或复杂的程序系统里尤其有用 -- 你可以在一个模块(如DLL)里专心实现模块核心的业务流程和技术功能,外围的扩展的功能只给出一个回调函数的接口,通过调用其他模块传递过来的回调函数地址的方式,将后续处理无缝地交给另一个模块,随它按自定义的方式处理。

 

  本文的例子使用了在DLL里的多线程延时后调用回调函数的方式,只是为了突出一下回调函数的效果,其实只要是在本进程之内,都可以随你高兴可以把函数地址传递来传递去,当成回调函数使用。

 

 这样的编程模式原理非常简单单一:就是把函数也看成一个指针一个地址来调用,没有什么别的复杂的东西,仅仅是编程里的一个小技巧。至于回调函数模式究竟能为你带来多少好处,就看你是否使用,如何使用这种编程模式了。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 电脑上找不到本地连接怎么办 网络无访问权限怎么办 电脑无网络访问怎么办 xp连不上网怎么办 xp系统本地连接不见了怎么办 电脑xp系统本地连接怎么办 xp系统本地连接失败怎么办 xp系统找不到本地连接怎么办 台式电脑连不上网络怎么办 win7局域网要密码怎么办 xp网络不能上网怎么办 台式电脑连宽带怎么办 win7宽带813错误怎么办 电脑不显示本地连接怎么办 w7宽带连接不了怎么办 网络接收器坏了怎么办 电脑网页打开慢怎么办 win10无法添加打印机怎么办 2018杭州禁摩怎么办 中山个人怎么办社保卡 昆山房产证丢了怎么办 于一机交宽带费怎么办 租房子的怎么办宽带 乐才注册不了怎么办 分期乐登录不了怎么办 股票忘记交易密码怎么办 信用卡交易密码忘记怎么办 乐购超市怎么办会员 手机分期付款0首付怎么办 首付手机违约了怎么办 买了假东西怎么办 pk10滚雪球挂了怎么办 好彩投彩票闪退怎么办 yeezy买小了怎么办 yeezy斑马变黄了怎么办 yeezy买大了怎么办 聚宝商城被骗怎么办 电瓶车碟刹抱死怎么办 电动车分期不还怎么办 车壳子凹进去怎么办 电车电瓶不耐用怎么办