您的位置:首页 > 编程语言

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

2016-08-28 18:27 225 查看

4.2 CreateProcess函数

    可以用C r e a t e P r o c e s s函数创建一个进程:

 BOOL CreateProcessW(

    _In_opt_ LPCWSTR lpApplicationName,

    _Inout_opt_ LPWSTR lpCommandLine,

    _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,

    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,

    _In_ BOOL bInheritHandles,

    _In_ DWORD dwCreationFlags,

    _In_opt_ LPVOID lpEnvironment,

    _In_opt_ LPCWSTR lpCurrentDirectory,

    _In_ LPSTARTUPINFOW lpStartupInfo,

    _Out_ LPPROCESS_INFORMATION lpProcessInformation

);

    当一个线程调用C r e a t e P r o c e s s时,系统就会创建一个进程内核对象,其初始使用计数是
1。该进程内核对象不是进程本身,而是操作系统管理进程时使用的一个较小的数据结构。可以将进程内核对象视为由进程的统计信息组成的一个较小的数据结构。然后,系统为新进程创建一个虚拟地址空间,并将可执行文件或任何必要的
D L L文件的代码和数据加载到该进程的地址空间中。

    然后,系统为新进程的主线程创建一个线程内核对象(其使用计数为 1) 。与进程内核对象一样,线程内核对象也是操作系统用来管理线程的小型数据结构。通过执行
C / C + +运行期启动代码,该主线程便开始运行,它最终调用
Wi n M a i n、w Wi n M a i n、m a i n或w
m a i n函数。如果系统成功地创建了新进程和主线程,C r e a t e P r o c e s s便返回T R U E。

    注意
在进程被完全初始化之前,C r e a t e P r o c e s s返回T R U E。这意味着操作系统加载程序尚未试图找出所有需要的
D L L。如果一个D L L无法找到,或者未能正确地初始化,那么该进程就终止运行。由于
C r e a t e P r o c e s s返回T R U E,因此父进程不知道出现的任何初始化问题。

    这就是总的概述。下面各节将分别介绍C r e a t e P r o c e s s的各个参数。

4.2.1 pszApplicationName和p s z C o m m a n d L i n e

    p s z A p p l i c a t i o n N a m e和p s z C o m m a n d L i n e参数分别用于设定新进程将要使用的可执行文件的名字和传递给新进程的命令行字符串。下面首先让我们谈一谈
p s z C o m m a n d L i n e参数。

    注意
请注意,p s z C o m m a n d L i n e参数的原型是P T S T R。这意味着C r e a t e P r o c e s s期望你将传递一个非常量字符串的地址。从内部来讲,
C r e a t e P r o c e s s实际上并不修改你传递给它的命令行字符串。不过,在C r e a t e P r o c e s s返回之前,它将该字符串恢复为它的原始形式。

    这个问题很重要,因为如果命令行字符串不包含在文件映象的只读部分中,就会出现违规访问的问题。例如,下面的代码就会导致违规访问的问题,因为 Visual C++将“N O T E PA D”字符串放入了只读内存:

STARTUPINFO si = {sizeof(si)};

