西西軟件園多重安全檢測(cè)下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁(yè)編程開(kāi)發(fā)Delphi → Delphi消息處理深入研究Win32的消息處理機(jī)制

Delphi消息處理深入研究Win32的消息處理機(jī)制

前往專題相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來(lái)源:西西整理時(shí)間:2012/8/20 17:01:40字體大。A-A+

作者:佚名點(diǎn)擊:20次評(píng)論:1次標(biāo)簽: Delphi

消息是Windows發(fā)出的一個(gè)通知,它告訴應(yīng)用程序某個(gè)事件發(fā)生了。在Delphi中,大多數(shù)情況下Windows的消息被封裝在VCL的事件中,我們只需處理相應(yīng)的VCL事件就可以了,但如果我們需要編寫自己的控件、截獲或過(guò)濾消息就必須深入研究Win32的消息處理機(jī)制。
Delphi中消息以TMessage記錄的方式定義。打開(kāi)Message.pas文件,我們可以看到Tmessage是這樣定義的:

type
TMessage = packed record
Msg: Cardinal;
case Integer of
0: ( WParam: Longint;
LParam: Longint;
Result: Longint);

1: ( WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
end;

其中,Msg是區(qū)別于其他消息的常量值,這些常量值可以是Windows單元中預(yù)定義的常量,也可以是用戶自己定義的常量。Wparam通常是一個(gè)與消息有關(guān)的常量值,也可以是窗口或控件的句柄。LParam通常是一個(gè)指向內(nèi)存中數(shù)據(jù)的指針。
Result是消息處理的返回值。Wparam、Lparam和Result都是32位的,如果想訪問(wèn)其中的低16位或高16位可以分別使用WparamLo、WparamHi、 LParamLo、LparamHi、ResultLo和ResultHi。
在Delphi中除了通用的Tmessage外,還為每個(gè)Windows定義了一個(gè)特殊的消息記錄。我們可以瀏覽Message.pas文件,下面是鍵盤的消息記錄:

TWMKey = packed record
Msg: Cardinal;
CharCode: Word;
Unused: Word;
KeyData: Longint;
Result: Longint;

與鍵盤相關(guān)的消息如:WM_KEYDOWN、 WM_KEYUP、 WM_CHAR、 WM_SYSKEYDOWN WM_SYSKEYUP、 WM_SYSCHAR的記錄也被定義為TWMkey。在Message.pas文件中有以下聲明:

TWMChar=TWMkey;
TWMKeyDown= TWMkey;
TWMKeyUp=TWMkey;
TWMSys -KeyDown=TWMkey;
TWMSysKeyUp= TWMkey;
TWMSysChar=TWMkey;

消息的發(fā)送
消息處理就是定義應(yīng)用程序如何響應(yīng)Windows的消息。在Delphi中每一個(gè)消息都有自己的處理過(guò)程,它必須是一個(gè)對(duì)象中的方法,且只能傳遞一個(gè)Tmessage或其他特殊的消息記錄,方法聲明后要有一個(gè)message命令,后接一個(gè)在0到32767之間的常量。
前面我們提到的消息都是標(biāo)準(zhǔn)的Windows消息(WM_X),除此之外還有VCL內(nèi)部消息、通知消息和用戶自定義消息。
VCL內(nèi)部消息通常以“CM_”開(kāi)頭,用于管理VCL內(nèi)部的事物。如果改變了某個(gè)屬性值或組件的其他一些特性后,需要通過(guò)內(nèi)部消息將該變化通知其他組件。例如,激活輸入焦點(diǎn)消息是向被激活的或被停用的組件發(fā)送的,用于接受或放棄輸入焦點(diǎn)。
另外還有通知消息,一個(gè)窗口內(nèi)的子控件發(fā)生了一些事情,需要通知父窗口,這是通過(guò)通知消息實(shí)現(xiàn)的。它只適用于標(biāo)準(zhǔn)的窗口控件,如按鈕、列表框、編輯框等等。打開(kāi)Message.pas文件,在標(biāo)準(zhǔn)的Windows后就是通知消息的聲明:

const
{$EXTERNALSYM BN_CLICKED}
BN_CLICKED = 0;
{$EXTERNALSYM BN_PAINT}
BN_PAINT = 1;
{$EXTERNALSYM BN_HILITE}
BN_HILITE = 2;

以上是按鈕的通知消息,分別表示用戶單擊了按鈕、按鈕應(yīng)當(dāng)重畫、用戶加亮了按鈕。
用戶也可以自己定義消息、給自己發(fā)送消息和編寫消息處理過(guò)程。消息的常量值為WM_USER+100到FFF, 這個(gè)范圍是Windows為用戶自定義消息保留的。
Delphi消息的發(fā)送有三種方法:
1.Tcontrol類的Perform對(duì)象方法?梢韵蛉魏我粋(gè)窗體或控件發(fā)送消息,只需要知道窗體或控件的實(shí)例。其聲明如下: function Tcontrol.Perform(Msg:Cardinal;Wparam,Lparam:Longint):Longint
2.Windows的API函數(shù)SendMessage()和Postmessage()。其聲明如下:
function SendMessage(hWnd: HWND; Msg: UINT;wParam:WPARAM; lParam: LPARAM):LRESULT;stdcall;
function PostMessage(hWnd: HWND; Msg: UINT;wParam: WPARAM; lParam:LPARAM):LRESULT;stdcall
PostMessage函數(shù)將消息添加到應(yīng)用程序的消息隊(duì)列中去。應(yīng)用程序的消息循環(huán)會(huì)從消息隊(duì)列中提取登記的該消息,再發(fā)送到相應(yīng)的窗口中。
SendMessage函數(shù)可以越過(guò)消息隊(duì)列直接向窗口過(guò)程發(fā)送。所以當(dāng)Windows需要立刻返回值時(shí)使用SendMessage,當(dāng)需要不同的應(yīng)用程序依次處理消息時(shí)使用PostMessage。而Perform從本質(zhì)上和SendMessage相似,它們直接向窗口過(guò)程發(fā)送。SendMessage、Postmessage函數(shù)只需要知道窗口的句柄就可以發(fā)送消息,所以它們可以向非Delphi窗體發(fā)送一條消息,但而Perform必須知道窗體或控件的實(shí)例。

Delphi的消息處理流程

TApplication.OnMessage只在應(yīng)用程序的消息隊(duì)列接收到一個(gè)消息時(shí)才被觸發(fā)。一般應(yīng)用程序接收到的消息是與窗口管理有關(guān)的消息(例如WM_PAINT和WM_SIZE),或由PostMessage()、PostAppMessage()或BroadcastSystemMessage()等API函數(shù)發(fā)送出的消息。但是,由于Windows或SendMessage()有可能會(huì)繞過(guò)消息隊(duì)列直接將消息發(fā)送給窗口過(guò)程。當(dāng)發(fā)生這種情況時(shí),TApplication.OnMessage就不會(huì)被觸發(fā)。

VCL的消息系統(tǒng):
VCL定義了消息分發(fā)系統(tǒng),該系統(tǒng)將所有的Windows消息傳給相應(yīng)的對(duì)象,由各對(duì)象的消息分發(fā)系統(tǒng)進(jìn)行處理。
VCL對(duì)象用于接受消息的方法叫做MainWndProc()。通過(guò)MainWndPorc()可以對(duì)消息進(jìn)行任何的處理。不過(guò),一般情況下很少直接調(diào)用MainWndProc()來(lái)處理消息,除非不想讓消息通過(guò)VCL的消息系統(tǒng)分發(fā)。
從MainWndProc()反回后,消息被傳遞給對(duì)象的WndProc()的方法,這就是該對(duì)象的窗體
過(guò)程,然后進(jìn)入VCL的分發(fā)機(jī)構(gòu)。分發(fā)機(jī)構(gòu)使用Disptch()方法把消息分發(fā)給一個(gè)消息句柄。
消息到達(dá)該消息的處理句柄(Handler)后,經(jīng)過(guò)該句柄的處理,這個(gè)消息處理過(guò)程就結(jié)束
了。事件--MainWndProc---WndProc----Dispatch----Handle


在平時(shí)寫程序時(shí),總是碰到窗體(TForm)與線程(TThread)消息通信問(wèn)題。令人煩惱的是窗體不能向線程(TThread)發(fā)送消息(線程沒(méi)有窗口句柄)。經(jīng)過(guò)幾天的折騰,想出二種解決方案,拿出來(lái)跟大家探討探討。
第一。我們知道VC++ 中的MFC類庫(kù)是自已封裝了消息處理(BEGINMESSAGE, ENDMESSAGE),在MFC中對(duì)消息的處理是通過(guò)建立一張消息映射表,而把方法(function)或過(guò)程(procedure)的地址保存到映射表里(消息處理實(shí)質(zhì)上是方法或過(guò)程的調(diào)用),再加上一個(gè)消息分發(fā)機(jī)制,來(lái)實(shí)現(xiàn)消息的接收發(fā)送 <詳見(jiàn)VC++技術(shù)內(nèi)幕>。所以我們只要為線程里建立一張消息映射表,并建立相應(yīng)的消息分發(fā)機(jī)制。這樣就可以處理窗體發(fā)送到線程的消息。以下代碼是實(shí)現(xiàn)消息映射表和消息分發(fā)的類(詳見(jiàn) <..消息處理設(shè)計(jì)(線程)1MessageHandle.pas> 中 )

unit MessageHandle;
interface
uses messages,Classes,SysUtils,Dialogs;
const PMSG_BASE = $BE00; //自定義消息基址;
PMSG_NUM = 200; //消息表大;
{**自定義消息處理類
*;功能 = 建立自定義消息表,處理線程之間
* 以及與主窗體之間的自定義消息(宏觀)
*}
//消息處理句柄
TMessageHandle = procedure(var Message: TMessage) of Object;
TPDispatcher = class(TObject)
private
//消息對(duì)應(yīng)表(消息ID為數(shù)組下標(biāo));
MessageHandles: array of TMessageHandle;
//從消息ID得到數(shù)組ID
function GetIndexFromMsgID(const aMessageID: cardinal): Integer;
public
constructor Create;
destructor Destroy;
//發(fā)送消息
procedure SendMessage(var Message: TMessage); overload;
//添加自定義消息到消息對(duì)應(yīng)表;
procedure AddHandle(const aMessageID: cardinal; aMessageHandle: TMessageHandle);
end;
//
implementation
{ TPDispatcher }
constructor TPDispatcher.Create;
var i: Integer;
begin
SetLength(MessageHandles,PMSG_NUM); //200個(gè)消息的消息對(duì)應(yīng)表
//初始化消息隊(duì)列;
for i := 0 to Pred(PMSG_NUM) do
MessageHandles[i] := nil;
end;
destructor TPDispatcher.Destroy;
begin
{釋放消息對(duì)應(yīng)表}
FreeAndNil(MessageHandles);
end;
procedure TPDispatcher.AddHandle(const aMessageID: cardinal;
aMessageHandle: TMessageHandle);
var tID: Integer;
begin
tID := GetIndexFromMsgID(aMessageID);
Assert((tID > 0) or (tID < Pred(PMSG_NUM)) );
Assert(Assigned(aMessageHandle));
MessageHandles[tID] := aMessageHandle;
end;
function TPDispatcher.GetIndexFromMsgID(const aMessageID: cardinal): Integer;
begin
Result := aMessageID - PMSG_BASE;
end;
procedure TPDispatcher.SendMessage(var Message: TMessage);
var tID: Integer;
tMsgHandle: TMessageHandle;
begin
tID := GetIndexFromMsgID(Message.Msg);
Assert((tID > 0) or (tID < Pred(PMSG_NUM)));
tMsgHandle := MessageHandles[tID];
if Assigned(tMsgHandle) then
tMsgHandle(Message);
end;

現(xiàn)在我們只需要注冊(cè)一下自定義的消息,然后通過(guò)消息分發(fā)類(TPDispatcher),實(shí)現(xiàn)對(duì)線程消息的處理。代碼如下<詳見(jiàn)..消息處理設(shè)計(jì)(線程)1testunit1.pas>:

Unit unit1
const
{自定久線程消息}
MY_MESSAGE2 = PMSG_BASE + 02;
type
TForm1 = class(TForm)
AddMsgList: TButton;
SendThead: TButton;
sendForm: TButton;
sendOther: TButton;
procedure SendTheadClick(Sender: TObject); //發(fā)送消息
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
Fdispatcher: TPDispatcher; 消息映射表類
Fhandle: TPHandler;
FThread: TPTHread; 自定義線程類
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.SendTheadClick(Sender: TObject);
var aMessage: TMessage;
begin
aMessage.Msg := MY_MESSAGE2;
aMessage.WParam := 1;
Fdispatcher.SendMessage(aMessage);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
{創(chuàng)建消息映射表類}
Fdispatcher := TPDispatcher.Create;
Fhandle := TPHandler.Create;
{創(chuàng)建線程}
FThread := TPThread.Create(false);
{向映射表中增加消息}
Fdispatcher.AddHandle(MY_MESSAGE2,FThread.DoMessage);
end;
procedure TForm1.FormDestroy(Sender: TObject);
var i: Integer;
begin
FreeAndNil(Fdispatcher);
FreeAndNil(Fhandle);
for i:= 0 to 3 do
FreeAndNil(FThread[i]);
end;

第二。窗口可以處理消息是因?yàn)樗写翱诰浔。為了使線程也能處理消息,我們可以通過(guò)為線程加上一個(gè)相應(yīng)窗口類的窗口名柄。(源碼在 <..消息處理設(shè)計(jì)(線程)2 pThread.pas >中)

