Win32无窗口应用程序-等待程序退出

4

我有一个无窗口的应用程序,其唯一目的是安装一个32位的钩子DLL文件,并等待父程序(一个64位程序)退出。64位程序是用C#编写的,无窗口应用程序是用C++编写的。我最初有一个GetMessage循环来保持程序开启:

while(GetMessage(&msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

我在使用C#中的Process.Kill方法关闭C++应用程序时发现这并不能使C++应用程序干净地关闭。而且,如果C#应用程序崩溃,C++应用程序将永远保持打开状态。因此,我让C++应用程序使用以下循环检查C#应用程序是否仍在运行:

while(true)
{
    if(PeekMessage(&msg, NULL, 0, 0, true))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if(!isMainProgramRunning())
        break;

    Sleep(1000);
}

由于某些原因,Sleep会导致问题。DLL文件安装的钩子是WH_CBT和WH_KEYBOARD。每当我在C++应用程序运行此循环时按下键时,键就会被吞噬。删除Sleep使其正常工作,但是预期会使用100% CPU,这是我不想要的。我尝试完全删除消息循环,而是在线程上使用无限超时的WaitForSingleObject,该线程将在isMainProgramRunning返回false时结束。这基本上会锁定整个计算机。
我真的不明白为什么GetMessage从未返回,但据我所知,它会无限期地挂起主线程,但WaitForSingleObject会导致单击它时冻结每个应用程序。如何使C++应用程序保持打开状态,直到C#应用程序关闭?
编辑:
既然有人指出在消息泵中睡眠是不好的,那么让我问一下:是否有一种方法可以在等待消息时指定超时,以便程序不会无限期地等待消息,而是会等待大约250毫秒,超时,让我运行isMainProgramRunning方法,然后再等待?
编辑2:

我尝试使用MsgWaitForMultipleObjects,不过和Leo建议的方式有些不同。这是我使用的循环:

while(MsgWaitForMultipleObjects (0, NULL, true, 250, QS_ALLPOSTMESSAGE) != WAIT_FAILED)
{
    if(PeekMessage(&msg, NULL, 0, 0, true))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if(!isMainProgramRunning())
        break;
}

再次遇到了与“睡眠”有关的问题。我还尝试过挂起主线程并让其他线程恢复它,但是仍然有同样的问题。GetMessage 做了什么可以在等待时不会引起这些问题?也许这应该成为另一篇文章的主题,但是为什么安装钩子的 C++ 应用程序休眠或挂起时,钩子中的所有处理似乎也会暂停呢?
编辑3:
这是C++应用程序调用以安装钩子的 DLL 方法:
extern "C" __declspec(dllexport) void install()
{
    cbtHook = SetWindowsHookEx(WH_CBT, hookWindowEvents, hinst, NULL);

    if(cbtHook == NULL)
        MessageBox(NULL, "Unable to install CBT hook", "Error!", MB_OK);

    keyHook = SetWindowsHookEx(WH_KEYBOARD, LowLevelKeyboardProc, hinst, NULL);

    if(keyHook == NULL)
        MessageBox(NULL, "Unable to install key hook", "Error!", MB_OK);
}
4个回答

4
您有两个不同的问题:
  1. 如何使无窗口的C++程序在C#程序退出(例如崩溃)时自动退出。

    在C++程序中,打开到C#程序的句柄。由于C#程序运行了C++程序,因此可以让C#程序将其自己的PID作为参数传递;然后C++程序可以使用OpenProcess打开对该进程的句柄。

    然后在消息循环中使用MsgWaitForMultipleObjects。如果C#程序退出,则您必须使用该句柄发出信号并唤醒您。(您还可以使用WaitForSingleObject(hProcess,0)==WAIT_OBJECT_0来检查进程是否已发出信号,例如验证您为什么被唤醒,尽管MsgWaitForMultipleObjects结果也会告诉您。)

    在退出时应关闭处理句柄(虽然当您退出时操作系统会为您执行此操作,但在其他上下文中重新使用此代码时最好这样做)。请注意,该句柄存续时间长于它所表示的进程,这就是为什么您可以等待它的原因。

  2. 如何使C#程序命令C++程序退出。

    如果您成功完成#1,则可能不需要此操作,但如果需要,则可以让C#程序向C++程序发送一条消息。

    不要使用PostQuitMessage,也不要跨线程或进程发布或发送WM_QUIT。

    而是使用PostThreadMessage发布双方都同意的其他消息(例如WM_APP + 1)。


我尝试使用MsgWaitForMultipleObjects做了类似的事情,但遇到了同样的问题。请看我的第二次编辑。 - Telanor
@Telanor,尝试使用QS_ALLEVENTS而不是QS_ALLPOSTMESSAGE。 - Leo Davidson
我尝试了一下,发生了同样的事情。 - Telanor
@Telanor,你能展示一下你是如何安装这个钩子的吗?顺便提一句,如果有MsgWaitForMultipleObjects调用,它应该将进程句柄作为参数传递,这样循环就会在进程退出时自动唤醒,不过在基本循环正常工作之前我不会太担心这个。 - Leo Davidson

1
您可以创建一个带名称的事件并使用:

MsgWaitForMultipleObjects

在您的消息循环中。

C# 应用程序只需要打开并触发此事件,以告知您的应用程序退出。

这是一种最小化的进程间通信方式。


如果C#应用程序崩溃或被终止,该事件将永远不会被设置。最好等待进程句柄本身(请参见我的答案)。 - Leo Davidson
@Leo 是的,你说得对。如果 C# 应用程序需要在不退出自身的情况下关闭 C++ 应用程序,那么两者可能都很有用。 - pagra

0

您应该通过向进程发送WM_QUIT消息并正确处理它来退出进程,如Raymond Chen在这篇文章(Modality)中所述。不要在循环内睡眠而不处理消息-这是错误的。您的应用程序应该处理消息或等待新消息。


问题是 GetMessage 函数从未返回。一旦调用它,它就会一直等待。我无法成功让 C# 应用程序发送 WM_QUIT 消息,但即使我这样做了,在 C# 应用程序没有发送 WM_QUIT 消息的情况下(如果它崩溃了),C++ 应用程序也将永远等待。 - Telanor

-1

GetMessage 没有返回是因为您没有创建窗口!

要使用消息队列,您必须拥有一些 GUI。例如,您可以创建一个隐藏的窗口。


我有点怀疑这个。普通的无窗口应用程序如何保持打开状态? - Telanor
我最终决定这样做,因为GetMessage似乎是唯一不会破坏一切的等待函数。 - Telanor
3
在没有窗口的情况下,使用GetMessage是可以的。但我认为这不是答案。如果正确使用MsgWaitForMultipleObject函数,它也不会返回任何消息(除非线程需要唤醒)。MSDN上有关PostThreadMessage的文档明确提到了在没有窗口的情况下可以使用GetMessage函数:“接收到消息的线程通过调用GetMessage或PeekMessage函数检索该消息。返回的MSG结构体的hwnd成员为空。” - Leo Davidson
@Telanor:普通的无窗口应用程序如何保持开启状态?要么通过执行大量工作,但更可能是等待对象事件。如果您使用CreateProcess(...)启动C#程序,则可以使用CreateProcess在PROCESS_INFORMATION参数中返回的句柄等待该进程终止。 - engf-010
@Telanor:刚刚发现你的C#程序是主程序,所以请忽略我上次评论的第二部分。 - engf-010

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接