PROCESS_INFORMATION pi;

    CreateProcess(NULL ,TEXT("NOTEPAD"),NULL ,NULL ,

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

当C r e a t e P r o c e s s试图修改该字符串时,就会发生违规访问(较早的
Visual C++版本将该字符串放入读/写内存,因此调用C r e a t e P r o c e s s不会导致违规访问的问题)


解决这个问题的最好办法是在调用 C r e a t e P r o c e s s之前像下面这样将常量字符串拷贝到临时缓存中:

STARTUPINFO si = {sizeof(si)};

PROCESS_INFORMATION pi;

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

CreateProcess(NULL ,szCommandLine ,NULL ,NULL ,

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

    也可以考虑使用Visual C++的/ G f和/ G F编译器开关,这些开关用于控制重复字符串的删除和确定这些字符串是否被放入只读内存部分(另外请注意,
/ Z I开关允许使用Visual Studio的Edit &Continue调试特性,它包含了/
G F开关的功能) 。能做的最好工作是使用/ G F编译器开关和临时缓存。M i c r o s o f t能做的最好事情是安装好C
r e a t e - P r o c e s s,使它能够制作一个该字符串的临时拷贝,这样我们就不必进行这项操作。也许将来的Wi n d o w s版本能够做到这一点。

    另外,如果调用Windows 2000上的C r e a t e P r o c e s s的A
N S I版本,就不会违规访问,因为系统已经制作了一个命令行字符串的临时拷贝(详细信息请见第
2章) 。

 


    可以使用p s z C o m m a n d L i n e参数设定一个完整的命令行,以便
C r e a t e P r o c e s s用来创建新进程。当C r e a t e P r o c e s s分析p
s z C o m m a n d L i n e字符串时,它将查看字符串中的第一个标记,并假设该标记是想运行的可执行文件的名字。如果可执行文件的文件名没有扩展名,便假设它的扩展名为. e x e。C r
e a t e P r o c e s s也按下面的顺序搜索该可执行文件:

1) 包含调用进程的. e x e文件的目录。

2) 调用进程的当前目录。

3) Wi n d o w s的系统目录。

4) Wi n d o w s目录。

5) PAT H环境变量中列出的目录。

    当然,如果文件名包含全路径,系统将使用全路径来查看可执行文件,并且不再搜索这些目录。如果系统找到了可执行文件,那么它就创建一个新进程,并将可执行文件的代码和数据映射到新进程的地址空间中。然后系统将调用 C / C + +运行期启动例程。正如前面我们讲过的那样,C
/ C + +运行期启动例程要查看进程的命令行,并将地址作为 ( w ) Wi n M a i n的p s z C m d L i n e参数传递给可执行文件的名字后面的第一个参数。

这一切都是在p s z A p p l i c a t i o n N a m e参数是N U L L(9
9 %以上的时候都应该属于这种情况)时发生的。如果不传递N U L L,可以将地址传递给p s z A p p l i c a t i o n N a m e参数中包含想运行的可执行文件的名字的字符串。请注意,必须设定文件的扩展名,系统将不会自动假设文件名有一个.
e x e扩展名。C r e a t e P r o c e s s假设该文件位于当前目录中,除非文件名前面有一个路径。如果在

当前目录中找不到该文件,C r e a t e P r o c e s s将不会在任何其他目录中查找该文件,它运行失败了。

    但是,即使在 p s z A p p l i c a t i o n N a m e参数中设定了文件名,
C r e a t e P r o c e s s也会将p s z C o m m a n d L i n e参数的内容作为它的命令行传递给新进程。例如,可以像下面这样调用

C r e a t e P r o c e s s :

 


    系统启动N o t e p a d应用程序,但是N o t e p a d的命令行是W
O R D PAD README.TXT。这种变异情况当然有些奇怪,不过这正是
C r e a t e P r o c e s s运行的样子。这个由p s z A p p l i c a t i o n N a m e参数提供的能力实际上被添加给了C
r e a t e P r o c e s s,以支持Windows 2000的P O S I X子系统。

4.2.2 psaProcess、p s a T h r e a d和b i n h e r i t H a n d l e s

    若要创建一个新进程,系统必须创建一个进程内核对象和一个线程内核对象(用于进程的主线程)
,由于这些都是内核对象,因此父进程可以得到机会将安全属性与这两个对象关联起来。可以使用p s a P r o c e s s和p s a T h r e a d参数分别设定进程对象和线程对象需要的安全性。可以为这些参数传递N
U L L,在这种情况下,系统为这些对象赋予默认安全性描述符。也可以指定两个S E C U R I T Y _ AT T R I B U T E S结构,并对它们进行初始化,以便创建自己的安全性权限,并将它们赋予进程对象和线程对象。

    将S E C U R I T Y _ AT T R I B U T E S结构用于p s a P r o c e s s和p
s a T h r e a d参数的另一个原因是,父进程将来生成的任何子进程都可以继承这两个对象句柄中的任何一个(第
3章已经介绍了内核对象句柄的继承性的有关理论) 。

    清单4 - 1显示了一个说明内核对象继承性的简单程序。假设
