Windows下实现控制台程序输出重定向到其他进程(iperf为例)

windows 专栏收录该内容
3 篇文章 0 订阅

本篇文章主要介绍windows下如何将一个应用程序的输出重定向到另一个进程中,主要原理是通过匿名管道实现进程信息的重定向。

工作中有时会遇到这样一个场景,应用程序A.exe需要调用应用程序B.exe进行一些辅助性操作,同时,A还需要获取B执行后的输出结果,以便于在A中根据B的输出结果进行相关的操作。本文主要解决的就是上述问题。以iperf为例,iperf是一个网络吞吐量测试工具,它可以测试当前的网络状况。本文使用一个控制台程序(A.exe)调用iperf.exe(B.exe)进行网络性能测试,然后将iperf.exe的输出信息重定位到A的控制台窗口进行输出。

参考博客:博客1博客2(了解匿名管道相关知识可以看看)

系统环境:Win10_x64、VS2017

内容安排:

一、下载iperf;

二、代码实现。

 

一、下载iperf

1.iperf下载地址:Iperf for Windows

下载iperf后将解压得到的iperf3.exe、cygwin1.dll放入对应项目目录下供应用程序A调用。

注:简单起见,建议重定向你自己的exe文件而不是iperf,这只需要修改main函数中的命令行即可。

 

二、代码实现

代码利用匿名管道实现输出重定向,其中对重定向部分的代码进行了封装,便于调用。下面给出具体实现:

1.封装类cmdhandler.h实现:

#ifndef __CMD_HANDLER_H__
#define __CMD_HANDLER_H__

#include <Windows.h>
#include <string>

/* buffer的最大长度 */
#define PIPE_BUFFER_SIZE 4096
#define PORT  12306

/* 命令参数 */
typedef struct _CHCmdParam CHCmdParam;

struct _CHCmdParam
{
	/* 初始化为 sizeof(CommandParam) */
	int iSize;

	/* 外部命令,不需要用则设置为-1, 提供给外部使用
	*/
	int iCommand;

	/* 超时时间,单位秒 */
	long long iTimeOut;

	/* 命令行要执行的命令 */
	char* szCommand;

	/* 用户数据 */
	void* pUserData;

	/* 命令执行后的回调 */
	void(*OnCmdEvent)(const CHCmdParam* pParam, HRESULT hResultCode, char* szResult);
};

class CCmdHandler
{
private:
	BOOL m_bInit;
	DWORD m_port;
	STARTUPINFO m_startupInfo;
	PROCESS_INFORMATION m_processInfo;
	SECURITY_ATTRIBUTES m_saOutPipe;
	DWORD m_dwErrorCode;
	HANDLE m_hPipeRead;
	HANDLE m_hPipeWrite;
	CHCmdParam m_CommandParam;
	TCHAR m_szReadBuffer[PIPE_BUFFER_SIZE];
	TCHAR m_szWriteBuffer[PIPE_BUFFER_SIZE];
	char m_szPipeOut[PIPE_BUFFER_SIZE];
	HRESULT ExecuteCmdWait();
public:
	CCmdHandler();
	~CCmdHandler();
	/*
	* 客户端初始化接口,调用其余接口之前调用
	* 成功返回S_OK
	*/
	HRESULT Cli_Initalize();
	/*
	* 服务器初始化接口,调用其余接口之前调用
	* 成功返回S_OK
	*/
	HRESULT Ser_Initalize(DWORD port = PORT);
	/*
	* 结束接口
	*/
	HRESULT Finish();
	/*
	* 执行命令接口,接口调用成功返回S_OK
	* param[in] pCommmandParam: 指向一个CHCmdParam命令参数结构的指针
	*/
	HRESULT HandleCommand(CHCmdParam* pCommmandParam);
	/*
	* 返回错误码,便于差距接口调用失败后产生什么错误
	*/
	DWORD GetErrorCode() { return m_dwErrorCode; }
};

#endif // !__CMD_HANDLER_H__

2.封装类cmdhandler.cpp实现:

#include "cmdhandler.h"
#include <tchar.h>
#include <string>
#ifdef WIN32
#include <Tlhelp32.h>
#endif
#include <algorithm>


