欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Win32 API (4) CreateProcess

程序员文章站 2022-03-05 09:57:14
...

CreateProcess

CreateProcess 是一个宏,根据是否定义符号常量 UNICODE 展开为 ASCII 和 UNICODE 两个版本,其实如果不是必须更推荐使用 UNICODE 版本的 API 函数,因为即便是 ASCII 版本的函数,内核层面也是通过 UNICODE 版本来实现的。

函数原型:
// ASCII 版本
BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);
// UNICODE 版本
BOOL CreateProcessW(
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

该函数用于创建一个新的进程和他的主线程,新进程运行在父进程安全上下文(Security Context)中。

如果父进程正在模拟(impersonate)另一个用户,此时创建的子进程使用父进程的访问令牌,而非模拟令牌(impersonation token)。如果想在模拟令牌表示的安全上下文中创建新进程,应该使用 CreateProcessAsUser 或者 CreateProcessWithLogonW

参数说明:

lpApplicationName

该参数是待执行的模块的名字,通常是一个基于 Windows 平台的应用程序,如果本地计算机支持也可以是 MS-DOS 或者 OS/2 应用。

该参数可以指定可执行文件名和包含文件的完整目录,当省略掉目录的时候会默认是用进程的工作目录来补全,而不会使用 PATH 变量搜索文件名。可执行文件必带有后缀名,不能省略。

该参数可以为 NULL,此时可执行文件名是 lpCommandLine (第二个参数)的第一个空字符分隔开的子字符串。此时如果 lpCommandLine 使用了一个包含空格的长路径,为了避免歧义应该使用引号区分开文件名和参数。例如如果第二个参数为 c:\program files\sub dir\program name,系统将使用下面的顺序来解析:

  1. c:\program.exe
  2. c:\program files\sub.exe
  3. c:\program files\sub dir\program.exe
  4. c:\program files\sub dir\program name.exe

对于16位应用程序来说,第一个参数必须为 NULL ,第二个参数应该同时包含文件名和命令行参数。

lpCommandLine

该参数是包含最多32768个字符(结尾的空字符也算在内了)的命令行参数,如果 lpApplicationName 是 NULL,则该参数中模块名最长为 MAX_PATH 。函数的 UNICODE 版本可能修改字符串的值,因此如果参数指向一块只读的内存(比如const变量,字符串字面量),会导致非法访问(Access Violation)错误。

这个参数也可以是 NULL,此时函数使用 lpApplicationName 指向的字符串作为命令行。如果前两个参数都不是 NULL,则 lpApplicationName 作为可执行文件名,lpCommandLine 作为命令行参数。新的进程可以使用函数 GetCommandLine 来获取完整的命令行,C语言编写的控制台应用程序则可以使用主函数参数 argcargv 来获取命令行。

lpApplicationName 是 NULL 时,第二个参数的首个空白分隔子字符串就是文件名,此时函数对文件名的处理方式和对第一个参数的处理有所区别,如果文件名没有后缀则默认使用.exe,如果文件名包含路径或者文件名以 . 结尾则不会自动附加文件后缀。如果文件名不包含目录,则会按照启动目录,工作目录,系统目录,Windows目录,PATH环境变量的顺序来搜索可执行文件名(和 WinExec 的工作方式相同)。该函数不会搜索应用程序的 PATH(通过注册表键 App Path 指定),如果需要可以通过 ShellExecute 函数来实现该功能。

lpProcessAttributes

这个参数是指向SECURITY_ATTRIBUTES 结构体的指针,用来决定 CreateProcess 得到的进程句柄能否被子进程所继承,参数为 NULL 表示不允许继承。

SECURITY_ATTRIBUTES 结构体有一个名为 lpSecurityDescriptor 的成员,该成员决定了新进程的安全描述符。如果参数 lpProcessAttributes 或者成员 lpSecurityDescriptor 为 NULL,则进程使用默认安全描述符,对于 Windows XP 以上操作系统来说线程默认安全描述符的访问控制列表(ACL) 来自创建者的主令牌(Primary Token),在此之前还可以是模拟令牌。

lpThreadAttributes

和上一个参数类似,也是 SECURITY_ATTRIBUTES 结构的指针,只不过此处用于描述线程。

bInheritHandles

子进程是否继承父进程中的可继承句柄,如果为 TRUE 则继承,为 FALSE 则不继承。继承来的句柄和原句柄有着相同的值和访问权限。

dwCreationFlags

进程创建标志

lpEnvironment

进程环境块指针,如果该参数为 NULL 则子进程使用父进程的环境块。

lpCurrentDirectory

进程工作目录,该参数应该是进程当前目录的完整路径。如果这个参数为 NULL 则新进程的工作目录和父进程相同。

lpStartupInfo

指向 STARTUPINFO 或者 STARTUPINFOEX 结构体的指针。设置扩展属性时要使用 STARTUPINFOEX 结构体,并且在参数 dwCreationFlags 中指定 EXTENDED_STARTUPINFO_PRESENT 。如果结构体 STARTUPINFO 或者 STARTUPINFOEX 中的句柄不再使用,必须通过函数 CloseHandle 关闭。

注意调用者要确保 STARTUPINFO 中的标准句柄成员取值合法。这些字段在拷贝给子进程时不经过校验,即便 dwFlags 成员给了值 STARTF_USESTDHANDLES 。错误的句柄值将会导致子进程行为异常或者崩溃。可以使用Application Verifier 运行时验证工具来检测非法句柄。

lpProcessInformation

这是一个输出参数(由CreateProcess 函数负责填充),它是指向 PROCESS_INFORMATION 结构体的指针,该结构体包含子进程的身份信息。PROCESS_INFORMATION 中的句柄成员如果不再使用需要调用 CloseHandle 关闭。

PROCESS_INFORMATION 结构体定义如下:

typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;
  HANDLE hThread;
  DWORD  dwProcessId;
  DWORD  dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;

四个成员从上到下分别是进程句柄,线程句柄,进程ID,线程ID。

返回值:

如果函数成功执行,返回非0值。

如果函数执行失败,返回0,详细错误信息可以通过调用 GetLastError 来获得。

该函数在进程完成初始化以前就已经返回,因此如果 DLL 加载或者初始化失败进程会终止。通过调用 GetExitCodeProcess 获取进程终止状态。

注意事项:

子进程创建以后会分配进程 ID ,子进程的主线程也会分配线程 ID ,他们可以分别用在 OpenProcessOpenThread 函数来获得进程和线程的句柄。在相应进程和线程终止以前,进程ID和线程ID都可以作为进程和线程的唯一合法标识。我们可以通过 PROCESS_INFORMATION 结构体来访问进程 ID 和线程 ID 。

鉴于 CreateProcess 并不会等待子进程初始化,如果我们需要同步父进程和子进程,可以在父进程调用函数 WaitForInputIdle ,该函数会在子进程初始化完毕并开始等待用户输入后才返回。在某些情况下这很有用,比如我们在想要在创建子进程以后查找子进程的窗口。

最好使用 ExitProcess 来关闭一个进程,这个函数会向所有附加在该进程上的动态链接库发送关闭通知。其他关闭方法不会通知进程相关的DLL。但线程调用 ExitProcess 之后,进程的其他线程也会立即关闭并且来不及执行任何其他代码。

父进程在创建子进程时可以直接修改子进程的环境变量。这是一个进程直接修改另一个进程的环境设置的唯一机会。

lpApplicationName 为 NULL 时,该函数和 WinExec 存在完全相同的安全隐患,具体可以参考这里。为了避免这个问题尽量不要给第一个参数设为 NULL。

依赖信息:
名称
Header processthreadsapi.h (include Windows Server 2003, Windows Vista, Windows 7, Windows Server 2008 Windows Server 2008 R2, Windows.h)
Library Kernel32.lib
DLL Kernel32.dll
相关标签: Windows API