65.9K
CodeProject 正在变化。 阅读更多。
Home

进程简介:异步进程通知

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (10投票s)

2000年5月17日

viewsIcon

183812

downloadIcon

2065

了解如何创建新进程以及如何有效地管理它们。

引言

论坛上经常出现的一个问题是:“我如何运行另一个程序?” 通常会附带一句“我读过有关进程的知识,但它们似乎太复杂了”。好吧,抱歉,你别无选择。你需要启动一个进程。本文讨论了几个进程管理的问题。本文主要为 C++/MFC 程序员而写。此讨论补充了我们在书中《Win32 Programming》的讨论,但您可以在不需要本书的情况下使用这里的信息。

创建进程

这有很多答案,取决于你需要完成什么。老式的 C 函数 spawnsystem 仍然有效。但是,它们被认为有些过时。它们无法提供你需要的控制来接收进程已完成的通知,因为你无法获得进程句柄。

用于启动进程的底层 Win32 API 调用是 ::CreateProcess。它还允许你为窗口应用程序指定窗口在屏幕上的显示位置。但是,::CreateProcess 是进程启动的最低级接口。Microsoft 建议你使用 ShellExecute,这仍然不够好;虽然它提供了与 Windows 环境最佳集成的、高级别的接口(例如,你可以给它一个 URL,如果 Internet Explorer 未运行,它会自动启动它,或者直接将请求发送到正在运行的实例),但它仍然无法提供接收通知所需的内容。

要确定进程是否已停止,你需要一个进程句柄。这是 Win32 用于将进程表示给应用程序的令牌。你可以使用 ::CreateProcess::ShellExecuteEx 来获取进程句柄。为了与 Windows 最佳集成,Microsoft 建议(敦促,要求)你使用 ::ShellExecute

以下是获取进程句柄并将其存储在变量 hProcess 中的两个示例:在这两种情况下,都使用要启动的程序名称和指向其命令行参数的指针来调用函数。如果没有参数,则参数指针可以为 NULL。函数返回已创建进程的 HANDLE,如果未能创建进程则返回 NULL。如果返回 NULL,调用者可以调用 ::GetLastError() 来确定出了什么问题。请注意,这些是“最基本”的启动器;如果你想要对位置、启动状态、控制台状态、初始视图等进行精细控制,你可以对这些方案进行主题和变体。如果你想启动一个控制台模式程序并向 stdin 馈送信息或从 stdout 接收数据,你将不得不使用 ::CreateProcess,但这将是另一篇文章的主题。

HANDLE launchViaCreateProcess(LPCTSTR program, LPCTSTR args)
{
    HANDLE hProcess = NULL;
    PROCESSINFO processInfo;
    STARTUPINFO startupInfo;
    ::ZeroMemory(&startupInfo, sizeof(startupInfo));
    startupInfo.cb = sizeof(startupInfo);
    if(::CreateProcess(program, (LPTSTR)args, 
                       NULL,  // process security
                       NULL,  // thread security
                       FALSE, // no inheritance
                       0,     // no startup flags
                       NULL,  // no special environment
                       NULL,  // default startup directory
                       &startupInfo,
                       &processInfo))
    { /* success */
        hProcess = processInfo.hProcess;
    } /* success */
    return hProcess;
}

HANDLE launchViaShellExecute(LPCTSTR program, LPCTSTR args)
{
    HANDLE hProcess = NULL;
    SHELLEXECUTEINFO shellInfo;
    ::ZeroMemory(&shellInfo, sizeof(shellInfo));
    shellInfo.cbSize = sizeof(shellInfo);
    shellInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
    shellInfo.lpFile = program;
    shellInfo.lpParameters = args;
    if(::ShellExecuteEx(&shellInfo))
    { /* success */
        hProcess = shellInfo.hProcess;
    } /* success */
    return hProcess;
}

Windows 的本质是,一个已启动的进程会独立运行。如果你有 Unix 背景,那么就没有类似 Unix 的“进程组”的东西。一旦进程启动,它就会独立运行。如果你保留进程句柄(以及窗口句柄,如果你获取了它),你就可以对其进行明确控制,但如果你的进程死掉了,你启动的任何进程都会继续运行。你的进程的存活或死亡对它们没有影响,除非它们在等待你的进程为它们做某事(例如提供 stdin 文本)。在这种情况下,它们不会死,但会阻塞等待所需事件。所以如果你想终止一个进程,你必须提供一种实现方法。

终止进程

你可能会认为终止进程的方法是调用显而易见的 API 调用 ::TerminateProcess。错误。坏主意。

当你调用 ::TerminateProcess 时,进程会停止。无论它在做什么,它都会立即死亡。如果它锁定了信号量或互斥量,或者正在内核代码中间,或者正在做其他重要的事情,那就太糟糕了。砰!进程消失。想象一下好莱坞大片中的大量特效,伴随着巨大的火球。不是一种美好的死亡方式。

进程应该始终有一个“干净”的关闭方式。如果你持有具有窗口的进程的句柄,你可以通过 PostMessage 向该窗口发送一个 WM_CLOSE 消息。如果它是一个控制台应用程序,你应该提供一种关闭它的方法,例如检测 stdin 上的 EOF,或接收特定的文本字符串。但不要使用 ::TerminateProcess,除非你愿意承担可能造成的严重后果。

何时停止?

