4
votes

I am developing an application with multithreading (RAD Studio XE5). At the start of the application I create a single thread that will live as long as the main form does.

I am able to dispatch messages from the thread to any form that has been created in my application, however I can't find a way to do the opposite, sending a message from the main VCL thread to the worker thread.

When creating the main form I create the worker thread and copy the handle in a public variable:

  serverThread := TMyThread.Create(True, ServerPort + 1);
  serverThreadHandle := serverThread.Handle; // SAVE HANDLE
  serverThread.Start;

then (from a different form FrmSender) I dispatch a message to the thread:

  PostMessage(uMain.serverThreadHandle, UM_LOC_VCLMSG, UM_LOC_VCLMSG, Integer(PStrListVar));

This is the thread's Execute procedure:

procedure TMyThread.Execute;
var
    (..)
    vclMSG : TMsg;
    str1, str2 : string;
    (..)
begin
    while not(Terminated) do
    begin
        Sleep(10);
        if Assigned(FrmSender) then
          if FrmSender.HandleAllocated then
            if PeekMessage(vclMSG, FrmSender.Handle, 0, 0, PM_NOREMOVE) then
              begin
                if vclMSG.message = UM_LOC_VCLMSG then
                  begin
                    try
                      pStrListVar := pStrList(vclMSG.lParam);
                      str1 := pStrListVar^.Strings[0];
                      str2 := pStrListVar^.Strings[1];
                    finally
                      Dispose(pStrListVar);
                    end;
                  end;
              end;  
        (.. do other stuff ..)
    end;
end;

However PeekMessage() never returns true as if it was never receiving any message. I've tried changing the parameters to PeekMessage():

PeekMessage(vclMSG, 0, 0, 0, PM_NOREMOVE);

But with no results. Any ideas?

2
You don't PostMessage to a thread handle, reading the documentation for PostMessage will help.Sertac Akyuz
Forgot to mention that the thread is derived from TThread classtsmr

2 Answers

5
votes

From MSDN PostMessage function documentation:

Places (posts) a message in the message queue associated with the thread that created the specified window and returns without waiting for the thread to process the message.

To post a message in the message queue associated with a thread, use the PostThreadMessage function.

Thus, you should use PostThreadMessage:

Posts a message to the message queue of the specified thread. It returns without waiting for the thread to process the message.

Give special attention to the Remarks section. The recipient thread needs a message queue. Force the thread having one by following these steps:

  • Create an event object, then create the thread.
  • Use the WaitForSingleObject function to wait for the event to be set to the signaled state before calling PostThreadMessage.
  • In the thread to which the message will be posted, call PeekMessage as shown here to force the system to create the message queue.

    PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE)
    
  • Set the event, to indicate that the thread is ready to receive posted messages.

Then, when using PeekMessage, you pass a handle value of -1 to the function, as documented.

5
votes
PeekMessage(vclMSG, FrmSender.Handle, 0, 0, PM_NOREMOVE)

The second argument means that you will only retrieve messages sent to the sender, FrmSender.Handle. But you sent the messages to the recipient, uMain.serverThreadHandle. This is one reason why PeekMessage can never return.

Accessing the VCL from the thread as you do is wrong. The form's handle is subject to VCL window re-creation and there is a clear race on HandleAllocated and Handle. So even if you needed to know FrmSender.Handle, it would be wrong to ask for it in the thread.

You are actually sending the message to a thread handle rather than a window handle. That means the message is never even sent, another reason why PeekMessage cannot return. Had you checked the return value when you called PostMessage you would have learnt that.

I'd use either PostThreadMessage, or post the message to a window allocated in the thread with a call to AllocateHWnd.

Once you do manage to actually send the message, your use of PM_NOREMOVE means that the message queue will never be emptied.

Your use of Sleep looks very dubious to me. Why not use GetMessage and so block until a message arrives. Anytime you see a call to Sleep, be very suspicious.

Your cast to Integer will lead to pointer truncation in a 64 bit build. The correct type to cast to is LPARAM.

I expect there are other mistakes, these are just the ones I could see in a quick 2 minute scan.