menu

hjk41的日志

Avatar

Win32中进程的输入输出重定向

在windows中,当一个进程创建子进程时,可以将该子进程的标准输入输出重定向到匿名管道中。这样通过对匿名管道进行读写,父进程就能控制子进程的输入输出。
输入输出重定向的步骤如下:

1. 创建两个匿名管道
创建两个管道,一个用于输入,一个用于输出。如下所示:

	
CreatePipe(&hReadStdin, &hWriteStdin, &sa, 0);
CreatePipe(&hReadStdout, &hWriteStdout, &sa, 0);


其中sa是一个SECURITY_ATTRIBUTES结构,其定义如下:


SECURITY_ATTRIBUTES sa;				
	sa.bInheritHandle=TRUE;					// 是否允许句柄被继承
	sa.lpSecurityDescriptor=NULL;			// 安全描述符
sa.nLength=sizeof(SECURITY_ATTRIBUTES);	// sa结构的长度


而hReadStdin, hWriteStdin, hReadStdout和hWriteStdout是四个句柄,分别用于读取,写入标准输入输出。CreatePipe () 函数的最后一个参数指定管道的缓冲区大小,当指定为0时,系统会使用默认的大小。

2. 初始化子进程的STARTUPINFO结构
进程的STARTUPINFO用于指定进程创建时的初始化信息,这里用它来指定进程的输入输出句柄。其定义如下:


STARTUPINFO si={0};
	si.cb=sizeof(STARTUPINFO);
	si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
	si.hStdError=lpPara->hWriteStdout;
	si.hStdOutput=lpPara->hWriteStdout;
	si.hStdInput=lpPara->hReadStdin;
	si.wShowWindow=SW_HIDE;


其中si.cb指定该结构体的大小,si.dwFlags用于指定该结构体哪些参数有用。这里使用的STARTF_USESTDHANDLES表示下面指定的输入输出句柄是有效的。而STARTF_USESHOWWINDOW是指定si.wShowWindow的有效性,当使用STARTF_USESHOWWINDOW参数时,表示si.wShowWindow有效,这里给它指定了SW_HIDE,表示子进程运行时不显示窗口,这样可以让子进程在后台运行,而不会显示窗口。

3. 创建进程
当以上工作都做好以后,就可以着手创建子进程了,这里要说明的是,要让子进程继承父进程的句柄,那么子进程与父进程必需以同一用户的身份运行。
这里使用CreateProcess()函数来创建子进程,代码如下:


CreateProcess( 0 , 				// 要创建的程序名
cmd	, 				// 要运行的命令行
0 ,					// 子进程的安全描述符
0 ,					// 主线程的安全描述符
TRUE ,				// 是否允许继承句柄
DEBUG_ONLY_THIS_PROCESS , 	// 创建时的参数
0 ,						// 环境变量
lpPara->strProgramPath ,	// 子进程的当前路径
&si , 				// 初始化信息
&pi);				// 子进程信息


其中子进程及其主线程的安全描述符为0,表示使用默认的安全性。这里最重要的就是要把第5个参数置为TRUE,这样才能让子进程继承句柄。而第6个参数中指定DEBUG_ONLY_THIS_PROCESS则是为了让子进程成为父进程的调试程序,这将在下面的“Win32调试程序编程接口”中详细介绍。环境变量为0,表示使用默认。子进程的当前路径,设置了子进程执行时使用的当前路径,必需是一个绝对路径。si是上面所创建的STARTUPINFO结构,而pi则是一个PROCESS_INFORMATION结构,用于接收子进程的信息,比如进程句柄,主线程句柄等。
这样父进程就创建了一个有输入输出重定向的子进程。

4. 对子进程进行输入输出
对子进程进行输入输出,实际上就是对匿名管道进行读写操作。要给子进程输入数据,只需要向输入管道写入数据;而要接收输出数据,则只需读取输出管道。读写匿名管道与读写文件是一样的,只需要使用ReadFile()函数和WriteFile()函数,这里不再赘述。
如果父进程需要知道管道时有没有数据,而又不想在ReadFile()上阻塞的话(ReadFile在读取文件时,会在空文件上阻塞,直到有内容可读),只需使用PeekNamedPipe()函数,这个函数可以从一个管道中复制数据,并能返回数据的大小,如果返回的数据大小为0,则说明管道中没有数据。虽然PeekNamedPipe()是用于命名管道的,但是它也可以用于匿名管道,因为在windows中匿名管道只是一种特殊的命名管道,事实上它是通过命名管道来实现的。
如果父进程想知道子进程什么时候在等待输入,只需要使用WaitForSingleObject(hReadStdin,0)。如果子进程在等待输入,hReadStdin句柄就会处于被使用状态,这时函数就会返回WAIT_TIMEOUT。这样就能知道什么时候给子进程输入数据,从而能有效的控制输入输出。
[/more]

进程的输入输出重定向网上说的不少,MSDN里也有,但是说的不是很清楚(可能是我理解能力不够),刚好我毕设又涉及这方面,所以就把它写出来,希望能比msdn清楚一点 :P

其中最后一段关于如果知道子进程等待输入的方法,是microsoft.public.win32.programmer.kernel新闻组中的一位朋友提供的,但是因为该帖时间过长,现在已经找不着了,不能记住他的名字,实在很抱歉。
对于这个问题,另一位新闻组中的MVP提供了在ReadConsole系统调用中设置断点的方法,当然也是可行的,但是实在太麻烦,所以没有采用。
在此非常感谢icrosoft.public.win32.programmer.kernel新闻组的各位朋友,没有他们,估计我的毕设是做不出来的。

另外,win api中有一个叫WaitForInputIdle的函数,用于等待子进程进入输入状态,但是它只能用于有消息队列的进程,对于console进程没有用。

给代码是最清楚地说法。

评论已关闭