回调函数

什么是回调函数?

简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。

为什么要使用回调函数?

因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。

如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。

回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。

另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。

不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。

一个简单的回调函数实现

下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。 

void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,

CompareFunction cmpFunc); void DLLDIR __stdcall Quicksort(byte* array,int size,int elem_size,

CompareFunction cmpFunc);

这两个函数接受以下参数:

◆byte * array:指向元素数组的指针(任意类型)。
◆int size:数组中元素的个数。
◆int elem_size:数组中一个元素的大小,以字节为单位。
◆CompareFunction cmpFunc:带有上述原型的指向回调函数的指针。

这两个函数的会对数组进行某种排序,但每次都需决定两个元素哪个排在前面,而函数中有一个回调函数,其地址是作为一个参数传递进来的。对编写者来说,不必介意函数在何处实现,或它怎样被实现的,所需在意的只是两个用于比较的元素的地址,并返回以下的某个值(库的编写者和使用者都必须遵守这个约定):

◆-1:如果第一个元素较小,那它在已排序好的数组中,应该排在第二个元素前面。
◆0:如果两个元素相等,那么它们的相对位置并不重要,在已排序好的数组中,谁在前面都无所谓。
◆1:如果第一个元素较大,那在已排序好的数组中,它应该排第二个元素后面。

基于以上约定,函数Bubblesort()的实现如下,Quicksort()就稍微复杂一点: 

void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,