#define EXCEPTIION_STATE_CHECK \
    if (!m_bInit) return E_NOTIMPL

CCmdHandler::CCmdHandler()
	: m_bInit(FALSE)
	, m_dwErrorCode(0)
	, m_hPipeRead(NULL)
	, m_hPipeWrite(NULL)
	, m_port(PORT)
{
	ZeroMemory(m_szReadBuffer, sizeof(m_szReadBuffer));
	ZeroMemory(m_szWriteBuffer, sizeof(m_szWriteBuffer));
	ZeroMemory(&m_CommandParam, sizeof(m_CommandParam));
}
CCmdHandler::~CCmdHandler()
{
}

HRESULT CCmdHandler::Cli_Initalize()
{
	// 初始化,创建匿名管道
	if (m_bInit) return S_OK;
	m_bInit = TRUE;
	ZeroMemory(m_szReadBuffer, sizeof(m_szReadBuffer));
	ZeroMemory(&m_saOutPipe, sizeof(m_saOutPipe));
	m_saOutPipe.nLength = sizeof(SECURITY_ATTRIBUTES);
	m_saOutPipe.lpSecurityDescriptor = NULL;
	m_saOutPipe.bInheritHandle = TRUE;
	ZeroMemory(&m_startupInfo, sizeof(STARTUPINFO));
	ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
	if (!CreatePipe(&m_hPipeRead, &m_hPipeWrite, &m_saOutPipe, PIPE_BUFFER_SIZE))
	{
		m_dwErrorCode = GetLastError();
		return E_FAIL;
	}
	return S_OK;
}

HRESULT CCmdHandler::Ser_Initalize(DWORD port)
{
	m_port = port;
	// 初始化,创建匿名管道
	if (m_bInit) return S_OK;
	m_bInit = TRUE;
	ZeroMemory(m_szReadBuffer, sizeof(m_szReadBuffer));
	ZeroMemory(&m_saOutPipe, sizeof(m_saOutPipe));
	m_saOutPipe.nLength = sizeof(SECURITY_ATTRIBUTES);
	m_saOutPipe.lpSecurityDescriptor = NULL;
	m_saOutPipe.bInheritHandle = TRUE;
	ZeroMemory(&m_startupInfo, sizeof(STARTUPINFO));
	ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
	if (!CreatePipe(&m_hPipeRead, &m_hPipeWrite, &m_saOutPipe, PIPE_BUFFER_SIZE))
	{
		m_dwErrorCode = GetLastError();
		printf("CreatePipe failed!\n");
		return E_FAIL;
	}
	
	//检测系统中是否已开启iperf3服务
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (INVALID_HANDLE_VALUE == hSnapshot)
	{
		printf("error:invalid handle value!\n");
		return E_FAIL;
	}
	PROCESSENTRY32 pi;
	pi.dwSize = sizeof(PROCESSENTRY32); //第一次使用必须初始化成员
	BOOL bRet = Process32First(hSnapshot, &pi);
	std::string item("iperf3.exe");
	while (bRet)
	{
		std::string process(pi.szExeFile);
		transform(process.begin(), process.end(), process.begin(), ::tolower);

		if (process.find(item) <= process.length())//find
		{
			HANDLE hProcess;
			//printf("Find the process! id:%d\n", pi.th32ProcessID);
			hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pi.th32ProcessID);
			if (hProcess)
			{
				printf("Close %d precess successful!\n", pi.th32ProcessID);
				TerminateProcess(hProcess, 0);
				CloseHandle(hProcess);//OpenProcess打开的也要关闭  
			}
			else
			{
				printf("Close iperf precess failed!\n");
			}
		}

		bRet = Process32Next(hSnapshot, &pi);
	}//close all iperf3 services
	

	CloseHandle(hSnapshot);
	return S_OK;
}

