关于回调函数

关于回调函数
关于回调函数

关于回调函数

一,回调函数

我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?

使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。

而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALL BACK(相当于FAR PASCAL),这主要是说明该函数的调用方式。至于钩子函数,只是回调函数的一个特例。习惯上把与SetWindow sHookEx函数一起使用的回调函数称为钩子函数。也有人把利用Vi rtualQueryEx安装的函数称为钩子函数,不过这种叫法不太流行。也可以这样,更容易理解:回调函数就好像是一个中断处理函数,系统在符合你设定的条件时自动调用。为此,你需要做三件事:

1. 声明;

2. 定义;

3. 设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。

声明和定义时应注意:回调函数由系统调用,所以可以认为它属于W INDOWS系统,不要把它当作你的某个类的成员函数。

二,回调函数、消息和事件例程

调用(calling)机制从汇编时代起已经大量使用:准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。借堆栈保护现场真是一项绝妙的发明,它使调用者和被调者可以互不相识,于是才有了后来的函数和构件。

此调用机制并非完美。回调函数就是一例。函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。例如,写一个快速排序函数供他人调用,其中必包含比较大小。麻烦来了:此时并不知要比较的是何类数据--整数、浮点数、字符串?于

是只好为每类数据制作一个不同的排序函数。更通行的办法是在函数参数中列一个回调函数地址,并通知调用者:君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值说明比较结果。排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。被调用者回头调用调用者的函数(够咬嘴的),故称其为回调(call back)。

回调函数使程序结构乱了许多。Windows API 函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。

无论何种事物,能以树形结构单向描述毕竟让人舒服些。如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。 Windows 系统还包含着另一种更为广泛的回调机制,即消息机制。消息本是 Windows 的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。消息所附带的 WParam 和LParam 相当于函数的参数,只不过比普通参数更通用一些。应用程序可以主动发送消息,更多情况下是坐等 Windows 发送消息。一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。操作系统本是为应用程序服务,由应用程序来调用。而

应用程序一旦启动,却要反过来等待操作系统的调用。这分明也是一种回调,或者说是一种广义回调。其实,应用程序之间也可以形成这种回调。假如进程 B 收到进程 A 发来的消息,启动了一段代码,其中又向进程 A 发送消息,这就形成了回调。这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。若是故意编写成此递归调用,并设好终止条件,倒是很有意思。但这种程序结构太隐蔽,除非十分必要,还是不用为好。

利用消息也可以构成狭义回调。上面所举排序函数一例,可以把回调函数地址换成窗口 handle。如此,当需要比较数据大小时,不是去调用回调函数,而是借 API 函数 SendMessage 向指定窗口发送消息。收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。所实现的功能与回调函数并无不同。当然,此例中改为消息纯属画蛇添脚,反倒把程序搞得很慢。但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。假如回调函数中包含文件处理之类的低速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。一个替代办法是借 API 函数 PostMessage 发送一个异步消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。

如今我们是活在一个 object 时代。只要与编程有关,无论何事都离不开 object。但 object 并未消除回调,反而把它发扬光

大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。应用程序要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理例程,以备构件代码来调用。何谓事件?它不过是一个指向事件例程的地址,与回调函数地址没什么区别。

不过,此种回调方式比传统回调函数要高明许多。首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。再者,地址是一个危险的东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如 VB 和 Java),其代价是降低了程序效率。事件例程(?)使编程者无需直接操作地址,但并不会使程序减速。

(例程似乎是进程的台湾翻译。)

三,精妙比喻:回调函数还真有点像您随身带的BP机:告诉别人号码,在它有事情时Call您。

回调用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调,例如作为一个驱动,是一个底层,他在收到一个数据时,除了完成本层的处理工作外,还将进

行回调,将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。其实回调和API非常接近,他们的共性都是跨层调用的函数。但区别是API是低层提供给高层的调用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,对于低层他是未知的,必须由高层进行安装,这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这个回调,在需要调用时,只需引用这个函数指针和相关的参数指针。其实:回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的触发下,低层通过该函数指针调用高层那个函数。

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

