超级难的问题,WndProc()是操作系统调用的函数,但回调机制是如何形成的?

XiaoRong2sxh 2003-09-29 07:15:52
众所周知,win32的回调函数WndProc()是操作系统调用的函数,win32用回调来处理消息循环!,
但回调机制是如何形成的?如何模拟这样的过程??

比如我如何写第一个模块(用来模拟操作系统),而写另第二个程序(来模拟用户程序),而第一个模块在某种事件(消息)下,会调用第二个程序的函数,从而模拟了,操作系统因处理消息而调用wndproc的过程!!
...全文
805 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
red-fly 2004-01-31
  • 打赏
  • 举报
回复
Very Good!
dawndu 2003-09-30
  • 打赏
  • 举报
回复
不就是一个函数指针嘛!
会思考的草 2003-09-30
  • 打赏
  • 举报
回复
2.对于消息的处理,Windows中其实有两个队列用于此目的,它们分别是系统队列和消息队列。每个要处理消息的窗口都有自己的一个消息队列。在系统队列中的事件仅仅是硬件事件的记录,GetMessage/PeekMessage会从这些事件中创建消息,并确定哪一个窗体将接收这个消息。
3.鼠标单击事件的过程:
首先,将计算该事件屏幕坐标的相应窗体。此计算(调用窗体点击测试)以桌面窗体开始,从头至尾的扫描细统中的每一个窗体(包括子窗体),直到找到一个包含这个鼠标坐标点的窗体,并且这个窗体没有任何同样包含这个坐标点的子窗体。
如果一个窗体使用SetCapture捕获鼠标,那么“系统队列扫描”代码将通过普通的点击测试,并将所有的鼠标消息返回到捕获的窗体。
如果这个被处理的事件是一个“鼠标键按下”事件(任何一个鼠标键),代码会检测这个事件是否会转化为双击事件。如果在两次鼠标键按下事件中,时间和距离的增量在允许的范围之中,该事件将会生成一个双击消息,否则它将生成一个标准的“按下”事件。所有的鼠标事件都将生成标准的鼠标消息,而双击测试只在鼠标事件指定的,包含CS_DBLCLKS类型的窗体中进行。
一个消息从鼠标事件中构造出来。
如果鼠标点击测试确定该事件发生在一个窗体的非客户区,如边框或标题栏,那么该构造出的消息映射到它相应的非客户区消息中。例如:一个WM_MOUSEMOVE事件会被映谢为WM_NCMOUSEMOVE消息。
与所有指定的消息过滤器进行对照,核查此消息,如果该消息不匹配过滤器,则重新从头开始“系统队列扫描”代码,查看队列中的下一个消息。
如果鼠标消息需要前往与当前任务不同的另一个任务的相关窗体,事件会被留在系统队列中,并且如果那个将会处理这个消息的任务在休眠之中,会被唤醒。这个新近被唤醒的任务不会在此刻立即运行,只会标记为准备运行,然后插入就绪队列中等待系统调度。如果消息前往了其它任务,并且在系统队列中没有要处理的事件被发现,“系统队列扫描”会代码返回到GetMessage/PeekMessage主代码。
如果安装了鼠标钩子,它将在此刻被调用。如果鼠标钩子返回了一个非零值,那么该鼠标事件被忽略,并从系统队列中被删除,然后重新从头开始“系统队列扫描”代码。如果钩子返回零,则继续处理。
如果消息是一个“鼠标键按下”消息,“系统队列扫描”则会在返回此消息之前,按照下面的方法激活窗体:沿着父链一直向上寻找该窗体的“最终顶层父窗体”,直到相遇,然后用SendMessage向该窗体的“最终顶层父窗体”发送一个WM_MOUSEACTIVATE消息。检查WM_MOUSEACTVATE的返回值,如果返回的值为空、MA_ACTIVATE或者MA_ACTIVATEANDEAT,ActivateWindow函数将被调用去激活那个“最终顶层父窗体”;如果返回的值是MA_NOACTIVATE或者MA_NOACTIVATEANDEAT,窗体则不被激活(注意:MA_ACTIVATEANDEAT和MA_NOACTIVATEANDEAT会导致“鼠标键按下”事件从系统队列中被删除,而不会生成一个鼠标按下消息);如果都不是以上的值,则一个WM_SETCURSOR消息被发送到窗体,充许窗体设置指针的轮廓。
如果鼠标钩子被调用,并且当前的鼠标事件从系统队列中被删除了,则检查“基于计算机训练”(CBT)的钩子。如果安装有有一个CBT钩子,将会携带HCBT_CLICKSKIPPED钩子码代调用它。
按键状态表包含了三个用于跟踪鼠标按键状态的入口。这些按键被分配予虚拟键代码(VK_LBUTTON,VK_RUTTON和VC_MBUTTON),它们和GetKeyState一起始用去确事实上鼠标键是弹起还是按下。在返回鼠标消息之前,“系统队列扫描”代码会(为弹起或按下消息)设置按键状态表并且从系统队列中删除消息。如果PeekMessage被调用时携带PM_NOREMOVE,则按键状态表不会被修改。
会思考的草 2003-09-30
  • 打赏
  • 举报
