Windows编程----CreateProcess函数
CreateProcess函数原型
CreateProcess 函数用于创建一个新进程(子进程)及其主线程,其函数原型如下:
BOOL CreateProcess(
LPCWSTR lpApplicationName,//指向可执行模块名称的指针
LPWSTR lpCommandLine,//指向命令行字符串的指针。
LPSECURITY_ATTRIBUTES lpProcessAttributes,//指向 SECURITY_ATTRIBUTES 结构的指针,指定新进程的安全属性。
LPSECURITY_ATTRIBUTES lpThreadAttributes,//指向 SECURITY_ATTRIBUTES 结构的指针,指定新线程的安全属性。
BOOL bInheritHandles,//如果为 TRUE,新进程将继承调用进程的句柄。
DWORD dwCreationFlags,//指定附加的、用来控制优先类和进程的创建的标志。
LPVOID lpEnvironment,//指向新进程的环境块的指针。如果为 NULL,新进程将使用调用进程的环境。
LPCWSTR lpCurrentDirectory,//指向新进程的当前目录的指针。如果为 NULL,新进程将使用调用进程的当前目录。
LPSTARTUPINFOW lpStartupInfo,//指向 STARTUPINFOW 结构的指针,指定新进程的主窗口特性。
LPPROCESS_INFORMATION lpProcessInformation//指向 PROCESS_INFORMATION 结构的指针,接收新进程的标识符和句柄。
);
为了演示用一个进程来启动一个新的进程,我们在这里首先准备一个NewApp的进程,表示即将被CreateProcess 函数启动的进程,NewApp的代码如下,代码打印出进程的命令行启动参数
//这是NewApp程序
#include <iostream>
#include <Windows.h>
int main(int argc, char** argv)
{
for (int i = 0; i < argc; i++) {
std::wcout << argv[i] << std::endl;
}
system("pause");
}
lpApplicationName和lpCommandLine
lpApplicationName
:即将启动的exe程序路径,该参数是一个字符串。我们可以传相对路径或者绝对路径,Windows在启动的时候,会按照一定的顺序查找exe。
1、如果该参数传递的是exe全路径,操作系统会直接启动指定的全路径exe,如果找不到要启动的exe文件,CreateProcess会启动失败。
2、查找主进程exe同级目录下是否存在要启动的exe文件。
3、查询主进程的当前目录下是否存在要启动的exe文件,一般情况下,主进程的当前目录和主进程exe是同一个目录,但是也不绝对,我们可以手动修改程序的当前目录。
4、查询Window系统目录下是否存在要启动的exe文件,就是GetSystemDirectory获取到的文件夹。
5、查询Window目录下是否存在要启动的exe文件。
6、查询环境变量Path所表示的那些目录下,数据存在要启动的exe文件。
从上面的流程看,操作系统查找要指定的exe文件是一个很复杂的流程,所以如果条件允许,我们建议传递全路径。如果不允许,也至少应该是将exe文件放到主进程exe的相对目录下,这也是我经常采用的一种方式。
lpCommandLine
:表示要启动的进程需要接受的命令行参数。
接下来,我们用主进程来启动NewApp.exe ,主进程的代码如下:
#include <iostream>
#include <Windows.h>
int main()
{
//即将启动的exe程序路径
LPCWSTR lpApplicationName = L"D:\\project\\ConsoleApp1\\x64\\Debug\\NewApp.exe";
//即将传递给exe程序的命令行参数
LPWSTR lpCommandLine = const_cast<LPWSTR>(L"key1=value1 --key2=value2 /key3=value3 --key4 value4 /key5 value5");
// 定义启动信息和进程信息结构
STARTUPINFOW si;
PROCESS_INFORMATION pi;
// 初始化启动信息结构
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
BOOL ret=CreateProcess(lpApplicationName,
lpCommandLine,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
system("pause");
return 0;
}
运行以上代码之后,我们会看到以下运行结果,很明显,我们的子进程NewApp被成功启动,并且打印出了主进程传递的命令行参数。

lpProcessAttributes、lpThreadAttributes、bInheritHandles
这两个参数代表子进程的进程安全属性和线程安全属性,都指向SECURITY_ATTRIBUTES
的一个结构体,一般情况下,我们可以传NULL,表示子进程使用默认的进程安全属性和 线程安全属性。bInheritHandles
表示子进程是否可以继承父进程的句柄(父进程设置了允许继承的安全属性)。如果设置为TRUE,则表示可以继承。
dwCreationFlags
dwCreationFlags
参数用于指定创建新进程的时候的一些附加标志,用于控制新进程的一些行为,下面是常用的一些标识:
1、CREATE_NEW_CONSOLE:为新进程创建一个新的控制台窗口。上面的代码我们使用了这个表示,主进程和新进程是两个控制台窗口,如果没有这个flag的话,主进程和新创建的进程共用一个 控制台程序。
2、CREATE_NO_WINDOW:不要为新进程启动一个窗口。如果我们需要创建一个在后台运行没有界面的进程的话,可以使用这个flag。
3、CREATE_SUSPENDED:创建新进程,不要立即执行,将进程挂起,直到调用ResumeThread函数的时候,才开始调用进程。如果我们创建多个子进程之后,需要有一个统一的同步策略,由主进程统一控制多个子进程的执行顺序的话,可以使用这个flag,在上面的代码上稍微做一点修改,新增一个flag和新增两行代码,运行起来,你会发现新创建的进程不会立即执行,而是等待5s之后由主进程控制它继续执行。
BOOL ret = CreateProcess(lpApplicationName,
lpCommandLine,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE | CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi);
Sleep(5000);
ResumeThread(pi.hThread);
4、CREATE_UNICODE_ENVIRONMENT:创建Unicode字符的环境变量,如果使用了该flag,那么lpEnvironment
参数指向的环境变量块将使用Unicode,否则将使用ANSI字符。
5、CREATE_DEFAULT_ERROR_MODE:每个进程都有自己的一个错误模式,可以通过SetErrorMode
函数设置,默认情况下,新进程继承父进程的错误模式,如果使用该flag,新进程将使用默认的错误模式。
6、DETACHED_PROCESS:新进程和父进程分离,不继承父进程的控制台。该flag会阻止子进程访问父进程的控制台窗口,一般我们也很少使用该flag。这个flag和CREATE_NO_WINDOW作用一致,不能同时使用,否则程序会报错。
lpEnvironment
默认情况下,子进程将继承父进程的环境变量。如果想给子进程单独设置环境变量块,可以传递该参数。下面我们修改子进程NewApp的代码,在NewApp中输出环境变量VAR1的值,这个环境变量将由主程序传递给子进程。
#include <iostream>
#include <Windows.h>
int main(int argc, char** argv)
{
// 定义环境变量名
LPCWSTR envVarName = L"VAR1";
// 分配缓冲区,接受环境变量值
std::wstring envVarValue(1024, L'\0');
// 获取环境变量值的长度
DWORD bufferSize = GetEnvironmentVariable(envVarName, &envVarValue[0], 1024);
// 输出环境变量值
std::wcout << envVarName << L" = " << envVarValue << std::endl;
while (true) {
Sleep(1000);
}
}
接下来我们来修改主程序的代码:
#include <iostream>
#include <Windows.h>
int main()
{
//即将启动的exe程序路径
LPCWSTR lpApplicationName = L"D:\\project\\ConsoleApp1\\x64\\Debug\\NewApp.exe";
//即将传递给exe程序的命令行参数
LPWSTR lpCommandLine = const_cast<LPWSTR>(L"key1=value1 --key2=value2 /key3=value3 --key4 value4 /key5 value5");
// 定义启动信息和进程信息结构
STARTUPINFOW si;
PROCESS_INFORMATION pi;
// 初始化启动信息结构
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
WCHAR* envVarChars = const_cast<WCHAR*>(L"VAR1=VALUE1\0VAR2=VALUE2\0\0\0\0");//需要传递给子进程的环境变量块
BOOL ret = CreateProcess(lpApplicationName,
lpCommandLine,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
envVarChars,
NULL,
&si,
&pi);
system("pause");
return 0;
}
上述代码中envVarChars
是需要传递给子进程的环境变量块,它是一个字符串。格式为key=value\0
,变量名和变量值用等号分开,后面跟一个\0区分多个变量。
如果环境变量参数lpEnvironment
是一个Unicode字符串的话,注意dwCreationFlags
要加上CREATE_UNICODE_ENVIRONMENT
这个flag,否则进程会创建失败。
请注意,ANSI 环境块由两个零字节终止:一个用于最后一个字符串,另一个用于终止该块。 Unicode 环境块由四个零字节终止:两个用于最后一个字符串,两个用于终止该块。
上面代码运行如下:我们可以看到控制台输出了VAR1的变量值为VALUE1,这个变量是从父进程那里继承来的。同时我们在ProcessExplorer软件中,可以看到子进程的环境变量有两个,分别是VAR1和VAR2。这说明子进程成功继承了父进程的环境变量。


lpCurrentDirectory
设置子进程的当前目录,如果为 NULL,新进程将使用调用进程的当前目录。我们修改NewApp的代码,在代码中输出NewApp进程的当前目录
#include <iostream>
#include <Windows.h>
int main(int argc, char** argv)
{
// 定义缓冲区大小
WCHAR currentDir[MAX_PATH];
// 获取当前工作目录
DWORD length = GetCurrentDirectory(MAX_PATH, currentDir);
// 输出当前工作目录
std::wcout << L"Current Directory: " << currentDir << std::endl;
system("pause");
}
然后我们修改主进程CreateProcess的参数,lpCurrentDirectory参数:
LPCWSTR cuuDir = L"D:\\Test\\";
BOOL ret = CreateProcess(lpApplicationName,
lpCommandLine,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
cuuDir,//设置子进程的当前目录
&si,
&pi);
调试上述代码之后,新创建的进程会输出D:\\Test\\
,运行效果如下:

lpStartupInfo
指定新进程的主窗体特性(指定窗体大小、初始位置、控制台背景颜色字体颜色、窗口标题等信息)、标准输入、输出、错误设备句柄等,其定义如下:
typedef struct _STARTUPINFOW {
DWORD cb;//结构的大小,以字节为单位。
LPWSTR lpReserved;//保留,必须为 NULL。
LPWSTR lpDesktop;//指向一个以空字符结尾的字符串,指定桌面名称。
LPWSTR lpTitle;//指向一个以空字符结尾的字符串,指定新进程的窗口标题。
DWORD dwX;//指定窗口的初始位置X
DWORD dwY;//指定窗口的初始位置Y
DWORD dwXSize;/指定窗口的初始大小X
DWORD dwYSize;//指定窗口的初始大小Y
DWORD dwXCountChars;//指定屏幕缓冲区的宽度,以字符为单位。
DWORD dwYCountChars;//指定屏幕缓冲区的高度,以字符为单位。
DWORD dwFillAttribute;//指定新控制台窗口的文本和背景颜色。
DWORD dwFlags;//指定有效的 STARTUPINFO 成员。
WORD wShowWindow;//指定窗口显示状态。
WORD cbReserved2;//保留,必须为 0。
LPBYTE lpReserved2;//保留,必须为 NULL。
HANDLE hStdInput;//新进程的标准输入句柄
HANDLE hStdOutput;//新进程的标准输出句柄
HANDLE hStdError;//新进程的标准错误设备句柄
} STARTUPINFOW, *LPSTARTUPINFOW;
关于这些信息大家可以自定去设置和实验,文章中就不给大家一一设置和实现了。
lpProcessInformation
lpProcessInformation是一个输出参数,进程创建之后,会把子进程的进程句柄、线程句柄、进程id、线程id返回给主进程。参数的定义如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
首先我们来说线程Id和进程Id,我们在创建成功进程之后,将进程id和线程id打印出来,代码如下:
BOOL ret = CreateProcess(lpApplicationName,
lpCommandLine,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE ,
NULL,
NULL,
&si,
&pi);
std::cout << "dwProcessId = " << pi.dwProcessId << std::endl;
std::cout << "dwThreadId = " << pi.dwThreadId << std::endl;
输出结果如下:

可以很明显看到进程Id为18492,线程Id为18736.然后我们到进程管理器查看这个进程的Id就是18492,如下:

关于线程Id:18736在进程管理器是无法查看的,但是我们可以通过ProcessExplorer软件查看主线程Id,查看方式如下:选中指定的进程,点击下方的Threads 标签栏,可以查看当前进程的所以线程,我们会发现只有一个主线程:18736.这和我们主程序打印出来的结果是一致的。

默认情况下,ProcessExploer下方的tab栏是不显示的,如果需要显示,可以通过选中View->ShowLowerPane来打开

关于进程Id和线程Id需要注意的是:Windows有一个ID编号池,用于给进程和线程分配ID,并且这个ID池中的ID永远不会重复,这意味着永远不会有进程ID和线程ID重复。不过当进程线程推出的时候,这个进程的ID会回到ID池,后续可能会分配给别的进程。
试想一下。我用进程111创建了一个子进程222,子进程222的父进程确实是111,但是因为某些原因进程111被回收,并且重新分配给了一个其他的进程,如果这个时候,子进程再拿着父进程的ID:111去处理业务的话,就会出现意想不到的错误。同理,线程ID也是如此。所以,一般情况下,我们习惯用句柄操作具体业务,很少会使用ID来处理业务。那么hProcess
参数和hThread
参数就代表子进程的进程句柄和主线程句柄,父进程可以使用这两个参数做对应的业务逻辑。
Windows编程----CreateProcess函数的更多相关文章
- windows 编程—— 常用函数 与 操作
目录: MessageBox() 和 PlaySound() 获得窗口 或屏幕大小 获得字体大小 输出文字 屏蔽和显示控制台窗口 1. MessageBox() 和 PlaySound() Messa ...
- windows 编程—— 使用函数笔记
目录: 创建滚动条 滚动条函数(新老版本) 取得设备内容句柄hdc 设置 hdc 中的属性 画点画线 画填充图形 使用自定义的 画笔 和 画刷 矩形.区域和剪裁 关于GDI映像模式 其他常用的方便计算 ...
- Windows编程MessageBox函数
API: int MessageBox(HWND hWnd, LPCTSTRlpText, LPCTSTRlpCaption, UINTuType); MSDN描述: This function cr ...
- 2.C++标准库函数:getline函数 定界流输入截取函数 -windows编程
引言:今天工作遇到了一个需要按行读取txt文件数据的需求,查询了一下getline()函数,发现这竟然是一个C++的标准库函数,而且设计的很好,特地做一下记录.getline本质是一个定界流输入截取函 ...
- Windows编程入门程序详解
引用:http://blog.csdn.net/jarvischu/article/details/8115390 1. 程序 /******************************* ...
- Direct3D 10学习笔记(四)——Windows编程
本篇将简单整理基本的Windows应用程序的实现,并作为创建Direct3D 10应用程序的铺垫.具体内容参照< Introduction to 3D Game Programming with ...
- windows编程 进程的创建销毁和分析
Windows程序设计:进程 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,在Windows编程环境下,主要由两大元素组成: • 一个是操作系统用来管理进程的内核对象.操作系统使用内 ...
- 【Windows编程】系列第六篇:创建Toolbar与Statusbar
上一篇我们学习了解了如何使用Windows GDI画图,该应用程序都是光光的静态窗口,我们使用Windows应用程序,但凡稍微复杂一点的程序都会有工具栏和状态栏,工具栏主要用于一些快捷功能按钮.比如典 ...
- 【Windows编程】系列第十一篇:多文档界面框架
前面我们所举的例子中都是单文档界面框架,也就是说这个窗口里面的客户区就是一个文档界面,可以编写程序在里面输入或者绘制文本和图形输出,但是不能有出现多个文档的情况.比如下面的UltraEdit就是一个典 ...
- 【Windows编程】系列第十篇:文本插入符
大家知道,在使用微软的编程环境创建工程时会让你选择是控制台模式还是Windows应用程序.如果选择控制台的console模式,就会在运行时出现一个黑洞洞的字符模式窗口,里面就有等待输入一闪一闪的插入符 ...
随机推荐
- Unity 3D简单使用C#脚本,脚本的执行顺序
Unity3D脚本间执行顺序 Unity3D中一个场景有时候需要多个脚本,可以挂在同一物体上执行,也可以挂在不同物体上执行 那么执行顺序是怎样的?我们来测试下 在上个项目基础上,再建一个Test2脚本 ...
- Qt采集本地摄像头推流成rtsp/rtmp(可网页播放/支持嵌入式linux)
一.功能特点 支持各种本地视频文件和网络视频文件. 支持各种网络视频流,网络摄像头,协议包括rtsp.rtmp.http. 支持将本地摄像头设备推流,可指定分辨率和帧率等. 支持将本地桌面推流,可指定 ...
- Qt编写安防视频监控系统58-子模块2窗口信息
一.前言 窗口信息一般用来打印输出文字信息,带时间,有些用户场景可能除了时间和内容以外,还需要其他的字段信息,可以自行在代码中增加字段即可,窗口信息一般以表格样式居多,上面是字段标题,下面是一行行的输 ...
- Qt编写的项目作品21-网络请求客户端/服务器
一.实现原理 http请求就是tcp通信,所以第一步实例化QTcpServer类监听端口,并绑定newConnection信号槽. 一旦有新的连接,交给专门的解包类处理,将对应的数据解包,http请求 ...
- Event-Stream技术
服务端 websocket和event-stream的优缺点 WebSocket和Event-Stream(Server-Sent Events)都是实现实时通信的技术,但是它们各自有不同的优缺点. ...
- Helm适配华为云OBS实践分享,更方便地部署、管理复杂应用
沃土云创开源开发者专项计划是华为给开源开发者提供专属激励资源,鼓励开发者积极参与开源 for Huawei适配,践行"让优秀开发者支持更优秀开发者"的理念. 此前我们介绍了Beam ...
- Android 系统使RNDIS网卡上网
背景说明: 一位台湾客户需要采购一批SIMCOM SIM6600CE模组用于Tinker board2s,需要适配Debain系统和Android系统. 主要修改点: 1.defconfig 增加RN ...
- Event Store-其它存储
背景 ENode是一个CQRS+Event Sourcing架构的开发框架,Event Sourcing需要持久化事件,事件可以持久化在DB,但是DB由于面向的是CRUD场景,是针对数据会不断修改或删 ...
- 日志数据采集-Flume
1. 前言 在一个完整的离线大数据处理系统中,除了hdfs+mapreduce+hive组成分析系统的核心之外,还需要数据采集.结果数据导出.任务调度等不可或缺的辅助系统,而这些辅助工具在hadoop ...
- bat脚本判断windows服务,判断windows进程
bat脚本判断windows服务是否存在,方式一: sc query|findstr /i "ZhuDongFangYu" &&echo "存在" ...