由VCL代码理解VCL的消息机制文档格式.docx

上传人:b****5 文档编号:17110158 上传时间:2022-11-28 格式:DOCX 页数:26 大小:25.85KB
下载 相关 举报
由VCL代码理解VCL的消息机制文档格式.docx_第1页
第1页 / 共26页
由VCL代码理解VCL的消息机制文档格式.docx_第2页
第2页 / 共26页
由VCL代码理解VCL的消息机制文档格式.docx_第3页
第3页 / 共26页
由VCL代码理解VCL的消息机制文档格式.docx_第4页
第4页 / 共26页
由VCL代码理解VCL的消息机制文档格式.docx_第5页
第5页 / 共26页
点击查看更多>>
下载资源
资源描述

由VCL代码理解VCL的消息机制文档格式.docx

《由VCL代码理解VCL的消息机制文档格式.docx》由会员分享,可在线阅读,更多相关《由VCL代码理解VCL的消息机制文档格式.docx(26页珍藏版)》请在冰豆网上搜索。

由VCL代码理解VCL的消息机制文档格式.docx

end;

end

else

FTerminate:

然后程序中的各个VCL对象又是如何接收到Windows消息的呢?

这还要从窗体的创建开始!

首先找到TWinControl.CreateWnd中的

Windows.RegisterClass(WindowClass)//调用RegisterClass注册一个窗体类

向上看

WindowClass.lpfnWndProc:

=@InitWndProc;

//这里指定了窗口的消息处理函数的指针为@InitWndProc!

再找到functionInitWndProc(HWindow:

HWnd;

Message,WParam,LParam:

Longint):

Longint;

发现了

CreationControl.FHandle:

=HWindow;

SetWindowLong(HWindow,GWL_WNDPROC,Longint(CreationControl.FObjectInstance));

没有?

原来InitWndProc初次被调用时候,又使用API函数SetWindowLong指定处理消息的窗口过程为FObjectInstance。

回到TWinControl.Create

FObjectInstance:

=Classes.MakeObjectInstance(MainWndProc);

找到关键所在了,也许有些朋友对MakeObjectInstance这个函数很熟了,它的作用就是将一个成员过程转换为标准过程。

绕了个圈子?

为什么呢?

很简单,因为窗体成员过程包括一隐含参数传递Self指针,所以需要转化为标准过程。

const

InstanceCount=313;

//这个不难理解吧?

314*13+10=4092,再大的话,记录TInstanceBlock的大小就超过了下面定义的PageSize

type

PObjectInstance=^TObjectInstance;

TObjectInstance=packedrecord

Code:

Byte;

Offset:

Integer;

caseIntegerof

0:

(Next:

PObjectInstance);

1:

(Method:

TWndMethod);

PInstanceBlock=^TInstanceBlock;

TInstanceBlock=packedrecord

Next:

PInstanceBlock;

array[1..2]ofByte;

WndProcPtr:

Pointer;

Instances:

array[0..InstanceCount]ofTObjectInstance;

InstBlockList:

InstFreeList:

PObjectInstance;