CompareFunction cmpFunc) {  for(int i=0; i < size; i++)  { for(int j=0; j < size-1; j++) {  //回调比较函数  if(1 == (*cmpFunc)(array+j*elem_size,array+(j+1)*elem_size))  { //两个相比较的元素相交换 byte* temp = new byte[elem_size]; memcpy(temp, array+j*elem_size, elem_size); memcpy(array+j*elem_size,array+(j+1)*elem_size,elem_size); memcpy(array+(j+1)*elem_size, temp, elem_size); delete [] temp;  } }  } }

注意:因为实现中使用了memcpy(),所以函数在使用的数据类型方面,会有所局限。

对使用者来说,必须有一个回调函数,其地址要传递给Bubblesort()函数。下面有二个简单的示例,一个比较两个整数,而另一个比较两个字符串: 

int __stdcall CompareInts(const byte* velem1, const byte* velem2) {  int elem1 = *(int*)velem1;  int elem2 = *(int*)velem2;  if(elem1 < elem2) return -1;  if(elem1 > elem2) return 1;  return 0; } int __stdcall CompareStrings(const byte* velem1, const byte* velem2) {  const char* elem1 = (char*)velem1;  const char* elem2 = (char*)velem2;  return strcmp(elem1, elem2); }

下面另有一个程序,用于测试以上所有的代码,它传递了一个有5个元素的数组给Bubblesort()和Quicksort(),同时还传递了一个指向回调函数的指针。 

int main(int argc, char* argv[]) {  int i;  int array[] = {5432, 4321, 3210, 2109, 1098};  cout << "Before sorting ints with Bubblesort/n";  for(i=0; i < 5; i++) cout << array[i] << '/n';  Bubblesort((byte*)array, 5, sizeof(array[0]), &CompareInts);  cout << "After the sorting/n";  for(i=0; i < 5; i++) cout << array[i] << '/n';  const char str[5][10] = {"estella","danielle","crissy","bo","angie"};  cout << "Before sorting strings with Quicksort/n";  for(i=0; i < 5; i++) cout << str[i] << '/n';  Quicksort((byte*)str, 5, 10, &CompareStrings);  cout << "After the sorting/n";  for(i=0; i < 5; i++) cout << str[i] << '/n';  return 0; }

如果想进行降序排序(大元素在先),就只需修改回调函数的代码,或使用另一个回调函数,这样编程起来灵活性就比较大了。

调用约定

上面的代码中,可在函数原型中找到__stdcall,因为它以双下划线打头,所以它是一个特定于编译器的扩展,说到底也就是微软的实现。任何支持开发基于Win32的程序都必须支持这个扩展或其等价物。以__stdcall标识的函数使用了标准调用约定,为什么叫标准约定呢,因为所有的Win32 API(除了个别接受可变参数的除外)都使用它。标准调用约定的函数在它们返回到调用者之前,都会从堆栈中移除掉参数,这也是Pascal的标准约定。但在C/C++中,调用约定是调用者负责清理堆栈,而不是被调用函数;为强制函数使用C/C++调用约定,可使用__cdecl。另外,可变参数函数也使用C/C++调用约定。

Windows操作系统采用了标准调用约定(Pascal约定),因为其可减小代码的体积。这点对早期的Windows来说非常重要,因为那时它运行在只有640KB内存的电脑上。

如果你不喜欢__stdcall,还可以使用CALLBACK宏,它定义在windef.h中: 

#define CALLBACK __stdcallor #define CALLBACK PASCAL //而PASCAL在此被#defined成__stdcall

作为回调函数的C++方法

因为平时很可能会使用到C++编写代码,也许会想到把回调函数写成类中的一个方法,但先来看看以下的代码: 

class CCallbackTester {  public:  int CALLBACK CompareInts(const byte* velem1, const byte* velem2); }; Bubblesort((byte*)array, 5, sizeof(array[0]), &CCallbackTester::CompareInts);

如果使用微软的编译器,将会得到下面这个编译错误: 

error C2664: 'Bubblesort' : cannot convert parameter 4 from

'int (__stdcall CCallbackTester::*)(const unsigned char *,

const unsigned char *)' to 'int (__stdcall *)(const unsigned char *,

const unsigned char *)' There is no context in which this conversion is possible

这是因为非静态成员函数有一个额外的参数:this指针,这将迫使你在成员函数前面加上static。当然,还有几种方法可以解决这个问题,但限于篇幅,就不再论述了。

 
 

在C中写回调函数非常方便,C++中定义回调函数要麻烦一些,成员函数牵涉到一个this指针问题。C++中的static成员函数没有this指针,也可以直接作为回调函数使用。C++的成员函数地址是唯一确定的,数据变量地址是可变的,因此编译器使用this指针把这两个部分连接起来,我们调用c++的成员函数,编译器会添加一个this指针到参数中。

有一个类CTest

class CTest
{
public:
void DoMsgFunc1(char* pMsg,int nID)
{
}

void RegiestMsg(int nSrcID,DoMessageFunc pFunc)
{
m_pFunc = pFunc;
}

void HandleMessage(int nMsgID, char* pMsg, int nID)
{
(this->*m_pFunc)(pMsg,nID);
}

private:
DoMessageFunc m_pFunc;
};

定义CTest中的一个回调函数原型:
typedef void (CTest::*DoMessageFunc)(char* pMsg,int nID);
这样在类中就可以直接使用了。

注册回调函数时候,使用如下例子
CTest obj;
obj.RegiestMsg(13,&CTest::DoMsgFunc1);

如果CTest有很多派生类,如
class CTest1 : public CTest

则CTest1注册回调函数的时候,需要强制转换一下,如下
CTest1 obj1;
obj1.RegiestMsg(11,(DoMessageFunc)&CTest1::DoMsgFunc2);

MFC中的消息映射也是同样的道理,能够进行消息映射的类都是从CCmdTarget派生下来的,看看ON_COMMAND宏定义的实现就明白了。

#define ON_COMMAND(id, memberFxn) /
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, /
static_cast AFX_PMSG (memberFxn) },

其中AFX_PMSG的定义如下:
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void)

 

“先泛后精,先浅后深”的道理可是知易行难,所以常常会给技术上的小细节纠缠着,搞得晕晕乎,其中“回调函数”就是其中之一了,虽然回用,但是老是不明白其中的含义,众多的书本也说得隐晦不清。直到不久前看到一篇文章才总算比较清晰了解,。
      
概括起来,回调机制包括两部分:服务执行者和服务方式制定者。
             1. 服务执行者先制定服务规范;
             2.服务方式制定者然后按照规范制定服务方式;
             3.然后执行者按照这个方式提供服务。
       回调函数的方式是把函数指针的作为参数传递进去,所以规范就是约定函数的参数类型,个数。

