C++父子进程之间匿名管道通信

我司主要业务都是跟工厂合作的,有时需要在自家软件中执行客户给的程序去检查设备,并根据返回值做响应处理,这中间会用到父子进程之间利用匿名管道通信的功能。

这类代码多看几遍就明白了,其实我们平时也经常创建进程并且去执行,最典型的就是cmd命令行格式,哪怕只是运行了一个"ipconfig"语句,或者输入"notepad"就能创建一个新的文本出来,这些都是创建进程的体现。

回归主题,有时客户会给我们单独的exe文件,需要在我们的软件中创建cmd进程去执行它并获得返回值,一般的做法如下:
1.创建匿名管道与数据读写端;
2.创建子进程去执行客户的exe文件,并将子进程的输出端重定向到匿名管道的写数据端,再从匿名管道的读数据端去接收返回值。

那么在此之前,要知道什么是匿名管道和创建进程的注意点;

1. 匿名管道
第一次接触这个概念可能会有些陌生,但写过几遍代码再查查资料就能明白它的作用了;匿名管道就是只有两个口子的容器,只能从一端往容器写入数据,在容器另一端接收数据,用图形表示大概如下:
在这里插入图片描述

创建匿名管道API:

BOOL WINAPI CreatePipe(
    PHANDLE hReadPipe,
    PHANDLE hWritePipe,
    LPSECURITY_ATTRIBUTES lpPipeAttributes,
    DWORD nSize
    );

参数说明:
PHANDLE hReadPipe
匿名管道的读数据端句柄;

PHANDLE hWritePipe
匿名管道的写数据端句柄;

LPSECURITY_ATTRIBUTES lpPipeAttributes
安全属性的结构体信息;

DWORD nSize
匿名管道数据流的大小,设置为0则使用系统的指定的默认大小;

2. 创建进程
创建进程API:

BOOL WINAPI CreateProcess(
    LPCSTR lpApplicationName,
    LPSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCSTR lpCurrentDirectory,
    LPSTARTUPINFOA lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
    );

参数说明:
LPCSTR lpApplicationName
子进程需要执行的文件名,是一个const char/wchar_t *的常量字符串指针;这个一般设置为NULL,因为在第二个参数里需要把完成的cmd执行路径和文件名写清楚,就包含了这个功能。

LPSTR lpCommandLine
cmd执行文件路径与文件名,是char/wchar_t *类型的字符串;如果在指定路径下找不到这个可执行文件,进程就会创建失败。值得注意的是,这里传进去的参数一定要是可写的字符串变量!变量!变量!不能是字符串常量,虽然这个API最后不会改变传进去的字符串,但是在中间会对这个字符串进行一些写操作并还原,所以传字符串常量进去一定会创建进程失败!这个设计感觉有点自相矛盾。
比如下面的写法1可以创建进程成功,但是写法2传进去的是字符串常量,就会创建进程失败:

/*写法1; 正确*/
TCHAR cmdLine[200] = _T("ipconfig");
CreateProcess(NULL, cmdLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);
/*写法2; 错误*/
CreateProcess(NULL, _T("ipconfig"), NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);

LPSECURITY_ATTRIBUTES lpProcessAttributes
子进程的进程属性结构体信息指针;会指出子进程句柄是否能被其创建的子进程继承,一般用不上,设置为NULL,即不能被继承。

LPSECURITY_ATTRIBUTES lpThreadAttributes
子进程的主线程属性结构体信息指针;会指出子进程的主线程句柄是否能被其创建的子进程继承,一般用不上,设置为NULL,即不能被继承。

BOOL bInheritHandles
设置当前主进程是否能被创建的子进程继承所有句柄,这个设置为TRUE,即可以被继承。

DWORD dwCreationFlags
设置子进程执行的优先级,一般设为NULL,立马执行。

LPVOID lpEnvironment
设置子进程的运行环境,一般设为NULL,即与主进程一样。

LPCSTR lpCurrentDirectory
设置子进程的执行路径,一般设置为NULL,与主进程保持一致。

LPSTARTUPINFOA lpStartupInfo
进程的启动信息结构体指针;这个参数很重要,在这里会设置进程的显示窗口、数据输出流向等信息。

LPPROCESS_INFORMATION lpProcessInformation
进程的句柄信息结构体指针;这个结构体里包含了进程的进程句柄、主线程句柄、进程ID、主线程ID。

下面的代码就是创建一个cmd窗口执行"ipconfig"命令进程并获取返回值的案例:

BOOL ExecuteCmdProcess()
{
	TCHAR cmdLine[200] = _T("ipconfig");
	TCHAR strBuffer[1024] = {0};
	CString strData;
	STARTUPINFO si = {0};    /*子进程的启动信息,包含了窗口显示和输出管道等重要信息*/
	PROCESS_INFORMATION pi = {0}; /*子进程的结构体信息*/
	SECURITY_ATTRIBUTES sa; /*匿名管道的属性设置*/
	HANDLE hRead;  /*匿名管道的读取句柄*/
	HANDLE hWrite; /*匿名管道的写数据句柄*/

	/*匿名管道的安全信息结构体设置*/
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.bInheritHandle = TRUE;
	sa.lpSecurityDescriptor = NULL;

	/*创建匿名管道,并指定读写端口句柄*/
	if (!CreatePipe(&hRead, &hWrite, &sa, 0))
	{
		AfxMessageBox("Create pipe fail!");
		CloseHandle(hRead);
		CloseHandle(hWrite);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		return FALSE;
	}

	/*子进程的属性设置*/
	ZeroMemory(&si,sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	GetStartupInfo(&si);    
	si.wShowWindow = FALSE; /*子进程不单独创建新的窗口*/
	si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
	si.hStdError = hWrite;
	si.hStdOutput = hWrite; /*子进程输出的内容,重定向到匿名管道的写数据端*/

	/*创建子进程*/
	if (!CreateProcess(NULL, cmdLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi))
	{
		AfxMessageBox(_T("Create process fail!"));
		CloseHandle(hRead);
		CloseHandle(hWrite);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		return FALSE;
	}

	/*在读取匿名管道里的数据之前,提前关闭写端*/
	CloseHandle(hWrite);

	/*在匿名管道的读取端,把数据输入到字符串里,注意字节长度一定要够!*/
	while (1)
	{
		if (ReadFile(hRead, strBuffer, 1024, NULL, NULL) == NULL)
		{
			break;
		}

		strData += strBuffer;
	}

	AfxMessageBox(strData);
	CloseHandle(hRead);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	return TRUE;
}

运行结果如下:
在这里插入图片描述
最后,这种父子进程通过匿名管道通信的代码,基本都有两个坑:

  1. 就是之前在CreateProcess的参数说明里的第二个参数,这个必须要传可写的字符串变量进去,不然100%会报错;

  2. 在最后从匿名管道读取数据的时候,可以看到提前关闭了hWrite(匿名管道写端句柄),因为ReadFile一般是当成阻塞函数来使用的,它从匿名管道读端获得数据期间,如果写端的句柄没有关闭,会被当成是一直在输入数据,从而把ReadFile函数给卡死了,致使程序堵塞。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值