函数原型 BOOL CreateProcess数组
(安全
LPCTSTR lpApplicationName,数据结构
LPTSTR lpCommandLine,ide
LPSECURITY_ATTRIBUTES lpProcessAttributes。函数
LPSECURITY_ATTRIBUTES lpThreadAttributes,this
BOOL bInheritHandles,编码
DWORD dwCreationFlags,spa
LPVOID lpEnvironment,操作系统
LPCTSTR lpCurrentDirectory,命令行
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
CreateProcess函数
CreateProcess函数用于建立进程:
线程调用CreateProcess时,系统会建立一个进程内核对象,将其引用计数初始化为1(进程内核对象并非进程自己,它只是操做系统用来管理进程的数据结构,其中包含了进程的一些统计信息)。而后系统为新进程开辟虚拟地址空间,并将可执行文件的代码和数据以及所需的DLL装载到该地址空间中。接着系统为进程主线程建立线程内核对象,并将其引用计数初始为1(同进程同样,线程内核对象也不是线程自己,并且操做系统用来管理线程的数据结构)。主线程将连接器设置的入口点函数做为C/C++运行时启动函数调用,这些启动函数最终又调用代码中的入口点函数如WinMain、wWinMain、main和 wmain。当操做系统成功建立了新的进程和主线程后,CreateProcess返回TRUE。以上是CreateProcess的简要介绍,下面咱们来详细讨论它的参数。
pszApplicationName和pszCommandLine
pszApplicationName和pszCommandLine分别表示进程使用的可执行文件名和向其传递的命令行字符串,咱们先来看看 pszCommandLine参数。注意pszCommandLine是PTSTR,这意味着你必须为其传递指向很是量字符串的地址。 CreateProcess内部会更改向其传递的命令行字符串,但在CreateProcess返回以前,它会将该字符串恢复原样。这一点是很是重要的,由于若是你向CreateProcess传递的命令行字符串位于进程的只读存储区,就会发生Access Violation错误。好比,下面的代码执行时会触发Access Violation,由于微软的C/C++编译器会把常量字符串放入只读存储区(注意早期的微软C/C++编译器会将常量字符串放在可读写存储区,所以下面的代码在旧的编译环境下不会出错):
解决这个问题的方法很简单,将命令行字符串复制到临时缓冲区既可,以下所示:
微软在其C++编译器选项中提供了/GF开关,/GF打开时,程序中全部用到的常量字符串将只维护单一副本,且位于只读存储部分。在调用 CreateProcess时,开发人员应该打开/GF开关并使用缓冲区。咱们但愿微软在将来版本的Windows中会改进CreateProcess,使其接受常量字符串做为命令行参数,并在其内部分配/释放临时缓冲区而不是让API调用者来作。另外,假如你使用常量ANSI字符串做为 CreateProcess参数,并不会发生Access Violation错误,咱们在前面的章节已经提到过,许多WinAPI函数的ANSI版本会将ANSI参数转换为UNIDOE编码后调用其 Unicode版本,CreateProcess会把ANSI字符串转换为Unicode编码后放在临时缓冲区,并调用Unicode版的 CreateProcess,所以不会触发Access Violation。
pszCommandLine参数指定了 CreateProcess建立新进程所需的完整命令行。当CreateProcess解析该参数时,它会检查命令行参数中的第一个标记,并将其做为进程要执行的可执行文件名,若是该文件名没有指定后缀,函数将把它看成exe文件。CreateProcess会按下面的顺序查找该文件:
1. 包含当前进程可执行文件的目录
2. 当前进程的当前目录
3. Windows系统目录,既GetSystemDirectory返回的目录
4. Windows目录
5. PATH环境变量列出的目录
固然,若是文件名包含了完整路径,系统将会在该路径中查找文件而不会再作上面的搜索。若是系统找到了可执行文件,它会建立一个新的进程并把可执行文件的代码和数据映射到进程的地址空间,而后调用CRT启动函数(linker选项卡中的入口点函数),接着CRT启动函数检查命令行参数,过滤掉其中的可执行文件部分,并把剩下字符串的地址做为pszCmdLine传给wWinMain/WinMain。
以上情形都是在pszApplicationName为NULL时发生的。 pszApplicationName指定了进程要执行的可执行文件的名称,假如没有指定文件后缀,系统并不会作任何处理。 pszApplicationName不包含完整路径时,CreateProcess只从当前目录中查找可执行文件,查找失败时函数失败并返回 FALSE。即便指定了pszApplicationName,CreateProcess仍然会将pszCommandLine参数做为新进程的命令行。好比下面的代码:
执行上面代码时,系统会打开notepad.exe(记事本),但它的命令行倒是WORDPAD README.TXT(WORDPAD是写字板),这看上去很是奇怪,但CreateProcess就是这样工做的。这种 pszApplicationName提供的特性被用来支持Windows的POSIX子系统。
psaProcess, psaThread和bInheritHandles
建立新进程时,系统会建立一个进程内核对象和一个线程内核对象(用于进程的主线程),和其它内核对象同样,建立者(在这儿是父进程)必须指定其安全属性。psaProcess和psaThread分别指定了新进程的进程内核对象和线程内核对象的安全属性。将其设为NULL时,系统为对应的内核对象指定默认的安全属性。你能够建立SECURITY_ATTRIBUTES类型的变量,设置其中各个域的值而后将变量地址传递给psaProcess或 psaThread,以应用指定的安全属性。正如在第3章讨论内核对象时谈到的,当你想要控制新的进内核对象的句柄可否被父进程之后建立的子进程继承时,你应该设置SECURITY_ATTRIBUTES变量的bInheritHandle域的值。
下面的Inherit.cpp展现了内核对象句柄继承。假设执行该代码的进程为A,它调用CreateProcess建立了进程B,接着又建立了子进程C。注意调用CreateProcess时A使用的psaProcess、psaThread和bInheritHandles参数,代码注释很详细的描述了这些参数的做用:
fdwCreate
fdwCreate参数用来控制进程被建立时的行为,下面列出了它可能的取值:
·DEBUG_PROCESS:父进程将调试子进程及子进程建立的全部进程,指定该参数后,在子进程或子进程建立的任意进程中发生特定事件时系统将通知父进程
·DEBUG_ONLY_THIS_PROCESS:父进程将调试子进程,指定该参数后,在子进程中发生特定事件时系统将通知父进程
·CREATE_SUSPENDED:进程建立后其主线程暂不执行。此时父进程能够在子进程运行以前更改子进程地址空间中的数据、更改子进程主线程优先级、将子进程添加到做业中等。父进程完成其更改后,能够调用ResumeThread函数恢复子进程主线程运行
·DETACHED_PROCESS:系统将阻止CUI程序向其父进程的CUI窗口写入其输出。当父进程为CUI进程时,建立的CUI子进程默认使用父进程的CUI窗口(如cmd.exe程序)。指定该参数后,新进程在须要输出到窗口时必须调用AllocConsole建立CUI窗口
·CREATE_NEW_CONSOLE:系统自动为新进程建立一个CUI窗口,该标志不能与DETACHED_PROCESS同时使用
·CREATE_NO_WINDOW:系统不为新进程建立CUI窗口,使用该标志能够建立不含窗口的CUI程序
·CREATE_NEW_PROCESS_GROUP:新进程将做为一个新的进程组的根进程,新的进程组将包含以根进程为祖先的全部进程。用户在进程组中的某个进程CUI窗口中按下Ctrl+C或Ctrl+B时,系统将通知进程组中的全部进程这一事件
·CREATE_DEFAULT_ERROR_MODE:子进程不继承父进程的任何错误标志
·CREATE_SEPARATE_WOW_VDM:仅用于16位Windows程序,不译
·CREATE_SHARED_WOW_VDM:仅用于16位Windows程序,不译
·CREATE_UNICODE_ENVIRONMENT:子进程的环境块为Unicode字符串。进程的环境块默认只包含ANSI字符串
·CREATE_FORCEDOS:强制系统运行内嵌在16位OS/2系统中的MS-DOS程序
·CREATE_BREAKAWAY_FROM_JOB:当父进程属于某个做业时,新建的子进程将再也不与该做业关联
·EXTENDED_STARTUPINFO_PRESENT:传递给CreateProcess函数的psiStartInfo参数是STARTUPINFOEX类型的变量
fdwCreate参数也能够用于设置新进程的优先级。但你没必要这样作,对大多数应用你也不该该这样作——系统会为新进程分配默认优先级。表4-5列出了可能的优先级常量:
这些常量决定了进程中的线程在CPU中调度的优先级,咱们在188页的“优先级概述”中会讨论该问题。
pvEnvironment
参数pvEnvironment指向一块内存区域,其中包含新进程用到的环境字符串。大多数状况下,你能够为其传递NULL,此时新进程将继承父进程的环境字符串。
pszCurDir
参数pszCurDir容许父进程设置子进程的当前驱动器和目录。若是该参数为NULL,子进程将使用父进程的当前驱动器和目录做为其当前驱动器和目录。若是pszCurDir非空,则其必须指向一个包含驱动器标识的以0结尾的路径字符串。
psiStartInfo
psiStartInfo是指向STARTUPINFO或STARTUPINFOEX变量的提针:
Windows建立新进程时会使用STARTUPINFO(EX)的成员变量,大多数状况下可使用这些变量的默认值,此时你应该该将其cb域设置为结构的大小,并将其他域清0,以下:
许多开发人员经常会忘记执行上述操做,若是你没有清空其内容,STARTUPINFO(EX)的内容会是调用线程堆栈上的一些数据。将这些垃圾数据传递给CreateProcess可能致使没法预料的结果,为了让CreateProcess正常工做,你必须将STARTUPINFO(EX)中没有用到的域清0。
表4-6列出了STARTUPINFO(EX)结构的成员,注意有些成员只在GUI应用中生效,而有些则只在CUI应用中生效:
如今咱们来讨论dwFlags成员。dwFlags包含一组标志用来指示如何建立子进程,其中大多数标志只是告诉CreateProcess是否使用STARTUPINFO结构中的某个成员,表4-7列出了dwFlags的可取值:
另外两个标志 STARTF_FORCEONFEEDBACK和STARTF_FORCEOFFFEEDBACK能够控制在建立子进程时如何显示鼠标指针。因为 Windows支持抢先式多任务调度,所以你能够在建立子程并等待子进程初始化时,执行另外的程序。若是你指定了 STARTF_FORCEONFEEDBACK,Windows会在新进程初始化时将鼠标光标指针更改成“后台运行”,以下图:
这个标志意味着系统后台正在处理某些任务(在这里是建立并初始化子进程),但你依然能够继续使用系统。当你指定了STARTF_FORCEOFFFEEDBACK标志时,CreateProcess不会更改鼠标指针样式。
若是指定了STARTF_FORCEONFEEDBACK,且子进程在CreateProcess调用后2秒内执行了GUI调用,CreateProcess会等待子进程显示窗口。若是该GUI调用后5秒以内尚未窗口显示,CreateProcess会将鼠标指针恢复原状,不然继续等待5秒,若是在这5秒以内子进程调用了GetMessage函数,CreateProcess会认为子进程已经完成初始化并将鼠标指针复位。
STARTUPINFO的wShowWindow变量将传递给wWinMain/WinMain的最后一个参数nCmdShow,它的取值是 ShowWindow函数接受的参数值之一,一般被指定为SW_SHOWNORMAL、SW_SHOWMINNOACTIVE或 SW_SHOWDEFAULT。
在结束本节以前,咱们来看看STARTUPINFOEX结构。经过使用同时兼容STARTUPINFOEX和STARTUPINFO结构的参数psiStartInfo,微软在保持CreateProcess签名的同时提升了其扩展性。下面是STARTUPINFOEX结构的定义:
lpAttributeList(属性链表)是_PROC_THREAD_ATTRIBUTE_LIST结构的链表,其中每一个结构包含一个key/value对,目前,_PROC_THREAD_ATTRIBUTE_LIST中key的取值只能是下面两种:
属性链表的内容是不透明的,所以咱们须要一些函数来建立空的属性链表。建立属性链表须要如下几个步骤,首先,为其分配存储空间,而后向其中添加键值对。函数InitializeProcThreadAttributeList用来建立新的属性链表并为其分配存储空间:
参数dwFlags必须指定为0,你能够先用以下方式得到属性链表所需的空间大小:
cbAttributeListSize 返回建立属性链表所需的内存大小,该大小与dwAttributeCount参数相关,dwAttributeCount指定了属性链表中的 key/value对的数目。接下来你能够用cbAttributeListSize为属性链表分配空间:
而后再次调用InitializeProcThreadAttributeList初始化属性链表的内容:
当属性链表初始化完成后,就能够调用UpdateProcThreadAttribute向其添加键/值对了:
pAttributeList是要添加键/值对的属性列表,Attribute可取 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS或PROC_THREAD_ATTRIBUTE_HANDLE_LIST,取前者时,pValue参数应指向另一个进程的句柄,cbSize取值应为sizeof(HANDLE),不然,pValue指向子进程要继承的全部内核对象的句柄数组,cbSize取值应是sizeof(HANDLE)乘以该数组的大小。参数dwFlags、pPreviousValue和 pReturnSize是保留参数,应分别赋0、NULL和NULL。
注意,若是在建立子进程时为其指定新的父进程,既使用了 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,那么在使用 PROC_THREAD_ATTRIBUTE_HANDLE_LIST时,pValue指向的句柄数组中的句柄应该是新父进程句柄表中对象的句柄,而不是调用CreateProcess的进程全部。
当你在CreateProcess的dwCreateFlags参数中指定了EXTENDED_STARTUPINFO_PRESENT时,你应该向CreateProcess的pStartupInfo参数传递一个STARTUPINFOEX结构的指针,以下面所示:
其中pAttributeList是按前面的方法建立的属性列表。当你再也不须要该属性列表时,应该调用下面的方法回收为其分配的内存:
最后,应用程序能够调用GetStartupInfo得到由其父进程在CreateProcess中指定的STARTUPINFO结构的拷贝:
注意,不管父进程在调用CreateProcess时参数pStartupInfo指向STARTUPINFO仍是STARTUPINFOEX结构,GetStartupInfo老是返回STARTUPINFO结构的拷贝。
ppiProcInfo
ppiProcInfo 参数是PROCESS_INFORMATION结构的指针,调用CreateProcess时该结构必须由开发人员手动分配。CreateProcess 在返回前会填充ppiProcInfo指向的结构的内容。PROCESS_INFORMATION定义以下:
CreateProcess会建立一个进程内核对象和一个线程内核对象,建立初期,系统将其引用计数分别置为1。CreateProcess返回以前会得到这两个对象的访问权限,这样每一个对象的引用计数会分别增长1,CreateProcess返回以后,两个对象的引用计数变成2。这意味着若是系统要释放CreateProcess进程/线程内核对象,相应的进程/线程必须终止,而且调用CreateProcess的线程必须调用 CloseHandle关闭相应的对象句柄,这样才能使得其引用计数变为0,系统方能释放。