Я пытаюсь (в D7) создать поток с насосом сообщений, который в конечном итоге я хочу перенести в DLL.
Вот соответствующие / нетривиальные части моего кода:
const
WM_Action1 = WM_User + 1;
scThreadClassName = 'MyThreadClass';
type
TThreadCreatorForm = class;
TWndThread = class(TThread)
private
FTitle: String;
FWnd: HWND;
FWndClass: WNDCLASS;
FCreator : TForm;
procedure HandleAction1;
protected
procedure Execute; override;
public
constructor Create(ACreator: TForm; const Title: String);
end;
TThreadCreatorForm = class(TForm)
btnCreate: TButton;
btnAction1: TButton;
Label1: TLabel;
btnQuit: TButton;
btnSend: TButton;
edSend: TEdit;
procedure FormShow(Sender: TObject);
procedure btnCreateClick(Sender: TObject);
procedure btnAction1Click(Sender: TObject);
procedure btnQuitClick(Sender: TObject);
procedure btnSendClick(Sender: TObject);
procedure WMAction1(var Msg : TMsg); message WM_Action1;
procedure FormCreate(Sender: TObject);
public
{ Public declarations }
WndThread : TWndThread;
ThreadID : Integer;
ThreadHWnd : HWnd;
end;
var
ThreadCreatorForm: TThreadCreatorForm;
implementation
{$R *.DFM}
procedure SendStringViaWMCopyData(HSource, HDest : THandle; const AString : String);
var
Cds : TCopyDataStruct;
Res : Integer;
begin
FillChar(Cds, SizeOf(Cds), 0);
GetMem(Cds.lpData, Length(Astring) + 1);
try
StrCopy(Cds.lpData, PChar(AString));
Res := SendMessage(HDest, WM_COPYDATA, HSource, Cardinal(@Cds));
ShowMessage(IntToStr(Res));
finally
FreeMem(Cds.lpData);
end;
end;
procedure TThreadCreatorForm.FormShow(Sender: TObject);
begin
ThreadID := GetWindowThreadProcessId(Self.Handle, Nil);
Assert(ThreadID = MainThreadID);
end;
procedure TWndThread.HandleAction1;
begin
//
end;
constructor TWndThread.Create(ACreator: TForm; const Title:String);
begin
inherited Create(True);
FTitle := Title;
FCreator := ACreator;
FillChar(FWndClass, SizeOf(FWndClass), 0);
FWndClass.lpfnWndProc := @DefWindowProc;
FWndClass.hInstance := HInstance;
FWndClass.lpszClassName := scThreadClassName;
end;
procedure TWndThread.Execute;
var
Msg: TMsg;
Done : Boolean;
S : String;
begin
if Windows.RegisterClass(FWndClass) = 0 then Exit;
FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, 0, 0, 0, 0, 0, 0, HInstance, nil);
if FWnd = 0 then Exit;
Done := False;
while GetMessage(Msg, 0, 0, 0) and not done do begin
case Msg.message of
WM_Action1 : begin
HandleAction1;
end;
WM_COPYDATA : begin
Assert(True);
end;
WM_Quit : Done := True;
else begin
TranslateMessage(msg);
DispatchMessage(msg)
end;
end; { case }
end;
if FWnd <> 0 then
DestroyWindow(FWnd);
Windows.UnregisterClass(FWndClass.lpszClassName, FWndClass.hInstance);
end;
Создав поток, я нахожу его дескриптор окна с помощью FindWindow, и он отлично работает.
Если я PostMessage , это мое определенное пользователем сообщение WM_Action1, оно получено GetMessage () и перехвачено оператором case в Execute потока, и это отлично работает.
Если я отправлю себе (то есть свою форму хоста) сообщение WM_CopyData с помощью процедуры SendStringViaWMCopyData (), которая работает нормально.
Однако: если я отправлю своему потоку сообщение WM_CopyData, GetMessage и оператор case в Execute никогда не увидят его, а SendMessage в SendStringViaWMCopyData вернет 0.
Итак, мой вопрос: почему сообщение WM_CopyData не получает GetMessage в .Execute? У меня неприятное ощущение, что я чего-то упускаю ...
2 ответа
WM_COPYDATA
не является опубликованным сообщением, это отправленное сообщение, поэтому оно не проходит через очередь сообщений, и поэтому цикл сообщений никогда его не увидит. Вам необходимо назначить оконную процедуру вашему классу окна и вместо этого обработать WM_COPYDATA
в этой процедуре. Не используйте DefWindowProc()
в качестве оконной процедуры.
Кроме того, при отправке WM_COPYDATA
поле lpData
выражается в байтах , а не в символах , поэтому вам необходимо принять это во внимание. И вы неправильно заполняете COPYDATASTRUCT
. Вам необходимо указать значения для полей dwData
и cbData
. И вам не нужно выделять память для поля lpData
, вместо этого вы можете указать его на существующую память вашего String
.
Попробуй это:
const
WM_Action1 = WM_User + 1;
scThreadClassName = 'MyThreadClass';
type
TThreadCreatorForm = class;
TWndThread = class(TThread)
private
FTitle: String;
FWnd: HWND;
FWndClass: WNDCLASS;
FCreator : TForm;
procedure WndProc(var Message: TMessage);
procedure HandleAction1;
procedure HandleCopyData(const Cds: TCopyDataStruct);
protected
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(ACreator: TForm; const Title: String);
end;
TThreadCreatorForm = class(TForm)
btnCreate: TButton;
btnAction1: TButton;
Label1: TLabel;
btnQuit: TButton;
btnSend: TButton;
edSend: TEdit;
procedure FormShow(Sender: TObject);
procedure btnCreateClick(Sender: TObject);
procedure btnAction1Click(Sender: TObject);
procedure btnQuitClick(Sender: TObject);
procedure btnSendClick(Sender: TObject);
procedure WMAction1(var Msg : TMsg); message WM_Action1;
procedure FormCreate(Sender: TObject);
public
{ Public declarations }
WndThread : TWndThread;
ThreadID : Integer;
ThreadHWnd : HWnd;
end;
var
ThreadCreatorForm: TThreadCreatorForm;
implementation
{$R *.DFM}
var
MY_CDS_VALUE: UINT = 0;
procedure SendStringViaWMCopyData(HSource, HDest : HWND; const AString : String);
var
Cds : TCopyDataStruct;
Res : Integer;
begin
ZeroMemory(@Cds, SizeOf(Cds));
Cds.dwData := MY_CDS_VALUE;
Cds.cbData := Length(AString) * SizeOf(Char);
Cds.lpData := PChar(AString);
Res := SendMessage(HDest, WM_COPYDATA, HSource, LPARAM(@Cds));
ShowMessage(IntToStr(Res));
end;
procedure TThreadCreatorForm.FormShow(Sender: TObject);
begin
ThreadID := GetWindowThreadProcessId(Self.Handle, Nil);
Assert(ThreadID = MainThreadID);
end;
function TWndThreadWindowProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
pSelf: TWndThread;
Message: TMessage;
begin
pSelf := TWndThread(GetWindowLongPtr(hWnd, GWL_USERDATA));
if pSelf <> nil then
begin
Message.Msg := uMsg;
Message.WParam := wParam;
Message.LParam := lParam;
Message.Result := 0;
pSelf.WndProc(Message);
Result := Message.Result;
end else
Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
end;
constructor TWndThread.Create(ACreator: TForm; const Title:String);
begin
inherited Create(True);
FTitle := Title;
FCreator := ACreator;
FillChar(FWndClass, SizeOf(FWndClass), 0);
FWndClass.lpfnWndProc := @TWndThreadWindowProc;
FWndClass.hInstance := HInstance;
FWndClass.lpszClassName := scThreadClassName;
end;
procedure TWndThread.Execute;
var
Msg: TMsg;
begin
if Windows.RegisterClass(FWndClass) = 0 then Exit;
FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, 0, 0, 0, 0, 0, 0, HInstance, nil);
if FWnd = 0 then Exit;
SetWindowLongPtr(FWnd, GWL_USERDATA, ULONG_PTR(Self));
while GetMessage(Msg, 0, 0, 0) and (not Terminated) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
end;
procedure TWndThread.DoTerminate;
begin
if FWnd <> 0 then
DestroyWindow(FWnd);
Windows.UnregisterClass(FWndClass.lpszClassName, FWndClass.hInstance);
inherited;
end;
procedure TWndThread.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_Action1 : begin
HandleAction1;
Exit;
end;
WM_COPYDATA : begin
if PCopyDataStruct(lParam).dwData = MY_CDS_VALUE then
begin
HandleCopyData(PCopyDataStruct(lParam)^);
Exit;
end;
end;
end;
Message.Result := DefWindowProc(FWnd, Message.Msg, Message.WParam, Message.LParam);
end;
procedure TWndThread.HandleAction1;
begin
//
end;
procedure TWndThread.HandleCopyData(const Cds: TCopyDataStruct);
var
S: String;
begin
if Cds.cbData > 0 then
begin
SetLength(S, Cds.cbData div SizeOf(Char));
CopyMemory(Pointer(S), Cds.lpData, Length(S) * SizeOf(Char));
end;
// use S as needed...
end;
initialization
MY_CDS_VALUE := RegisterWindowMessage('MY_CDS_VALUE');
end.
Сообщение с копией данных отправляется синхронно. Это означает, что он не будет возвращен GetMessage
. Поэтому вам необходимо предоставить оконную процедуру для обработки сообщения, потому что отправленные сообщения отправляются непосредственно оконной процедуре их окон, будучи синхронной, а не асинхронной.
Помимо этого, другая проблема заключается в том, что вы не указываете длину данных в структуре копируемых данных, cbData
. Это необходимо при отправке сообщения через поток, чтобы система могла упорядочить ваши данные.
Вы должны установить dwData
, чтобы получатель мог проверить, обрабатывает ли он предполагаемое сообщение.
Здесь вам вообще не нужно использовать GetMem
, вы можете использовать строковый буфер напрямую. Дескриптор окна - это HWND
, а не THandle
. Окно только для сообщений здесь было бы наиболее подходящим.
cbData
также имеет решающее значение.
dwData
, чтобы различать разные WM_COPYDATA
сообщения, чтобы вы обрабатывали только свое собственное сообщение и случайно не обрабатывали чужое сообщение. VCL, например, использует WM_COPYDATA
внутри.
dwData
— единственная защита, предлагаемая WM_COPYDATA
, и большинство (но не все) реализаций используют RegisterWindowMessage()
, чтобы гарантировать, что dwData
— уникальное значение.
Похожие вопросы
Связанные вопросы
Новые вопросы
multithreading
Для вопросов, касающихся многопоточности, способность компьютера или программы выполнять работу одновременно или асинхронно, используя несколько одновременных потоков выполнения (обычно называемых потоками).
WM_Action1
в окно темы.cbData
.lpData
, но да, это также относится и кcbData
. Его код может быть сегодня в D7 и, таким образом, используетAnsiString
, что является 8-битным. Но если он когда-нибудь перейдет на D2009+, гдеString
— 16 бит, его исходный код, который выделял и заполнилlpData
, потерпит неудачу.