When writing programs, you always encounter problems with the communication between the form (TForm) and the thread (TThread) message. What is annoying is that the form cannot send messages to the thread (TThread) (the thread does not have a window handle). After several days of hard work, I came up with two solutions and took them out to discuss with you.
First. We know that the MFC class library in VC++ has encapsulated message processing (BEGINMESSAGE, ENDMESSAGE). In MFC, the processing of messages is to save the address of a method (function) or procedure (PRocedure) by establishing a message mapping table. Into the mapping table (message processing is essentially a call to a method or process), plus a message distribution mechanism to realize the reception and transmission of messages <see VC++ Technical Insider> for details. So we just need to establish a message mapping table for the thread and establish a corresponding message distribution mechanism. This allows you to process messages sent by the form to the thread. The following code is a class that implements message mapping table and message distribution (see <../Message Processing Design (Thread) 1/MessageHandle.pas> for details)
unit MessageHandle;
interface
uses messages, Classes, SysUtils, Dialogs;
const PMSG_BASE = $BE00; //Custom message base address;
PMSG_NUM = 200; //Message table size;
{**Custom message processing class
*;Function = Create a custom message table and process between threads
* and custom messages with the main form (macro)
*}
//Message processing handle
TMessageHandle = procedure(var Message: TMessage) of Object;
TPDispatcher = class(TObject)
Private
//Message corresponding table (Message ID is an array subscript);
MessageHandles: array of TMessageHandle;
//Get the array ID from the message ID
function GetIndexFromMsgID(const aMessageID: cardinal): Integer;
public
constructor Create;
destructor Destroy;
//Send a message
procedure SendMessage(var Message: TMessage); overload;
//Add a custom message to the message corresponding table;
procedure AddHandle(const aMessageID: cardinal; aMessageHandle: TMessageHandle);
end;
//
Implementation
{ TPDispatcher }
constructor TPDispatcher.Create;
var i: Integer;
Begin
SetLength(MessageHandles,PMSG_NUM); //Message correspondence table for 200 messages
//Initialize the message queue;
for i := 0 to Pred(PMSG_NUM) do
MessageHandles[i] := nil;
end;
destructor TPDispatcher.Destroy;
Begin
{Release message correspondence table}
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;
Now we only need to register a custom message and then use the message distribution class (TPDispatcher) to process thread messages. The code is as follows <see../Message Processing Design (Thread) 1/test/unit1.pas> for details:
Unit unit1
const
{Custom long-term thread message}
MY_MESSAGE2 = PMSG_BASE + 02;
type
TForm1 = class(TForm)
AddMsgList: TButton;
SendThehead: TButton;
sendForm: TButton;
sendOther: TButton;
procedure SendTheadClick(Sender: TObject); //Send message
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
Private
Fdispatcher: TPDispatcher; message mapping table class
Fhandle: TPHandler;
FThread: TPTHread; custom thread class
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
{Create a message map table class}
Fdispatcher := TPDispatcher.Create;
Fhandle := TPHandler.Create;
{create thread}
FThread := TPThread.Create(false);
{Add message to the mapping table}
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;
second. A window can handle messages because it has a window handle. In order for the thread to handle messages, we can add a window handle to the thread of the corresponding window class. (Source code is in <../Message Processing Design (Thread) 2 / pThread.pas >)
unit pThread;
interface
uses classes, sysutils, Windows, Messages, Dialogs;
const MY_MESSAGE1 = $BD00 + 01;
Type
{** Message processing thread class
*;Function = Add thread processing capability,
*}
TPMsgThread = class(TThread)
Private
//Window handle
FWndHandle: HWND;
//Window data information
FWndClass: WNDCLASS;
// Pointer to window callback function
FObjectInstance: Pointer;
//Initialize window data
procedure InitWnd;
//Create a hidden window
procedure CreateWnd;
//Register hidden window
procedure RegistWnd;
procedure DestroyWnd;
//Window callback function
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);
//Replace window callback function
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;