Creating a Window Inside TThread
Asked Answered
C

4

6

im trying to send a message between 2 separate projects, but my problem is that im trying to make the receiver run inside a TThread Object, but WndProc wont work from inside an Object, must be a function, is there anyway to create a window inside a TThread that can process messages inside the thread?

here is what i mean

function TDataThread.WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
 Result := 0;
 case uMsg of
   WM_DATA_AVA: MessageBox(0, 'Data Avaibale', 'Test', 0);
  else Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
 end;
end;

Procedure TDataThread.Create(const Title:String);
begin
 HAppInstance := HInstance;
 with WndClass do
 begin
  Style := 0;
  lpfnWndProc := @WindowProc;          //The Error Lies here (Variable Required)
  cbClsExtra := 0;
  cbWndExtra := 0;
  hInstance := HAppInstance;
  hIcon := 0;
  hCursor := LoadCursor(0, IDC_ARROW);
  hbrBackground := COLOR_WINDOW;
  lpszMenuName := nil;
  lpszClassName := 'TDataForm';
 end;
 Windows.RegisterClass(WndClass);
 MainForm := CreateWindow('TDataForm', PAnsiChar(Title), WS_DLGFRAME , XPos, YPos, 698, 517, 0, 0, hInstance, nil);
end;

i need to have a form so i can get its handle from another application Using FindWindow and FindWindowEx if needed

Cobbler answered 3/9, 2010 at 18:38 Comment(0)
R
11

Running a wndproc in a background thread can be done in Win32, but it's widely regarded as a bad idea.

To do it, you must ensure that your background thread contains a message dispatch loop: GetMessage/TranslateMessage/DispatchMessage. You must ensure that the window handle you want to process messages in the background thread is created on the background thread (CreateWindow is called in the context of the background thread) and all its child windows as well. And you must ensure that your background thread calls its message loop frequently in addition to whatever else it's doing (which kinda defeats the purpose of using a background thread!)

If your background thread doesn't have a message loop, the window handles that are created on the background thread will never receive any messages, so nothing will happen.

Now then, why you shouldn't do this: Windows are message-driven, which means they are inherently a cooperatively multitasked dispatch system. Every GUI windows app has to have a message loop in the main thread to get anything done. That message loop will support virtually any number of windows, all on the main thread. A properly implemented UI will not do anything in the main thread to block execution, so the message loop will always be ready and responsive.

So if the existing message loop on the main thread will handle all your window messaging needs without blocking or freezing, why would you want to make your life more complicated by trying to run a second message loop in a background thread? There is no advantage to using a background thread.

Rosemaryrosemond answered 3/9, 2010 at 19:3 Comment(15)
As suggestion, let the main thread get the message and signal your working thread when there's new data available to process.Coelho
All threads in Windows are equal, there's nothing "main" or "background" about them. They differ in whether they have a message loop or not, and there is one that was created first in a process, but that's about it as far as differences go. Interaction with COM may require a thread to have a message loop, working with windows in a thread requires it to have a message loop. A message loop is also a fine way to communicate with a thread. Apart from the VCL being a bad match for it there's nothing wrong with multiple message loops in a process.Stereotype
so there is no way i can make my thread communicate with other processes? since each process must send back a reply to its thread to show that its ready to map a file for data !Cobbler
Sure, there are plenty of ways to do it. The question is how much work do you want to put into it. The window handle doesn't have to live in the context of your background thread. You could use a normal window handle in your UI thread and when it receives a particular message from the other process, the message handler can signal an event that your background thread is waiting on.Rosemaryrosemond
Since you mention the other process sending back a reply to indicate data is ready, you could also consider using a named mutex per process/thread pair. The thread starts the process, passing the name of the mutex as a param, then the thread blocks waiting for the mutex to signal. The process gets the named mutex and signals it when the work is done. No message loops required. (This assumes you have control of the source code for the process as well as the thread)Rosemaryrosemond
therer will be more than one thread for more than one instance of the sender there is no way that if i used the main app's thread that it would know which thread this message is sent toCobbler
@mghie: Yes, all threads in Windows are equal. All programmers, however, are not. If there is a solution that will get the job done without extensive use of threads, use it. If there is a solution that can get the job done without using thread-bound window handles, background threads, and COM in the same sentence, even better. People are drawn to threads like moths to flame, with similar results.Rosemaryrosemond
user: What message number is the process sending to the thread to indicate the data is ready?Rosemaryrosemond
i dont think the message number is that important :S like WM_USER + 60 or something like thatCobbler
and i already have the solution without using threads, but i wanna know how to do it, i may need that in another project somehowCobbler
The message number is relevant. If each process sent a different message to signal its respective thread, you could differentiate the messages on the receiving end. The same is true if the process included some distinctive data value in the dword message params.Rosemaryrosemond
@user: "already have the solution without threads / want to know how to do it". See the last part of my reply to @mghie. :>Rosemaryrosemond
@user: If you want to use threads, use threads. You have all the info you need in the 2nd paragraph of my answer.Rosemaryrosemond
@dthorpe: "People are drawn to threads like moths to flame, with similar results" - There seem to be two kinds of thinking, either to consider threads the moment there is any code that may block or take longer than a few 100 ms, the other to eschew multiple threads as long as possible, and then some more. The latter brought us programs (certain IDEs come to mind) that hang for half a minute when one simply wants to enter text or open the help window, that's why I'm for the former approach.Stereotype
@mghie: There are plenty of appropriate uses of threads, no argument there. I just find that folks jump into threads prematurely and create a bigger mess of things. A properly designed multithreaded solution to an appropriate problem is a beautiful thing, and equally rare.Rosemaryrosemond
N
8

