在平時寫程序時,總是碰到窗體(TForm)與線程(TThread)消息通信問題。令人煩惱的是窗體不能向線程(TThread)發送消息(線程沒有窗口句柄)。經過幾天的折騰,想出二種解決方案,拿出來跟大家探討探討。
第一。我們知道VC++ 中的MFC類庫是自已封裝了消息處理(BEGINMESSAGE, ENDMESSAGE),在MFC中對消息的處理是通過建立一張消息映射表,而把方法(function)或過程(PRocedure)的地址保存到映射表裡(消息處理實質上是方法或過程的調用),再加上一個消息分發機制,來實現消息的接收發送<詳見VC++技術內幕>。所以我們只要為線程裡建立一張消息映射表,並建立相應的消息分發機制。這樣就可以處理窗體發送到線程的消息。以下代碼是實現消息映射表和消息分發的類(詳見<../消息處理設計(線程)1/MessageHandle.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
//消息對應表(消息ID為數組下標);
MessageHandles: array of TMessageHandle;
//從消息ID得到數組ID
function GetIndexFromMsgID(const aMessageID: cardinal): Integer;
public
constructor Create;
destructor Destroy;
//發送消息
procedure SendMessage(var Message: TMessage); overload;
//添加自定義消息到消息對應表;
procedure AddHandle(const aMessageID: cardinal; aMessageHandle: TMessageHandle);
end;
//
implementation
{ TPDispatcher }
constructor TPDispatcher.Create;
var i: Integer;
begin
SetLength(MessageHandles,PMSG_NUM); //200個消息的消息對應表
//初始化消息隊列;
for i := 0 to Pred(PMSG_NUM) do
MessageHandles[i] := nil;
end;
destructor TPDispatcher.Destroy;
begin
{釋放消息對應表}
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;
現在我們只需要註冊一下自定義的消息,然後通過消息分發類(TPDispatcher),實現對線程消息的處理。代碼如下<詳見../消息處理設計(線程)1/test/unit1.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); //發送消息
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
{創建消息映射表類}
Fdispatcher := TPDispatcher.Create;
Fhandle := TPHandler.Create;
{創建線程}
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;
第二。窗口可以處理消息是因為它有窗口句柄。為了使線程也能處理消息,我們可以通過為線程加上一個相應窗口類的窗口名柄。 (源碼在<../消息處理設計(線程)2 / pThread.pas >中)
unit pThread;
interface
uses classes,sysutils,Windows,Messages,Dialogs;
const MY_MESSAGE1 = $BD00 + 01;
Type
{** 消息處理線程類
*;功能= 添加線程處理消息能力,
*}
TPMsgThread = class(TThread)
private
//窗口句柄
FWndHandle: HWND;
//窗口數據信息
FWndClass: WNDCLASS;
//指向窗口回調函數的指針
FObjectInstance: Pointer;
//初始化窗口數據
procedure InitWnd;
//創建隱藏窗口
procedure CreateWnd;
//註冊隱藏窗口
procedure RegistWnd;
procedure DestroyWnd;
//窗口回調函數
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);
//置換窗口回調函數
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;