4.3 终止进程的运行

若要终止进程的运行,可以使用下面四种方法:

• 主线程的进入点函数返回(最好使用这个方法) 。

• 进程中的一个线程调用E x i t P r o c e s s函数(应该避免使用这种方法) 。

• 另一个进程中的线程调用Te r m i n a t e P r o c e s s函数(应该避免使用这种方法) 。

• 进程中的所有线程自行终止运行(这种情况几乎从未发生) 。

这一节将介绍所有这四种方法,并且说明进程结束时将会发生什么情况。

4.3.1 主线程的进入点函数返回

始终都应该这样来设计应用程序,即只有当主线程的进入点函数返回时,它的进程才终止运行。这是保证所有线程资源能够得到正确清除的唯一办法。

让主线程的进入点函数返回,可以确保下列操作的实现:

• 该线程创建的任何C + +对象将能使用它们的析构函数正确地撤消。

• 操作系统将能正确地释放该线程的堆栈使用的内存。

• 系统将进程的退出代码(在进程的内核对象中维护)设置为进入点函数的返值。

• 系统将进程内核对象的返回值递减1。

4.3.2 ExitProcess函数

当进程中的一个线程调用E x i t P r o c e s s函数时,进程便终止运行:

VOID ExitProcess(UINT fuExitCode)

该函数用于终止进程的运行,并将进程的退出代码设置为 f u E x i t C o d e。E x i t P r o c e s s函数并不返回任何值,因为进程已经终止运行。如果在调用 E x i t P r o c e s s之后又增加了什么代码,那么该代码将永远不会运行。

当主线程的进入点函数( Wi n M a i n、w Wi n M a i n、m a i n或w m a i n)返回时,它将返回给C / C + +运行期启动代码,它能正确地清除该进程使用的所有的C运行期资源。当C运行期资源被释放之后,C运行期启动代码就显式调用 E x i t P r o c e s s,并将进入点函数返回的值传递给它。这解释了为什么只需要主线程的进入点函数返回,就能够终止整个进程的运行。请注意,进程中运行的任何其他线程都随着进程而一道终止运行。

Windows Platform SDK文档声明,进程要等到所有线程终止运行之后才终止运行。就操作系统而言,这种说法是对的。但是, C / C + +运行期对应用程序采用了不同的规则,通过调用E x i t P r o c e s s,使得C / C + +运行期启动代码能够确保主线程从它的进入点函数返回时,进程便终止运行,而不管进程中是否还有其他线程在运行。不过,如果在进入点函数中调用 E x i t T h r e a d,而不是调用E x t i P r o c e s s或者仅仅是返回,那么应用程序的主线程将停止运行,但是,如果进程中至少有一个线程还在运行,该进程将不会终止运行。

注意,调用E x i t P r o c e s s或E x i t T h r e a d可使进程或线程在函数中就终止运行。就操作系统而言,这很好,进程或线程的所有操作系统资源都将被全部清除。但是, C / C + +应用程序应该避免调用这些函数,因为C / C + +运行期也许无法正确地清除。请看下面的代码:

只要让主线程的进入点函数返回, C / C + +运行期就能够执行它的清除操作,并且正确地撤消任何或所有的C + +对象。顺便讲一下,这个说明不仅仅适用于 C + +对象。C + +运行期能够代表进程执行许多操作,最好允许运行期正确地将它清除。

注意 显式调用E x i t P r o c e s s和E x i t T h r e a d是导致应用程序不能正确地将自己清除的常见原因。在调用E x i t T h r e a d时,进程将继续运行,但是可能会泄漏内存或其他资源。

4.3.3 Te r m i n a t e P r o c e s s函数

调用Te r m i n a t e P r o c e s s函数也能够终止进程的运行:

BOOL TerminateProcess(

_In_ HANDLE hProcess,

_In_ UINT uExitCode

);

