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

killercode
  • 1,666
  • 5
  • 29
  • 42

4 Answers4

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.

dthorpe
  • 35,318
  • 5
  • 75
  • 119
  • 1
    As suggestion, let the main thread get the message and signal your working thread when there's new data available to process. – jachguate Sep 03 '10 at 21:30
  • 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. – mghie Sep 03 '10 at 22:09
  • 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 ! – killercode Sep 03 '10 at 23:11
  • 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. – dthorpe Sep 03 '10 at 23:28
  • 1
    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) – dthorpe Sep 03 '10 at 23:31
  • 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 to – killercode Sep 03 '10 at 23:34
  • 1
    @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. – dthorpe Sep 03 '10 at 23:36
  • user: What message number is the process sending to the thread to indicate the data is ready? – dthorpe Sep 03 '10 at 23:40
  • i dont think the message number is that important :S like WM_USER + 60 or something like that – killercode Sep 03 '10 at 23:50
  • and i already have the solution without using threads, but i wanna know how to do it, i may need that in another project somehow – killercode Sep 03 '10 at 23:53
  • 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. – dthorpe Sep 04 '10 at 00:32
  • @user: "already have the solution without threads / want to know how to do it". See the last part of my reply to @mghie. :> – dthorpe Sep 04 '10 at 00:32
  • @user: If you want to use threads, use threads. You have all the info you need in the 2nd paragraph of my answer. – dthorpe Sep 04 '10 at 00:34
  • @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. – mghie Sep 04 '10 at 22:02
  • @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. – dthorpe Sep 05 '10 at 02:28
7

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; 
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • +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. – mghie Sep 07 '10 at 20:50
  • 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. – Remy Lebeau Sep 07 '10 at 21:25
  • 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. – mghie Sep 07 '10 at 21:53
  • Which is why I would normally add a check to make sure it was registered before unregistering it. This was merely an example. – Remy Lebeau Sep 08 '10 at 00:48
  • 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. – Jerry Dodge Mar 11 '17 at 17:52
  • @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. – Remy Lebeau Mar 11 '17 at 18:12
  • `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`. – Jerry Dodge Mar 11 '17 at 18:16
  • 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. – Jerry Dodge Mar 11 '17 at 18:19
  • @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);`. – Jerry Dodge Mar 11 '17 at 20:28
  • 1
    @JerryDodge a `BOOL` is a 4-byte integer (`LongBool` in Delphi). `GetMessage()` is capable of returning -1, 0, and > 0, though [-1 is rare](https://blogs.msdn.microsoft.com/oldnewthing/20130322-00/?p=4873). `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. – Remy Lebeau Mar 11 '17 at 20:34
  • Thank you, can you elaborate on my question please? http://stackoverflow.com/questions/42739404/how-to-receive-wm-powerbroadcast-inside-of-a-thread – Jerry Dodge Mar 11 '17 at 20:36
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;
Remko
  • 7,214
  • 2
  • 32
  • 52
  • yea, but how am i gonna know the handle of this thread to send messages to? since the sender is from another process – killercode Sep 03 '10 at 23:11
  • Use PostThreadMessage (http://msdn.microsoft.com/en-us/library/ms644946(VS.85).aspx), it takes the ThreadId instead of a Window Handle. – Remko Sep 04 '10 at 06:21
  • 1
    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. – Remy Lebeau Sep 07 '10 at 20:21
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;
  • `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. – Remy Lebeau Aug 05 '13 at 22:42