调用的基础。

对于不同类型的语言(如结构化语言和对象语言)、平台(Wi n32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。

对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。

在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object Pascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。

Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。

对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。

下面我们集中比较具有代表性的语言(C、Object Pascal)和架构(CORBA)来分析回调的实现方式、具体作用等。

2 过程语言中的回调(C)

2.1 函数指针

回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子:

void Func(char *s);// 函数原型

void (*pFunc) (char *);//函数指针

可以看出,函数的定义和函数指针的定义非常类似。

一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。

typedef void(*pcb)(char *);

回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。

被调函数的例子:

void GetCallBack(pcb callback)

{

/*do something*/

}

用户在调用上面的函数时,需要自己实现一个pcb类型的回调函数:

void fCallback(char *s)

{

/* do something */

}

然后,就可以直接把fCallback当作一个变量传递给GetCa llBack,

GetCallBack(fCallback);

如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。

2.2 参数传递规则

到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI

C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastc all调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:

// 被调用函数是以int为参数,以int为返回值

__stdcall int callee(int);

// 调用函数以函数指针为参数

void caller( __cdecl int(*ptr)(int));

// 在p中企图存储被调用函数地址的非法操作

__cdecl int(*p)(int) = callee; // 出错

指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列

2.3 应用举例

C语言的标准库函数中很多地方就采用了回调函数来让用户定制处理过程。如常用的快速排序函数、二分搜索函数等。

快速排序函数原型:

void qsort(void *base, size_t nelem, size_t wi dth, int (_USERENTRY *fcmp)(const void *, const vo id *));

二分搜索函数原型:

void *bsearch(const void *key, const void *bas e, size_t nelem,

size_t width, int (_USERENTRY *fcmp)(const voi d *, const void *));

其中fcmp就是一个回调函数的变量。

下面给出一个具体的例子:

#include

#include

int sort_function( const void *a, const void * b);

int list[5] = { 54, 21, 11, 67, 22 };

int main(void)

{

int x;

qsort((void *)list, 5, sizeof(list[0]), sort_func tion);

for (x = 0; x < 5; x++)

printf("%i\n", list[x]);

return 0;

}

int sort_function( const void *a, const void *b) {

return *(int*)a-*(int*)b;

}

2.4 面向对象语言中的回调(Delphi)

Dephi与C++一样,为了保持与过程语言Pascal的兼容性,它在引入面向对象机制的同时,保留了以前的结构化特性。因此,对回调的实现,也有两种截然不同的模式,一种是结构化的函数回调模式,一种是面向对象的接口模式。

回调函数

对于很多初学者来说,往往觉得回调函数很神秘,很想知道回调函数的工作原理。本文将要解释什么是回调函数、它们有什么好处、为什么要使用它们等等问题,在开始之前,假设你已经熟知了函数指针。 什么是回调函数? 简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。 为什么要使用回调函数? 因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为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(),这两个方法原型相同,但实现了不同的排序算法。

回调函数与回调机制

回调函数与回调机制 1. 什么是回调函数 回调函数(callback Function),顾名思义,用于回调的函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。回调函数包含下面几个特性: ?属于工作流的一个部分; ?必须按照工作流指定的调用约定来申明(定义); ?他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能; 2. 回调机制 回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。 如上图所示,工作流提供了两个对外接口(获取参数、显示结果),以回调函数的形式实现。 ?“获取参数”回调函数,需要工作流使用者设定工作流计算需要的参数。 ?“显示结果”回调函数,提供计算结果给工作流使用者。

再以Windows的枚举顶级窗体为例。函数EnumWindows用于枚举当前系统中的所有顶级窗口,其函数原型为: BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ); 其中lpEnumFunc是一个回调函数,他用于返回枚举过程中的获得的窗口的句柄。其定义约定为: BOOL CALLBACK EnumWindowsProc( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value ); 在这个例子中,EnumWindows 是一个工作流,这个工作流用于遍历windows的所有窗口并获得其句柄。用户使用EnumWindows工作流的目的是想通过工作流来来获取窗口的句柄以便针对特定的一个或多个窗口进行相关处理。于是EnumWindows就扩展出接口lpEnumFunc,用于返回遍历的窗口句柄。 EnumWindows工作流的结束有两个方式:1,用户在回调函数中返回FALSE;2,再也找不到顶级窗口。我们可以推测EnumWindows的实现机制如下: 注:下列代码中的FindFirstTopWindows(), FindNextTopWindow()为假设的,Windows API 没有此函数,只是为了表明Enumwindows的内部流程。 BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ) { BOOL bRet = TRUE; HWND hWnd = ::FindFirstTopWindows(); // 此函数是假设的,查找第一个顶级窗口 // 当hWnd为0时表示再也找不到顶级窗口 while( hWnd ) { bRet = (*lpEnumFunc)( hWnd, value ); if( !bRet) break; // 终止EnumWindows工作流; hWnd = ::FindNextWindow(); // 此函数是假设的,查找下一个顶级窗口 } } 在EnumWindows(...)函数中,实现了窗口枚举的工作流,他通过回调机制把用户关心(顶级窗口句柄)的和枚举工作流分开,用户不需要知道EnumWindows的具体实现,用户只要知道,设定了lpEnumFunc函数,然后把函数指针传给EnumWindwos就可以获得想要的窗口句柄。

hook的使用实例

在网上找了好久都没有找到消息hook的实例,下面是我的例子给大家分享一下 下面是dll中的代码: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //我的经验,编译的时候会提示DllMain,已在DllMain.cpp中定义,把DllMain.cpp从源文件里删掉就好了 #include "stdafx.h" #include HHOOK hkey=NULL; HINSTANCE h_dll; #pragma data_seg(".MySec") //定义字段,段名.MySec HWND h_wnd=NULL; #pragma data_seg() #pragma comment(linker,"/section:.MySec,RWS") BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved) { h_dll=hinstDLL; // MessageBox(0,"运行dllman","",MB_OK); return TRUE; } LRESULT CALLBACK my_test(int nCode,WPARAM wParam,LPARAM iParam)// { /* if(nCode==HC_ACTION) { MessageBox(0,"成功!!","标题",MB_OK); } else { MessageBox(0,"失败!!","标题",MB_OK); } */ MessageBox(0,"被截取","",MB_OK); UnhookWindowsHookEx(hkey); return 1; } void SetHook(HWND hwnd) { h_wnd = hwnd; // MessageBox(0,"运行sethook","",MB_OK); hkey=SetWindowsHookEx(WH_KEYBOARD,my_test,h_dll,0); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 下面是EXE的代码:有很多头文件是没用上的,我个人习惯都带着- -,虽然这不是好习惯

关于回调函数的几个例子(c)

以下是一个简单的例子。实现了一个repeat_three_times函数,可以把调用者传来的任何回调函数连续执行三次。 例 1. 回调函数 /* para_callback.h */ #ifndef PARA_CALLBACK_H #define PARA_CALLBACK_H typedef void (*callback_t)(void *); extern void repeat_three_times(callback_t, void *); #endif /* para_callback.c */ #include "para_callback.h" void repeat_three_times(callback_t f, void *para) { f(para); f(para); f(para); } /* main.c */ #include #include "para_callback.h" void say_hello(void *str) { printf("Hello %s\n", (const char *)str); } void count_numbers(void *num) { int i; for(i=1; i<=(int)num; i++) printf("%d ", i); putchar('\n');

} int main(void) { repeat_three_times(say_hello, "Guys"); repeat_three_times(count_numbers, (void *)4); return 0; } 回顾一下前面几节的例子,参数类型都是由实现者规定的。而本例中回调函数的参数按什么类型解释由调用者规定,对于实现者来说就是一个void *指针,实现者只负责将这个指针转交给回调函数,而不关心它到底指向什么数据类型。调用者知道自己传的参数是char *型的,那么在自己提供的回调函数中就应该知道参数要转换成char *型来解释。 回调函数的一个典型应用就是实现类似C++的泛型算法(Generics Algorithm)。下面实现的max函数可以在任意一组对象中找出最大值,可以是一组int、一组char或者一组结构体,但是实现者并不知道怎样去比较两个对象的大小,调用者需要提供一个做比较操作的回调函数。 例 2. 泛型算法 /* generics.h */ #ifndef GENERICS_H #define GENERICS_H typedef int (*cmp_t)(void *, void *); extern void *max(void *data[], int num, cmp_t cmp); #endif /* generics.c */ #include "generics.h" void *max(void *data[], int num, cmp_t cmp) { int i; void *temp = data[0];

