目录
1、CreateProcess()函数
1.1 lpApplicationName & lpCommandLine
1.2 lpProcessAttributes & lpThreadAttributes
1.3 bInheritHandles
1.4 dwCreationFlags
1.5 lpEnvironment
1.6 lpCurrentDirectory
1.7 lpStartupInfo
1.8 lpProcessInformation
1、CreateProcess()函数
- WIN32API函数CreateProcess用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。
- 头文件:#include <windows.h>
函数代码段:
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)的代码和数据加载到这个地址空间,然后系统为新进程的主线程创建一个线程内核对象.
1.1 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) ;
两种状态:
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参数
// #include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int main()
{
STARTUPINFO startupInfo = { 0 };
PROCESS_INFORMATION processInformation = { 0 };
BOOL bSuccess = CreateProcess(
TEXT("C:\\Windows\\notepad.exe"), NULL, NULL,
NULL, FALSE, NULL, NULL, NULL, &startupInfo,
&processInformation);
if (bSuccess)
{
cout << "Process started." << endl
<< "Process ID:\t"
<< processInformation.dwProcessId << endl;
}
else
{
cout << "Cannot start process!" << endl
<< "Error code:\t" << GetLastError() << endl;
}
return system("pause");
}
1.2 lpProcessAttributes & lpThreadAttributes
为了创建一个新的进程,系统必须创建一个进程内核对象和新进程的主线程的线程内核对象,这两个参数就标识着这两个内核对象的安全描述符。
1.3 bInheritHandles
设置父子进程的句柄描绘表的继承性
1.4 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
含义:指示这个进程拥有可用的最高优先级。一个拥有实时优先级的进程的线程可以打断所有其他进程线程的执行,包括正在执行重要任务的系统进程。例如,一个执行时间稍长一点的实时进程可能导致磁盘缓存不足或鼠标反映迟钝。
1.5 lpEnvironment
指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。
一个环境块存在于一个由以NULL结尾的字符串组成的块中,这个块也是以NULL结尾的。每个字符串都是name=value的形式。
因为相等标志被当作分隔符,所以它不能被环境变量当作变量名。
与其使用应用程序提供的环境块,不如直接把这个参数设为空,系统驱动器上的当前目录信息不会被自动传递给新创建的进程。对于这个情况的探讨和如何处理,请参见注释一节。
环境块可以包含Unicode或ANSI字符。如果lpEnvironment指向的环境块包含Unicode字符,那么dwCreationFlags字段的CREATE_UNICODE_ENVIRONMENT标志将被设置。如果块包含ANSI字符,该标志将被清空。
请注意一个ANSI环境块是由两个零字节结束的:一个是字符串的结尾,另一个用来结束这个快。一个Unicode环境块石油四个零字节结束的:两个代表字符串结束,另两个用来结束块。
1.6 lpCurrentDirectory
指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包含驱动器名的绝对路径。如果这个参数为NULL,新进程将使用与调用进程相同的驱动器和目录。这个选项是一个需要启动启动应用程序并指定它们的驱动器和工作目录的外壳程序的主要条件。
1.7 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,则新进程可能创建失败.
dwFlags包含一组标志,大多数标志都只是告诉CreateProcess函数,STARTUPINFO 中其他成员是否包含有用的信息,或者是否应该忽略一些成员
标志 含义
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秒内必须显示窗口,否则光标同样还原
1.8 lpProcessInformation
指向必须指定的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使你能非常容易识别系统中的进程和线程,一些实用工具(如Task Manager)对ID使用最多,而高效率的应用程序则使用得很少,因于这个原因,大多数应用程序完全忽略ID。
如果应用程序使用ID来跟踪进程和线程,必须懂得系统会立即复用进程ID和线程ID,如,一个进程被创建时,ID值122,如果创建其他新进程对象,系统不会把相同的ID赋予给它,但是,如果第一个进程对象被释放,系统就可以把122赋予创建的下一个进程对象,因此,如果应用程序想要与它的“创建者”进行通信,最好不要使用ID,应该定义一个持久性更好的机制,对如内核对象和窗口句柄等。
如果想创建一个新进程,并等待结果,可用以下类似代码:
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
// STARTUPINFO si = {sizeof(si)};
STARTUPINFO si = {0};
PROCESS_INFORMATION pi;
cout << sizeof(si) << endl;
cout << "before process Creat: pi.dwProcessId = " << pi.dwProcessId << endl;
cout << "before process Creat: pi.dwThreadId = " << pi.dwThreadId << endl;
cout << "before process Creat: pi.hProcess = " << pi.hProcess << endl;
cout << "before process Creat: pi.hThread = " << pi.hThread << endl;
cout << "--------------------------" << endl;
TCHAR szCommandLine[] = TEXT("NOTEPAD");
BOOL fSuccess = CreateProcess(NULL, szCommandLine, NULL, NULL,
FALSE, 0, NULL, NULL, &si, &pi);
cout << "after process Creat: pi.dwProcessId = " << pi.dwProcessId << endl;
cout << "after process Creat: pi.dwThreadId = " << pi.dwThreadId << endl;
cout << "after process Creat: pi.hProcess = " << pi.hProcess << endl;
cout << "after process Creat: pi.hThread = " << pi.hThread << endl;
cout << "--------------------------" << endl;
DWORD dwExitCode;
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. 暂停主进程的执行,直到child终止,该代码才可以继续运行
WaitForSingleObject(pi.hProcess, INFINITE);
cout << "INFINITE = " << INFINITE << endl;
//The child process terminated;
//get its exit code.
GetExitCodeProcess(pi.hProcess,
&dwExitCode);
cout << "dwExitCode = " << dwExitCode << endl;
//Close the process handle as soon as
//it is no longer needed.
CloseHandle(pi.hProcess);
}
cout << "--------------------------" << endl;
cout << "CloseHandle process : pi.dwProcessId = " << pi.dwProcessId << endl;
cout << "CloseHandle process : pi.dwThreadId = " << pi.dwThreadId << endl;
cout << "CloseHandle process : pi.hProcess = " << pi.hProcess << endl;
cout << "CloseHandle process : pi.hThread = " << pi.hThread << endl;
system("pause");
}
104
before process Creat: pi.dwProcessId = 4199744
before process Creat: pi.dwThreadId = 0
before process Creat: pi.hProcess = 0
before process Creat: pi.hThread = 0x4019c0
--------------------------
after process Creat: pi.dwProcessId = 5280
after process Creat: pi.dwThreadId = 6172
after process Creat: pi.hProcess = 0x114
after process Creat: pi.hThread = 0x10c
--------------------------
INFINITE = 4294967295
dwExitCode = 0
--------------------------
CloseHandle process : pi.dwProcessId = 5280
CloseHandle process : pi.dwThreadId = 6172
CloseHandle process : pi.hProcess = 0x114
CloseHandle process : pi.hThread = 0x10c
需要说明的是,只有当进程对象终止运行时,WaitForSingleObject才能得到通知,因此对WaitForSingleObject的调用会将父进程的线程挂起,直到子进程终止运行,当WaitForSingleObject返回时,通过GetExitCodeProcess,就可以得到子进程的退出码