这篇文章如下:
        调用(calling)机制从汇编时代起已经大量使用:准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。 CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。借堆栈保护现场真是一项绝妙的发明,它使调用者和被调者可以互不相识,于是才有了后来的函数和构件,使吾辈编程者如此轻松愉快。若评选对人类影响最大之发明,在火与车轮之后,笔者当推压栈调用。
       话虽这样说,此调用机制并非完美。回调函数就是一例。函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。例如,写一个快速排序函数供他人调用,其中必包含比较大小。麻烦来了:此时并不知要比较的是何类数据--整数、浮点数、字符串?于是只好为每类数据制作一个不同的排序函数。更通行的办法是在函数参数中列一个回调函数地址,并通知调用者:君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值说明比较结果。排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。被调用者回头调用调用者的函数(够咬嘴的),故称其为回调(callback)。
        回调函数使程序结构乱了许多。Windows API 函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。无论何种事物,能以树形结构单向描述毕竟让人舒服些。如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。
       Windows 系统还包含着另一种更为广泛的回调机制,即消息机制。消息本是 Windows 的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。消息所附带的 WParam 和 LParam 相当于函数的参数,只不过比普通参数更通用一些。应用程序可以主动发送消息,更多情况下是坐等 Windows 发送消息。一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。操作系统本是为应用程序服务,由应用程序来调用。而应用程序一旦启动,却要反过来等待操作系统的调用。这分明也是一种回调,或者说是一种广义回调。其实,应用程序之间也可以形成这种回调。假如进程 B 收到进程 A 发来的消息,启动了一段代码,其中又向进程 A 发送消息,这就形成了回调。这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。若是故意编写成此递归调用,并设好终止条件,倒是很有意思。但这种程序结构太隐蔽,除非十分必要,还是不用为好。
       利用消息也可以构成狭义回调。上面所举排序函数一例,可以把回调函数地址换成窗口handle。如此,当需要比较数据大小时,不是去调用回调函数,而是借 API 函数 SendMessage 向指定窗口发送消息。收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。所实现的功能与回调函数并无不同。当然,此例中改为消息纯属画蛇添脚,反倒把程序搞得很慢。但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。假如回调函数中包含文件处理之类的低速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。一个替代办法是借 API 函数 PostMessage发送一个异步消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。
       如今我们是活在一个 object 时代。只要与编程有关,无论何事都离不开 object。但 object 并未消除回调,反而把它发扬光大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。应用程序要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理例程,以备构件代码来调用。何谓事件?它不过是一个指向事件例程的地址,与回调函数地址没什么区别。
    不过,此种回调方式比传统回调函数要高明许多。首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。再者,地址是一个危险的东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如 VB 和 Java),其代价是降低了程序效率。事件例程使编程者无需直接操作地址,但并不会使程序减速。更妙的是,此一改变,本是有损程序结构之奇技怪巧变成一种崭新设计理念,不仅免去被人抨击,而且逼得吾等凡人净手更衣,细细研读,仰慕至今。只是偶然静心思虑,发觉不过一瓶旧酒而已,故引得此番议论,让诸君见笑了。 事件驱动程序设计是围绕着消息基础形成的,发生一个事件,伴随着一大堆的消息。
       我理解“回调机制”是window 在执行某个API函数的过程中,调用指定的一个函数。我们可以模拟一下:
假设 ms 提供一个函数叫做  EnumFont ,该函数是得到所有的字体,假设它的实现是
EnumFont()
{
  while ( (f =FindNextFont()) !=NULL)
  {
       printf("fontname: " + f.name);
  }
}
这样就循环显示出所有的字体名称。但是,开发者可能对字体信息另有用处,那么如何才能让开发者能使用这些信息呢,于是做改进:
EnumFont( void*  userFunc )
{
  while ( (f =FindNextFont()) !=NULL)
  {
       printf("fontname: " + f.name);
       if ( userFunc!=NULL)  userFunc( f) ;
  }
}
假设userFunc 是一个函数 void f( FontObject font).这样使用者只需要定义一个函数:
  void myfunc( FontObject font)
             {
                 listCtrl.Addstring ( font.name);
              }
