模块A有个foo它向模块B传递foo地址然后在B里面发生某种事件(event)时通过从A里面传递过来foo地址foo通知A发生了什么事情让A作出相应反应 那么我们就把foo称为回调
例子:
回调是个很有用也很重要概念当发生某种事件时系统或其他将会自动你定义段回调在Windows编程使用场合很多比如Hook回调:MouseProcGetMsgProc以及EnumWindowsDrawState回调等等还有很多系统级回调过程本文不准备介绍这些和过程而是谈谈实现自己回调些经验
的所以产生使用回调这个想法是现在使用VC和Delphi混合编程用VC写个DLL进行些时间比较长异步工作工作完成的后需要通知使用DLL应用:某些事件已经完成请处理事件后续部分开始想过使用同步对象文件影射消息等实现DLL到应用通知后来突然想到可不可以在应用端先写个等需要处理后续事宜时候在DLL里直接这个即可于是就动手写了个回调原形在VC和 Delphi里都进行了测试
:声明回调类型
VC版
typedef (WINAPI *PFCALLBACK)( Param1, Param2);
Delphi版
PFCALLBACK = function(Param1:eger; Param2:eger):eger;stdcall;
实际上是声明了个返回值为传入参数为两个指向指针
由于C和PASCAL编译器对参数入栈和返回处理有可能不致把类型用WINAPI(WINAPI宏展开就是__stdcall)或stdcall统修饰
2:声明回调原形
声明原形
VC版
WINAPI CBFunc( Param1, Param2);
Delphi版
function CBFunc(Param1,Param2:eger):eger;stdcall;
以上为全局如果要使用个类里作为回调原形把该类声明为静态即可
3: 回调者
回调我把它放到了DLL里这是个很简单VC生成WIN32 DLL并使用DEF文件输出其名TestCallBack实现如下:
PFCALLBACK gCallBack=0;
void WINAPI TestCallBack(PFCALLBACK Func)
{
(FuncNULL);
gCallBack=Func;
DWORD ThreadID=0;
HANDLE hThread = CreateThread( NULL, NULL, Thread1, LPVOID(0), &ThreadID );
;
}
此工作把传入 PFCALLBACK Func参数保存起来等待使用并且启动个线程声明了个指针PFCALLBACK gCallBack保存传入地址
4: 回调如何被使用:
TestCallBack被后启动了个线程作为演示线程人为进行了延时处理并且把线程运行过程打印在屏幕上.
本段线程代码也在DLL工程里实现
ULONG WINAPI Thread1(LPVOID Param)
{
TCHAR Buffer[256];
HDC hDC = GetDC(HWND_DESKTOP);
Step=1;
MSG Msg;
DWORD StartTick;
//个延时循环
for(;Step<200;Step)
{
StartTick = GetTickCount;
/*这段为线程交出部分运行时间以让系统处理其他事务*/
for(;GetTickCount-StartTick<10;)
{
(PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE) )
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
/*把运行情况打印到桌面这是vcbear调试时最喜欢干事情*/
sprf(Buffer,"Running %04d",Step);
(hDC!=NULL)
TextOut(hDC,30,50,Buffer,strlen(Buffer));
}
/*延时段时间后回调*/
(*gCallback)(Step,1);
/*结束*/
::ReleaseDC (HWND_DESKTOP,hDC);
0;
}
5:万事具备
使用VC和Delphi各建立了个工程编写回调实现部分
VC版
WINAPI CBFunc( Param1, Param2)
{
res= Param1+Param2;
TCHAR Buffer[256]="";
sprf(Buffer,"callback result = %d",res);
MessageBox(NULL,Buffer,"Testing",MB_OK); //演示回调被
res;
}
Delphi版
function CBFunc(Param1,Param2:eger):eger;
begin
result:= Param1+Param2;
TForm1.Edit1.Text:=tostr(result); / /演示回调被
end;
使用静态连接思路方法连接DLL里出口 TestCallBack,在工程里添加 Button( 对于Delphi工程还需要在Form1上放个EditControl控件默认名为Edit1)
响应ButtonClick事件 TestCallBack
TestCallBack(CBFunc) //参数CBFunc为回调地址
创建线程后立刻返回应用可以同时干别事情去了现在可以看到屏幕上不停显示串表示dll里创建线程运行正常会的后线程延时部分结束结束vc应用弹出MessageBox,表示回调被并显示根据Param1Param2运算结果DelphieditControl控件里文本则被改写成Param1Param2 运算结果
可见使用回调编程模式可以根据区别需求传递区别回调地址或者定义各种回调原形(同时也需要改变使用回调参数和返回值约定)实现多种回调事件处理可以使控制灵活多变也是种高效率清晰模块的间耦合方式在些异步或复杂系统里尤其有用——你可以在个模块(如DLL)里专心实现模块核心业务流程和技术功能外围扩展功能只给出个回调接口通过其他模块传递过来回调地址方式将后续处理无缝地交给另个模块随它按自定义方式处理
本文例子使用了在DLL里多线程延时后回调方式只是为了突出下回调效果其实只要是在本进程的内都可以随你高兴可以把地址传递来传递去当成回调使用
这样编程模式原理非常简单单:就是把也看成个指针个地址来没有什么别复杂东西仅仅是编程里个小窍门技巧至于回调模式究竟能为你带来多少好处就看你是否使用如何使用这种编程模式了
msdn上这么说:
有关指针知识
使用例子可以很好地介绍说明指针使用方法首先看看 Win32 API 中 EnumWindows :
Declare Function EnumWindows lib "user32" _
(ByVal lpEnumFunc as Long, _
ByVal lParam as Long ) As Long
EnumWindows 是个枚举它能够列出系统中每个打开窗口句柄EnumWindows 工作方式是重复地传递给它第个参数(lpEnumFunc指针)每当 EnumWindows EnumWindows 都传递个打开窗口句柄
在代码中 EnumWindows 时可以将个自定义作为第个参数传递给它用来处理系列值例如可以编写个将所有值添加到个列表框中将 hWnd 值转换为窗口名字以及其它任何操作!
为了表明传递参数是个自定义在名称前面要加上 AddressOf 关键字第 2个参数可以是合适任何值例如如果要把 MyProc 作为参数可以按下面方式 EnumWindows:
x = EnumWindows(AddressOf MyProc, 5)
在过程时指定自定义被称为回调回调(通常简称为“回调”)能够对过程提供数据执行指定操作
回调参数集必须具有规定形式这是由使用回调 API 决定有关需要什么参数如何它们请参阅 API 文档
我谈下自己对回调点理解不对地方请指教
我刚开始接触回调时也是团雾水很多人解释这个问题时总是拿API来举例子本来菜鸟最惧怕就是API^_^. 回调跟API没有必然联系
其实回调就是种利用指针进行过程
为什么要用回调呢?比如我要写个子模块给你用来接收远程发来命令当我接收到命令后需要你主模块来进行相应处理但是我不知道你要用哪个来处理这个命令我也不知道你主模块是什么.cpp或者.h或者说我根本不用关心你在主模块里如何处理它也不应该关心用什么处理它……如何办?使用回调
我在我模块里先定义回调类型以及回调指针
typedef void (CALLBACK *cbkSendCmdToMain) (AnsiString sCmd);
cbkSendCmdToMain SendCmdToMain;
这样SendCmdToMain就是个指向拥有个AnsiString形参返回值为void指针
这样在我接收到命令时就可以这个啦
...
SendCmdToMain(sCommand);
...
但是这样还不够我得给个接口(比如Init)让你在主模块里Init来注册这个回调
在你主模块里可能这样
void CALLBACK YourSendCmdFun(AnsiString sCmd); //声明
...
void CALLBACK YourSendCmdFun(AnsiString sCmd); //定义
{
ShowMessage(sCmd);
}
...
Init向我模块注册回调可能这样:
Init(YourSendCmdFun, ...);
这样预期目就达到了
需要注意点回调般都要声明为全局如果要在类里使用回调前面需要加上其实也相当于全局
最新评论