Process A创建了Process B,方法是调用C r e a t e P r o c e s s,为p
s a P r o c e s s参数传递一个S E C U R I T Y _ AT T R I B U T E S结构的地址,在这个结构中,b I n h e r i t H a n d l e s成员被置为T
R U E。在同样这个函数调用中,p s a T h r e a d参数指向另一个S E C U R I T Y _ AT T R I B U T E S结构,在这个结构中,b
I n h e r i t H a n d l e s成员被置为FA L S E。

    当系统创建Process B时,它同时指定一个进程内核对象和一个线程内核对象,并且将句柄返回给p p i P r o c I n f o参数(很快将介绍该参数)指向的结构中的Process
A。这时,使用这些句柄,Process A就能够对新创建的进程对象和线程对象进行操作。

    现在,假设Process A第二次调用C r e a t e P r o c e s s函数,以便创建Process
C。Process A可以决定是否为Process C赋予对Process
A能够访问的某些内核对象进行操作的能力。B I n h e r i t H a n d l e s参数可以用于这个目的。如果b I n h e r i t H a n d l e s被置为T
R U E,系统就使Process C继承Process A中的任何可继承句柄。在这种情况下,
Process B的进程对象的句柄是可继承的。无论C r e a t e P r o c e s s的b
I n h e r i t H a n d l e s参数的值是什么,Process B的主线程对象的句柄均不能继承。同样,如果Process
A调用C r e a t e P r o c e s s,为b I n h
e r i t H a n d l e s传递FA L S E,那么Process
C将不能继承Process A目前使用的任何句柄。

4.2.3 fdwCreate

    f d w C r e a t e参数用于标识标志,以便用于规定如何来创建新进程。如果将标志逐位用
O R操作符组合起来的话,就可以设定多个标志。

    • E B U G _ P R O C E S S标志用于告诉系统,父进程想要调试子进程和子进程将来生成的任何进程。本标志还告诉系统,当任何子进程(被调试进程)中发生某些事件时,将情况通知父进程(这时是调试程序) 。

    • D E B U G _ O N LY _ T H I S _ P R O C E S S标志与D E B U G _ P R O C E S S标志相类似,差别在于,调试程序只被告知紧靠父进程的子进程中发生的特定事件。如果子进程生成了别的进程,那么将不通知调试程序在这些别的进程中发生的事件。

    • C R E AT E _ S U S P E N D E D标志可导致新进程被创建,但是,它的主线程则被挂起。这使得父进程能够修改子进程的地址空间中的内存,改变子进程的主线程的优先级,或者在进程有机会执行任何代码之前将进程添加给一个作业。一旦父进程修改了子进程,父进程将允许子进程通过调用R
e s u m e T h r e a d函数来执行代码(第7章将作详细介绍) 。

    • D E TA C H E D _ P R O C E S S标志用于阻止基于C U I的进程对它的父进程的控制台窗口的访问,并告诉系统将它的输出发送到新的控制台窗口。如果基于
C U I的进程是由另一个基于C U I的进程创建的,那么按照默认设置,新进程将使用父进程的控制台窗口(当通过命令外壳程序来运行C编译器时,新控制台窗口并不创建,它的输出将被附加在现有控制台窗口的底部)
。通过设定本标志,新进程将把它的输出发送到一个新控制台窗口。

    • C R E AT E _ N E W _ C O N S O L E标志负责告诉系统,为新进程创建一个新控制台窗口。如果同时设定C R E AT E _ N E W _ C O N S O L E和D
E TA C H E D _ P R O C E S S标志,就会产生一个错误。

    • C R E AT E _ N O _ W I N D O W标志用于告诉系统不要为应用程序创建任何控制台窗口。可以使用本标志运行一个没有用户界面的控制台应用程序。

    • C R E AT E _ N E W _ P R O C E S S _ G R O U P标志用于修改用户在按下