functionStdWndProc(Window:

HWND;

Message,WParam:

LParam:

stdcall;

assembler;

asm

XOREAX,EAX

PUSHEAX

PUSHLParam

PUSHWParam

PUSHMessage

MOVEDX,ESP;

将堆栈中构造的记录TMessage指针赋给EDX

MOVEAX,[ECX].Longint[4];

传递Self指针给EAX,类中的Self指针也就是指向VMT入口地址

CALL[ECX].Pointer;

调用MainWndProc方法

ADDESP,12

POPEAX

functionCalcJmpOffset(Src,Dest:

Pointer):

=Longint(Dest)-(Longint(Src)+5);

functionMakeObjectInstance(Method:

TWndMethod):

BlockCode:

array[1..2]ofByte=(

$59,{POPECX}

$E9);

{JMPStdWndProc}

PageSize=4096;

Block:

Instance:

ifInstFreeList=nilthen

Block:

=VirtualAlloc(nil,PageSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);

//分配虚拟内存,并指定这块内存为可读写并可执行

Block^.Next:

=InstBlockList;

Move(BlockCode,Block^.Code,SizeOf(BlockCode));

Block^.WndProcPtr:

=Pointer(CalcJmpOffset(@Block^.Code[2],@StdWndProc));

Instance:

=@Block^.Instances;

repeat

Instance^.Code:

=$E8;

{CALLNEARPTROffset}

Instance^.Offset:

=CalcJmpOffset(Instance,@Block^.Code);

Instance^.Next:

=InstFreeList;

InstFreeList:

=Instance;

Inc(Longint(Instance),SizeOf(TObjectInstance));

untilLongint(Instance)-Longint(Block)>

=SizeOf(TInstanceBlock);

InstBlockList:

=Block;

=Instance^.Next;

Instance^.Method:

=Method;

(注:

上面出现的那些16进制代码其实就是些16进制的机器代码$59=PopECX$E8=Call$E9=Jmp)

以上代码看起来有点乱,但综合起来看也很好理解!

MakeObjectInstance实际上就是构建了一个Block链表

其结构看看记录TInstanceBlock的结构可知其结构如下:

Next//下一页指针

Code//PopECX和Jmp

WndProcPtr//和StdWndProc间的地址偏移

Instances//接下来是314个Instance链表

Instance链表通过记录TObjectInstance也很好理解其内容

Code//Call

Offset//地址偏移

Method//指向对象方法的指针(结合TMethod很好理解TWndMethod这类对象方法指针指向数据的结构)

好现在来把这个流程回顾一遍,Windows回调的是什么呢?

其实是转到并执行一段动态生成的代码:

先是执行Calloffset,根据偏移量转去执行PopECX,当然由于在Call这之前会将下一条指令入栈,所以这里弹出的就是指向对象方法的指针。

接下来就是执行jmp[StdWndProc],其中将堆栈中构造的记录TMessage指针赋给了EDX,而根据上面的解释结合TMethod去理解,很容易理解

MOVEAX,[ECX].Longint[4];

CALL[ECX].Pointer;

现在终于豁然开朗了,Windows消息就是这样被传递到了TWinControl.MainWndProc,相比MFC中的回调全局函数AfxWndProc来根据窗体句柄检索对应的对象指针的方法效率要高的多!

VCL比MFC优秀的又一佐证!

^_^

现在终于找到了VCL接收消息的方法MainWndProc

procedureTWinControl.MainWndProc(varMessage:

TMessage);

try

WindowProc(Message);

//由于TControl创建实例时已经将FWindowProc指向WndProc,所以这里实际也就是调用WndProc

finally

FreeDeviceContexts;

FreeMemoryContexts;

//调用FreeDeviceContexts和FreeMemoryContexts是为了保证VCL线程安全

except

Application.HandleException(Self);

这里也不能忽略了TWinControl.WndProc

procedureTControl.WndProc(varMessage:

Form:

TCustomForm;

KeyState:

TKeyboardState;

WheelMsg:

TCMMouseWheel;

...

//省略以上的消息相关处理代码,研究某些特定消息时可自行查看

Dispatch(Message);

//调用Dispatch处理

接下来,先不急着查看Dispatch中的相应代码。

想想看,忘了什么?

上面只是继承于TWinControl的有句柄的控件,那继承于TGraphicControl的没有句柄的控件是如何获得并处理消息的?

下面以鼠标消息为例:

TWinControl.WndProc中有下面的代码:

caseMessage.Msgof

...

WM_MOUSEFIRST..WM_MOUSELAST:

//注1:

下面再解释这段

ifIsControlMouseMsg(TWMMouse(Message))then

{CheckHandleAllocatedbecauseIsControlMouseMsgmighthavefreedthe

windowifusercodeexecutedsomethinglikeParent:

=nil.}

if(Message.Result=0)andHandleAllocatedthen

DefWindowProc(Handle,Message.Msg,Message.wParam,Message.lParam);

Exit;

inheritedWndProc(Message);

//执行祖先类的WndProc方法

functionTWinControl.IsControlMouseMsg(varMessage:

TWMMouse):

Control:

TControl;

P:

TPoint;

ifGetCapture=Handlethen

Control:

=nil;

if(CaptureControl<

nil)and(CaptureControl.Parent=Self)then

=CaptureControl;

endelse

=ControlAtPos(SmallPointToPoint(Message.Pos),False);

//这里通过ControlAtPos获得了鼠标所在控件

ifControl<

nilthen

P.X:

=Message.XPos-Control.Left;

P.Y:

=Message.YPos-Control.Top;

Message.Result:

=Control.Perform(Message.Msg,Message.Keys,Longint(PointToSmallPoint(P)));

//调用Perform方法发送消息给对应的实例

propertyWindowProc:

TWndMethodreadFWindowProcwriteFWindowProc;

functionTControl.Perform(Msg:

Cardinal;

WParam,LParam:

Message:

TMessage;

Message.Msg:

=Msg;

Message.WParam:

=WParam;

Message.LParam:

=LParam;

Message.Result:

=0;

ifSelf<

nilthenWindowProc(Message);

=Message.Result;

VCL中就是这样将消息分发给了那些继承于TGraphicControl的没有句柄的图形控件。

上面说的都是Windows消息(WindowsMessages),似乎还应该说说两条经常用到的VCL中自定义消息:

CM_MOUSEENTER,CM_MOUSELEAVE(CM=ShortofControlMessage)

它们是如何被处理的呢?

还是看上面的(ifnotProcessMessage(Msg)thenIdle(Msg);

),这两条不是Windows消息,所以会触发Idle

procedureTApplication.Idle(constMsg:

TMsg);

Done:

=DoMouseIdle;

//调用DoMouseIdle方法

functionTApplication.DoMouseIdle:

CaptureControl:

GetCursorPos(P);

=FindDragTarget(P,True);

//获取当前鼠标所停留在的控件

if(Result<

nil)and(csDesigninginResult.ComponentState)then

CaptureControl:

=GetCaptureControl;

ifFMouseControl<

Resultthen//判断以前记录的鼠标指针所指向的控件和现在所指向的控件是否相同

if((FMouseControl<

nil)and(CaptureControl=nil))or

((CaptureControl<

nil)and(FMouseControl=CaptureControl))then

FMouseControl.Perform(CM_MOUSELEAVE,0,0);

//发送消息CM_MOUSELEAVE给以前记录的鼠标指针所指向的控件

FMouseControl:

=Result;

//记录当前鼠标指针所指向的控件

FMouseControl.Perform(CM_MOUSEENTER,0,0);

//发送消息CM_MOUSEENTER给鼠标指针现在所在的控件

functionFindDragTarget(constPos:

AllowDisabled:

Boolean):

Window:

TWinControl;

Window:

=FindVCLWindow(Pos);

//这里返回的是TWinControl,是一个有句柄的控件

ifWindow<

=Window;

=Window.ControlAtPos(Window.ScreenToClient(Pos),AllowDisabled);

//鼠标所指向处可能还存在一继承于TGraphicControl的图形控件,而上面返回的只是其容器控件

nilthenResult:

=Control;

//如果存在就返回用ControlAtPos所得到的控件

于是又转到了上面的TControl.Perform

现在所有的问题又都集中到了Dispatch的身上,消息是如何触发事件的处理方法的呢?

首先看条消息处理方法的申明:

procedureCMMouseEnter(varMessage:

messageCM_MOUSEENTER;

这实际可以认为是申明了一个动态方法,调用Dispatch实际上就是通过消息号在DMT(动态方法表)中找到相应的动态方法指针,然后执行

//上面已经提到了,寄存器EAX中是类的Self指针,即VMT入口地址,寄存器EDX中是指向记录Message的指针

procedureTObject.Dispatch(varMessage);

PUSHESI

MOVSI,[EDX];

消息号,也就是记录TMessage中Msg的值,对应CM_MOUSEENTER就是$B013(45075)

ORSI,SI

JE@@default

CMPSI,0C000H

JAE@@default

PUSHEAX

MOVEAX,[EAX];

VMT入口地址

CALLGetDynaMethod;

调用GetDynaMethod查找

POPEAX

JE@@default;

在GetDynaMethod中如果找到会将标志位寄存器的值置为0,如果是1,表示未找到,执行跳转

MOVECX,ESI;

传递指针给ECX

POPESI

JMPECX;

跳转到ECX所指向的位置,也就完成了通过消息号调用CMMouseEnter的过程

@@default:

MOVECX,[EAX]

JMPdwordptr[ECX].vmtDefaultHandler;

如果此构件和它的祖先类中都没有对应此消息的处理句柄,调用Defaulthandler方法

procedureGetDynaMethod;

{functionGetDynaMethod(vmt:

TClass;

selector:

Smallint):

}

{->

EAXvmtofclass}

{SIdy

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 城乡园林规划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1