CreateProcess函数详细使用说明.docx
《CreateProcess函数详细使用说明.docx》由会员分享,可在线阅读,更多相关《CreateProcess函数详细使用说明.docx(12页珍藏版)》请在冰豆网上搜索。
CreateProcess函数详细使用说明
CreateProcess函数详细使用说明
函数原型BOOLCreateProcess
(
LPCTSTRlpApplicationName,
LPTSTRlpCommandLine,
LPSECURITY_ATTRIBUTESlpProcessAttributes。
LPSECURITY_ATTRIBUTESlpThreadAttributes,
BOOLbInheritHandles,
DWORDdwCreationFlags,
LPVOIDlpEnvironment,
LPCTSTRlpCurrentDirectory,
LPSTARTUPINFOlpStartupInfo,
LPPROCESS_INFORMATIONlpProcessInformation
);
CreateProcess函数用于创建进程:
1.BOOLCreateProcess(
2.PCTSTRpszApplicationName,
3.PTSTRpszCommandLine,
4.PSECURITY_ATTRIBUTESpsaProcess,
5.PSECURITY_ATTRIBUTESpsaThread,
6.BOOLbInheritHandles,
7.DWORDfdwCreate,
8.PVOIDpvEnvironment,
9.PCTSTRpszCurDir,
10.PSTARTUPINFOpsiStartInfo,
11.PPROCESS_INFORMATIONppiProcInfo);
线程调用CreateProcess时,系统会创建一个进程内核对象,将其引用计数初始化为1(进程内核对象并不是进程本身,它只是操作系统用来管理进程的数据结构,其中包含了进程的一些统计信息)。
然后系统为新进程开辟虚拟地址空间,并将可执行文件的代码和数据以及所需的DLL装载到该地址空间中。
接着系统为进程主线程创建线程内核对象,并将其引用计数初始为1(同进程一样,线程内核对象也不是线程本身,而且操作系统用来管理线程的数据结构)。
主线程将链接器设置的入口点函数作为C/C++运行时启动函数调用,这些启动函数最终又调用代码中的入口点函数如WinMain、wWinMain、main和wmain。
当操作系统成功创建了新的进程和主线程后,CreateProcess返回TRUE。
以上是CreateProcess的简要介绍,下面我们来详细讨论它的参数。
pszApplicationName和pszCommandLine
pszApplicationName和pszCommandLine分别表示进程使用的可执行文件名和向其传递的命令行字符串,我们先来看看pszCommandLine参数。
注意pszCommandLine是PTSTR,这意味着你必须为其传递指向非常量字符串的地址。
CreateProcess内部会更改向其传递的命令行字符串,但在CreateProcess返回之前,它会将该字符串恢复原样。
这一点是非常重要的,因为如果你向CreateProcess传递的命令行字符串位于进程的只读存储区,就会发生AccessViolation错误。
比如,下面的代码执行时会触发AccessViolation,因为微软的C/C++编译器会把常量字符串放入只读存储区(注意早期的微软C/C++编译器会将常量字符串放在可读写存储区,因此下面的代码在旧的编译环境下不会出错):
1.STARTUPINFOsi={sizeof(si)};
2.PROCESS_INFORMATIONpi;
3.CreateProcess(NULL,TEXT("NOTEPAD"),NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
解决这个问题的方法很简单,将命令行字符串复制到临时缓冲区既可,如下所示:
1.STARTUPINFOsi={sizeof(si)};
2.PROCESS_INFORMATIONpi;
3.TCHARszCommandLine[]=TEXT("NOTEPAD");
4.CreateProcess(NULL,szCommandLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
微软在其C++编译器选项中提供了/GF开关,/GF打开时,程序中所有用到的常量字符串将只维护单一副本,且位于只读存储部分。
在调用CreateProcess时,开发人员应该打开/GF开关并使用缓冲区。
我们希望微软在未来版本的Windows中会改进CreateProcess,使其接受常量字符串作为命令行参数,并在其内部分配/释放临时缓冲区而不是让API调用者来做。
另外,假如你使用常量ANSI字符串作为CreateProcess参数,并不会发生AccessViolation错误,我们在前面的章节已经提到过,许多WinAPI函数的ANSI版本会将ANSI参数转换为UNIDOE编码后调用其Unicode版本,CreateProcess会把ANSI字符串转换为Unicode编码后放在临时缓冲区,并调用Unicode版的CreateProcess,因此不会触发AccessViolation。
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参数作为新进程的命令行。
比如下面的代码:
1.//Makesurethatthepathisinaread/writesectionofmemory.
2.TCHARszPath[]=TEXT("WORDPADREADME.TXT");
3.//Spawnthenewprocess.
4.CreateProcess(TEXT("C:
\\WINDOWS\\SYSTEM32\\NOTEPAD.EXE"),szPath,...);
执行上面代码时,系统会打开notepad.exe(记事本),但它的命令行却是WORDPADREADME.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参数,代码注释很详细的描述了这些参数的作用:
1./************************************************************
2.Modulename:
Inherit.cpp
3.Notices:
Copyright(c)2008JeffreyRichter&ChristopheNasarre
4.************************************************************/
5.#include
6.intWINAPI_tWinMain(HINSTANCEhInstanceExe,HINSTANCE,
7.PTSTRpszCmdLine,intnCmdShow){
8.//PrepareaSTARTUPINFOstructureforspawningprocesses.
9.STARTUPINFOsi={sizeof(si)};
10.SECURITY_ATTRIBUTESsaProcess,saThread;
11.PROCESS_INFORMATIONpiProcessB,piProcessC;
12.TCHARszPath[MAX_PATH];
13.//PreparetospawnProcessBfromProcessA.
14.//Thehandleidentifyingthenewprocess
15.//objectshouldbeinheritable.
16.saProcess.nLength=sizeof(saProcess);
17.saProcess.lpSecurityDescriptor=NULL;
18.saProcess.bInheritHandle=TRUE;
19.//Thehandleidentifyingthenewthread
20.//objectshouldNOTbeinheritable.
21.saThread.nLength=sizeof(saThread);
22.saThread.lpSecurityDescriptor=NULL;
23.saThread.bInheritHandle=FALSE;//SpawnProcessB.
24._tcscpy_s(szPath,_countof(szPath),TEXT("ProcessB"));
25.CreateProcess(NULL,szPath,&saProcess,&saThread,
26.FALSE,0,NULL,NULL,&si,&piProcessB);
27.//Thepistructurecontainstwohandles
28.//relativetoProcessA:
29.//hProcess,whichidentifiesProcessB'sprocess
30.//objectandisinheritable;andhThread,whichidentifies
31.//ProcessB'sprimarythreadobjectandisNOTinheritable.
32.//PreparetospawnProcessCfromProcessA.
33.//SinceNULLispassedforthepsaProcessandpsaThread
34.//parameters,thehandlestoProcessC'sprocessand
35.//primarythreadobjectsdefaultto"noninheritable."
36.//IfProcessAweretospawnanotherprocess,thisnew
37.//processwouldNOTinherithandlestoProcessC'sprocess
38.//andthreadobjects.
39.//BecauseTRUEispassedforthebInheritHandlesparameter,
40.//ProcessCwillinheritthehandlethatidentifiesProcess
41.//B'sprocessobjectbutwillnotinheritahandleto
42.//ProcessB'sprimarythreadobject.
43._tcscpy_s(szPath,_countof(szPath),TEXT("ProcessC"));
44.CreateProcess(NULL,szPath,NULL,NULL,
45.TRUE,0,NULL,NULL,&si,&piProcessC);
46.return(0);
47.}
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变量的提针:
1.typedefstruct_STARTUPINFO{
2.DWORDcb;
3.PSTRlpReserved;
4.PSTRlpDesktop;
5.PSTRlpTitle;
6.DWORDdwX;
7.DWORDdwY;
8.DWORDdwXSize;
9.DWORDdwYSize;
10.DWORDdwXCountChars;
11.DWORDdwYCountChars;
12.DWORDdwFillAttribute;
13.DWORDdwFlags;
14.WORDwShowWindow;
15.WORDcbReserved2;
16.PBYTElpReserved2;
17.HANDLEhStdInput;
18.HANDLEhStdOutput;
19.HANDLEhStdError;
20.}STARTUPINFO,*LPSTARTUPINFO;
21.
22.typedefstruct_STARTUPINFOEX{
23.STARTUPINFOStartupInfo;
24.struct_PROC_THREAD_ATTRIBUTE_LIST*lpAttributeList;
25.}STARTUPINFOEX,*LPSTARTUPINFOEX;
Windows创建新进程时会使用STARTUPINFO(EX)的成员变量,大多数情况下可以使用这些变量的默认值,此时你应该该将其cb域设置为结构的大小,并将其余域清0,如下:
1.STARTUPINFOsi={sizeof(si)};
2.CreateProcess(...,&si,...);
许多开发人员常常会忘记执行上述操作,如果你没有清空其内容,STARTUPINFO(EX)的内容会是调用线程堆栈上的一些数据。
将这些垃圾数据传递给CreateProcess可能导致无法预料的结果,为了让CreateProcess正常工作,你必须将STARTUPINFO(EX)中没有用到的域清0。
表4-6列出了STARTUPINFO(EX)结构的成员,注意有些成员只在GUI应用中生效,而有些则只在CUI应用中生效:
现在我们来讨论dwFlags成员。
dwFlags包含一组标志用来指示如何创建子进程,其中大多数标志只是告诉CreateProcess是否使用STARTUPINFO结构中的某个成员,表4-7列出了dwFlags的可取值:
现在我们来讨论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结构的定义:
1.typedefstruct_STARTUPINFOEXA{
2.STARTUPINFOAStartupInfo;
3.struct_PROC_THREAD_ATTRIBUTE_LIST*lpAttributeList;
4.}STARTUPINFOEXA,*LPSTARTUPINFOEXA;
5.typedefstruct_STARTUPINFOEXW{
6.STAR