C t r l + C或C t r l + B r e a k键时得到通知的进程列表。如果在用户按下其中的一个组合键时,你拥有若干个正在运行的C
U I进程,那么系统将通知进程组中的所有进程说,用户想要终止当前的操作。当创建一个新的C U I进程时,如果设定本标志,可以创建一个新进程组。如果该进程组中的一个进程处于活动状态时用户按下C t r l
+ C或C t r l _ B r e a k键,那么系统只通知用户需要这个进程组中的进程。

    • C R E AT E _ D E FA U LT _ E R R O R _ M O D E标志用于告诉系统,新进程不应该继承父进程使用的错误模式(参见本章前面部分中介绍的S e t E r r o r M o d
e函数) 。

    • C R E AT E _ S E PA R AT E _ W O W _ V D M标志只能当你在Windows 2000上运行1
6位Wi n d o w s应用程序时使用。它告诉系统创建一个单独的
D O S虚拟机(V D M) ,并且在该V D M中运行1
6位Wi n d o w s应用程序。按照默认设置,所有
1 6位Wi n d o w s应用程序都在单个共享的V D M中运行。在单独的VDM
中运行应用程序的优点是,如果应用程序崩溃,它只会使单个V D M停止工作,而在别的
V D M中运行的其他程序仍然可以继续正常运行。另外,在单独的V D M中运行的1 6位Wi
n d o w s应用程序有它单独的输入队列。这意味着如果一个应用程序临时挂起,在各个V D M中的其他应用程序仍然可以继续接收输入信息。运行多个V D M的缺点是,每个V
D M都要消耗大量的物理存储器。Windows 98在单个V D M中运行所有的1
6位Wi n d o w s应用程序,不能改变这种情况。

    • C R E AT E _ S H A R E D _ W O W _ V D M标志只能当你在Windows 2000上运行1
6位Wi n d o w s应用程序时使用。按照默认设置,除非设定了
C R E AT E _ S E PA R AT E _ W O W _ V D M标志,否则所有
1 6位Wi n d o w s应用程序都必须在单个
V D M中运行。但是,通过在注册表中将

H K E Y _ L O C A L _ M A C H I N E \ s y s t e m \ C u r r e n t C o n t r o l S e t \ C o n t r o l \ W O W下的D e f a u l t S e p a r a t eV D M设置为“
y e s” ,就可以改变该默认行为特性。这时,
C R E AT E _ S H A R E D _W O W _ V D M标志就在系统的共享V D M中运行1 6位Wi
n d o w s应用程序。

    • C R E AT E _ U N I C O D E _ E N V I R O N M E N T标志用于告诉系统,子进程的环境块应该包含U n i c o d e字符。按照默认设置,进程的环境块包含的是A
N S I字符串。

    • C R E AT E _ F O R C E D O S标志用于强制系统运行嵌入1 6位O
S / 2应用程序的M O S - D O S应用程序。

    • C R E AT E _ B R E A K AWAY _ F R O M _ J O B标志用于使作业中的进程生成一个与作业相关联的新进程(详细信息见第5章)


f d w C r e a t e参数也可以用来设定优先级类。不过用不着这样做,并且对于大多数应用程序来说不应该这样做,因为系统会为新进程赋予一个默认优先级。表4 - 5显示了各种可能的优先级类别。





    这些优先级类将会影响进程中包含的线程如何相对于其他进程的线程来进行调度。详细说明请见第7章。

注意 B E L O W _ N O R M A L _ P R I O R I T Y _ C L A S S和A B O V E _ N O R M A L _ P R I O R I T Y _ C L A S S这两个优先级类在Windows
2000中是新类,Windows NT 4(或更早的版本)、Windows
95或Windows 98均不支持这两个类。

4.2.4 pvEnvironment

    p v E n v i r o n m e n t参数用于指向包含新进程将要使用的环境字符串的存块。在大多数情况下,为该参数传递N U L L,使子进程能够继承它的父进程正在使用的一组环境字符串。也可以使用G
e t E n v i r o n m e n t S t r i n g s函数

PVOID GetEnvironmentStrings();