OpenGL一个简单的例子

先编译运行一个简单的例子,这样我们可以有一个直观的印象。从这个例子我们可以看到OpenGL可以做什么,当然这个例子只做了很简单的一件事--绘制一个彩色的三角形。除此以外,我们还可以看到典型的OpenGL程序结构及openGL的运行顺序。 例1:本例在黑色的背景下绘制一个彩色的三角形,如图一所示。

图一:一个彩色的三角形首先创建工程,其步骤如下:

1)创建一个Win32 Console Application。 2)链接OpenGL libraries。在Visual C++中先单击Project,再单击Settings,再找到Link单击,最后在Object/library modules 的最前面加上OpenGL32.lib GLu32.lib GLaux.lib 3)单击Project Settings中的C/C++标签,将Preprocessor definitions 中的_CONSOLE改为__WINDOWS。最后单击OK。 现在你可以把下面的例子拷贝到工程中去,编译运行。你可以看到一个彩色的三角形。 我们先看看main函数。函数中以glut开头的函数都包含在glut.h中。GLUT库的函数主要执行如处理多窗口绘制、处理回调驱动事件、生成层叠式弹出菜单、绘制位图字体和笔画字体,以及各种窗口管理等任务。 ·glutInit用来初始化GLUT库并同窗口系统对话协商。 ·glutInitDisplayMode用来确定所创建窗口的显示模式。本例中的参数GLUT_SINGLE 指定单缓存窗口,这也是缺省模式,对应的模式为GLUT_DOUBLE 双缓存窗口。参数GLUT_RGB指定颜色RGBA模式,这也是缺省模式,对应的模式为GLUT_INDEX 颜色索引模式窗口。 ·glutInitWindowSize初始化窗口的大小,第一个参数为窗口的宽度,第二个参数为窗口的高度,以像素为单位。 ·glutInitWindowPosition设置初始窗口的位置,第一个参数为窗口左上角x的坐标,第二个参数为窗口左上角y的坐标,以像素为单位。屏幕的左上角的坐标为(0,0),横坐标向右逐渐增加,纵坐标向下逐渐增加。 ·glutCreateWindow创建顶层窗口,窗口的名字为扩号中的参数。 ·background() 这是自己写的函数,设置背景。其实这个函数中的语句可以写在display 函数中,但为了使功能块更加清晰,所以把背景这一部分单独提出来。 ·glutReshapeFunc注册当前窗口的形状变化回调函数。当改变窗口大小时,该窗口的形状改变回调函数将被调用。在此例中就是myReshape指定形状变化函数。 ·glutDisplayFunc注册当前窗口的显示回调函数。当一个窗口的图像层需要重新绘制时,GLUT将调用该窗口的的显示回调函数。在此例中的mydisplay就是显示回调函数,显示回调函数不带任何参数,它负责整个图像层的绘制。我们的大部分工作将集中在这个函数中。 ·glutMainLoop进入GLUT事件处理循环。glutMainLoop函数在GLUT程序中最多只能调用一次,它一旦被调用就不再返回,并且调用注册过的回调函数。所以这个函数必须放在注册回调函数的后面,此例中为glutReshapeFunc,glutDisplayFunc。

