2

I have a Delphi XE4 service application under development. The service starts threads for some long-running tasks, and the threads communicate status back with a PostThreadMessage call.

The main ServiceExecute loop looks like this:

procedure TScanService.ServiceExecute(Sender: TService);
var
  CurrentMessage: TMsg;
begin
  LogServerEvent('ServiceExecute', 'Starting');
  while not Terminated do
  begin
    if not PeekMessage(CurrentMessage, 0, WM_NULL, msgHigh, PM_NOREMOVE) then
    begin
      Sleep(1000);
      Continue;
    end;

    GetMessage(CurrentMessage, 0, WM_NULL, msgHigh);

    LogServerEvent('ServiceExecute', 'CurrentMessage.message', IntToStr(CurrentMessage.message));
    LogServerEvent('ServiceExecute', 'CurrentMessage.wParam', IntToStr(CurrentMessage.wParam));
    LogServerEvent('ServiceExecute', 'CurrentMessage.lParam', IntToStr(CurrentMessage.lParam));

In the thread, the message sending looks like this:

    gThreadNumber: Integer;

    LogThreadEvent('Execute', 'Found Notice, Thread number: ' + IntToStr(gThreadNumber));
    PostThreadMessage(ParentThreadID, msgFound, gThreadNumber, 6);

The message arrives fine, and the message number is correct (msgFound = WM_USER + 1); however, I sent 0, 6 for wParam, lParam, and what I received is 4, 0. What am I missing?

Note: The code only has 2 threads running, and one is a timer that uses a different message number, and isn't sending anything when this happens.

Danilo Casa
  • 506
  • 1
  • 9
  • 18
  • 2
    Strange message loop, you can use GetMessage alone to block until a message is posted. – Sertac Akyuz Feb 27 '15 at 18:53
  • 1
    I don't see an issue in the code you posted, which means the issue probably isn't in the code you posted. Can you reduce it down to a small sample that demonstrates the problem that we can actually compile and run to reproduce it? – Ken White Feb 27 '15 at 18:55
  • 1
    You're not being selective about your `HWND`... RTL uses some messages in the `WM_USER + x` range; without more code it's difficult to say, but it is possible that you are picking up stray messages. If you use `-1` for the `HWND` parameter of `PeekMessage` and `GetMessage` you will restrict to picking up only messages posted with a `NULL` HWND (otherwise you also get window messages) - the former would only include messages sent with `PostMessage` with a null handle and messages sent using `PostThreadMessage`. – J... Feb 27 '15 at 19:36
  • @J...: Nice catch. I missed it. I typically use PostThreadMessage to communicate between a thread and the GUI, and always check for `Msg.hwnd = 0` when the message is received; I didn't think about doing so from a service. – Ken White Feb 27 '15 at 19:46
  • @KenWhite Indeed. I often create a window for such a purpose and feed the handle to the thread, just to be sure that nothing else is going to intrude on the user space. Null handles always seem a bit scary to me... Still, in this case it's only a possibility... I'm not sure that this should be necessary in a service, but I'm not certain that it's not necessary either. – J... Feb 27 '15 at 19:53
  • PostThreadMessage?!!! Gah! Stop it. Also, what's the deal with that Sleep? Why would you go out of your way to make your service unresponsive? Or is there some good reason for that? – David Heffernan Feb 27 '15 at 19:54
  • 2
    CM_SERVICE_CONTROL_CODE is WM_USER + 1, wparam as 4 is SERVICE_CONTROL_INTERROGATE. TService.Controller may be posting it. If that's the case checking for hwnd will not help. – Sertac Akyuz Feb 27 '15 at 20:12
  • @SertacAkyuz Well, I think that settles it. Alternatives would, I suppose, be to choose `WM_USER + (something bigger)` on the hope that the message space stays clear there or (I think preferably) to explicitly `AllocateHWND` and use that instead. – J... Feb 27 '15 at 20:29
  • @J...: or use `WM_APP` instead of `WM_USER`. And `TService` runs in a thread, but `AllocateHWND()` is not thread-safe, so use `CreateWindowEx()` directly to create a [Message-Only Window](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632599.aspx#message_only) (and get rid of the `OnExecute` handler so `TService`'s internal message loop can service the window for you). Or just stop using window/thread messages altogether and use a different signaling mechanism. – Remy Lebeau Feb 27 '15 at 21:24

1 Answers1

0

In the TService code you can find this code:

const
  CM_SERVICE_CONTROL_CODE = WM_USER + 1;
....
procedure TService.Controller(CtrlCode: DWord);
begin
  PostThreadMessage(ServiceThread.ThreadID, CM_SERVICE_CONTROL_CODE, CtrlCode, 0);
  if ServiceThread.Suspended then ServiceThread.Resume;
end;

So TService is sending messages with the same message number as your messages. How can you tell them apart? Well, you cannot.

From the documentation for WM_USER:

Used to define private messages for use by private window classes, usually of the form WM_USER+x, where x is an integer value.

Usually this would be used for messages sent to a window and the meaning of the message would be class specific. As I read the TService code, the designers have taken the point of view that they are in full control of the message queue and have the right to determine the meaning of its private messages. The net result of this, is that you cannot use thread messages in the WM_USER range because they have been reserved by the TService class.

Your main options:

  • Send your messages to a window.
  • Use RegisterWindowMessage to ensure message id uniqueness.
  • Stop using windows messages (not especially intended for use in a service) and communicate using some other mechanism.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • If we'd comply with the docs, ['RegisterWindowMessage'](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644947%28v=vs.85%29.aspx) is no good either. *"Only use RegisterWindowMessage when more than one application ..."*. I don't see any real danger with WM_USER+, it will never be dispatched to a window when you use 'PostThreadMessage'. Then its meaning is what you want it to mean. WM_APP+ would perhaps be better though.. – Sertac Akyuz Feb 27 '15 at 21:19
  • @Sertac ok, if the user would check the hwnd then there could be no confusion. However, I would argue that WM_USER is for window classes. Were it me I'd create a window and never ever call PostThreadMessage. – David Heffernan Feb 27 '15 at 21:23
  • -1 "This means that you cannot use messages in this range with PostThreadMessage" - the `WM_USER` range of message IDs works just fine with `PostThreadMessage()` (if it didn't, `TService` itself would not run, as it uses `PostThreadMessage(WM_USER+1)` internally to post SCM requests to the `TService` message queue). There is no limitation on the Message IDs that `PostThreadMessage()` accepts. All it cares about is a valid thread ID with a message queue attached to it. The other parameters are posted to that thread's message queue as-is. – Remy Lebeau Feb 27 '15 at 21:31
  • The OP simply chose a message ID that was already in use by `TService`, and was posting his message to the same thread message queue that `TService` posts its private messages to. That's all. That is the real danger of using `WM_USER`, not that it can't be used with `PostThreadMessage()` at all. – Remy Lebeau Feb 27 '15 at 21:36
  • OK, I was able to confirm that the messages I got weren't from my thread, so as a temporary solution, I moved my message numbers higher. – Kevin Davidson Feb 28 '15 at 04:41
  • Another reason not to implement serviceExecute and use your own thread instead... – whosrdaddy Feb 28 '15 at 13:49