该函数用于获得调用进程正在使用的环境字符串数据块的地址。可以使用该函数返回的地址,作为C r e a t e P r o c e s s的p v E n v i r o n m e n t参数。如果为p
v E n v i r o n m e n t参数传递N U L L,那么这正是C r e a t e P r o c e s s函数所做的操作。当不再需要该内存块时,应该调用
F r e e E n v i r o n m e n t S t r i n g s函数将内存块释放:

BOOL FreeEnviromentStrings(PTSTR pszEnviromentBlock);

4.2.5 pszCurDir

    p s z C u r D i r参数允许父进程设置子进程的当前驱动器和目录。如果本参数是
N U L L,则新进程的工作目录将与生成新进程的应用程序的目录相同。如果本参数不是
N U L L,那么p s z C u r D i r必须指向包含需要的工作驱动器和工作目录的以
0结尾的字符串。注意,必须设定路径中的驱动器名。

4.2.6 psiStartInfo

p s i S t a r t I n f o参数用于指向一个S TA RT U P I N F O结构:

typedef struct _STARTUPINFOW {

    DWORD   cb;

    LPWSTR  lpReserved;

    LPWSTR  lpDesktop;

    LPWSTR  lpTitle;

    DWORD   dwX;

    DWORD   dwY;

    DWORD   dwXSize;

    DWORD   dwYSize;

    DWORD   dwXCountChars;

    DWORD   dwYCountChars;

    DWORD   dwFillAttribute;

    DWORD   dwFlags;

    WORD    wShowWindow;

    WORD    cbReserved2;

    LPBYTE  lpReserved2;

    HANDLE  hStdInput;

    HANDLE  hStdOutput;

    HANDLE  hStdError;

} STARTUPINFOW, *LPSTARTUPINFOW;

    当Wi n d o w s创建新进程时,它将使用该结构的有关成员。大多数应用程序将要求生成的应用程序仅仅使用默认值。至少应该将该结构中的所有成员初始化为零,然后将
c b成员设置为该结构的大小:

STARTUPINFO si = {sizeof(si)};

    如果未能将该结构的内容初始化为零,那么该结构的成员将包含调用线程的堆栈上的任何无用信息。将该无用信息传递给C r e a t e P r o c e s s,将意味着有时会创建新进程,有时则不能创建新进程,完全取决于该无用信息。有一点很重要,那就是将该结构的未用成员设置为零,这样,C
r e a t e P r o c e s s就能连贯一致地运行。不这样做是开发人员最常见的错误。

    这时,如果想要对该结构的某些成员进行初始化,只需要在调用 C r e a t e P r o c e s s之前进行这项操作即可。我们将依次介绍每个成员。有些成员只有在子应用程序创建一个重叠窗口时才有意义,而另一些成员则只有在子应用程序执行基于
C U I的输入和输出时才有意义。下表描述了每个成员的作用。

 




     现在介绍d w F l a g s的成员(其实这个参数目前没在函数定义里看到,我现在是没有看到单独的d
w F l a g s和上面的fdwCreate而是一个新的dwCreationFlags)。该成员包含一组标志,用于修改如何来创建子进程。大多数标志只是告诉C
r e a t e P r o c e s s,S TA RT U P I N F O结构的其他成员是否包含有用的信息,或者某些成员是否应该忽略。下表标出可以使用的标志及其含义。

 


    另外还有两个标志,即 S TA RT F _ F O R C E O N F E E D B A C K和S TA RT F _+F
O R C E O F F F -E E D B A C K,当启动一个新进程时,它们可以用来控制鼠标的光标。由于
Wi n d o w s支持真正的多任务抢占式运行方式,因此可以启动一个应用程序,然后在进程初始化时,使用另一个程序。为了向用户提供直观的反馈信息,
C r e a t e P r o c e s s能够临时将系统的箭头光标改为一个新光标,即沙漏箭头光标:

    该光标表示可以等待出现某种情况,也可以继续使用系统。当启动另一个进程时,C r e a t e P r o c e s s函数使你能够更好地控制光标。当设定S TA RT F _ F O R C E O F F F E E D B
A C K标志时,C r e a t e P r o c e s s并不将光标改为沙漏。

    S TA RT F _ F O R C E O N F E E D B A C K可使C r e a t e P r o c e s s能够监控新进程的初始化,并可根据结果来改变光标。当使用该标志来调用