通过使用 EnumFont ( myfunc) 就可以将所有额字体信息添加到一个列表框中。那么我们称 myfunc是一个回调函数,即让某个系统函数调用的函数。因此可以得出结论:
1 回调函数是由开发者按照一定的原型进行定义的函数
2 回调函数并不由开发者直接调用执行
3 回调函数通常作为参数传递给系统API,由该API来调用。
4 回调函数可能被系统API调用一次,也可能被循环调用多次。
比如 函数int EnumFontFamilies(
  HDC hdc,             // handle to device control
  LPCTSTR lpszFamily,  // pointer to family-name string
  FONTENUMPROC lpEnumFontFamProc,
                       // pointer to callback function
  LPARAM lParam        // pointer to application-supplied data
);
其中的   FONTENUMPROC lpEnumFontFamProc就是一个回调函数,该函数遵照格式
int CALLBACK EnumFontFamProc( ENUMLOGFONT FAR *lpelf,  NEWTEXTMETRIC FAR *lpntm, int FontType,  LPARAM lParam )进行定义。
如同mutant所说,回调函数主要用于一些比较费时的操作,或响应不知道何时将会发生的事件,回调函数提供了一种异步的机制,相对于同步执行,提高了效率.前者的例子如WriteFileEx,ReadFileEx等,函数的最后一个参数是一个回调函数的指针,程序中调用WriteFileEx以后,就直接返回了,可以继续进行其他工作,系统在读写操作完成后通知程序作善后处理.后者的例子就是windows的事件机制回调函数的另一个用途,是用于一些枚举函数,如EnumDisplayModes等,每找到一种支持的显示模式,就通知回调函数,由回调函数具体处理,这是因为 EnumDisplayModes本身并不知道用户要如何处理.能,用户提供回调函数,定制系统的功能,这样,不同的用户提供不同的回调函数,可以使系统具有不同的功能.这就是所谓的plugin.使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。

 

回调函数说白了就是事件响应程序,Windows的每个消息可以理解为一个事件,事件的响应代码要由用户自己来定义。用户定义了事件响应的代码,但还要Windows知道这段代码的位置(要不然Windows就不知道如何去调用,这也没有用),于是用户需要将回调函数的指针告诉Windows,最典型的例子是在窗口类的结构(WNDCLASS)中给lpfnWndProc分量赋回调函数指针值。
回调函数的参数格式是由回调函数的调用者(一般是Windows)来定义的,而回调函数的实现者必须遵循这种格式。Windows程序是以事件驱动模型为基础的,这就必然要用到回调函数这种机制。
要透彻了解回调函数,多看看SDK Samples。而MFC中的消息映射机制已经将窗口消息响应的回调函数隐藏起来了,这也符合C++的编程思想,回调函数终究是一种全局函数,它不能在类中实现,而消息映射机制的目的是使消息响应的代码最终封装在窗口类(CWnd类的子类)中。

如果有时间,不妨看看MESSAGE_MAP宏,消息映射是回调函数,只是这种回调函数的用法不同而已。普通的回调函数是要你提供地址,传进某个函数,由它去调用;而消息映射函数,却是由你定义函数,由MESSAGE_MAP宏去取得地址,并实现它的调用。

回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。

CODE:
typedef void (*f1) ();// 为函数指针声明类型定义
void (*p) (); //p是指向某函数的指针
void func1()
{
/* do something */
printf("From func1(), Hello World!/n");
}

void caller(void(*ptrfunc1)())
{
ptrfunc1(); /* 调用ptr指向的函数 */
}

//typedef bool (*f2) (int *);// 为函数指针声明类型定义
//bool (*q) (int *); //p是指向某函数的指针

bool func2(int* t_i)
{
/* do something */
printf("From func2() = %d, Hello World!/n", (*t_i)++);
return true;
}

void caller2(bool (*ptrfunc2)(int *), int * i)
{
ptrfunc2(i); /* 调用ptr指向的函数 */
}