unit pThread;
interface
uses classes,sysutils,Windows,Messages,Dialogs;
const MY_MESSAGE1 = $BD00 + 01;
Type
{** 消息處理線程類
*;功能 = 添加線程處理消息能力,
*}
TPMsgThread = class(TThread)
private
//窗口句柄
FWndHandle: HWND;
//窗口數(shù)據(jù)信息
FWndClass: WNDCLASS;
//指向窗口回調(diào)函數(shù)的指針
FObjectInstance: Pointer;
//初始化窗口數(shù)據(jù)
procedure InitWnd;
//創(chuàng)建隱藏窗口
procedure CreateWnd;
//注冊(cè)隱藏窗口
procedure RegistWnd;
procedure DestroyWnd;
//窗口回調(diào)函數(shù)
procedure pWndProc(var Message: TMessage); virtual;
protected
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(CreateSuspended: Boolean); virtual;
property WndHandle: HWND read FWndHandle write FWndHandle;
end;

implementation
const WND_NAME = 'PY20';
{ TPMsgThread }
constructor TPMsgThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FWndHandle := Integer(nil);
InitWnd;
RegistWnd;
CreateWnd;
end;

procedure TPMsgThread.CreateWnd;
begin
if(WndHandle = Integer(nil)) then
WndHandle := CreateWindow(FWndClass.lpszClassName, FWndClass.lpszClassName,
WS_POPUP or WS_CAPTION or WS_CLIPSIBLINGS or WS_SYSMENU
or WS_MINIMIZEBOX,
GetSystemMetrics(SM_CXSCREEN) div 2,
GetSystemMetrics(SM_CYSCREEN) div 2,
0, 0, 0, 0, FWndClass.hInstance, nil);
//置換窗口回調(diào)函數(shù)
SetWindowLong(WndHandle, GWL_WNDPROC, Longint(FObjectInstance));
end;
procedure TPMsgThread.DestroyWnd;
begin
UnregisterClass(FWndClass.lpszClassName,FWndClass.hInstance);
DestroyWindow(WndHandle);
end;
procedure TPMsgThread.DoTerminate;
begin
inherited;
DestroyWnd;
end;
procedure TPMsgThread.Execute;
begin
end;
procedure TPMsgThread.InitWnd;
begin
FwndClass.lpszClassName := PChar(WND_NAME);
FWndClass.hInstance := Handle;
FWndClass.lpfnWndProc := @DefWindowProc;
end;
procedure TPMsgThread.pWndProc(var Message: TMessage);
begin
end;procedure TPMsgThread.RegistWnd;
begin
FObjectInstance := Classes.MakeObjectInstance(pWndProc);
if(FWndClass.hInstance <> Integer(nil)) then
RegisterClass(FWndClass);
end;

    相關(guān)評(píng)論

    閱讀本文后您有什么感想? 已有人給出評(píng)價(jià)!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過(guò)難過(guò)
    • 5 囧
    • 3 圍觀圍觀
    • 2 無(wú)聊無(wú)聊

    熱門評(píng)論

    最新評(píng)論

    發(fā)表評(píng)論 查看所有評(píng)論(1)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字?jǐn)?shù): 0/500 (您的評(píng)論需要經(jīng)過(guò)審核才能顯示)