Creating a window inside a TThread works fine, provided the TThread implements a message loop, AND CreateWindow() is called inside the same thread context as the message loop. In other words, you must call CreateWindow() from inside the TThread's Execute() method, NOT from inside its constructor, eg:

type
  TDataThread = class(TThread)
  private
    FTitle: String;
    FWnd: HWND;
    FWndClass: WNDCLASS;
    FRegistered: boolean;
    class function WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; static;
  protected
    procedure Execute; override;
    procedure DoTerminate; override;
  public
    constructor Create(const Title:String); reintroduce;
  end;

constructor TDataThread.Create(const Title: String); 
begin 
  inherited Create(False);
  FTitle := Title;
  with FWndClass do 
  begin 
    Style := 0; 
    lpfnWndProc := @WindowProc;
    cbClsExtra := 0; 
    cbWndExtra := 0; 
    hInstance := HInstance; 
    hIcon := 0; 
    hCursor := LoadCursor(0, IDC_ARROW); 
    hbrBackground := COLOR_WINDOW; 
    lpszMenuName := nil; 
    lpszClassName := 'TDataForm'; 
  end; 
end; 

procedure TDataThread.Execute; 
var
  Msg: TMsg;
begin
  FRegistered := Windows.RegisterClass(FWndClass) <> 0;
  if not FRegistered then Exit;
  FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, XPos, YPos, 698, 517, 0, 0, HInstance, nil); 
  if FWnd = 0 then Exit;
  while GetMessage(Msg, FWnd, 0, 0) > 0 do
  begin
    TranslateMessage(msg);
    DispatchMessage(msg)
  end;
end;

procedure TDataThread.DoTerminate;
begin
  if FWnd <> 0 then DestroyWindow(FWnd);
  if FRegistered then Windows.UnregisterClass(FWndClass.lpszClassName, HInstance);
  inherited;
end;

function TDataThread.WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  Result := 0;
  case uMsg of
    WM_DATA_AVA:
      MessageBox(0, 'Data Available', 'Test', 0);
  else
    Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
  end;
end; 
Nonie answered 7/9, 2010 at 20:35 Comment(11)
+1, this is the important technical information from dthorpe's answer, which was a little buried in caveats. There's no need to have a member FWndClass though, put everything into Execute(), get rid of DoTerminate(), and things will be clearer. If both class name and window title were parameters to the constructor this would make for a nice helper base class.Stereotype
I prefer to use DoTerminate() because it allows the thread to cleanup after itself regardless of whether Execute() exits cleanly or due to an uncaught exception. Putting a try/except around the entire Execute() code is a bit ugly for me.Nonie
Artificially putting setup, use of and destruction of a data structure into different methods is much worse. Your code for example will happily call UnregisterClass() even if RegisterClass() has failed.Stereotype
Which is why I would normally add a check to make sure it was registered before unregistering it. This was merely an example.Nonie
Copied your entire code, pasted in Delphi 10 Seattle, some functions have changed since D7, so made the necessary changes, but this only receives the first message, and the second call to GetMessage freezes the thread and never returns.Dunford
@JerryDodge nothing in the code I showed should have needed to be changed for newer Delphi versions. And if GetMessage() is freezing then no message is available in the mesaage queue for the window. Double check the posting code is not failing, or that the first message handler is not doing something to swallow the second message.Nonie
GetMessage returns a BOOL so it failed to compile at while GetMessage(Msg, FWnd, 0, 0) > 0 do. I changed it to while GetMessage(Msg, FWnd, 0, 0) = True do. I'm not actually posting anything myself, I'm trying to receive messages from Windows. Before this, I used AllocateHWnd(WndMethod) but the particular message I need WM_POWERBROADCAST is never received, and I saw elsewhere that I need to implement a message loop in the thread. Using AllocateHWnd(WndMethod) I receive everything except for WM_POWERBROADCAST.Dunford
PS - Please forgive me for trying to get an answer without asking a question :-) I'm trying to seek out all possible solutions before I give up and ask an actual question.Dunford
@Remy Indeed, but it failed to compile, so it forced me to make that change. Also, I had to change UnregisterClass to Winapi.Windows.UnregisterClass(PChar(Self.ClassName), FWndClass.hInstance);.Dunford
@JerryDodge a BOOL is a 4-byte integer (LongBool in Delphi). GetMessage() is capable of returning -1, 0, and > 0, though -1 is rare. AllocateHWnd() is not thread safe, thus the use of CreatWindow() directly. GetMessage() can only return posted messages from PostMessage() and PostThreadMessage() (though it is needed for dispatching sent messages from SendMessage() across thread boundaries). WM_POWERBROADCAST is not a posted message, so a message loop will not see it, you need a window procedure to handle it.Nonie
Thank you, can you elaborate on my question please? #42739904Dunford
W
4

