WinAPI执行外部程序和创建新进程:CreateProcess()的使用
一、基本原理和流程
执行一个外部程序实质上可以认为就是创建一个进程
windows系统下创建一个进程意味着:
1、创建一个内核对象:内核对象是windows系统用于管理进程的一个工具,可以认为是一个数据结构。
2、创建一个地址空间:用于存放可执行文件的代码和数据
当调用CreateProcess()时,windows会自动创建一个进程内核对象,将进程内核对象引用计数设置为1,并将可执行文件的代码和数据加载到地址空间,然后再创建相应的主线程的内核对象,最终执行入口函数
二、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
);
三、参数详解
CreatProcess()需要若干参数来指定新进程的运行方式,但实际使用中多半参数都是用不到的,可以设置为NULL
1、lpApplicationName
这个参数用于指定可执行文件的名称也可以是可执行文件的路径
该参数的类型是LPCTSTR,其实就是一个const char*的传统C字符串,要求以’0’结尾,需要注意的是这必须是个常量字符串
需要注意的是在lpApplicationName中必须指定扩展名(.exe)并且如果没有指定路径,windows将只在当前目录中寻找,没有找到就会出错返回False。另一方面,即使指定了lpApplicationName也会把lpCommmandLine(命令行参数)原封不动的作为命令行参数传递,所以应避免重复传递可执行文件名的问题
实际使用中常常把lpApplicationName设置为Null,用lpCommandLine参数来指定可执行文件名,这样可以扩大windows寻找可执行文件的范围
2、lpCommandLine
这个参数用于指定传递给新进程的命令行参数,更多时候我们把可执行文件名也包含在内(这意味着lpApplicationName应该设置为Null)
该参数的类型是LPTSTR,即一个以’0’结尾的char*的C传统字符串,而关键在于:这是个非常量字符串!非常量!非常量!CreateProcess会在内部对这个字符串进行更改,然后在返回前改回去!如果传递了一个常量字符串就必然出错(常量字符串保存在只读内存区域,不能进行写操作!)
通常的做法:
LPWSTR cmdLine = (LPWSTR)"XXX.exe -X -Y";
如果这个地方出错可能是另一个问题:MSVC“智能”地把字符串放到了只读内存区域,这个问题会不会出现和如何解决我也不知道……应该是比较少见的……这个是Microsoft做的比较不好的一个地方,为什么不在CreateProcess内部创建一个副本呢……
一般会把lpApplicationName置Null,然后在ipCommandLine中传递完整的命令行,即:
XXX.exe -X -Y
这样做的好处是windows会自动搜索当前目录、系统目录和PATH环境变量中的目录,不过如果已经给了完整路径,自然就不会进行如上搜索了
如果lpApplicationName不为Null,那lpCommandLine就会原封不动的作为命令行参数传递,所以当指定了lpApplicationNAme后就不应该再在lpCommandLine中写可执行文件名了(我被这个坑过)
3、lpProcessAttributes
这个是一个指向SECURITY_ATTRIBUTES结构的指针,用来给进程对象设置安全性,即返回的新进程对象句柄能否被子进程继承
这个东西我不怎么用的到,就没仔细研究,一般传递NULL,让windows把进程内核对象设为默认安全性,即新进程对象句柄不能被子进程继承,MSDN写的也不是很详细,如果有需要建议查询MSDN,或者等到某一天我用到这个东西的时候,查阅完资料再来更新……
4、lpThreadAttributes
这个和上一个参数差不多,同样是一个指向SECURITY_ATTRIBUTES结构的指针,只不过是用来给线程对象设置安全性的,即返回的新线程对象句柄能否被子线程继承
同样,这个一般也传递NULL设置为默认安全性,也就是句柄不能被继承,详情参阅MSDN……
5、bInheritHandles
这个同样是关于安全性的标识符,是个BOOL型变量,用于控制新进程是否可以从调用进程处继承所有可继承的句柄,而被继承的句柄与原进程拥有完全相同的值和访问权限
设置为TRUE新进程将继承调用CreateProcess的原进程的所有可继承的句柄,而设置为FALSE则不会继承原进程的任何句柄
同样,懒惰的我也把这个设置为FALSE……详情请参阅MSDN……
6、dwCreationFlags
这是个DWORD的标识,用于设置新进程创建的方式,各个标识符以宏的形式定义,可以用位或组合使用,同时指定多个标识符
这个标识符还是挺多的,这里我只把我可能用到的几个列出来,其他的还是请教MSDN吧:)
CREATE_NEW_CONSOLE //要求系统为新进程创建一个新的控制台窗口(否则会和原进程共用一个控制台窗口)
CREATE_NO_WINDOW //不为新进程创建窗口,可以用这个来创建一个没有窗口的应用程序
DEBUG_PROCESS //将新进程作为被调试程序而原进程被当做调试器,新进程和其创建的其他进程中发生的特定事件都会被通知原进程
CREATE_UNICODE_ENVIRONMENT //告诉系统新进程的环境块包含Unicode字符(默认为ANSI)
另一方面还可以手动指定新进程的优先级类,不过一般不建议这么做,应该由系统指定默认优先级类
可能用到的优先级:
BELOW_NOEMAL_PRIORTY_CLASS //低于标准
NORMAL_PRIORITY_CLASS //标准
ABOVE_NORMAL_PRIORITY_CLASS //高于标准
HIGH_PRIORITY_CLASS //高
7、lpEnvironment
这个参数指向一块包含新进程要使用的环境字符串的内存,通常传入NULL表示继承原进程的环境字符串,一般是用不到的,放心传NULL就好
8、lpCurrentDirectory
该参数用于设置新进程的当前驱动器和目录,必须是一个以’0’结尾的绝对路径字符串,如果用不到特殊设置可以直接传递NULL表示新进程的工作目录和创建新进程的原进程工作目录一致,一般就用NULL吧
9、lpStartupInfo
这个参数指向一个STARTUPINFO或STARTUPINFOEX的结构,一般应用程序会期待仅使用默认值,所以大可以全部置零
结构定义如下:
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR 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;
} STARTUPINFOA, *LPSTARTUPINFOA;
其中可能用到的几个列举如下
cb
必须初始化为sizeof(STARTUPINFO),
或sizeof*STARTUOINFOEX
dwX和dwY
指定新进程窗口在屏幕上的位置,只当执行的应用程序用CW_USEDEFAULT创建窗口时生效,如果是控制台的话就直接指定控制台窗口位置
dwXSize和dwYSize
指定新窗口的宽和高,同样仅当新窗口用CW_USEDEFAULT时生效,控制台窗口则直接指定宽和高
wShowWindow
指定主窗口如何显示,详情参阅MSDN
dwFlags
这个变量是个标志,可以用位或组合各个标志
基本左右是告知CreateProcess在lpStartupInfo里包含哪些有用的信息
常用的:
STARTF_USEPOSITION //表示使用了dwX和dwY
STARTF_USESIZE //表示使用了dwXSize和deYSize
STARTF_RUNFULLSCREEN //使控制台程序全屏运行
10、lpProcessInformation
是一个指向LPPROCESS_INFOMATION结构的指针,这个结构由调用CreateProcess的进程创建,由CreateProcess更改,用于返回新建进程的信息,其结构定义如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION,*LPPROCESS_INFORMATION;
分别是进程句柄、线程句柄、进程ID和线程ID
之前在原理中说过每创建一个新进程都会产生一个进程内核对象和一个线程内核对象,前两个参数就是对应的进程对象的句柄和主线程对象的句柄,而每一个进程和线程windows都会分配一个独一无二的标识符,后两个参数就对应创建的进程的ID和进程的主线程的ID
通常情况下我们会忽略进程ID和线程ID,一般使用相应的句柄进行跟踪控制,如果一定要使用ID的话应该注意一件事:系统中ID是可重用的,这意味着当一个进程或线程终止后同一个ID会分配给其他的新进程或线程,应当及时更新ID,避免出错
四、例程
一个简单的例程,其中各个参数可以参照上文更改,以后有时间再补上详细的例程吧
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
int main()
{
LPWSTR filename = (LPWSTR)"notepad\0";
LPPROCESS_INFORMATION info = NULL;
STARTUPINFO si = { sizeof(si) };
CreateProcess(NULL,
filename,
NULL,
NULL,
FALSE,
NULL,
NULL,
NULL,
&si,
info
);
return 0;
}