服务应用程序中的 PostMessage
有一个问题我无法解决。 我在 Delphi 中创建了两个服务应用程序并尝试在其中发布消息。 当然,此类应用程序中没有窗口,PostMessage 需要窗口句柄参数来发送消息。
因此,我使用 AllocateHWnd(MyMethod: TWndMethod) 函数创建了一个窗口句柄,并将我希望在收到消息时调用的过程作为“MyMethod”参数传递。 如果它是一个窗口应用程序,则使用 AllocateHWnd 方法返回的句柄调用 PostMessage() 肯定会发送一条消息,然后该消息将由“MyMethod”过程接收。
然而,在我的服务应用程序中情况有所不同。 我不明白为什么,但在其中一种情况下,以这种方式发布消息效果很好,而在第二种情况下则不然(根本没有收到消息)。 仅当服务停止时,我才注意到“MyMethod”收到两条消息:WM_DESTROY 和 WM_NCDESTROY。 此过程永远不会收到我使用 PostMessage 发送的消息。 另一方面,第一个服务总是接收我发送的所有消息。
您能否给我一个线索,帮助我找到第二个服务未收到我的消息的原因? 我不知道它们有何不同。 我检查了服务的设置,它们似乎是相同的。 为什么其中一个可以正常工作而第二个却不能(就发送消息而言)?
感谢您的任何建议。 马吕斯。
There is a problem I am unable to solve. I created two service applications in Delphi and tried to post messages within them. Of course, there are no windows in such applications and PostMessage needs a window handle parameter to send a message.
Therefore, I created a window handle using the AllocateHWnd(MyMethod: TWndMethod) function and passed, as the 'MyMethod' parameter, a procedure I want to be called when a message is received. If it was a windowed application, PostMessage() called using the handle returned by the AllocateHWnd method would certainly send a message that would then be received by the 'MyMethod' procedure.
The situation, however, is different in my service applications. I do not understand why, but in one of them posting messages this way works fine, whereas in the second one it does not (the messages are not received at all). Only when the service is being stopped do I notice that two messages are received by 'MyMethod': WM_DESTROY and WM_NCDESTROY. The messages I send using PostMessage are never received by this procedure. On the other hand, the first service always receives all messages I send.
Could you please give me a clue that would help me find the reason of the second service not receiving my messages? I do not know in what way they can differ. I checked the settings of the services and they seem to be identical. Why then one of them works fine and the second one does not (as far as sending messages is concerned)?
Thanks for any advice.
Mariusz.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
如果没有更多信息,将很难帮助您调试此问题,特别是为什么它在一个服务中有效但在另一个服务中无效。 但是:
您可能不想尝试解决代码中的问题,而是希望完全删除窗口,并使用 PostThreadMessage() 而不是 PostMessage()。 为了使消息发布正常工作,您需要一个消息循环,但不一定需要接收窗口。
编辑:我正在尝试一次性回复您的所有答案。
首先 - 如果您想让自己的生活变得轻松,您应该真正查看 OmniThreadLibrary by gabr。 我不知道它是否在 Windows 服务应用程序中工作,我什至不知道是否已经尝试过。 你可以去论坛问一下。 然而,它有很多很棒的功能,值得研究,即使只是为了学习效果。
当然,您也可以自己编写此程序,并且对于 Delphi 2007 之前的 Delphi 版本,您必须这样做。我将简单地添加我们内部库中的一些片段,该库已经发展了多年,并且可以在几十个程序中运行。 但我并不声称它没有错误。 您可以将其与您的代码进行比较,如果有任何问题,请随时询问,我会尽力澄清。
这是工作线程基类的简化 Execute() 方法:
重要的是不要让异常得不到处理,因此所有内容都有一个顶级异常处理程序。 您对异常执行的操作是您的选择,并且取决于应用程序,但必须捕获所有异常,否则应用程序将被终止。 在服务中,您唯一的选择可能就是记录它们。
有一个特殊的方法来启动线程关闭,因为线程在GetMessage()内部时需要被唤醒:
发送WM_QUIT将导致消息循环退出。 然而,存在一个问题,即后代类中的代码可能依赖于线程关闭期间正确处理的 Windows 消息,尤其是在使用 COM 接口时。 这就是为什么使用以下代码而不是简单的 WaitFor() 来释放所有正在运行的线程:
这是在重写的 BeforeDestruction() 方法中,因为需要释放所有线程在后代控制器类的析构函数开始释放线程可能使用的任何对象之前。
Without more information it will be difficult to help you debug this, especially why it works in one service but not in the other. However:
Instead of trying to fix the problem in your code you might want to remove the windows altogether, and use PostThreadMessage() instead of PostMessage(). For the posting of messages to work correctly you need a message loop, but not necessarily receiving windows.
Edit: I'm trying to reply to all your answers in one go.
First - if you want to make your life easy you should really check out OmniThreadLibrary by gabr. I don't know whether it does work in a Windows service application, I don't even know whether that has been tried yet. You could ask in the forum. It has however a lot of great features and is worth looking into, if only for the learning effect.
But of course you can also program this for yourself, and you will have to for Delphi versions prior to Delphi 2007. I will simply add some snippets from our internal library, which has evolved over the years and works in several dozen programs. I don't claim it to be bug-free though. You can compare it with your code, and if anything sticks out, feel free to ask and I'll try to clarify.
This is the simplified Execute() method of the worker thread base class:
It is important to not let exceptions get unhandled, so there is a top-level exception handler around everything. What you do with the exception is your choice and depends on the application, but all exceptions have to be caught, otherwise the application will get terminated. In a service your only option is probably to log them.
There is a special method to initiate thread shutdown, because the thread needs to be woken up when it is inside of GetMessage():
Posting WM_QUIT will cause the message loop to exit. There is however the problem that code in descendant classes could rely on Windows messages being properly handled during shutdown of the thread, especially when COM interfaces are used. That's why instead of a simple WaitFor() the following code is used to free all running threads:
This is in the overridden BeforeDestruction() method because all threads need to be freed before the destructor of the descendant controller class begins to free any objects the threads might use.
我建议您考虑使用命名管道进行 IPC。 这就是它们的设计目的:
寻找进程间通信中使用的 Windows 消息的替代方案
I'd suggest you consider using named pipes for IPC. That is what they are designed to do:
Looking for an alternative to windows messages used in inter-process communication
正如 Mghie 提到的,您需要一个消息处理循环。 这就是 PeekMessage 正确返回消息的原因。 并不是消息不存在,而是您没有处理它们。 在标准应用程序中,Delphi 创建一个 TApplication 类并调用 Application.Run。 这是普通应用程序的消息处理循环。 它基本上包括:
如果您希望服务应用程序处理消息,您将需要执行相同类型的工作。
这里有一个使用服务和处理 PostThreadMessage 调度的示例 。 请记住,正如 Mick 提到的,您不能在不同安全上下文的应用程序之间使用消息处理(特别是在 Vista 中)。 您应该使用命名管道或类似的。 Microsoft 在此处对此进行了讨论。
编辑:
根据您发布的代码片段,您可能只是在解决线程问题。 AllocHWnd 不是线程安全的。 有关详细信息,请参阅此处问题的解释以及在线程中正常工作的版本。
当然,这仍然让我们回到为什么不使用 PostThreadMessage 的原因。 根据代码示例的结构方式,将消息处理作为线程的函数,然后将其传递到类中进行处理是很简单的。
As Mghie mentioned, you need a message processing loop. That's why PeekMessage returns the messages correctly. It's not that the messages aren't there, it's that you aren't processing them. In a standard application, Delphi creates a TApplication class and calls Application.Run. This IS the message processing loop for a normal app. It basically consists of:
If you want your service application to handle messages, you'll need to perform the same kind of work.
There's an example of using a service and handling PostThreadMessage dispatches here. Keep in mind, as Mick mentioned, you cannot use message handling between applications of differing security contexts (particularly in Vista). You should use named pipes or similar. Microsoft discusses this here.
Edit:
Based on the code snippet that you posted, you may just be fighting a threading issue. AllocHWnd is not thread safe. See here for a really detailed explanation of the issue and a version that works correctly in threads.
Of course, this still leads us back to why you aren't using PostThreadMessage instead. The way your code sample is structured, it would be trivial to make the message handling a function of the thread and then pass it down into the class for disposition.
感谢您的所有回答。 我想我们可以忘记这个问题。 我创建了一个新的服务应用程序并执行了快速的消息发布测试。 消息已正确传递,因此我希望现在可以声明,正常情况下一切正常,仅我描述的一项服务出现问题。 我知道这很愚蠢,但我只会尝试将代码片段从“坏”服务复制到新服务中。 也许这会帮助我找到问题的原因。
我希望我现在可以认为消息等待循环是不必要的,只要没有它一切都可以正常工作,不是吗?
如果说到权限,微软表示:“UAC使用WIM来阻止Windows消息在不同权限级别的进程之间发送”。 我的 Vista 的 UAC 已关闭,并且我没有为我描述的那些服务设置任何权限。 除此之外,我不在不同进程之间发送消息。 消息在一个进程内发送。
为了让您了解我正在做什么,我将向您展示测试服务应用程序中的代码片段。
这只是一个示例,但我的真实代码的运行基于相同的想法。 当您创建此类的对象时,它将创建一个线程,该线程将开始向该类发送消息。 Log.Log() 将数据保存到文本文件中。 当我在新的服务应用程序中使用此代码时,一切正常。 当我将其放入“损坏”服务时,它没有。 请注意,我不使用任何消息等待循环来接收消息。 我创建了一个新服务,然后将上面的代码放入其中,然后创建了该类的一个对象。 就这样。
如果我知道为什么这在“损坏的”服务中不起作用,我会写下来。
感谢您为我付出的时间。
马吕斯。
Thanks for all your answers. I think we can forget about the problem. I created a new service application and performed quick post message tests. The messages were delivered correctly, so I hope I can now state that normally everything works fine and something is wrong only with this one service I described. I know it is stupid, but I will just try to copy one fragment of code after another from the 'bad' service to a new one. Maybe this will help me find the reason of the problem.
I hope I can now consider the message-waiting loop unnecessary as long as everything works fine without it, can't I?
If it comes to the privileges, Microsoft says: "UAC uses WIM to block Windows messages from being sent between processes of different privilege levels.". My Vista's UAC is off and I did not set any privileges for those services I described. Apart from that I do not send messages between different processes. Messages are sent within one process.
To give you the idea of what I am doing, I'll show you a code snippet from a test service application.
This is only an example, but functioning of my real code bases on the same idea. When you create an object of this class, it will create a thread that will start sending messages to that class. Log.Log() saves data into a text file. When I use this code in a new service application, everything works fine. When i put it into the 'broken' service, it does not. Please note that I do not use any message-waiting loop to receive messages. I created a new service and just put the code above into it, then created an object of the class. That's all.
If I get to know why this does not work in the 'broken' service, I'll write about it.
Thanks for the time you devoted me.
Mariusz.
这是我要尝试的:
我需要更多信息才能进一步帮助您。
Here's what I would try:
I have to have more information to help you further.
我花了很长时间试图找出未收到消息的原因。 正如我在代码片段中所示,该类的构造函数创建了一个窗口句柄,我用它来发送消息。 只要该类是由主线程构造的,窗口句柄(如果我理解正确的话)就存在于主线程的上下文中,一切都工作正常,默认情况下,等待消息。 在“损坏的”服务中,正如我错误地调用的那样,我的类是由另一个线程创建的,因此句柄必须存在于该线程的上下文中。 因此,当我使用此窗口句柄发送消息时,它们是由该线程接收的,而不是由主线程接收的。 由于该线程没有任何消息等待循环,因此根本没有收到我的消息。
我只是不知道它是这样运作的。 为了以简单的方式解决问题,我在主线程中创建并销毁该类,即使我在第二个线程中使用它。
感谢您的时间以及您给我的所有信息。
I spent long hours trying to find the reason of the messages not being received. As I showed in my code snippet, the constructor of the class creates a window handle which I used to send messages to. As long as the class was constructed by the main thread, everything worked fine for the window handle (if I understand it correctly) existed in the context of the main thread which, by default, awaits messages. In the 'broken' service, as I called it by mistake, my class was created by another thread, so the handle must have existed in the context of that thread. Therefore, when I sent messages using this window handle, they were received by that thread, not by the main one. Because of the fact that this thread did not have any message-waiting loop, my messages were not received at all.
I just did not know it worked this way. To solve the problem in an easy way, I create and destroy the class in the main thread even though I use it in the second one.
Thanks for your time and all the information you gave me.
Mghie,我认为你是完全正确的。 我是这样实现消息等待循环的:
我是第一次这样做,所以请您检查一下代码是否正确? 事实上,这是我真实的类代码片段(日志将替换为真实代码)。 它处理重叠的通信端口。 有两个线程向上面的线程发送线程消息,通知它它们从通讯端口写入或接收了一些数据等。当线程收到这样的消息时,它会采取行动 - 它从队列中获取接收到的数据,线程首先将其放置在其中,然后调用外部方法来分析接收到的数据。 我不想透露细节,因为这并不重要:)。 我发送这样的线程消息:PostThreadMessage(MyThreadId, WM_DATA_READ, 0, 0)。
正如我所检查的,这段代码工作正常,但我想确保一切都正确,所以我向你询问这一点。 如果您能回答,我将不胜感激。
为了释放线程,我执行以下操作:
我希望这是结束消息循环的正确方法。
非常感谢您的帮助。
顺便说一句,我希望我的英语是可以理解的,不是吗? :) 抱歉,如果您很难理解我的意思。
Mghie, I think you are absolutely right. I implemented a message waiting loop this way:
I'm doing it for the first time, so please, could you check the code whether it is correct? In fact, this is my real class code snippet (the logs will be substituted with a real code). It handles overlapped comm port. There are two threads that send thread messages to the thread above, informing it that they wrote or received some data from comm port, etc. When the thread gets such a message, it takes an action - it gets the received data from a queue, where the threads first put it and then calls an external method that, lets say, analyses the received data. I don't want to go into details for it is unimportant :). I send thread messages like this: PostThreadMessage(MyThreadId, WM_DATA_READ, 0, 0).
This code works properly as I checked, but I would like to be sure everything is correct, so I'm asking you about that. I would be grateful if you answered.
To free the thread I do the following:
I hope this is the proper way to end the message loop.
Thank you very much for your help.
BTW, I hope my English language is understandable, isn't it? :) Sorry if you have difficulties understanding me.
线程中的消息循环有一个技巧。 Windows 不会立即为线程创建消息队列,因此有时向线程发布消息会失败。 详细信息位于此处。 在我的消息循环线程中,我使用 MS 提出的技术:
但在你的情况下,我强烈建议使用其他 IPC 方法。
There's one trick in message loops in threads. Windows won't create a message queue for a thread immediately so there will be some time when posting messages to a thread will fail. Details are here. In my msg loop thread I use the technique MS proposes:
But in your case I'd strongly recommend using other means of IPC.