该函数与E x i t P r o c e s s有一个很大的差别,那就是任何线程都可以调用 Te r m i n a t e P r o c e s s来终止另一个进程或它自己的进程的运行。 h P r o c e s s参数用于标识要终止运行的进程的句柄。当进程终止运行时,它的退出代码将成为你作为f u E x i t C o d e参数来传递的值。

只有当无法用另一种方法来迫使进程退出时,才应该使用 Te r m i n a t e P r o c e s s。终止运行的进程绝对得不到关于它将终止运行的任何通知,因为应用程序无法正确地清除,并且不能避免自己被撤消(除非通过正常的安全机制) 。例如,进程无法将内存中它拥有的任何信息迅速送往磁盘。

虽然进程确实没有机会执行自己的清除操作,但是操作系统可以在进程之后进行全面的清除,使得所有操作系统资源都不会保留下来。这意味着进程使用的所有内存均被释放,所有打开的文件全部关闭,所有内核对象的使用计数均被递减,同时所有的用户对象和 G D I对象均被撤消。

一旦进程终止运行(无论采用何种方法) ,系统将确保该进程不会将它的任何部分遗留下来。绝对没有办法知道该进程是否曾经运行过。进程一旦终止运行,它绝对不会留下任何蛛丝马迹。希望这是很清楚的。

注意 Te r m i n a t e P r o c e s s函数是个异步运行的函数,也就是说,它会告诉系统,你想要进程终止运行,但是当函数返回时,你无法保证该进程已经终止运行。因此,如果想要确切地了解进程是否已经终止运行,必须调用Wa i t F o r S i n g l e O b j e c t函数(第9章介绍)或者类似的函数,并传递进程的句柄。

进程中的线程何时全部终止运行。

如果进程中的所有线程全部终止运行(因为它们调用了 E x i t T h r e a d函数,或者因为它们已经用Te r m i n a t e P r o c e s s函数终止运行) ,操作系统就认为没有理由继续保留进程的地址空间。这很好,因为在地址空间中没有任何线程执行任何代码。当系统发现没有任何线程仍在运行时,它就终止进程的运行。出现这种情况时,进程的退出代码被设置为与终止运行的最后一个线程相同的退出代码。

4.3.4 进程终止运行时出现的情况

当进程终止运行时,下列操作将启动运行:

1) 进程中剩余的所有线程全部终止运行。

2) 进程指定的所有用户对象和G D I对象均被释放,所有内核对象均被关闭(如果没有其他进程打开它们的句柄,那么   这些内核对象将被撤消。但是,如果其他进程打开了它们的句柄,内核对象将不会撤消) 。

3) 进程的退出代码将从S T I L L _ A C T I V E改为传递给E x i t P r o c e s s或Te r m i n a t e P r o c e s s的代码。

4) 进程内核对象的状态变成收到通知的状态(关于传送通知的详细说明,参见第 9章) 。系统中的其他线程可以挂起,直到进程终止运行。

5) 进程内核对象的使用计数递减1。

注意,进程的内核对象的寿命至少可以达到进程本身那么长,但是进程内核对象的寿命可能大大超过它的进程寿命。当进程终止运行时,系统能够自动确定它的内核对象的使用计数。如果使用计数降为0,那么没有其他进程拥有该对象打开的句柄,当进程被撤消时,对象也被撤消。

不过,如果系统中的另一个进程拥有正在被撤消的进程的内核对象的打开句柄,那么该进程内核对象的使用计数不会降为 0。当父进程忘记关闭子进程的句柄时,往往就会发生这样的情况。这是个特性,而不是错误。记住,进程内核对象维护关于进程的统计信息。即使进程已经终止运行,该信息也是有用的。例如,你可能想要知道进程需要多少 C P U时间,或者,你想通过调用G e t E x i t C o d e P r o c e s s来获得目前已经撤消的进程的退出代码:

BOOL GetExitCodeProcess(

HANDLE hProcess,

PDWORD pdwExitCode;

该函数查看进程的内核对象(由h P r o c e s s参数来标识) ,取出内核对象的数据结构中用于标识进程的退出代码的成员。该退出代码的值在p d w E x i t C o d e参数指向的D W O R D中返回。

可以随时调用该函数。如果调用 G e t E x i t C o d e P r o c e s s函数时进程尚未终止运行,那么该函数就用S T I L L _ A C T I V E标识符(定义为0 x 1 0 3)填入D W O R D。如果进程已经终止运行,便返回数据的退出代码值。

也许你会认为,你可以编写代码,通过定期调用 G e t E x i t C o d e P r o c e s s函数并且检查退出代码来确定进程是否已经终止运行。大多数情况下,这是可行的,但是效率不高。下一段将介绍用什么正确的方法来确定进程何时终止运行。

再一次提醒你,应该通过调用 C l o s e H a n d l e函数,告诉系统你对进程的统计数据已经不再感兴趣。如果进程已经终止运行,C l o s e H a n d l e将递减内核对象的使用计数,并将它释放。

4.4 子进程

当你设计应用程序时,可能会遇到这样的情况,即想要另一个代码块来执行操作。通过调用函数或子例程,你可以一直象这样分配工作。当调用一个函数时,在函数返回之前,代码将无法继续进行操作。大多数情况下,需要实施这种单任务同步。让另一个代码块来执行操作的另一种方法是在进程中创建一个新线程,并让它帮助进行操作。这样,当其他线程在执行需要的操作时,代码就能继续进行它的处理。这种方法很有用,不过,当线程需要查看新线程的结果时,它会产生同步问题。

另一个解决办法是生成一个新进程,即子进程,以便帮助你进行操作。比如说,需要进行的操作非常复杂。若要处理该操作,只需要在同一个进程中创建一个新线程。你编写一些代码,对它进行测试,但是得到一些不正确的结果。也许你的算法存在错误,也可能间接引用的对象不正确,并且不小心改写了地址空间中的某些重要内容。进行操作处理时,如果要保护地址空间,方法之一是让一个新进程来执行这项操作。然后,在继续进行工作之前,可以等待新进程终止运行,或者可以在新进程工作时,继续进行工作。

不过,新进程可能需要对地址空间中包含的数据进行操作。这时最好让进程在它自己的地址空间中运行,并且只让它访问父进程地址空间中的相关数据,这样就能保护与手头正在执行的任务无关的全部数据。Wi n d o w s提供了若干种方法,以便在不同的进程中间传送数据,比如动态数据交换(D D E) 、O L E、管道和邮箱等。共享数据最方便的方法之一是,使用内存映射文件(关于内存映射文件的详细说明请参见第1 7章) 。

如果想创建新进程,让它进行一些操作,并且等待结果,可以使用类似下面的代码:

STARTUPINFO si = {sizeof(si)};

PROCESS_INFORMATION pi;

TCHAR szCommandLine[] = TEXT("NOTEPAD");

BOOL fSuccess = CreateProcess(NULL ,szCommandLine ,NULL ,NULL ,

FALSE ,0 ,NULL ,NULL ,&si ,&pi);

if(fSuccess)

{

CloseHandle(pi.hThread);

WaitForSingleObject(pi.hProcess ,INFINITE);

DWORD dwExitCode;

GetExitCodeProcess(pi.hProcess ,&dwExitCode);

CloseHandle(pi.hProcess);

}

第9章将全面介绍 Wa i t F o r S i n g l e O b j e c t函数。现在,必须知道的情况是,它会一直等到h O b j e c t参数标识的对象得到通知的时候。当进程对象终止运行时,它们才会得到通知。因此对Wa i t F o r S i n g l e O b j e c t的调用会将父进程的线程挂起,直到子进程终止运行。当Wa i t F o r S i n g l e O b j e c t返回时,通过调用G e t E x i t C o d e P r o c e s s函数,就可以获得子进程的退出代码。

在上面的代码段中调用C l o s e H a n d l e函数,可使系统为线程和进程对象的使用计数递减为0,从而使对象的内存得以释放。

你会发现,在这个代码段中,在C r e a t e P r o c e s s返回后,立即关闭了子进程的主线程内核对象的句柄。这并不会导致子进程的主线程终止运行,它只是递减子进程的主线程对象的使用计数。这种做法的优点是,假设子进程的主线程生成了另一个线程,然后主线程终止运行,这时,如果父进程不拥有子进程的主线程对象的句柄,那么系统就可以从内存中释放子进程的主线程对象。但是,如果父进程拥有子进程的线程对象的句柄,那么在父进程关闭句柄前,系统将不能释放该对象。

运行独立的子进程

大多数情况下,应用程序将另一个进程作为独立的进程来启动。这意味着进程创建和开始运行后,父进程并不需要与新进程进行通信,也不需要在完成它的工作后父进程才能继续运行。这就是E x p l o r e r的运行方式。当E x p l o r e r为用户创建一个新进程后,它并不关心该进程是否继续运行,也不在乎用户是否终止它的运行。

若要放弃与子进程的所有联系,E x p l o r e r必须通过调用C l o s e H a n d l e来关闭它与新进程及它的主线程之间的句柄。下面的代码示例显示了如何创建新进程以及如何让它以独立方式来运行:

CloseHandle(pi.hThread); CloseHandle(pi.hProcess);

4.5 枚举系统中运行的进程

许多软件开发人员都试图为 Wi n d o w s编写需要枚举正在运行的一组进程的工具或实用程序。Windows API原先没有用于枚举正在运行的进程的函数。不过, Windows NT一直在不断更新称为Performance Data的数据库。该数据库包含大量的信息,并且可以通过注册表函数来访问(比如以H K E Y _ P E R F O R M A N C E _ D ATA为根关键字的R e g Q u e r y Va l u e E x函数) 。由于下列原因,很少有Wi n d o w s程序员知道性能数据库的情况:

• 它没有自己特定的函数,它只是使用现有的注册表函数。

• Windows 95和Windows 98没有配备该数据库。

• 该数据库中的信息布局比较复杂,许多软件开发人员都不愿使用它。这妨碍了人们通过言传口说来传播它的存在。

为了使该数据库的使用变得更加容易, M i c r o s o f t开发了一组Performance Data Helper函数(包含在P D H . d l l文件中) 。若要了解它的详细信息,请查看 Platform SDK文档中的P e r f o r m a n c eData Helper的内容。

如前所述,Windows 95和Windows 98没有配备该数据库。不过它们有自己的一组函数,可以用于枚举关于它们的进程和信息。这些函数均在 ToolHelp API中。详细信息请参见Platform SDK文档中的P r o c e s s 3 2 F i r s t和P r o c e s s 3 2 N e x t函数。

更加有趣的是,M i c r o s o f t的Windows NT开发小组因为不喜欢To o l H e l p函数,所以没有将这些函数添加给 Windows NT。相反,他们开发了自己的 Process Status函数,用于枚举进程(这些函数包含在P S A P I . d l l文件中) 。关于这些函数的详细说明,请参见Platform SDK文档中的E n u m P r o c e s s e s函数。

M i c r o s o f t似乎使得工具和实用程序开发人员的日子很不好过,不过我高兴地告诉他们,M i c r o s o f t已经将To o l H e l p函数添加给Windows 2000。最后,开发人员终于有了一种方法,可以为Windows 95、Windows 98和Windows 2000编写具有公用源代码的工具和实用程序。

Windows核心编程 第四章 进程(下)相关推荐

  1. Windows核心编程 第四章 进程(上)

    第4章 进 程     本章介绍系统如何管理所有正在运行的应用程序.首先讲述什么是进程,以及系统如何创建进程内核对象,以便管理每个进程.然后将说明如何使用相关的内核对象来对进程进行操作.接着,要介绍进 ...

  2. Windows核心编程 第四章 进程(中)

    4.2 CreateProcess函数 可以用C r e a t e P r o c e s s函数创建一个进程: BOOL CreateProcessW( _In_opt_ LPCWSTR lpAp ...

  3. windows核心编程 第四章 进程

    获得当前目录: DWORD GetFullPathName(PCTSTR pszFile,DWORD cchPath,PTSTR pszPath,PTSTR *ppszFilePart); 获取C盘的 ...

  4. Windows核心编程(四)进程-1

    如何定义进程 进程一般定义为正在运行的程序的一个实例,由以下两部分组成: 一个内核对象,用于保存进程统计信息并管理进程 一的地址空间,其中包括所有可执行文件或动态链接库(DLL)模板的代码和数据,与此 ...

  5. Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)

    7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...

  6. Windows核心编程_修改其它进程里的内存值+示例:修改游戏分数

    最近一直忙于Opencv图像处理方面的学习,以及工作,没有更新C/C++专栏方面的博客了,所以今天就给大家写个应用层方面的编程代码,可用于参考学习,本篇博客将运用WindowsSDK库所提供的API来 ...

  7. Windows核心编程 第十七章 -内存映射文件(下)

    17.3 使用内存映射文件 若要使用内存映射文件,必须执行下列操作步骤: 1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件. 2) 创建一个文件映射内核对象,告诉系统该 ...

  8. Windows核心编程 第六章 线程基础知识 (下)

    6.6 线程的一些性质 到现在为止,讲述了如何实现线程函数和如何让系统创建线程以便执行该函数.本节将要介绍系统如何使这些操作获得成功. 图6 - 1显示了系统在创建线程和对线程进行初始化时必须做些什么 ...

  9. Windows核心编程 第25章 未处理异常和C ++异常(下)

    这一节东西比较少,本应该归并在上一节里,但是昨天太晚了.就先把那些东西分为上了.这节里面就一个问题,C++异常与结构性异常的对比(try和__try的区别): C++异常与结构性异常的对比 S E H ...