C r e a t e P r o c e s s时,光标改为沙漏。过2 s后,如果新进程没有调用G U I,CreateProcess
将光标恢复为箭头。

    如果该进程在2 s内调用了G U I,C r e a t e P r o c
e s s将等待该应用程序显示一个窗口。这必须在

    进程调用G U I后5 s内发生。如果没有显示窗口,
C r e a t e P r o c e s s就会恢复原来的光标。如果显示了一个窗口,
C r e a t e P r o c e s s将使沙漏光标继续保留
5 s。如果某个时候该应用程序调用了G e t M e s s a g e函数,指明它完成了初始化,那么C r e a t
e P r o c e s s就会立即恢复原来的光标,并且停止监控新进程。

    在结束这一节内容的介绍之前,我想讲一讲S TA RT U P I N F O的w S h o w Wi n d o w成员。你将该成员初始化为传递给(
w ) Wi n M a i n的最后一个参数n C m d S h o w的值。该成员显示你想要传递给新进程的( w ) Wi n M a i n函数的最后一个参数n
C m d S h o w的值。它是可以传递给S h o w Wi n d o w函数的标识符之

一。通常,n C m d S h o w的值既可以是S W _ S H O W N O R M A L,也可以是SW_
SHOWMINNOACTIVE。但是,它有时可以是S W _ S H O W D E FA U LT。

    当在E x p l o r e r中启动一个应用程序时,该应用程序的
( w ) Wi n M a i n函数被调用,而S W _ S H O W N O R M A L则作为n C m d S
h o w参数来传递。如果为该应用程序创建了一个快捷方式,可以使用快捷方式的属性页来告诉系统,应用程序的窗口最初应该如何显示。下图显示了运行N o t e p a d的快捷方式的属性页。注意,使用R u
n选项的组合框,就能够设定如何显示N o t e p a d的窗口。

  


    当使用 E x p l o r e r来启动该快捷方式时,E x p l o r e r会正确地准备S
TA RT U P I N F O结构并调用C r e a t e P r o c e s s。这时N o t e p a d开始运行,并且为n
C m d S h o w参数将S W _ S H O W M I N N O A C T I V E传递给它的( w ) Wi n M a i n函数。运用这样的方法,用户能够很容易地启动一个应用程序,其主窗口可以用正常状态、最小或最大状态进行显示。

    最后,应用程序可以调用下面的函数,以便获取由父进程初始化的 S TA RT U P I N F O结构的拷贝。子进程可以查看该结构,并根据该结构的成员的值来改变它的行为特性。

    VOID GetStartupInfo(LPSTARTUPINFO pStartupInfo);

注意
虽然Wi n d o w s文档没有明确地说明,但是在调用G e t S t a r t I n f o函数之前,必须像下面这样对该结构的c b成员进行初始化:

STARTUPINFO si = {sizeof(si)};

GetStartupInfo(&si);

4.2.7 ppiProcInfo

    p p i P r o c I n f o参数用于指向你必须指定的P R O C E S S _ I N F O R M AT I O N结构。C
r e a t e P r o c e s s在返回之前要对该结构的成员进行初始化。该结构的形式如下面所示:

typedef struct _PROCESS_INFORMATION {

   HANDLE hProcess;

   HANDLE hThread;

   DWORD dwProcessId;

   DWORD dwThreadId;

}PROCESS_INFORMATION,*PPROCESS_INFORMATION,*LPPROCESS_INFORMATION;

    如前所述,创建新进程可使系统建立一个进程内核对象和一个线程内核对象。在创建进程的时候,系统为每个对象赋予一个初始使用计数值 1。然后,在c r e a t e P r o c e s s返回之前,该函数打开进程对象和线程对象,并将每个对象的与进程相关的句柄放入
P R O C E S S _ I N F O R M AT I O N结构的h P r o c e s s和h T h r
e a d成员中。当C r e a t e P r o c e s s在内部打开这些对象时,每个对象的使用计数就变为2。

    这意味着在系统能够释放进程对象前,该进程必须终止运行(将使用计数递减为 1) ,并且父进程必须调用C l o s e H a n d l e(再将使用计数递减1,使之变为0)
