CreateProcess
说明:
WIN32API函数CreateProcess用来创建一个新的进程和它的主线程,这个新进程执行指定的可执行文件。
函数原型:
BOOL CreateProcess
(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
一个线程调用它来首先创建一个进程内核对象,进程内核对象是用来管理这个新的进程的,然后,系统为新进程创建虚拟地址空间,并将可运行文件(和DLL)的代码和数据载入到这个地址空间,然后系统为新进程的主线程创建一个线程内核对象.
先解释下lpApplicationName和lpCommandLine
lpApplicationName和lpCommandLine分别指向新进程要使用的可运行文件的名称,以及要传给新进程的命令行字符串,lpCommandLine的类型为LPTSTR,这是由于在内部,CreateProcess实际上会改动我们传给他的命令行字符串,当然在它返回前,它会把这个字符串还原,所以这种代码是错误的:
STARTUPINFO si = {sizeof(si)} ;
PROCESS_INFORMATION pi ;
CreateProcess(NULL,TEXT("NOTEPAD"),NULL,NULL,
FALSE,0,NULL,NULL,&si,&pi) ;
解决这个BUG的方法是把常量字符串拷贝到一个暂时缓冲区中,例如以下所看到的:
STARTUPINFO si = {sizeof(si)} ;
PROCESS_INFORMATION pi ;
TCHAR szCommandLine[] = TEXT("NOTEPAD") ;
CreateProcess(NULL,szCommandLine,NULL,NULL,
FALSE,0,NULL,NULL,&si,&pi) ;
所以,期待Microsoft能在windows未来版本号修正,自己能创建字符串的一个暂时副本,让我们解放吧
两种状态:
1:lpApplicationName为NULL,(99%都设为NULL)
在这样的情况下,可运行模块的名字必须处于 lpCommandLine 參数的最前面并由空格符与后面的字符分开,当CreateProcess解析lpCommandLine 字符串时,它会检查字符串中的第一个标记(token),并假记此标记为我们想运行的可运行文件的名称,假设可运行文件的名称没有扩展名,默觉得.exe,而且假设文件名称不包括一个完整的路径,CreateProcess还会按下面顺序来搜索可运行文件:
(1)主调进程.exe文件所在的文件夹
(2)主调进程的当前文件夹
(3)windows系统文件夹,即GetSystemDirectory返回的System32子文件夹
(4)windows文件夹
(5)PATH环境变量中列出的文件夹
2:lpApplicationName不为NULL
在这样的情况下,必须指定文件扩展名,系统不会像1那样自己主动假定扩展名了,而且假设文件名称不包括一个完整的路径,CreateProcess仅仅会在当前目录查找可运行文件,不会在其它不论什么文件夹查找了。
一旦系统找到了可运行文件,就创建一个新进程,并将可运行文件的代码和数据映射到新进程的地址空间,然后启动例程,C/C++会将可运行文件名称之后的第一个实參的地址传给(w)WinMain的pszCmdLine參数
再解释下lpProcessAttributes和lpThreadAttributes
为了创建一个新的进程,系统必须创建一个进程内核对象和新进程的主线程的线程内核对象,这两个參数就标识着这两个内核对象的安全描写叙述符。
bInheritHandles就不用说了,设置父子进程的句柄描绘表的继承性
dwCreationFlags标识了影响新进程创建方式的标志
值:CREATE_DEFAULT_ERROR_MODE
含义:新的进程不继承调用进程的错误模式。CreateProcess函数赋予新进程当前的默认错误模式作为替代。应用程序能够调用SetErrorMode函数设置当前的默认错误模式。
这个标志对于那些执行在没有硬件错误环境下的多线程外壳程序是十分实用的。
对于CreateProcess函数,默认的行为是为新进程继承调用者的错误模式。设置这个标志以改变默认的处理方式。
值:CREATE_NEW_CONSOLE
含义:新的进程将使用一个新的控制台,而不是继承父进程的控制台。这个标志不能与DETACHED_PROCESS标志一起使用。
值:CREATE_NEW_PROCESS_GROUP
含义:新进程将使一个进程树的根进程。进程树种的所有进程都是根进程的子进程。新进程树的用户标识符与这个进程的标识符是同样的,由lpProcessInformation參数返回。进程树常常使用GenerateConsoleCtrlEvent函数同意发送CTRL+C或CTRL+BREAK信号到一组控制台进程。
值:CREATE_SEPARATE_WOW_VDM
含义:(仅仅适用于Windows NT)这个标志仅仅有当执行一个16位的Windows应用程序时才是有效的。假设被设置,新进程将会在一个私有的虚拟DOS机(VDM)中执行。另外,默认情况下全部的16位Windows应用程序都会在同一个共享的VDM中以线程的方式执行。单独执行一个16位程序的长处是一个应用程序的崩溃仅仅会结束这一个VDM的执行;其它那些在不同VDM中执行的程序会继续正常的执行。相同的,在不同VDM中执行的16位Windows应用程序拥有不同的输入队列,这意味着假设一个程序临时失去响应,在独立的VDM中的应用程序可以继续获得输入。
值:CREATE_SHARED_WOW_VDM
含义:(仅仅适用于Windows NT)这个标志仅仅有当执行一个16位的Windows应用程序时才是有效的。假设WIN.INI中的Windows段的DefaultSeparateVDM选项被设置为真,这个标识使得CreateProcess函数越过这个选项并在共享的虚拟DOS机中执行新进程。
值:CREATE_SUSPENDED
含义:新进程的主线程会在创建后被挂起,直到调用ResumeThread函数被调用时才运行,这样一来,父进程就能够改动子进程地址空间中的内存,更改子进程的主线程的优先级,或者在进程运行不论什么代码前,把它增加到一个作业中,父进程改动好子进程后,再调用ResumeThread来同意子进程运行代码。
值:CREATE_UNICODE_ENVIRONMENT
含义:假设被设置,由lpEnvironment參数指定的环境块使用Unicode字符,假设为空,环境块使用ANSI字符。
值:DEBUG_PROCESS
含义:假设这个标志被设置,调用进程将被当作一个调试程序,而且新进程会被当作被调试的进程。系统把被调试程序发生的全部调试事件通知给调试器。
假设你使用这个标志创建进程,仅仅有调用进程(调用CreateProcess函数的进程)能够调用WaitForDebugEvent函数。
值:DEBUG_ONLY_THIS_PROCESS
含义:假设此标志没有被设置且调用进程正在被调试,新进程将成为调试调用进程的调试器的还有一个调试对象。假设调用进程没有被调试,有关调试的行为就不会产生。
值:DETACHED_PROCESS
含义:对于控制台进程,新进程没有訪问父进程控制台的权限。新进程能够通过AllocConsole函数自己创建一个新的控制台。这个标志不能够与CREATE_NEW_CONSOLE标志一起使用。
dwCreationFlags參数还用来控制新进程的优先类,优先类用来决定此进程的线程调度的优先级。假设以下的优先级类标志都没有被指定,那么默认的优先类是NORMAL_PRIORITY_CLASS,除非被创建的进程是IDLE_PRIORITY_CLASS。在这样的情况下子进程的默认优先类是IDLE_PRIORITY_CLASS。
能够以下的标志中的一个:
优先级:HIGH_PRIORITY_CLASS
含义:指示这个进程将执行时间临界的任务,所以它必须被马上执行以保证正确。这个优先级的程序优先于正常优先级或空暇优先级的程序。一个样例是Windows任务列表,为了保证当用户调用时能够立马响应,放弃了对系统负荷的考虑。确保在使用高优先级时应该足够慎重,由于一个高优先级的CPU关联应用程序能够占用差点儿所有的CPU可用时间。
优先级:IDLE_PRIORITY_CLASS
含义:指示这个进程的线程仅仅有在系统空暇时才会执行而且能够被不论什么高优先级的任务打断。比如屏幕保护程序。空暇优先级会被子进程继承。
优先级:NORMAL_PRIORITY_CLASS
含义:指示这个进程没有特殊的任务调度要求。
优先级:REALTIME_PRIORITY_CLASS
含义:指示这个进程拥有可用的最高优先级。一个拥有实时优先级的进程的线程能够打断全部其它进程线程的运行,包含正在运行重要任务的系统进程。比如,一个运行时间稍长一点的实时进程可能导致磁盘缓存不足或鼠标反映迟钝。
lpEnvironment:
指向一个新进程的环境块。假设此參数为空,新进程使用调用进程的环境。
一个环境块存在于一个由以NULL结尾的字符串组成的块中,这个块也是以NULL结尾的。每一个字符串都是name=value的形式。
由于相等标志被当作分隔符,所以它不能被环境变量当作变量名。
与其使用应用程序提供的环境块,不如直接把这个參数设为空,系统驱动器上的当前文件夹信息不会被自己主动传递给新创建的进程。对于这个情况的探讨和怎样处理,请參见凝视一节。
环境块能够包括Unicode或ANSI字符。假设lpEnvironment指向的环境块包括Unicode字符,那么dwCreationFlags字段的CREATE_UNICODE_ENVIRONMENT标志将被设置。假设块包括ANSI字符,该标志将被清空。
请注意一个ANSI环境块是由两个零字节结束的:一个是字符串的结尾,还有一个用来结束这个快。一个Unicode环境块石油四个零字节结束的:两个代表字符串结束,另两个用来结束块。
lpCurrentDirectory:
指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包括驱动器名的绝对路径。假设这个參数为NULL,新进程将使用与调用进程同样的驱动器和文件夹。这个选项是一个须要启动启动应用程序并指定它们的驱动器和工作文件夹的外壳程序的主要条件。
lpStartupInfo:
typedef struct _STARTUPINFO
{
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR 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;
} STARTUPINFO, *LPSTARTUPINFO;
指向一个用于决定新进程的主窗口怎样显示的STARTUPINFO结构体。
大多数应用程序都希望生成的应用程序仅仅是使用默认值,最起码要所有初始化为0,再把cb成员设为此结构体的大小,假设没有清0,则新进程可能创建失败.
表4-6 STARTUPINFO 结构的成员
成员 | 窗体,控制台还是两者兼有 | 作用 |
cb | 两者兼有 | 包括S TA RT U P I N F O 结构中的字节数。假设M i c r o s o f t 将来扩展该结构,它可用作版本号控制手段。应用程序必须将c b 初始化为s i z e o f ( S TA RT U P I N F O ) |
lpReserved | 两者兼有 | 保留。必须初始化为N U L L |
lpDesktop | 两者兼有 | 用于标识启动应用程序所在的桌面的名字。假设该桌面存在,新进程便与指定的桌面相关联。假设桌面不存在,便创建一个带有默认属性的桌面,并使用为新进程指定的名字。假设l p D e s k t o p 是N U L L (这是最常见的情况),那么该进程将与当前桌面相关联 |
lpTitle | 控制台 | 用于设定控制台窗体的名称。假设l p Ti t l e 是N U L L ,则可运行文件的名字将用作窗体名 |
dwX dwY |
两者兼有 | 用于设定应用程序窗体在屏幕上应该放置的位置的x 和y 坐标(以像素为单位)。仅仅有当子进程用C W _ U S E D E FA U LT 作为C r e a t e Wi n d o w 的x 參数来创建它的第一个重叠窗体时,才使用这两个坐标。若是创建控制台窗体的应用程序,这些成员用于指明控制台窗体的左上角 |
dwXSize | 两者兼有 | 用于设定应用程序窗体的宽度和长度(以像素为单位)仅仅有dwYsize 当子进程将C W _ U S E D E FA U LT 用作C r e a t e Wi n d o w 的n Wi d t h參数来创建它的第一个重叠窗体时,才使用这些值。若是创建控制台窗体的应用程序,这些成员将用于指明控制台窗体的宽度 |
dwXCountChars dwYCountChars |
控制台 | 用于设定子应用程序的控制台窗体的宽度和高度(以字符为单位) |
dwFillAttribute | 控制台 | 用于设定子应用程序的控制台窗体使用的文本和背景颜色 |
dwFlags | 两者兼有 | 请參见下一段和表4 - 7 的说明 |
wShowWindow | 窗体 | 用于设定假设子应用程序初次调用的S h o w Wi n d o w 将S W _ S H O W D E FA U LT 作为n C m d S h o w 參数传递时,该应用程序的第一个重叠窗体应该怎样出现。本成员能够是通经常使用于Show Wi n d o w 函数的不论什么一个S W _ *标识符 |
cbReserved2 | 两者兼有 | 保留。必须被初始化为0 |
lpReserved2 | 两者兼有 | 保留。必须被初始化为N U L L |
hStdInput hStdOutput hStdError |
控制台 | 用于设定供控制台输入和输出用的缓存的句柄。依照默认设置,h S t d I n p u t 用于标识键盘缓存,h S t d O u t p u t 和h S t d E r r o r用于标识控制台窗体的缓存 |
dwFlags包括一组标志,大多数标志都仅仅是告诉CreateProcess函数,STARTUPINFO 中其它成员是否包括实用的信息,或者是否应该忽略一些成员
表4-7 使用标志及含义
标志 | 含义 |
STARTF_USESIZE | 使用d w X S i z e 和d w Y S i z e 成员 |
STARTF_USESHOWWINDOW | 使用w S h o w Wi n d o w 成员 |
STARTF_USEPOSITION | 使用d w X 和d w Y 成员 |
STARTF_USECOUNTCHARS | 使用d w X C o u n t C h a r s 和dwYCount Chars 成员 |
STARTF_USEFILLATTRIBUTE | 使用d w F i l l A t t r i b u t e 成员 |
STARTF_USESTDHANDLES | 使用h S t d I n p u t 、h S t d O u t p u t 和h S t d E r r o r 成员 |
STARTF_RUN_FULLSCREEN | 强制在x 8 6 计算机上执行的控制台应用程序以全屏幕方式启动执行 |
另外还有两个标志即STARTF_ORCEONFEEDBACK和STARTFFORCEOFFFEEDBACK,当启动一个新进程时,它们能够用来控制鼠标的光标,因为windows支持真正的多任务抢占式执行方式,因此能够启动一个应用程序,然后在进程初始化时使用还有一个程序,为了向用户提供视觉反馈,CreateProcess暂时会把系统的光标改成一个新的光标,但
假设指定了STARTF_ORCEONFEEDBACK,CreateProcess就不会改变光标
假设指定了STARTFFORCEOFFFEEDBACK,CreateProcess会改变成新的光标,在2秒之后,假设新进程没有运行不论什么GUI调用,光标还原,假设运行了GUI调用,则在5秒内必须显示窗体,否则光标相同还原,
lpStartupInfo
指向必须指定的PROCESS_INFORMATION结构体
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
如前所述,创建新进程可使系统建立一个进程内核对象和一个线程内核对象,在创建进程的时候,系统为每一个对象赋予一个初始使用计数值1,然后,在CreateProcess返回之前,该函数打开进程内核对象和线程内核对象,并将每一个对象的与进程相关的句柄放_PROCESS_INFORMATION中的hProcess和hThread,当CreateProcess在内部打开这些对象时,每一个对象的使用计数就变为2。
所以注意必须关闭子进程和它的主线程的句柄,以避免在应用程序执行中泄漏资源,当然当进程终止执行时,系统如自己主动消除这些泄漏现象,可是,当进程不再须要訪问子进程和它的线程时,编写得较好的软件可以显示关闭这些句柄(通过调用CloseHandle函数来关闭),不能关闭这些句柄是开发者最常犯的错误之中的一个.
当进程和线程内核对象创建时,系统都为赋予该对象一个独一无二的,系统范围内的ID号,进程ID和线程ID共享同样的号码池,这意味着进程和线程不可能拥有同样的ID,另外ID不能为0,同样,在CreateProcess返回时,dwProcessId和dwThreadId被填充,ID使你能非常easy识别系统中的进程和线程,一些有用工具(如Task Manager)对ID使用最多,而高效率的应用程序则使用得非常少,因于这个原因,大多数应用程序全然忽略ID。
假设应用程序使用ID来跟踪进程和线程,必须懂得系统会马上复用进程ID和线程ID,如,一个进程被创建时,ID值122,假设创建其它新进程对象,系统不会把同样的ID赋予给它,可是,假设第一个进程对象被释放,系统就能够把122赋予创建的下一个进程对象,因此,假设应用程序想要与它的“创建者”进行通信,最好不要使用ID,应该定义一个持久性更好的机制,对如内核对象和窗体句柄等。
假设想创建一个新进程,并等待结果,可用下面相似代码:
PROCESS_INFORMATION pi;
DWORD dwExitCode;
//Spawn the child process.
BOOL fSuccess = CreateProcess(..., π);
if(fSuccess)
{
//Close the thread handle as soon as
//it is no longer needed!
CloseHandle(pi.hThread);
//Suspend our execution until
//the child has terminated.
WaitForSingleObject(pi.hProcess,INFINITE);
//The child process terminated;
//get its exit code.
GetExitCodeProcess(pi.hProcess,
&dwExitCode);
//Close the process handle as soon as
//it is no longer needed.
CloseHandle(pi.hProcess);
}
须要说明的是,仅仅有当进程对象终止执行时,WaitForSingleObject才干得到通知,因此对WaitForSingleObject的调用会将父进程的线程挂起,直到子进程终止执行,当WaitForSingleObject返回时,通过GetExitCodeProcess,就能够得到子进程的退出码
凝视:
CreateProcess函数用来执行一个新程序。WinExec和LoadModule函数依然可用,可是它们相同通过调用CreateProcess函数实现。
另外CreateProcess函数除了创建一个进程,还创建一个线程对象。这个线程将连同一个已初始化了的堆栈一起被创建,堆栈的大小由可运行文件的文件头中的描写叙述决定。线程由文件头处開始运行。
新进程和新线程的句柄被以全局訪问权限创建。对于这两个句柄中的任一个,假设没有安全描写叙述符,那么这个句柄就能够在不论什么须要句柄类型作为參数的函数中被使用。当提供安全描写叙述符时,在接下来的时候当句柄被使用时,总是会先进行訪问权限的检查,假设訪问权限检查拒绝訪问,请求的进程将不能使用这个句柄訪问这个进程。
这个进程会被分配给一个32位的进程标识符。直到进程中止这个标识符都是有效的。它能够被用来标识这个进程,或在OpenProcess函数中被指定以打开这个进程的句柄。进程中被初始化了的线程一样会被分配一个32位的线程标识符。这个标识符直到县城中止都是有效的且能够用来在系统中唯一标识这个线程。这些标识符在PROCESS_INFORMATION结构体中返回。
当在lpApplicationName或lpCommandLine參数中指定应用程序名时,应用程序名中是否包括扩展名都不会影响执行,仅仅有一种情况例外:一个以.com为扩展名的MS-DOS程序或Windows程序必须包括.com扩展名。
调用进程能够通过WaitForInputIdle函数来等待新进程完毕它的初始化并等待用户输入。这对于父进程和子进程之间的同步是极事实上用的,由于CreateProcess函数不会等待新进程完毕它的初始化工作。举例来说,在试图与新进程关联的窗体之前,进程应该先调用WaitForInputIdle。
首选的结束一个进程的方式是调用ExitProcess函数,由于这个函数通知这个进程的全部动态链接库(DLLs)程序已进入结束状态。其它的结束进程的方法不会通知关联的动态链接库。注意当一个进程调用ExitProcess时,这个进程的其它县城没有机会执行其它不论什么代码(包含关联动态链接库的终止代码)。
ExitProcess, ExitThread, CreateThread, CreateRemoteThread,当一个进程启动时(调用了CreateProcess的结果)是在进程中序列化进行的。在一段地址空间中,同一时间内这些事件中仅仅有一个能够发生。这意味着以下的限制将保留:
*在进程启动和DLL初始化阶段,新的线程能够被创建,可是直到进程的DLL初始化完毕前它们都不能開始执行。
*在DLL初始化或卸下例程中进程中仅仅能有一个线程。
*直到全部的线程都完毕DLL初始化或卸下后,ExitProcess函数才返回。
在进程中的全部线程都终止且进程全部的句柄和它们的线程被通过调用CloseHandle函数终止前,进程会留在系统中。进程和主线程的句柄都必须通过调用CloseHandle函数关闭。假设不再须要这些句柄,最好在创建进程后立马关闭它们。
当进程中最后一个线程终止时,下列的事件发生:
*全部由进程打开的对象都会关闭。
*进程的终止状态(由GetExitCodeProcess函数返回)从它的初始值STILL_ACTIVE变为最后一个结束的线程的结束状态。
*主线程的线程对象被设置为标志状态,供其它等待这个对象的线程使用。
*进程对象被设置为标志状态,供其它等待这个对象的线程使用。
如果当前在C盘上的文件夹是/MSVC/MFC且有一个环境变量叫做C:,它的值是C:/MSVC/MFC,就像前面lpEnvironment中提到过的那样,这种系统驱动器上的文件夹信息在CreateProcess函数的lpEnvironment參数不为空时不会被自己主动传递到新进程里。一个应用程序必须手动地把当前文件夹信息传递到新的进程中。为了这样做,应用程序必须直接创建环境字符串,并把它们按字母顺序排列(由于Windows NT和Windows 95使用一种简略的环境变量),并把它们放进lpEnvironment中指定的环境块中。相似的,他们要找到环境块的开头,又要反复一次前面提到的环境块的排序。
一种获得驱动器X的当前文件夹变量的方法是调用GetFullPathName("x:",..)。这避免了一个应用程序必须去扫描环境块。假设返回的绝对路径是X:/,就不须要把这个值当作一个环境数据去传递了,由于根文件夹是驱动器X上的新进程的默认当前文件夹。
由CreateProcess函数返回的句柄对于进程对象具有PROCESS_ALL_ACCESS的訪问权限。
由lpcurrentDirectory參数指定的当前文件夹室子进程对象的当前文件夹。lpCommandLine參数指定的第二个项目是父进程的当前文件夹。
对于Windows NT,当一个进程在指定了CREATE_NEW_PROCESS_GROUP的情况下被创建时,一个对于SetConsoleCtrlHandler(NULL,True)的调用被用在新的进程上,这意味着对新进程来说CTRL+C是无效的。这使得上层的外科程序能够自己处理CTRL+C信息并有选择的把这些信号传递给子进程。CTRL+BREAK依然有效,并可被用来中断进程/进程树的运行。
參见
AllocConsole, CloseHandle, CreateRemoteThread, CreateThread, ExitProcess, ExitThread, GenerateConsoleCtrlEvent, GetCommandLine, GetEnvironmentStrings, GetExitCodeProcess, GetFullPathName, GetStartupInfo, GetSystemDirectory, GetWindowsDirectory, LoadModule, OpenProcess, PROCESS_INFORMATION, ResumeThread, SECURITY_ATTRIBUTES, SetConsoleCtrlHandler, SetErrorMode, STARTUPINFO, TerminateProcess, WaitForInputIdle, WaitForDebugEvent, WinExec
快捷信息:
导入库:kernel32.lib
头文件:Winbase.h