CreateProcess()接收程序的输出

环境:win10 64位,vs2015 mfc程序

目的:记录CreateProcess()接收启动程序的输出。注:cmd程序,而不是带ui的程序

起始原由:通过调用git工具来完成自己的一些需求

参考:

https://bbs.csdn.net/topics/190138594,会读取控制台输出,但是如何向控制台输入数据?交互?
https://blog.csdn.net/waitig1992/article/details/23766833,MFC执行CMD命令并获得其返回信息源代码

之前几乎没有用过CreateProcess()函数,偶尔需要启动其它程序,一般用system(),也没有啥特殊要求,本次有点不一样,需要接收git的输出,以便进行分析其结果。先上个代码,由于代码是多次调试的结果,所以看上去可能会有点乱,但不影响分析过程。

void LoadGitDirs()
{
	CString strText;
	PROCESS_INFORMATION pi;
	SECURITY_ATTRIBUTES sa;
	memset(&pi, 0, sizeof(pi));
	memset(&sa, 0, sizeof(sa));
	sa.nLength = sizeof(sa);
	sa.bInheritHandle = TRUE;

	HANDLE readPipe = NULL;
	HANDLE wrtePipe = NULL;
	if (!CreatePipe(&readPipe, &wrtePipe, &sa, 1024*20))
	{
		AfxMessageBox(TEXT("创建管道失败,无法继续!"), MB_ICONSTOP);
		return;
	}

	STARTUPINFO si;
	memset(&si, 0, sizeof(si));
	si.cb = sizeof(si);
	si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_HIDE;
	si.hStdOutput = wrtePipe;
	si.hStdError = wrtePipe;

	CString strCmdExe = TEXT("c:\\windows\\system32\\cmd.exe");
	DWORD dwErr = 0;
	CString strPars = m_strGitTool;
	//strPars = TEXT("cmd dir");
	strPars = TEXT(" /c ") + strPars;
	BOOL isOk = CreateProcess(strCmdExe, (LPTSTR)strPars.GetString(), &sa, &sa, TRUE, 0, NULL, NULL, &si, &pi);
	strPars.ReleaseBuffer();

	CloseHandle(wrtePipe);
	wrtePipe = NULL;		// 这里不使用输入

	if (!isOk)
	{
		dwErr = GetLastError();
		CloseHandle(readPipe);
		readPipe = NULL;

		AfxMessageBox(TEXT("启动git命令失败,无法继续!"), MB_ICONSTOP);
		return;
	}

	// 开始等待结束
	DWORD dwWaitResult;
	DWORD dwReadLen = 0;
	CString strGitRes;
	char szBuf[1024 * 24] = { 0 };
	while (true)
	{
		dwWaitResult = WaitForSingleObject(pi.hProcess, 200);
		if (dwWaitResult == WAIT_OBJECT_0 || dwWaitResult == WAIT_ABANDONED)
			break;
		if (ReadFile(readPipe, szBuf, 1024 * 20, &dwReadLen, NULL) && dwReadLen > 0)
		{
			strGitRes += szBuf;
		}
		memset(szBuf, 0, sizeof(szBuf));
		dwReadLen = 0;
	}
	while(ReadFile(readPipe, szBuf, 1024 * 20, &dwReadLen, NULL) && dwReadLen > 0)
	{
		strGitRes += szBuf;
		memset(szBuf, 0, sizeof(szBuf));
		dwReadLen = 0;
	}

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	CloseHandle(readPipe);
	readPipe = NULL;

	if (strGitRes != TEXT(""))
		AfxMessageBox(strGitRes, MB_ICONINFORMATION);
}

本段代码是调试通过的,m_strGitTool是git.exe的绝对路径,原工程用的是unicode编码,所以实际上在ReadFile()之后,szBuf是转换为unicode之后加到strGitRes上的,本文进行了简化,去掉了转换过程,所以如果试验此代码,需要在多字符编码下试验,如果是在unicode下,先对szBuf转换后再加到strGitRes上,否则最后的提示可能就是一堆乱码。

本代码只是调试通过,能正确接收git工具的输出,而在实际使用中是否还有其它问题,本文暂不考虑,因为我只是在调通之后来写这文章,还没有实际使用过,不知道中间是否会有其它一些问题。

以上程序的运行结果,就是弹出一个框,内容就是git的输出,如下所示:

参考的文章,以及搜索出来的其它文章,我觉得本身只是简单记录,并没有考虑的全面,但上面的代码,考虑了启动程序长时间运行时的情况(当然本文说的并不全面),每过200毫秒接收一次,直到程序退出后再继续接收直到接收不到内容为止。

本文主要记录调试过程中遇到的两个问题,另外再加上之前遇到的一些问题说明。

先说sa,即 SECURITY_ATTRIBUTES 结构。这个安全结构,早期的时候我都没用过,有它的地方,全都是设置成了NULL,当然那时候主要是线程上会用到。话说有一天,又在一个程序中创建了线程,最后发现有的电脑上正常运行,而有的电脑上运行失败,通过跟踪发现是创建线程失败,再反复查找问题,最终把安全参数传入的NULL换成了一个sa把解决问题了,当然也就是像上面代码中那样,定义变量-->清空所有成员-->设置长度成员,没有进行其它的设置,这是我记得很清楚的一件事,至于更深层的原因,当时就没有追究了,直到现在也没有再去追究过,只是知道通过这个方法解决了那个问题,所以后来,直到现在,只要是有这个参数的,我一律传入没有经过特殊设置的sa参数而不再传入NULL。