。同样,若要释放线程对象,该线程必须终止运行,父进程必须关闭线程对象的句柄(关于释放线程对象的详细说明,请参见本章后面“子进程”一节的内容) 。

    注意
必须关闭子进程和它的主线程的句柄,以避免在应用程序运行时泄漏资源。当然,当进程终止运行时,系统会自动消除这些泄漏现象,但是,当进程不再需要访问子进程和它的线程时,编写得较好的软件能够显式关闭这些句柄(通过调用C l o s e H a n d l e函数来关闭) 。不能关闭这些句柄是开发人员最常犯的错误之一。由于某些原因,许多开发人员认为,关闭进程或线程的句柄,会促使系统撤消该进程或线程。实际情况并非如此。关闭句柄只是告诉系统,你对进程或线程的统计数据不感兴趣。进程或线程将继续运行,直到它自己终止运行。

    当进程内核对象创建后,系统赋予该对象一个独一无二的标识号,系统中的其他任何进程内核对象都不能使用这个相同的I D号。线程内核对象的情况也一样。当一个线程内核对象创建时,该对象被赋予一个独一无二的、系统范围的
I D号。进程I D和线程I D共享相同的号码池。这意味着进程和线程不可能拥有相同的
I D。另外,对象决不会被赋予
0作为其
I D。在C r e a t e P r o c e s s返回之前,它要用这些
I D填入P R O C E S S _ I N F O R M AT I O N结构的d w P r o c e s s
I d和d w T h r e a d I d成员中。I D使你能够非常容易地识别系统中的进程和线程。一些实用工具(如
Ta s kM a n a g e r)对I D使用得最多,而高效率的应用程序则使用得很少。由于这个原因,大多数应用程序完全忽略I
D。

    如果应用程序使用 I D来跟踪进程和线程,必须懂得系统会立即复用进程
I D和线程I D。例如,当一个进程被创建时,系统为它指定一个进程对象,并为它赋予
I D值1 2 2。如果创建了一个新进程对象,系统不会将相同的I D赋予给它。但是,如果第一个进程对象被释放,系统就可以将1
2 2赋予创建的下一个进程对象。记住这一点后,就能避免编写引用不正确的进程对象或线程对象的代码。获取进程
I D是很容易的,保存该
I D也不难,但是,接下来你应该知道,该I D标识的进程已被释放,新进程被创建并被赋予相同的
I D。当使用已经保存的进程
I D时,最终操作的是新进程,而不是原先获得I D的进程。

    有时,运行的应用程序想要确定它的父进程。首先应该知道只有在生成子进程时,才存在进程之间的父子关系。在子进程开始执行代码前, Wi n d o w s不再考虑存在什么父子关系。较早的Wi n d o w s版本没有提供让进程查询其父进程的函数。现在,
To o l H e l p函数通过P R O C E S S E N T RY 3 2结构使得这种查询成为可能。在这个结构中有一个
t h 3 2 P a r e n t P r o c e s s I D成员,根据文档的说明,它能返回进程的父进程的I D。

    系统无法记住每个进程的父进程的I D,但是,由于I D是被立即重复使用的,因此,等到获得父进程的I
D时,该I D可能标识了系统中一个完全不同的进程。父进程可能已经终止运行。如

果应用程序想要与它的“创建者”进行通信,最好不要使用
I D。应该定义一个持久性更好的机制,比如内核对象和窗口句柄等。

    若要确保进程I D或线程I D不被重复使用,唯一的方法是保证进程或线程的内核对象不会被撤消。如果刚刚创建了一个新进程或线程,只要不关闭这些对象的句柄,就能够保证进程对象不被撤消。一旦应用程序结束使用该
I D,那么调用C l o s e H a n d l e就可以释放内核对象,要记住,这时使用或依赖进程
I D,对来说将不再安全。如果使用的是子进程,将无法保证父进程或父线程的有效性,除非父进程复制了它自己的进程对象或线程对象的句柄,并让子进程继承这些句柄。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息