HRESULT CCmdHandler::Finish()
{
	EXCEPTIION_STATE_CHECK;
	if (m_hPipeRead)
	{
		CloseHandle(m_hPipeRead);
		m_hPipeRead = NULL;
	}
	if (m_hPipeWrite)
	{
		CloseHandle(m_hPipeWrite);
		m_hPipeWrite = NULL;
	}
	return S_OK;
}
HRESULT CCmdHandler::HandleCommand(CHCmdParam* pCommmandParam)
{
	EXCEPTIION_STATE_CHECK;
	if (!pCommmandParam || pCommmandParam->iSize != sizeof(CHCmdParam))
		return E_INVALIDARG;
	if (strlen(pCommmandParam->szCommand) <= 0)
		return E_UNEXPECTED;
	memset(&m_CommandParam, 0, sizeof(m_CommandParam));
	m_CommandParam = *pCommmandParam;
	return ExecuteCmdWait();
}
HRESULT CCmdHandler::ExecuteCmdWait()
{
	EXCEPTIION_STATE_CHECK;
	HRESULT hResult = E_FAIL;
	DWORD dwReadLen = 0;
	DWORD dwStdLen = 0;
	m_startupInfo.cb = sizeof(STARTUPINFO);
	m_startupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
	m_startupInfo.hStdOutput = m_hPipeWrite;
	m_startupInfo.hStdError = m_hPipeWrite;
	m_startupInfo.wShowWindow = SW_HIDE;
	DWORD dTimeOut = (DWORD)m_CommandParam.iTimeOut;// >= 3000 ? m_CommandParam.iTimeOut : 5000;


	if (!CreateProcess(NULL, m_CommandParam.szCommand,
		NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL,
		&m_startupInfo, &m_processInfo))
	{
		m_dwErrorCode = GetLastError();
		hResult = E_FAIL;
		goto over;
	}
	//等待时间到达或进程退出,进程不退出将一直读取管道数据(每隔dTimeOut读取一次)
	while (WAIT_TIMEOUT == WaitForSingleObject(m_processInfo.hProcess, dTimeOut))
	{
		// 预览管道中数据的内容
		if (!PeekNamedPipe(m_hPipeRead, NULL, 0, NULL, &dwReadLen, NULL)
			|| dwReadLen <= 0)
		{
			m_dwErrorCode = GetLastError();
			hResult = E_FAIL;
			continue;
		}
		else
		{
			ZeroMemory(m_szPipeOut, sizeof(m_szPipeOut));
			// 读取管道中的数据
			if (ReadFile(m_hPipeRead, m_szPipeOut, dwReadLen, &dwStdLen, NULL))
			{
				hResult = S_OK;
				if (m_CommandParam.OnCmdEvent)
					m_CommandParam.OnCmdEvent(&m_CommandParam, S_OK, m_szPipeOut);
				//break;
			}
			else
			{
				m_dwErrorCode = GetLastError();
				//break;
			}
		}
	}
	// 预览管道中数据的内容
	if (!PeekNamedPipe(m_hPipeRead, NULL, 0, NULL, &dwReadLen, NULL)
		|| dwReadLen <= 0)
	{
		m_dwErrorCode = GetLastError();
		hResult = E_FAIL;
		//continue;
	}
	else
	{
		ZeroMemory(m_szPipeOut, sizeof(m_szPipeOut));
		// 读取管道中的数据
		if (ReadFile(m_hPipeRead, m_szPipeOut, dwReadLen, &dwStdLen, NULL))
		{
			hResult = S_OK;
			if (m_CommandParam.OnCmdEvent)
				m_CommandParam.OnCmdEvent(&m_CommandParam, S_OK, m_szPipeOut);
			//break;
		}
		else
		{
			m_dwErrorCode = GetLastError();
			//break;
		}
	}


over:

	if (m_processInfo.hThread)
	{
		CloseHandle(m_processInfo.hThread);
		m_processInfo.hThread = NULL;
	}
	if (m_processInfo.hProcess)
	{
		CloseHandle(m_processInfo.hProcess);
		m_processInfo.hProcess = NULL;
	}
	return hResult;
}

3.测试main函数示例:

iperf的使用需要一个客户端和一个服务端,下面给出的示例为客户端的代码,服务端代码可直接copy一份该代码到另一台电脑上(此时两个电脑(需位于局域网内)上的两个应用程序将分别调用各自的iperf.exe并将iperf输出结果重定向到自身控制台中进行输出)。替换下列两句代码即可:

将语句

std::string szCmd = "cmd.exe /C iperf3.exe -u -c 192.168.1.1 -b 1000M -p 12306 -R -l 60k && echo S_OK || echo E_FAIL";//cmd执行成功返回S_OK,失败返回E_FAIL  192.168.1.1为服务器地址

注释,并替换为:

std::string szCmd = "cmd.exe /C iperf3.exe -s -p 12306";

将语句

cmdResult = cmdHandler.Cli_Initalize();

注释,并替换为:

cmdResult = cmdHandler.Ser_Initalize();

即可。

当然,简单起见,也可以不使用iperf而直接修改命令行(定义szCmd的值,如szCmd = "cmd.exe /C 123.exe";),使用你需要重定向的*.exe文件。

// iperf.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <string>
#include "cmdhandler.h"

#define SYSTEM_PAUSE system("pause")

//回调函数,用于打印输出信息
void OnCommandEvent(const CHCmdParam* pParam, HRESULT hResultCode, char* szResult);

int main(int argc, TCHAR** argv)
{
	CHCmdParam cmdParam;
	CCmdHandler cmdHandler;
	HRESULT cmdResult = S_OK;

	ZeroMemory(&cmdParam, sizeof(cmdParam));
	cmdParam.iSize = sizeof(CHCmdParam);
	//std::string szCmd = "cmd.exe /C iperf3.exe -s -p 12306";
	std::string szCmd = "cmd.exe /C iperf3.exe -u -c 192.168.1.1 -b 1000M -p 12306 -R -l 60k && echo S_OK || echo E_FAIL";//cmd执行成功返回S_OK,失败返回E_FAIL  192.168.1.1为服务器地址
	cmdParam.szCommand = (char*)szCmd.c_str();
	cmdParam.OnCmdEvent = OnCommandEvent;
	cmdParam.iTimeOut = 1000;//设置等待时间,每隔1s读取一次子进程管道数据


	//cmdResult = cmdHandler.Ser_Initalize();
	cmdResult = cmdHandler.Cli_Initalize();
	if (cmdResult != S_OK)
	{
		printf("cmd handler init error\n");
		SYSTEM_PAUSE;
		return 0;
	}
	cmdResult = cmdHandler.HandleCommand(&cmdParam);
	if (cmdResult != S_OK)
	{
		printf("cmd handler exec error\n");
		cmdHandler.Finish();
		SYSTEM_PAUSE;
		return 0;
	}
//	std::string close("taskkill /f /t /im iperf3.exe");
//	WinExec(close.c_str(), SW_HIDE);
	system("pause");
	return 0;
}

void OnCommandEvent(const CHCmdParam* pParam, HRESULT hResultCode, char* szResult)
{
	if (!szResult || !szResult[0]) return;
	if (!pParam || hResultCode != S_OK) return;
	
	//printf("========================================\n");
	printf("%s\n", szResult);
	//printf("========================================\n");
}

iperf重定向的测试结果:

 

注意事项:

1.输出重定向时,被重定向进程(B.exe)输出每条信息时需要手动刷新缓冲区(通过fflush(stdout)语句实现),不然父进程不能立马读取到通过匿名管道传送的子进程信息,这点需要注意(参考博客2)。iperf默认输出信息时没有刷新,所以如果想每1s读取到重定向的输出信息,可手动修改源码(建议更换命令行进行其他exe测试而不是iperf,本文主要是需要重定向iperf才这么做的),不然只能等子进程退出时才可得到重定向的输出。

2.代码中设置只有子进程退出时才会得到重定向信息,如果需要实时获取重定向的输出信息,请在B.exe中每条打印语句后添加刷新缓冲区语句:fflush(stdout);。注意如果既没有添加刷新语句,子进程也一直不退出,则A.exe中无法看到重定向的信息。

3.示例代码中可设置读取管道数据的时间间隔,具体视实际情况而定。

4.修改示例中的szCmd的值即可重定向其他的exe文件。

 

  • 1
    点赞
  • 0
    评论
  • 5
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值