转载地址:http://blog.csdn.net/morewindows/article/details/7390441
上一篇《进程通信之二 管道技术第一篇 输入输出的重定向》示范了增加若干程序代码来完成程序输入输出的重定向,并提出了如果没有程序源代码,只有程序文件如何来完成重定向。本篇就介绍如何使用匿名管道来完成这一任务。
计算机中管道pipe类似于现实世界中的水管道,在一端放入水流,另一端就会流出来。在计算机机中水流自然被数据流所代替了。计算机中管道分为匿名管道和命名管道,本篇将主要介绍用匿名管道来完成这一重定向输出任务,命名管道就留给下一篇来介绍了。
先来看看如何创建和使用匿名管道。
第一个 CreatePipe
函数功能:创建管道
函数原型:
BOOLWINAPICreatePipe(
PHANDLEhReadPipe,
PHANDLEhWritePipe,
LPSECURITY_ATTRIBUTESlpPipeAttributes,
DWORDnSize
);
函数说明:
第一个参数返回新创建的管道的读取端句柄。
第二个参数返回新创建的管道的写入端句柄。
注意不能在管道的读取端写入数据也不能在写入端读取数据。
第三个参数表示管道的安全属性,通常可以作如下设置:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
第四个参数表示管道的缓冲区容量,为0表示使用默认大小。
函数执行成功返回TRUE,否则返回FALSE。
第二个 ReadFile
函数功能:从管道中读取数据
函数原型:
BOOLReadFile(
HANDLEhFile,
LPVOIDlpBuffer,
DWORDnNumberOfBytesToRead,
LPDWORDlpNumberOfBytesRead,
LPOVERLAPPEDlpOverlapped
);
函数说明:
第一个参数为句柄,可以是创建文件函数CreateFile()的返回值也可以是管道。
第二个参数是一个指向缓冲区的指针,函数将读取的数据写入该缓冲区。
第三个参数的表达非常好,光从名字上就可以知道这是用来指定读取的字节数。
第四个参数将返回实际读取到的字节数。
第五个参数是用于异步操作方面,一般传入NULL即可。
第三个 WriteFile
函数功能:向管道写入数据
函数原型:
BOOLWriteFile(
HANDLEhFile,
LPCVOIDlpBuffer,
DWORDnNumberOfBytesToWrite,
LPDWORDlpNumberOfBytesWritten,
LPOVERLAPPEDlpOverlapped
);
函数说明:
第一个参数为句柄,可以是创建文件函数CreateFile()的返回值也可以是管道。
第二个参数是一个指针,该指针指向待写入管道的数据。
第三个参数表示要写入的字节数。
第四个参数将返回实际写入管道的字节数。
第五个参数是用于异步操作方面,一般传入NULL即可。
第四个CloseHandle
函数功能:关闭管道的一端
函数原型:BOOLCloseHandle(HANDLEhObject);
函数说明:当读取和写入端都关闭后,系统会关闭管道并回收资源。
从后面三个函数可以看出,向管道中读取和写入数据就和向文件中读取和写入数据是一样的(事实上管道也是一种特殊的文件——内存映射文件)。
使用管道要注意的一个地方是:读取和写入数据时,一定要注意顺序,MSDN上说,如果管道中没有数据,调用ReadFile()会造成阻塞,直到有其它线程将数据写入管道。同样,当有线程正在管道中读取数据时,其它试图将数据写入管道的的线程也会被阻塞。
因此对上一篇的示例程序进行重定向时,可以先创建二个管道,一个用来存放输入数据,称为数据输入管道,另一个用来存放输出数据,称为数据输出管道。然后从输入文件中读取数据并写入数据输入管道。再启动示例程序作为子进程,子进程的输入输出已经改成从数据输入管道中读取和输出到数据输出管道。子进程运行结束后,从数据输出管道中将数据写入到输出文件即可。整个流程图如下所示:
下面给出使用管道的示例代码:
//用管道来完成子进程的重定向。
//流程如下:
// infile.txt -> Input管道 -> 标准程序.exe -> Output管道 -> outfile.txt
#include <windows.h>
#include <stdio.h>
int main()
{
printf(" 使用管道来重定向子进程的输入输出\n");
printf(" --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");
char sz[3][50] = {"示例程序.exe", "infile.txt", "outfile.txt"};
HANDLE hPipeInputRead, hPipeInputWrite, hPipeOutputRead, hPipeOutputWrite;
//创建两个管道
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
//数据输入管道
CreatePipe(&hPipeInputRead, &hPipeInputWrite, &sa, 0);
//数据输出管道
CreatePipe(&hPipeOutputRead, &hPipeOutputWrite, &sa, 0);
printf("创建数据输入管道和数据输出管道完毕\n");
//从文件中读取数据,写入管道ReadFile中.
const int BUFSIZE = 4096;
CHAR chBuf[BUFSIZE] = {0};
DWORD dwRead, dwWritten;
BOOL fSuccess;
HANDLE hInputFile = CreateFile(sz[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
while (true)
{
//从文件中读取数据
fSuccess = ReadFile(hInputFile, chBuf, BUFSIZE, &dwRead, NULL);
if (!fSuccess || dwRead == 0)
break;
//将数据写入管道
fSuccess = WriteFile(hPipeInputWrite, chBuf, dwRead, &dwWritten, NULL);
if (!fSuccess)
break;
}
//关闭输入数据管道
CloseHandle(hInputFile);
hInputFile = NULL;
CloseHandle(hPipeInputWrite);
hPipeInputWrite = NULL;
printf("已经从文件中读取数据并写入数据输入管道\n");
printf("启动示例程序并重定向到管道中\n....\n");
//启动示例程序作为子进程
STARTUPINFO si;
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.hStdInput = hPipeInputRead; //输入由标准输入 -> 从管道中读取
si.hStdOutput = hPipeOutputWrite; //输出由标准输出 -> 输出到管道
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi;
CreateProcess( sz[0], NULL, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
//关闭输入数据管道
CloseHandle(hPipeInputRead);
hPipeInputRead = NULL;
CloseHandle(hPipeOutputWrite);
hPipeOutputWrite = NULL;
printf("示例程序完成处理,现在将数据输出管道中的数据写入文件\n");
//将输出数据管道中的数据写入到文件中
HANDLE hOutputFile = CreateFile(sz[2], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
while (true)
{
//从管道中读取
fSuccess = ReadFile(hPipeOutputRead, chBuf, BUFSIZE, &dwRead, NULL);
if( !fSuccess || dwRead == 0)
break;
//写入输出文件
fSuccess = WriteFile(hOutputFile, chBuf, dwRead, &dwWritten, NULL);
if (!fSuccess)
break;
}
//关闭输出数据管道
CloseHandle(hOutputFile);
hOutputFile = NULL;
CloseHandle(hPipeOutputRead);
hPipeOutputRead = NULL;
printf("数据输出管道中的数据写入文件完毕\n");
return 0;
}
运行结果如下图:
结果完全正确,说明我们的程序已经完成了启动其它程序并对它进行重定向这一功能。
对匿名管道总结一下:匿名管道有读取端和写入端。匿名管道创建(CreatePipe)后就可以像读写文件一样的对管道中的数据读写(ReadFile与WriteFile),但要注意读写顺序。匿名管道在关闭两端后会由系统销毁并回收资源。
匿名管道的使用比较常见,下面是二个安装程序的截图。
QQ游戏的安装过程截图:
五笔编码及时查的安装过程截图:
对比下这二个截图,显示的内容都差不多,都是解压缩文件并移动到指定地方。唯一不同的是一个是控制台界面,另一个是图形界面。联想上面的程序,不难得知QQ游戏的安装实际也是使用管道将一个控制台程序的输出内容显示到图形界面,这样既美观又便于维护。
下一篇《进程通信之二 管道技术第三篇 命名管道》将介绍命名管道的使用,欢迎参阅。
注:不知道程序代码的情况下还可以使用批处理来完成。批处理使用>和<来重定向,>为输出到文件,如果文件不存在就创建,已存在就清空原文件后再写入,<为从文件读取。批处理文件的内容可以这样写:
@echo off
<infile.txt 标准程序.exe >outfile.txt
也可以这样写:
@echo off
标准程序.exe <infile.txt >outfile.txt
批处理重定向的内部实现原理当然也是使用匿名管道。