int main(int argc, char* argv[])
{
printf("From main(), Hello World!/n");
printf("/n");

//无参数调用
p = func1; /* 传递函数地址地址 */
caller(p); /* 传递函数地址到调用者 */

//有参数调用
int i = 0;
for (int j = 0; j < 10; j++)
{
caller2(func2, &i); //* 传递函数地址到调用者 */
}
 
 
 

"软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础,因此,下面我们着重讨论回调机制在不同软件架构中的实现。"[1]

关于回调,我的个人感觉就是B定义了接口或者是模板,A作为调用者自己实现它,但是A将自己的实现嵌入了B的环境中。抽象一点,其实就是A的定义域嵌入到B的执行域。大部分时候,这两个是在一起的。



//有参数调用第二次

i = 0;
//q = func2; /* 传递函数地址地址 */
//caller2(q, &i); /* 传递函数地址到调用者 */

printf("/n");
printf("From main(), Hello World!/n");

getchar();

return 0;
}
 
 
回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给 
调用者从而实现调用。回调函数使用是必要的,在我们想通过一个统一接口实现不 
同的内容,这时用回掉函数非常合适。比如,我们为几个不同的设备分别写了不同 
的显示函数:void TVshow(); void ComputerShow(); void NoteBookShow()...等 
等。这是我们想用一个统一的显示函数,我们这时就可以用回调函数了。void sho 
w(void (*ptr)()); 使用时根据所传入的参数不同而调用不同的回调函数。 
      不同的编程语言可能有不同的语法,下面举一个c语言中回调函数的例子, 
其中一个回调函数不带参数,另一个回调函数带参数。 
      例子1: 
//Test.c 
#include  
#include  
int Test1() 

  int i; 
  for (i=0; i<30; i++) 
  { 
    printf("The %d th charactor is: %c\n", i, (char)('a' + i%26)); 

  } 
  return 0; 

int Test2(int num) 

  int i; 
  for (i=0; i   { 
   printf("The %d th charactor is: %c\n", i, (char)('a' + i%26)); 

  } 
  return 0; 

void Caller1(void (*ptr)())//指向函数的指针作函数参数 

  (*ptr)(); 

void Caller2(int n, int (*ptr)())//指向函数的指针作函数参数,这里第一个参数是为指向函数的指针服务的, 
{                                               //不能写成void Caller2(int (*ptr)(int n)),这样的定义语法错误。 
  (*ptr)(n); 
  return; 

int main() 

  printf("************************\n"); 
  Caller1(Test1); //相当于调用Test2(); 
  printf("&&&&&&************************\n"); 
  Caller2(30, Test2); //相当于调用Test2(30); 
  return 0; 

      以上通过将回调函数的地址传给调用者从而实现调用,但是需要注意的是带 
参回调函数的用法。要实现回调,必须首先定义函数指针。函数指针的定义这里稍 
微提一下。比如: 
    int (*ptr)(); 这里ptr是一个函数指针,其中(*ptr)的括号不能省略,因为 
括号的优先级高于星号,那样就成了一个返回类型为整型的函数声明了
 
 
callback回调函数:
  凡是由你设计却由windows系统呼叫的函数,统称为callback函数。某些API函数要求以callback作为你参数之一。如SetTimer,LineDDA,EnumObjects。
  回调函数是由开发者按照一定的原形进行定义的函数(每个回调函数都必须遵循这个原则来设计)
  例如:
  ----------------------------------------
  BOOL CALLBACK DialogProc(
  HWND hwndDlg, // handle of dialog box
  UINT uMsg, // message
  WPARAM wParam, // first message parameter
  LPARAM lParam // second message parameter
  );
  ----------------------------------------
  说明:
  回调函数必须有关键词 CALLBACK;
  回调函数本身必须是全局函数或者静态函数,不可定义为某个特定的类的成员函数
  2 回调函数并不由开发者直接调用执行(只是使用系统接口API函数作为起点)
  3 回调函数通常作为参数传递给系统API,由该API来调用
  4 回调函数可能被系统API调用一次,也可能被循环调用多次
 
 
 

你可能感兴趣的:(C++,windows,callback,byte,pascal,api)