通常,你希望启动一个进程(通常是控制台应用程序),让它运行直到完成。当它完成后,你就可以处理它的结果。例如,我有一个案例,我启动(最令人惊讶的是)一个 16 位编译器(它用汇编代码编写,而且,我对此毫无办法;我只是不得不在一个客户应用程序中使用它)。我用命令行启动它

compilername inputfile, listingfile, outputfile

在用户检查列表文件或下载输出文件之前,我必须等待它完成。

这是一个简单的情况,因为编译器处理非常小的程序,运行时间不到 5 秒。所以对于这个应用程序,我只需要等待它完成。

HANDLE process = launcher_of_your_choice(program, args);
if(process != NULL)
{ /* success */
    ::WaitForSingleObject(process, INFINITE);
    ::CloseHandle(process);
} /* success */

但是,并非所有程序都具有此属性。在这种情况下,你希望获得完成的异步通知。我通过一个看似复杂但实际上非常简单的方法来做到这一点:我启动一个线程,该线程阻塞在进程句柄上。当进程完成时,线程恢复执行,向我的主 GUI 窗口发送一条消息,然后终止。

我在这里重现 WaitInfo 类的代码,因为它非常小。这也是你可以从本网站下载的演示项目的一部分。链接在文章顶部。

// WaitInfo.h
class WaitInfo {
    public:
       WaitInfo() {hProcess = NULL; notifyee = NULL; }
       virtual ~WaitInfo() { }
       void requestNotification(HANDLE pr, CWnd * tell);
       static UINT UWM_PROCESS_TERMINATED;
    protected:
       HANDLE hProcess; // process handle
       CWnd * notifyee; // window to notify
       static UINT waiter(LPVOID p) { ((WaitInfo *)p)->waiter(); return 0; }
       void waiter();
};

/****************************************************************************
*                           UWM_PROCESS_TERMINATED
* Inputs:
*       WPARAM: ignored
*       LPARAM: Process handle of process
* Result: LRESULT
*       Logically void, 0, always
* Effect: 
*       Notifies the parent window that the process has been terminated
* Notes:
*       It is the responsibility of the parent window to perform a
*       ::CloseHandle operation on the handle. Otherwise there will be
*       a handle leak.
****************************************************************************/
#define UWM_PROCESS_TERMINATED_MSG \
          _T("UWM_PROCESS_TERMINATED-{F7113F80-6D03-11d3-9FDD-006067718D04}")


// WaitInfo.cpp
#include "stdafx.h"
#include "WaitInfo.h"
UINT WaitInfo::UWM_PROCESS_TERMINATED
                      = ::RegisterWindowMessage(UWM_PROCESS_TERMINATED_MSG);

/****************************************************************************
*                        WaitInfo::requestNotification
* Inputs:
*       HANDLE pr: Process handle
*    CWnd * wnd: Window to notify on completion
* Result: void
*       
* Effect: 
*       Spawns a waiter thread
****************************************************************************/
void WaitInfo::requestNotification(HANDLE pr, CWnd * wnd)
{
    hProcess = pr;
    notifyee = wnd;
    AfxBeginThread(waiter, this);
} // WaitInfo::requestNotification

/****************************************************************************
*                              WaitInfo::waiter
* Result: void
*       
* Effect: 
*       Waits for the thread to complete and notifies the parent
****************************************************************************/
void WaitInfo::waiter()
{
     ::WaitForSingleObject(hProcess, INFINITE);
     notifyee->PostMessage(UWM_PROCESS_TERMINATED, 0, (LPARAM)hProcess);
} // WaitInfo::waiter

其使用方式是:在创建进程后,调用 requestNotification 方法请求通知。你传递进程的句柄和要接收通知的窗口。当进程终止时,会向指定窗口发送一条通知消息。你必须有一个在调用 requestNotification 之前创建并且在接收到通知消息之前一直有效的 WaitInfo 对象;这意味着它不能是堆栈上的变量。在我提供的示例代码中,我将其放在启动程序的窗口类的类头中。

在我的类的头文件中,我添加了以下内容

WaitInfo requestor;
afx_msg LRESULT OnCompletion(WPARAM, LPARAM)

在窗口的 MESSAGE_MAP 中,你需要为处理程序添加一行。由于这使用了限定名称,ClassWizard 在情感上无法处理它,因此你必须按照所示方式将其放在 //}}AFX_MSG_MAP 行之后。

    //}}AFX_MSG_MAP
    ON_REGISTERED_MESSAGE(WaitInfo::UWM_PROCESS_COMPLETED, OnCompletion)
END_MESSAGE_MAP()

启动进程后,我执行

HANDLE process = launcher_of_your_choice(program, args);
if(process != NULL)
{ /* success */
    requestor.requestNotification(process, this);
} /* success */

处理程序非常简单

LRESULT CMyClass::OnCompletion(WPARAM, LPARAM lParam)
{
    // whatever you want to do here
    ::CloseHandle((HANDLE)lParam);
    return 0;
}

你可以在示例文件中研究我做了什么。

如果以上内容中有一些看起来令人困惑,你可能想阅读我关于消息管理工作线程的文章。


这些文章中表达的观点是作者的观点,不代表,也不被微软认可。

发送邮件至newcomer@flounder.com提出关于本文的问题或评论。
版权所有 © 1999The Joseph M. Newcomer Co.保留所有权利。
www.flounder.com/mvp_tips.htm
© . All rights reserved.