回复
1.确切讲,__stdcall不是ANSI C的关键字,它是Windows扩充的,(教材上讲的都是ANSI C,以DOS为平台的,所以你看不到这些奇怪的符号),这里面牵涉到一个调用规则和堆栈管理的问题。我们知道,在发生函数调用的时候,函数的参数会先压栈(别告诉我你不知道什么叫做“栈”——什么?你不知道?那还是回去拿本数据结构看看再来吧)。一直以来,存在两种压栈规则:PASCAL和C规则,C规则是,参数按照从右到左的顺序依次入栈,函数返回时,由调用者负责清理栈;PASCAL约定和C约定正好相反,它规定参数是从左向右传递,由被调用者恢复堆栈。这两种约定在Win16中都存在的,所以有时候还能看到PASCAL的修饰符。举个例子:
假如有函数:foo(int arg1,int arg2,int arg3),
用C规则的话就是:
push [arg1]
push [arg2]
push [arg3]
call foo
add esp, 3 * 4 ;调用者自己恢复堆栈指针
但是在Win32中的__stdcall和两者都不完全相同,它采用的是C的压栈顺序(即从右到左),但是由被调用者清理栈。这就有个好处,即,在编译的时候,被调用函数中无须生成清理栈的代码,所以编译产生的目标代码小一些。Win32只用__stdcall约定,它是Windows API函数中默认的调用约定,VB、VFP等也采用这个约定,但除了一个特例,即wsprintf。
但是C调用规则(__cdecl)还是相当有用的,譬如我们都知道,printf函数的参数是可变的,这个时候用__stdcall规则显然不可能,因为被调用的函数怎么能事先知道有多少个参数呢?所以这个时候采用C规则就方便得多。
除了__stdcall和__cdecl,还有几种规则:
__fastcall调用约定是“人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。
__thiscall仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。
说到现在,其实这些修饰符就是编译器开关,指定编译器如何产生代码,相当于汇编中的伪指令它们可以直接加在要输出的函数前,也可以在编译环境的Setting...\C/C++ \Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。
XiaoRong2sxh 2003-09-29
  • 打赏
  • 举报
回复
up
XiaoRong2sxh 2003-09-29
  • 打赏
  • 举报
回复
to dawndu(东南飞) ( )

你这篇文章也是,回调函数的用法,
在这段函数里,
DoSomething;
WndProc(lpMsg->message,lpMsg->wParam,lpMsg->lParam);//我们的回调
DoSomething;


这位兄弟,说得有点道理,,,
XiaoRong2sxh 2003-09-29
  • 打赏
  • 举报
回复
to windows_editor(色即是空)

这篇文章并不是我想要的答案啊,,
这篇文章是,是如何,使用回调函数,,

我需要的是回调函数的具体实现是什么,比如不用CALLBACK标识符,如何定义自己的回调函数??如何写第一个模块(用来模拟操作系统),而写另第二个程序(来模拟用户程序),而第一个模块在某种事件(消息)下,会调用第二个程序的函数,从而模拟了,操作系统因处理消息而调用wndproc的过程!!









dawndu 2003-09-29
  • 打赏
  • 举报
回复
我也不知道微软是怎么搞的,给个例子看看,博大家一笑也是好的:
用户程序中间核心是一个消息泵,什么消息执行什么动作,这个动作就是WndProc我们定义的
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

DispatchMessage可能就是这样的
OS:

void DispatchMessage(LPMSG lpMsg)
{
DoSomething;
WndProc(lpMsg->message,lpMsg->wParam,lpMsg->lParam);//我们的回调
 DoSomething;


}
我现在也常常用这样的方法来定义自己的系统,这个方法可以用来写插件!

windows_editor 2003-09-29
  • 打赏
  • 举报
回复
回调函数是一个很有用,也很重要的概念。当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。回调函数在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里的多线程延时后调用回调函数的方式,只是为了突出一下回调函数的效果,其实只要是在本进程之内,都可以随你高兴可以把函数地址传递来传递去,当成回调函数使用。

 

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




资料整理:编程先锋 http://wlbookwl.myrice.com 站长:小黑侠
XiaoRong2sxh 2003-09-29
  • 打赏
  • 举报
回复
自己先顶
XiaoRong2sxh 2003-09-29
  • 打赏
  • 举报
回复
up

16,466

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