使用回调接口实现ActiveX控件和它的容器程序的通讯

本文阅读基础:有一定的C++基础知识(了解继承、回调函数),对MFC的消息机制有一定了解,对COM的基础知识有一定了解,对ActiveX控件有一定了解。 一.前言 ActiveX控件和它的容器程序如何通讯是一个值得研究的问题,因为这涉及到ActiveX控件和它的容器程序如何交互的问题。VC知识库的杨老师写了一系列博客介绍了一些通讯方式。链接如下: COM 组件设计与应用(十三)--事件和通知(VC6.0) COM 组件设计与应用(十四)--事件和通知(https://www.360docs.net/doc/1f12191498.html,) COM 组件设计与应用(十五)--事件和通知(VC6.0) COM 组件设计与应用(十六)--事件和通知(https://www.360docs.net/doc/1f12191498.html,) 这些文章写得真的很好,语言幽默风趣,深入浅出。我看后决心把它应用在ActiveX控件的回调实现上,经过实践,觉得有些地方语焉不详,自己做些摸索,写就此文,算是对杨老师文章的一点补充。 二.通知的方法 ActiveX控件是一个窗口,它的容器程序自然也有一个父窗口;同时ActiveX控件是一个接口;ActiveX控件本质是一个COM组件,COM组件的客户端和服务器端本身有自己的通讯方式。从这两点我们可以想到二者之间的几种通讯方式: 我和我的同事曾争论ActiveX控件接口能否像一般C++的DLL那样在导出函数参数列表里设置一个回调函数指针那样实现回调,那时我认为是不行的。现在我看了ActiveX控件接口的参数类型,更加坚定了我的看法。其实从COM的初衷来看应该也是不行的,因为COM的初衷之一是提供一种跨语言的调用接口,而回调函数指针只对客户端是C++程序是有意义,对于VB、C#则无回调函数指针一说的。 三.实践检验

Matlab的GUI回调函数

Kinds of Callbacks The following table lists the callback properties that are available, their triggering events, and the components to which they apply. Note:User interface controls include push buttons, sliders, radio buttons, check boxes, editable text boxes, static text boxes, list boxes, and toggle buttons. They are sometimes referred to as uicontrols.

GUIDE Callback Arguments All callbacks in a GUIDE-generated GUI code file have the following standard input arguments: hObject —Handle of the object, e.g., the GUI component, for which the callback was triggered. For a button group SelectionChangeFcn callback, hObject is the handle of the selected radio button or toggle button. eventdata — Sequences of events triggered by user actions such as table selections emitted by a component in the form of a MATLAB struct (or an empty matrix for components that do not generate eventdata) handles — A MATLAB struct that contains the handles of all the objects in the GUI, and may also contain application-defined data. See handles Structure for information about this structure. Object Handle The first argument is the handle of the component issuing the callback. Use it to obtain relevant properties that the callback code uses and change them as necessary. For example, theText = get(hObject,'String'); places the String property (which might be the contents of static text or name of a button) into the local variable theText. You can change the property by setting it, for example set(hObject,'String',date) This particular code changes the text of the object to display the current date. Event Data Event data is a stream of data describing user gestures, such as key presses, scroll wheel movements, and mouse drags. The auto-generated callbacks of GUIDE GUIs can access event data for Handle Graphics? and uicontrol and uitable object callbacks. The following ones receive event data when triggered: CellEditCallback in a uitable CellSelectionCallback in a uitable KeyPressFcn in uicontrols and figures KeyReleaseFcn in a figure SelectionChangeFcn in a uibuttongroup WindowKeyPressFcn in a figure or any of its child objects WindowKeyReleaseFcn in a figure or any of its child objects WindowScrollWheelFcn in a figure Event data is passed to GUIDE-generated callbacks as the second of three standard arguments. For components that issue no event data the argument is empty. For those that provide event data, the argument contains a structure, which varies in composition according to the component that generates it and the type of event. For example, the event data for a key-press provides information on the key(s) currently being pressed. Here is a GUIDE-generated KeyPressFcn callback template: % --- Executes on key press with focus on checkbox1 and none of its controls. function checkbox1_KeyPressFcn(hObject, eventdata, handles)

WH_CBT回调函数

CBTProc Callback Function An application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the keyboard focus; or before synchronizing with the system message queue. A computer-based training (CBT) application uses this hook procedure to receive useful notifications from the system. The HOOKPROC type defines a pointer to this callback function. CBTProc is a placeholder for the application-defined or library-defined function name. Syntax 复制 LRESULT CALLBACK CBTProc( __in int nCode, __in WPARAM wParam, __in LPARAM lParam ); Parameters nCode [in] Type: int The code that the hook procedure uses to determine how to process the message. If nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function without further processing and should return the value returned by CallNextHookEx. This parameter can be one of the following values.

一种使类成员函数成为 Windows 回调函数的方法

问题:一种使类成员函数成为Windows 回调函数的方法( 积分:100, 回复:62, 阅读:3393 ) 分类:Object Pascal ( 版主:menxin, cAkk ) 来自:savetime, 时间:2004-6-20 2:41:00, ID:2672562 [显示:小字体| 大字体] 一种使类成员函数成为Windows 回调函数的方法 https://www.360docs.net/doc/1f12191498.html, savetime2k@https://www.360docs.net/doc/1f12191498.html, 2004.6.20 本文排版格式为: 正文由窗口自动换行;所有代码以80 字符为边界;中英文字符以空格符分隔。 未经作者同意请勿在在任何公共媒体转载 大富翁satanmonkey 提出一个问题:HOOK 的时候,那个回调函数怎么弄才能做成类的成员?现在回调函数不能是类成员函数,访问不了类的成员变量。 https://www.360docs.net/doc/1f12191498.html,/delphibbs/dispq.asp?lid=2624773 后来又在另一篇贴子上也看到类似的问题,看来解决这个问题还有点用(我现在还不知道这有什么用处),所以趁着今天周末思考一下。 (太想睡了,下面只好草率地说明,如有不清楚请提问,或者日后有空再详作解释) 一开始我的想法是在类成员的回调函数内部复制参数的值,差不多理顺了,后来发现如果回调函数有返回值时,这种方法不行... 只好重新开工,用手工编制机器码的方法完成,其中查询JMP $00001111 这样的立即数跳转机器指令花了一个小时,结果是没有找到,只好以JMP [$00001111] 这个代码代替。如果有谁知道前一种跳转指令的机

回调函数的概念及其使用

回调函数的概念及其使用

回调函数的概念及其使用 1 什么是回调 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础,因此,下面我们着重讨论回调机制在不同软件架构中的实现。 对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。 对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。

在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object Pascal 这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。 Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。 对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。 下面我们集中比较具有代表性的语言(C、Object Pascal)和架构(CORBA)来分析回调的实现方式、具体作用等。 2 过程语言中的回调(C) 2.1 函数指针 回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子: void Func(char *s);// 函数原型 void (*pFunc) (char *);//函数指针 可以看出,函数的定义和函数指针的定义非常类似。 一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。 typedef void(*pcb)(char *); 回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。 被调函数的例子:

unity3D学习委托进阶、回调函数(三)

下面开始委托进阶部分的分享 在此我分3个部分来说明表述 1.带返回值的委托 2.泛型委托 3.委托的异步处理 下面正式进入我们的主题 委托进阶 一、带有返回值的委托 问:委托需要承载哪些信息呢? 通过前面与大家分享的委托帖子中,不难答出,它存储了方法名,还有参数列表(方法签名). 如: //============================ public delegate void testDelegate(int num); //============================ 其实,仔细看看上面语句,就会发现委托还同时承载了返回的类型,我把上面语句格式化下,相信大家就会明白了 //=================================

public delegate 返回类型ProcessDelegate(int num); //================================= 上面委托定义的蓝色部分是声明委托的关键字,红色部分是返回的类型,黑色部分为委托的类型名,最后小括号中的就是参数部分啦. 因此,要实现该委托就得满足下面2个条件: 1、方法的返回类型和委托的返回类型必须一致; 2、方法的参数也必须跟委托相同,这里是int类型. OK,就然我们一起尝试下吧!文章来自【狗刨学习网】 代码如下: using UnityEngine; using System.Collections; public class babyTest : MonoBehaviour { // 定义具有返回值bool的委托 public delegate bool ComparisonEventHandler(int cryid); public int cryid = 0; public GameObject[] objs; // Use this for initialization void Start () { ComparisonEventHandler _Comparison = Comparison01; //new ComparisonEventHandler(new Test().Comparison01); _Comparison(cryid);

回调函数实现dll向主程序传递数据

回调函数实现dll向主程序传递数据 用dll封装窗口主要是封装对话框。软件开发中经常要使用的一个功能是导入数据,且要求可视化操作,如为对应的变量选择对应的列,设置数据的起始和终止行等。希望将界面做成如下形式: 由于这个界面和数据导入的通用性,我们希望将其封装到dll,提供一些接口进行窗口显示和数据传递就可以了。但是我们遇到一个问题:这里的数据传递我们希望是在点击OK之后获取对话框也就是dll中的数据传递到主程序,而在主程序中我们装载dll的函数是一个顺序的执行过程,不能进行消息响应。因此我们想到了使用回调函数。在这里的具体思想就是:在点dll的时候,我们使用回调函数将数据传到主程序,调用主程序的函数进行处理。虽然这样的过程有些打乱程序的执行顺序,但是可以到达我们的目的。下面介绍实现过程: 1、我们建立一个MFC的规则dll将写好的导入数据的对话框代码进行复制, 封装dll。 2、除了导出读取数据和显示对话框的函数之外,导出一个传递函数(回调 函数)形参的函数,这里我们采用一个在一个h文件中定义导出函数, 并利用宏切换功能,实现dll和主程序包含同一个h文件就可以实现函数 的导出和导入。具体代码见程序实例中的DllExport.h 3、具体介绍使用回调函数传递数据功能的实现。在DllExport.h中声明一个 函数的指针 typedef void (* pFunc)(double **pdata,int* nConut); 其中的两个参数pdata和nCount分别为我们要传递数据(一个二维数组)的指针,和一共有多少行数据。 接着声明一个以这个函数指针为形参的导出函数:

直调、回调、异调

1. 什么是回调函数 回调函数(callback Function),顾名思义,用于回调的函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。回调函数包含下面几个特性: 1、属于工作流的一个部分; 2、必须按照工作流指定的调用约定来申明(定义); 3、他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能; 2. 回调机制 回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。 ======================================================= java回调机制: 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。 同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用; 回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口; 异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。 回调和异步调用的关系非常紧密:使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。 ======================================================== 用Java里的例子: package callbackexample; public interface ICallBack { //需要回调的方法public void postExec(); } 另外的一个类: package callbackexample; public class FooBar { //组合聚合原则 private ICallBack callBack; public void setCallBack(ICallBack callBack) { this.callBack = callBack; doSth(); } public void doSth() { callBack.postExec(); } } 第二个类在测试类里面,是一个匿名类: package callbackexample; public class Test { public static void main(String[] args) { FooBar foo = new FooBar(); foo.setCallBack(new

labview的深入探索----labview与回调函数

labview的深入探索----labview与回调函数 回调函数是WINDOWS 编程(API 编程)的核心内容之一,在许多高级编程语 言,如VB,VC(MFC)中已经封装了回调函数,取而代之的是事件响应函数,但是,追 溯其本质,实际就是回调函数.所谓WINDOWS 回调函数,就是按照WINDOWS 的规范,编写的(CALLBACK)函数,当WINDOWS 检测到事件发生时,自动调用的 函数,WINDOWS 是通过函数指针调用的,因此,回调函数的内容是由用户决定的, 而何时调用是由操作系统决定的.我们看一下CVI 中的一般回调函数的定义int callback aaaa(int panel,int control,int event1,int event2,callbackdata *data);回调函数的参数是有操作系统提供的,比如上面的回调函数,panel---表示的哪个面板(窗口) 发生的事件control---表示的面板上哪个控件发生的事件event1 event2 表示事件 的类型和相应数据,比如鼠标坐标等回调函数是一般高级编程语言的基本功能, 但是,在LABVIEW8.X 之前是不支持的,这极大限制了LABVIEW 功能的扩展, 因为ACTIVEX,.NET 都需要回调函数.8.X 中,增加了回调函数的功能,主要用于ACTIVE,.NET 和LABVIEW 自身控件,LABVIEW 例子程序中提供了几个例子, 是有关ACTIVEX 和.NET 调用的,下面,我们通过LABVIEW 自身控件说明一下 回调函数的使用方法.在.NET 摸板中也提供了这个节点,从分类上就可以看出,注 册回调函数主要是用于ACTIVEX 和.NET 的.下面我们做一个简单的回调函数 的程序,有两个功能,返回当前值的变化和记录控件被点击的次数注册回调函数 需要三个参数:控件参考,用户参数和自动生成的回调函数,有了控件参考,我们就 可以选择事件的类型,用户参数主要是用于返回结果,因为回调函数是由操作系 统调用的,没有办法通过数据流返回处理结果.添加了这两个参数后,就可以自动 生成回调函数了回调函数如下图所示简单编程,CONTROL 的值传递给 INDICATOR 这样值变化的回调函数完成了,下面我们通过鼠标UP 事件来记录

函数指针的使用方法

对指针的应用是C语言编程的精髓所在,而回调函数就是C语言里面对函数指针的高级应用。简而言之,回调函数是一个通过函数指针调用的函数。如果你把函数指针(函数的入口地址)传递给另一个函数,当这个函数指针被用来调用它所指向的函数时,我们就说这个函数是回调函数。 为什么要使用回调函数呢?我们先看一个小例子: Node * Search_List (Node * node, const int value) { while (node != NULL) { if (node -> value == value) { break; } node = node -> next; } return node; } 这个函数用于在一个单向链表中查找一个指定的值,返回保存这个值的节点。它的参数是指向这个链表第一个节点的指针以及要查找的值。这个函数看上去很简单,但是我们考虑一个问题:它只能适用于值为整数的链表,如果查找一个字符串链表,我们不得不再写一个函数,其实大部分代码和现在这个函数相同,只是第二个参数的类型和比较的方法不同。 其实我们更希望令查找函数与类型无关,这样它就能用于查找存放任何类型值的链表了,因此必须改变比较的方式,而借助回调函数就可以达到这个目的。我们编写一个函数(回调函数),用于比较两个同类型的值,然后把一个指向这个函数的指针作为参数传递给查找函数,查找函数调用这个比较函数来执行比较,采用这个方法,任何类型的值得都可以进行比较。 我们还必须给查找函数传递一个指向待比较的值的指针而不是值本身,也就是一个void *类型的形参,这个指针会传递给回调函数,进行最终的比较。这样的修改可以让我们传递指向任何类型的指针到查找函数,从而完成对任何类型的比较,这就是指针的好处,我们无法将字符串、数组或者结构体作为参数传递给函数,但是指向它们的指针却可以。 现在,我们的查找函数就可以这样实现: NODE *Search_List(NODE *node, int (*compare)(void const *, void const *) , void const *desired_value); { while (node != NULL) { if (compare((node->value_address), desired_value) == 0) { break; } node = node->next; } return node; }

相关文档
最新文档