最新文章

  1. 深度学习-Tensorflow2.2-深度学习基础和tf.keras{1}-梯度下降算法概述-03
  2. 用 GDB 调试Linux程序及有用技巧
  3. vscode html 格式化_详解VSCode 格式化不符合预期的问题
  4. @ConfigurationProperties 在IDEA中出现红色波浪线问题
  5. 关系型数据库学习手记——初见倾心PostgreSQL、MySQL、SQLite、MongoDB
  6. Adobe Acrobat Reader离线安装包下载
  7. 海康球机3D定位功能编码实现VC(对前一篇补充改进)
  8. 微信服务号开发说明:测试号申请、自定义菜单添加第三方连接
  9. 从0到1构建基于Springboot+SpringCloud的微信点餐系统
  10. 切勿妄谈Hadoop,以及4个数据管道打造实践
  11. txt文本怎么去除重复项
  12. ImageWarping--反距离加权插值(IDW)方法实现及报告
  13. WPF实现仿Glitch闪烁特效
  14. 公众平台服务号、订阅号、企业微信以及小程序的相关说明
  15. 4.3.3 运行心跳任务
  16. 国足历届世界杯对战图关系
  17. flash +asp.net +fcs实现视频聊天
  18. PowerPivot
  19. python自动翻译视频字幕_python利用google翻译字幕文件方法代码示例
  20. 二维码制作完成后,还能修改二维码的内容吗?

热门文章

  1. 46.Express框架 GET 方法和POST 方法
  2. topcoder srm 325 div1
  3. python模块介绍-locustio:性能测试工具locustio
  4. python binary lib on win/各种python库的二进制包
  5. textview的基本设置
  6. Ubuntu 12.04 MySQL改utf-8 启动不了
  7. N!的尾部连续0的个数
  8. 对于python中的self,cls,decorator的理解
  9. UVA1601万圣节的早上
  10. python+pywinauto之PC端自动化一