试验一:去掉 /c 参数

对strPars的修改如下:

	CString strPars = m_strGitTool;
	//strPars = TEXT(" /c");
	strPars = TEXT(" ") + strPars;

这个修改,是把上面的代码这一行分成了两行,同时注释掉参数/c。运行结果:

	while (true)
	{
		dwWaitResult = WaitForSingleObject(pi.hProcess, 200);
		if (dwWaitResult == WAIT_OBJECT_0 || dwWaitResult == WAIT_ABANDONED)
			break;
		if (ReadFile(readPipe, szBuf, 1024 * 20, &dwReadLen, NULL) && dwReadLen > 0)
		{
			gStr2U(szBuf, strText);
			strGitRes += strText;
		}
		memset(szBuf, 0, sizeof(szBuf));
		dwReadLen = 0;
	}

这个while循环运行了一次,而且ReadFile成功,strGitRes的内容为:“Microsoft Windows [版本 10.0.18363.1139]\r\n(c) 2019 Microsoft Corporation。保留所有权利。\r\n\r\nE:\\myproject\\projectname>”,当循环第二次时,程序卡在了 ReadFile() 这一行上,只好终止调试。

 

试验二:

把关闭写的管道放在第二个while之后,像下面这样子:

	CloseHandle(wrtePipe);
	wrtePipe = NULL;		// 这里不使用输入

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	CloseHandle(readPipe);
	readPipe = NULL;

注意成功的代码中,这个关闭是放在CreateProcess()后面的。现在这样修改之后,第一个while循环会直接break,第二个while的ReadFile()会卡住,程序无法继续往下走。

 

第三点,注意对CreateProcess()参数si的输出赋值,

	si.hStdOutput = wrtePipe;
	si.hStdError = wrtePipe;

输出应该是赋给写管道的句柄,刚开始我把它赋成了读管道的句柄,一直收不到任何信息。这个读写,是对启动程序来说的,对于宿主程序,则是相反的理解。

 

第四点,注意对CreateProcess()参数si的dwFlags赋值,这点非常重要,成功时的赋值如下(如第一段代码中):

	si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_HIDE;

我没有仔细研究其它的项,仅说明我遇到的情况。
STARTF_USESTDHANDLES:比较好理解,就是要用本结构中的输入输出,当它们的值不为NULL时有效。
STARTF_USESHOWWINDOW:这个标志非常重要,如果不设置,则后面的ReadFile()全部失败,即接收不到任何的程序输出。如果设置了此标志,则其后的 wShowWindow就非常重要了,wShowWindow可取值同CreateWindow(),这里只讨论两个值:SW_HIDE和SW_SHOW,如果设置了SW_SHOW,则cmd的黑色窗口将会显示出来(当然对于命令立马就执行完的情况时,这个黑色窗口就是闪一下而已,也就是说,它是在启动程序运行的过程中出现,启动程序结束时窗口也自然结束),此时,宿主程序将接收不到任何启动程序的输出,所以此项必需设置为SW_HIDE。
对这两个成员的设置总结就是,dwFlags必需设置 STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW(至于是否可以加其它标志,并没有研究过),wShowWindow必需设置为SW_HIDE,否则宿主程序无法接收到启动程序的打印信息。

 

本程序的思路:在子程序启动后,在while循环中最长不超过200毫秒不断地检测子程序是否已经退出,如果已经退出,则结束此while循环,否则就读取一次打印,如果读取成功,就保存收到的打印信息。当第一个while循环退出后,就表示子程序已经结束了运行,此时再尝试继续接收剩余的打印,直到没有打印信息可接收时结束整个任务。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CreateProcess函数是Windows API中的一个函数,用于创建一个新的进程,并返回一个进程的句柄。它可以接收程序输出,以便在创建新进程后可以进行处理。 使用CreateProcess函数时,我们需要传入一个结构体PROCESS_INFORMATION作为参数,其中包含了关于新进程的信息,包括进程标识符和主线程标识符。通过这个结构体,我们可以获取新进程的句柄。 为了接收程序输出,我们还需要在创建进程时指定相应的标志,以告诉操作系统我们希望创建的进程的标准输入、输出和错误输出都重定向到我们指定的句柄。通常情况下,我们可以指定一个匿名管道接收程序输出。 具体操作如下:首先,我们创建一个匿名管道,这个管道的写入端将作为新进程的标准输出,我们得到这个写入端的句柄。然后,我们创建新进程时将这个写入端的句柄作为参数传入,这样新进程的输出就被重定向到了匿名管道。接下来,我们从这个管道的读取端读取新进程的输出。 通过以上的操作,我们就可以使用CreateProcess函数来创建一个新进程,并且接收该进程的输出。 需要注意的是,接收程序输出的过程需要调用一些其他API函数,如CreatePipe函数、SetHandleInformation函数和ReadFile函数等。在具体使用时,我们还需要检测函数调用的返回值,以确保操作的成功。 总之,通过使用CreateProcess函数并重定向标准输出,我们可以在创建新进程后接收程序输出,并进行处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值