WIN32汇编语言教程:第13章 进程控制 · 13.2 执行可执行文件(4)
PROCESS_INFORMATION STRUCT
hProcess DWORD ? ;进程句柄
hThread DWORD ? ;进程的主线程句柄
dwProcessId DWORD ? ;进程ID
dwThreadId DWORD ? ;进程的主线程ID
PROCESS_INFORMATION ENDS
新进程被创建的时候其主线程也同时被创建,主线程句柄也常常会被用到,所以函数要返回的值不仅仅是进程句柄,因此,单靠函数的返回值是无法返回足够的信息的,这就是CreateProcess函数用PROCESS_INFORMATION结构来返回信息的原因。同样,可以通过检测PROCESS_INFORMATION结构是否被填写来判断函数是否执行成功。
理解了这些参数的含义,就会发现CreateProcess函数的使用其实是很简单的,因为大部分参数都可以用默认值。例子程序中用下面的代码来创建新进程,读者可以看到大部分的参数都使用默认的NULL:
invoke lstrcpy,addr @szBuffer,addr szFileName
.if szCmdLine
invoke lstrcat,addr @szBuffer,addr szBlank
invoke lstrcat,addr @szBuffer,addr szCmdLine
.endif
invoke GetStartupInfo,addr stStartUp
invoke CreateProcess,NULL,addr @szBuffer,NULL,NULL,NULL,\
NORMAL_PRIORITY_CLASS,NULL,NULL,addr stStartUp,addr stProcInfo
例子代码中将lpApplicationName参数设置为NULL,并将文件名szfileName和命令行参数szCmdLine合成一个字符串存放在@szBuffer中,然后一并在lpCommandLine参数中指定,为什么不直接使用两个字符串呢,就像下面的代码一样:
invoke CreateProcess,addr szFileName,addr szCmdLine,NULL,NULL,NULL,\
NORMAL_PRIORITY_CLASS,NULL,NULL,addr stStartUp,addr stProcInfo
这是因为,这种用法在某种情况下可能引起错误。来做下面的实验。
首先将例子程序中的CreateProcess改成分开使用文件名和参数字符串,然后用这个程序去执行13.1.2一节中的Cmdline.exe程序,并尝试输入不同的内容就可以发现,当不指定命令行参数的时候,运行结果如图13.3左图所示:Windows会自动将文件名当做命令行参数的第一个组成部分传递给子进程,一切正常。
图13.3 CreateProcess中的命令行
但是指定了命令行以后,问题就出来了,右图是输入参数“aaa bbb ccc”时的结果,也就是说,当指定了命令行以后,Windows就不会自动在前面加上文件名了,假如被执行的文件将命令行中的第一项当做文件名来看待并将它忽略的话,就会丢失一个参数,遗憾的是,几乎所有的程序都是这样做的!读者可以通过这种方法试将一个文本文件名传递给Windows自带的Notepad.exe,结果就是Notepad.exe把它给丢弃了。
为了避免这个错误,程序需要将文件名添加到命令行字符串的前面,但这样的话,指定lpApplicationName参数也就变得多此一举了,因为这时不用指定这个参数函数也可以正常执行。
2. 结束进程
要结束一个进程的执行,可以使用ExitProcess函数。对于我们来说,这个函数是最熟悉的,因为在所有的例子程序中都用它来结束程序的执行:
invoke ExitProcess,dwExitCode
与线程结束时有个退出码类似,进程结束时也可以指定一个退出码,dwExitCode就用来指定进程的退出码。
ExitProcess函数只能用来结束当前进程,不能用于结束其他进程,包括当前进程创建的子进程,因为它并没有参数可以用来输入进程句柄。如果需要结束其他进程的执行,可以使用TerminateProcess函数:
invoke TerminateProcess,hProcess,dwExitCode
hProcess参数用来指定需要结束的进程的句柄,dwExitCode用来指定进程的退出码。
TerminateProcess函数不是一个推荐使用的函数,一般仅在很极端的情况下使用(如任务管理器用来结束停止响应的进程),因为它将目标进程无条件结束,被结束的进程根本没有机会进行扫尾工作,同时,目标进程使用的dll文件也不会收到结束通知,所以极有可能造成数据丢失。
当进程被结束的时候,系统做下面的工作:
(1)进程创建或打开的所有对象句柄被关闭。
(2)进程中的所有线程被终止。
(3)进程及进程中所有线程的状态被改为置位状态,以便让WaitForSingleObject函数正确检测。
(4)进程对象中的退出码字段从STILL_ACTIVE被改为指定的退出码。
当一个进程被结束的时候,并不影响它创建的子进程,进程对象也不会马上从内存中删除,因为可能其他进程还需要通过进程句柄检测进程状态,直到使用CloseHandle函数将进程句柄关闭以后,进程对象才真正被删除。
大家还记得在DOS下编写批处理文件的时候使用的ERRORLEVEL吗?批处理中可以通过检测ERRORLEVEL来执行不同的逻辑,这个ERRORLEVEL就是命令行窗口中上次执行的可执行程序返回的退出码。Win32中窗口程序的退出码是无法做这个用途了,但它也可以用来在程序退出后向父进程传递简单的状态信息。
要检测进程的退出码,可以使用GetExitCodeProcess函数:
invoke GetExitCodeProcess,hProcess,lpExitCode
hProcess参数指定被检测进程的进程句柄,lpExitCode指向一个双字变量,用来接收函数返回的退出码。如果执行成功,函数返回非0值并将退出码返回到lpExitCode指定的变量中,如果执行失败函数返回0。如果被检测的进程没有结束,那么返回到lpExitCode中的是STILL_ACTIVE。
通过检测子进程的退出码是否是STILL_ACTIVE,就可以得知子进程是否已经结束,但如果需要在父进程中等待子进程结束时,就没有必要在一个循环中不停地检测退出码。在上一章中介绍的WaitForSingleObject函数也可以用于等待进程结束,在程序中只要如下使用就可以了:
invoke WaitForSingleObject,hProcess,dwMilliseconds
如果超时参数dwMilliseconds指定INFINITE,表示在子进程结束前函数不会返回。
当不再使用进程句柄的时候,不要忘记关闭PROCESS_INFORMATION结构中返回的进程句柄和主线程句柄,关闭这两个句柄使用CloseHandle函数。