进程简介:异步进程通知






4.83/5 (10投票s)
2000年5月17日

183812

2065
了解如何创建新进程以及如何有效地管理它们。
引言
论坛上经常出现的一个问题是:“我如何运行另一个程序?” 通常会附带一句“我读过有关进程的知识,但它们似乎太复杂了”。好吧,抱歉,你别无选择。你需要启动一个进程。本文讨论了几个进程管理的问题。本文主要为 C++/MFC 程序员而写。此讨论补充了我们在书中《Win32 Programming》的讨论,但您可以在不需要本书的情况下使用这里的信息。
创建进程
这有很多答案,取决于你需要完成什么。老式的 C 函数 spawn
和 system
仍然有效。但是,它们被认为有些过时。它们无法提供你需要的控制来接收进程已完成的通知,因为你无法获得进程句柄。
用于启动进程的底层 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; }
你可以在示例文件中研究我做了什么。
如果以上内容中有一些看起来令人困惑,你可能想阅读我关于消息管理和工作线程的文章。
这些文章中表达的观点是作者的观点,不代表,也不被微软认可。