You don't need a Window to receive messages, try the following. In the thread (once) make a call to PeekMessage to force the creation of a Message Queue, example:

  // Force Message Queue Creation
  PeekMessage(Msg, 0, WM_USER, WM_USER, PM_NOREMOVE);

Then setup a Message Loop/Pump, example:

  // Run until terminated
  while not Terminated do
  begin

    if GetMessage(@Msg, 0, 0, 0) then
    begin
      case Msg.message of
        WM_DATA_AV: MessageBox(0, 'Data Avaibale', 'Test', 0); 
      else begin
        TranslateMessage(@Msg);
        DispatchMessage(@Msg);
      end;
    end;
  end;
Wryneck answered 3/9, 2010 at 20:22 Comment(3)
yea, but how am i gonna know the handle of this thread to send messages to? since the sender is from another processCobbler
Use PostThreadMessage (msdn.microsoft.com/en-us/library/ms644946(VS.85).aspx), it takes the ThreadId instead of a Window Handle.Wryneck
But then you have the problem of the sending app needing to locate the receiving thread's ID. Using a window makes that search easier.Nonie
M
0
TTestLoopThread = class(TThread)
      private
        FWinHandle: HWND;
        procedure DeallocateHWnd(Wnd: HWND);
      protected
        procedure Execute; override;
        procedure WndProc(var msg: TMessage);
      public
        constructor Create;
        destructor Destroy; override;
      end;

    implementation

    var
      WM_SHUTDOWN_THREADS: Cardinal;

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      WM_SHUTDOWN_THREADS := RegisterWindowMessage('TVS_Threads');
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    begin
      TTestLoopThread.Create;
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    begin
      SendMessage(wnd_broadcast, WM_SHUTDOWN_THREADS, 0, 0);
    end;

    { TTestLoopThread }

    constructor TTestLoopThread.Create;
    begin
      inherited Create(False);
    end;

    destructor TTestLoopThread.Destroy;
    begin
      inherited;
    end;

    procedure TTestLoopThread.DeallocateHWnd(Wnd: HWND);
    var
      Instance: Pointer;
    begin
      Instance := Pointer(GetWindowLong(Wnd, GWL_WNDPROC));
      if Instance <> @DefWindowProc then
        // make sure we restore the old, original windows procedure before leaving
        SetWindowLong(Wnd, GWL_WNDPROC, Longint(@DefWindowProc));
      FreeObjectInstance(Instance);
      DestroyWindow(Wnd);
    end;

    procedure TTestLoopThread.Execute;
    var
      Msg: TMsg;
    begin
      FreeOnTerminate := True;
      FWinHandle := AllocateHWND(WndProc); //Inside Thread
      try
      while GetMessage(Msg, 0, 0, 0) do
        begin
         TranslateMessage(Msg);
         DispatchMessage(Msg);
        end;
      finally
      DeallocateHWND(FWinHandle);
      end;
    end;

    procedure TTestLoopThread.WndProc(var msg: TMessage);
    begin
      if Msg.Msg = WM_SHUTDOWN_THREADS then
      begin
       Form1.Memo1.Lines.Add('Thread ' + IntToStr(ThreadID) + ' shutting down.');
       PostMessage(FWinHandle, WM_QUIT, 0, 0);
      end
      else
       Msg.Result := DefWindowProc(FWinHandle, Msg.Msg, Msg.wParam, Msg.lParam);
    end;
Moorish answered 5/8, 2013 at 22:24 Comment(1)
AlocateHWND(), DeallocateHWND(), MakeObjectInstance(), FreeObjectInstance() - these functions are NOT thread-safe, as they use global resources that are not protected from concurrent access across threads. The main thread makes fairly extensive use of these functions, so unsafe worker threads that also use them can really mess them up. That being said, there are third-party custom implementations floating around that are thread-safe. Otherwise, don't use them at all, and just use Win32 API function calls directly (CreateWindow(), SetWindowLong()) which work fine in worker threads.Nonie

© 2022 - 2024 — McMap. All rights reserved.