Windows viaC/C++:终止进程

来源:互联网 发布:本地音乐播放器 知乎 编辑:程序博客网 时间:2024/05/18 00:23

结束进程

进程可以以4种方式结束:

  • 主线程入口点函数(_tmain/_tWinMain)返回时结束,建议所有进程都以这种方式结束
  • 进程中的某线程调用了ExitThread函数,应当避免这种情况
  • 另一进程中的某线程调用了TerminateProcess函数,应当避免这种情况
  • 进程中的所有线程中止,这种情况比较罕见

下面我们来分别讨论这4种情形。

 

主线程入口点函数返回

在设计应用程序时,你应该保证其进程只有在主线程入口点函数返回时才终止,这也是确保能够正确释放主线程使用的所有资源的唯一途径。主线程入口点函数正常返回时,系统将会执行下面的动作:

  • 主线程创建的所有C++对象将被正确的销毁
  • 主线程堆栈占用的内存空间将被释放
  • 系统会把入口点函数的返回值作为进程的退出码写入进程内核对象中
  • 进程/主线程内核对象引用计数减1

调用ExitProcess函数

当进程中的任意线程调用ExitProcess函数时,进程将被终止。我们在前面的章节提过,C/C++运行时启动函数在主线程入口点函数(WinMain、wWinMain、main或wmain)返回时,会清理进程使用的所有C运行时资源,然后显式调用ExitProcess,并将入口点函数的返回值传给ExitProcess。这也解释了主线程入口点函数返回会导致进程中止的原因,注意此时进程中的所有线程也会随进程一起终止。

根据Windows PlatformSDK文档的说法,当进程中仍然有线程在执行时,进程不会终止。对于操作系统而言这是正确的,可是C/C++运行时的实现却并非如此:当主线程入口点函数返回时,无法有进程中是否有其它正在执行的线程,进程都会被ExitProcess函数终止。然而如果你在入口点函数中调用了ExitThread而不是将其返回或调用ExitProcess,主线程会停止执行,但进程会一直存在直到其中最后一个线程中止。

调用ExitThread和ExitProcess会导致线程或进程在函数内立刻终止,对于操作系统来说这无关紧要,因为系统会保证释放线程/进程用到的系统资源,但是,C/C++程序应该避免调用这些函数,C/C++运行时可能无法及时执行清理工作,比如下面的代码:

  1. #include   
  2. #include   
  3.   
  4. class CSomeObj  
  5. public 
  6.    CSomeObj()  printf("Constructor/r/n");  
  7.    ~CSomeObj() printf("Destructor/r/n");  
  8. };  
  9.   
  10.   
  11. CSomeObj g_GlobalObj;  
  12.   
  13. void main ()  
  14.    CSomeObj LocalObj;  
  15.    ExitProcess(0);    // This shouldn't be here  
  16.   
  17.    // At the end of this function, the compiler automatically added  
  18.    // the code necessary to call LocalObj's destructor.  
  19.    // ExitProcess prevents it from executing.  
  20.  
#include class CSomeObj { public: CSomeObj() {printf("Constructor/r/n"); } ~CSomeObj() {printf("Destructor/r/n"); } }; CSomeObj g_GlobalObj; void main () {CSomeObj LocalObj; ExitProcess(0); // This shouldn't be here // Atthe end of this function, the compiler automatically added // thecode necessary to call LocalObj's destructor. // ExitProcessprevents it from executing.}  执行上面的代码时,输出如下:
  1. Constructor  
  2. Constructor  
 代码创建了两个对象,一个全局对象一个局部对象。然而,ExitProcess的调用导致对象没有被正确的析构,在移除ExitProcess之后,程序输出正常结果:
[c-sharp] view plaincopyprint?
  1. Constructor  
  2. Constructor  
  3. Destructor  
  4. Destructor  
  

调用TerminateProcess函数

TerminateProcess也会导致进程中止:

[c-sharp] view plaincopyprint?
  1. BOOL TerminateProcess(HANDLE hProcess, UINT fuExitCode);  
  与ExitProcess最大的不同在于,TerminateProcess除了可以中止当前进程,还可以中止其它进程。参数hProcess指定了要终止的进程句柄,其退出码由fuExitCode指定。当你无法使用别的方法结束某进程时,你才应该调用TerminateProcess。被结束的进程不会得到任何关于它将被终止的通知,它也没机会做任何清理工作。然而进程用到的系统资源将被系统释放,记住,当进程终止时,它用到的一切资源都会回收。TerminateProcess是异步方法,这意味着它返回时目标进程可能尚未被终止,因此你可能需要调用WaitForSingleObject等待其结束。

所有线程中止

当进程内的所有线程终止时,操作系统将销毁进程地址空间,进程的退出码将被设置为最后一个线程的退出码。

进程终止时发生了什么

当进程终止时,下面的动作会被触发:

  • 进程内的所有线程将被终止
  • 进程创建的所有的用户对象和GUI对象将被释放,其引用的所有内核对象引用计数减1
  • 进程的退出码从STILL_ACTIVE变为ExitProcess或TerminateProcess函数的相应参数
  • 进程的内核对象状态变成signaled(关于内核对象状态,可参阅第9章)
  • 进程的内核对象引用计数减1

 

注意进程内核对象的生命周期和进程的生命周期并不完全相同,进程终止只是将其内核对象的引用计数减1,如果此时另外的进程仍持有该对象的句柄,那么进程内核对象并不会销毁。这通常发生在父进程忘记关闭子进程的句柄时,这是一个特性,而不是BUG。进程内核对象维护着进程的统计信息,这些信息在进程终止之后依然是可用的。比如,你可以获得进程占用的CPU时间总数,或者用GetExitCodeProcess获得其退出码:

[c-sharp] view plaincopyprint?
  1. BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pdwExitCode);  
  当目标进程尚未结束时,GetExitCodeProcess的pdwExitCode会返回STILL_ACTIVE,否则返回其退出码。你可能会想到可以在一个循环中使用GetExitCodeProcess以判断某进程是否结束,这的确可以,不过显然效率比较低,在下一节我会提到一种更恰当的判断进程是否结束的方法。

再说一遍,当你对进程的统计信息不再感兴趣时,要记得调用CloseHandle关闭内核对象句柄,否则进程内核对象会一直存在。

原创粉丝点击