重定向子进度控制台程序的输入输出

重定向子进程控制台程序的输入输出

重定向所做的劳作都在父进程,但须要子进度遵从下边的条条框框:

子进程程序在出口代码后,等待输入以前要求调用fflush(stdout)函数,那样把出口的始末放入缓冲区,父进度才能及时的读到输出数据。

不听从上述规则就不能落到实处有效的竞相了,cmd.exe是信守这几个规则的样板,半数以上控制台程序都不听从那一个规则。前天自我准备给谷歌的V8
Javascript
的Shell搞一个GUI,方便自己输入Javascript程序,就赶上了v8_shell不遵守这一个的题材。好在有源代码,探讨了一晃,加上多个fflush(stdout)就行了。

再有一个索要专注的就是,在向子进程的stdin写内容时,一定要在最后加上”\r\n”,否则直到父进度终止时子进程才能吸纳这么些输入,那时已经太晚了。那很想获得,但真正如此。加上fflush(stdin)应该会越来越有限辅助。

MSDN的CreatePipe函数的Examples的链接内容对于这么些宗旨也有描述,但不够清晰扼要。
CodeProject也有个例证,可以科学运行,但指出你用cmd.exe做子进度展开考试。

管道是一种有三个端点的通讯通道。你采纳管道在多个经过间或同一进程内沟通数据。它有点像手提对讲机,双方一人一个就足以打电话了。
存在二种管道:匿名管道和命名管道。匿名管道是匿名的,使用时你无需通晓它的名字。命名管道相反,使用时您不能够不了解它的名字。
另一种分类:单向管道和双向管道。单向管道数据流式单向的,像发传真;双向管道数据流是双向的,像打电话。
匿名管道连接单向的,命名管道可以是单向或双向的。命名管道可以用在互联网环境,服务器通过它连接到多少个客户。在本读本,我们详细啄磨匿名管道。匿名管道用于父子进度间的通讯,或子进度间的通讯。当您处理控制台程序时,匿名管道真的很有用。控制台程序类似于DOS窗口,不过它是截然的32位Windows程序,可以动用此外GUI函数,只不过它比通常的GUI程序多了一个控制台而已。控制台程序有多少个句柄,标准输入、标准输出、错误输出。
你可以调用GetStdHandle得到控制台程序的三个句柄。GUI程序没有控制台,要是你调用GetStdHandle,会再次回到错误。要是您想在GUI程序中应用控制台,那么可以利用AllocConsole,用完后请调用FreeConsole。
匿名管道常用于重定向控制台子进度的输入输入,父进度能够是控制台或GUI程序。大家得以轮换控制台程序的输入和输出,而那么些控制台程序浑然不觉。引用面向对象编程的术语,那是一种多态。那种方法很强劲,大家不用对子进度做此外变动。
其余你要知道的是控制台程序从何地得到那多少个专业句柄。当控制台进程被创制时,父进度有三个接纳:为子进程成立一个新的控制台,或者让子进程继续它的控制台。
介绍一下CreatePipe函数,该函数用于成立匿名管道。
BOOL WINAPI CreatePipe(
  __out     PHANDLE hReadPipe,
  __out     PHANDLE hWritePipe,
  __in_opt  LPSECURITY_ATTRIBUTES lpPipeAttributes,
  __in      DWORD nSize
);
hReadPipe 接受管道读句柄的变量指针
hWritePipe 接受管道写句柄的变量指针
lpPipeAttributes 安全品质,决定子进程是还是不是足以持续再次来到的读写句柄
nSize  管道保留使用的缓冲大小,那只有是一个提出大小,你可以使用NULL表示使用缺省大大小小。
假定调用成功,再次回到值非0,否则,重回值为0。
比方调用成功,你会取得多个句柄,一头用于读,一头用于写。下边我列出重定向子进度正式输出到父进程的步子,大家接纳GUI做为父进度,因为那更有意义。

1
使用CreatePipe创立一个匿名管道。不要遗忘设置SECURITY_ATTRIBUTES的bInheritable成员为TRUE,那样子进程就可以一而再这个句柄。
2
调用CreateProcess成立子进度。我们需求未雨绸缪CreateProcess函数的STARTUPINFO参数
      cb : STARTUPINFO结构体的尺寸。
      dwFlags :
flag枚举。为了达到目标,大家应当利用STARTF_USESHOWWINDOW |
STARTF_USESTDHANDLES
      hStdOutput and hStdError :
你期望子进度使用的科鲁班出和谬误输出的句柄。为了达到目标,大家把这么些管道的写端用于子进度的业内输出和不当输出。那样当子进程输出错误和其余新闻时,实际是在向管道写入。
      wShowWindow
决定窗口的来得状态。为了达到目标,大家拔取SW_HIDE来隐藏控制台窗口。
 子进度创制后,是处于休眠状态的。
3
使用CloseHandle关闭管道的写句柄。这是必须的。父进度不行使这一个句柄,若是存在三个写句柄,那么管道不会做事。可是,不要在调用CreateProcess前关闭管道的写句柄,否则管道就作废了。你应当在CreateProcess调用重回后,从读管道端读取子进程的数量以前关闭管道。
4
使用ReadFile从读管道端读取数据。在您调用ReadFile后,子进程会起初运行。你可以把读数据想象为用吸管喝饮料。你一向选用ReadFile读数据,直到它再次回到0。对于读来的数码,你想怎么就如何,比如放进一个Edit控件。
5 最后,关闭读管道句柄。

 

 

http://www.adintr.com/article/278

Windows管道(Pipe)重定向stdout,stderr,stdin

by other 
March 25, 2012, 1:50 p.m.

stdin是规范输入,stdout是标准输出,stderr是专业错误输出。
大部的指令行程序从stdin输入,输出到stdout或stderr,有时大家需求重定向stdout,stderr,stdin。比如:将出口写入文件,又或者大家要将下令行程序输出结果呈现到Windows对话框中。

在Windows编程中,重定向必要用到管道(Pipe)的定义。管道是一种用于在进程间共享数据的机制。一个管道类似于一个管敬仲的双方,一端是写入的,一端是读出的。由一个经过从写入端写入、另一个经过从读出端读出,从而落成通讯,就向一个“管道”一样。

重定向的原理是:

率先表明三个概念:主程序(重定向的了然者)、子进度(被重定向的子进度)

万一要重一直stdout的话,先生成一个管道,
管道的写入端交给子过程去写,主程序从管道的读出端读数据,然后能够把数量写成文件、彰显等等。重定向stderr和stdout是如出一辙的。

同理,要重定向stdin的话,生成一个管道,
管道的写入端由主程序写,子进度从管道的读出端读数据。

个中需要使用多少个Windows API :  CreatePipe, DuplicateHandle,
CreateProcess, ReadFile, WriteFile 等,函数详解可参见MSDN.

一、编程达成原理 ( C语言)

[html] view
plain
copy

 

  1. #include <windows.h>  
  2.   
  3.   //定义句柄: 构成stdin管道的两端句柄  
  4.   HANDLE  hStdInRead;         //子进度用的stdin的读入端  
  5.   HANDLE  hStdInWrite;        //主程序用的stdin的读入端  
  6.   
  7.   //定义句柄: 构成stdout管道的两端句柄  
  8.   HANDLE  hStdOutRead;     ///主程序用的stdout的读入端  
  9.   HANDLE  hStdOutWrite;    ///子进度用的stdout的写入端  
  10.   
  11.   //定义句柄: 构成stderr管道的句柄,由于stderr一般等于stdout,大家尚无概念hStdErrRead,直接用hStdOutRead即可  
  12.   HANDLE  hStdErrWrite;       ///子进度用的stderr的写入端  
  13.   
  14.   //定义一个用以产生子进度的STARTUPINFO结构体 (定义见CreateProcess,函数表达)  
  15.   STARTUPINFO siStartInfo;  
  16.   //定义一个用以爆发子进度的PROCESS_INFORMATION结构体 (定义见CreateProcess,函数表明)  
  17.   PROCESS_INFORMATION piProcInfo;  
  18.   
  19.   
  20.   //爆发一个用以stdin的管道,得到五个HANDLE:  hStdInRead用于子进度读出多少,hStdInWrite用于主程序写入数据  
  21.   //其中saAttr是一个STARTUPINFO结构体,定义见CreatePipe函数表明  
  22.   if   (!CreatePipe(&hStdInRead, &hStdInWrite,&saAttr, 0))  
  23.           return;  
  24.   
  25.   //发生一个用来stdout的管道,获得三个HANDLE:  hStdInRead用于主程序读出多少,hStdInWrite用于子程序写入数据  
  26.   if  (!CreatePipe(&hStdOutRead, &hStdOutWrite,&saAttr, 0))  
  27.         return;  
  28.   //由于stderr一般就是stdout, 直接复制句柄hStdOutWrite,获得 hStdErrWrite  
  29.    if (!DuplicateHandle(GetCurrentProcess(), hStdOutWrite, GetCurrentProcess(), &hStdErrWrite, 0, TRUE, DUPLICATE_SAME_ACCESS))  
  30.       return;  
  31.   
  32.   //对STARTUPINFO结构体赋值,对stdin,stdout,stderr的Handle设置为刚刚收获的管道HANDLE  
  33.   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );  
  34.   siStartInfo.cb = sizeof(STARTUPINFO);  
  35.   siStartInfo.dwFlags  |= STARTF_USESHOWWINDOW;  
  36.   siStartInfo.dwFlags  |= STARTF_USESTDHANDLES;  
  37.   siStartInfo.hStdOutput = hStdOutWrite;     //意思是:子进度的stdout输出到hStdOutWrite  
  38.   siStartInfo.hStdError  =  hStdErrWrite;        //意思是:子进度的stderr输出到hStdErrWrite  
  39.   siStartInfo.hStdInput  = hStdInRead;  
  40.   
  41.   // 爆发子进度,具体参数表明见CreateProcess函数  
  42.   bSuccess = CreateProcess(NULL,  
  43.       CommandLine,    // 子进度的命令行  
  44.       NULL,                   // process security attributes  
  45.       NULL,                   // primary thread security attributes  
  46.       TRUE,                   // handles are inherited  
  47.       0,                          // creation flags  
  48.       NULL,                  // use parent’s environment  
  49.       NULL,                  // use parent’s current directory  
  50.       &siStartInfo,      // STARTUPINFO pointer  
  51.       &piProcInfo);     // receives PROCESS_INFORMATION  
  52.   
  53.    //如果败北,退出  
  54.    if (!bSuccess ) return;  
  55.   
  56.     //然后,就足以读写管道了  
  57.     //写入stdin,具体代码在一个WriteToPipe函数中  
  58.     WriteToPipe();  
  59.   
  60.    //不断子检测进程有否甘休  
  61.     while (GetExitCodeProcess(piProcInfo.hProcess,&process_exit_code))  
  62.         {  
  63.             //读stdout,stderr  
  64.            ReadFromPipe();  
  65.            //即使子进度为止,退出循环  
  66.            if (process_exit_code!=STILL_ACTIVE) break;  
  67.         }  

 
现实看一下WriteToPipe(), ReadFromPipe函数:

[html] view
plain
copy

 

  1. //写入stdin  
  2. BOOL WriteToPipe()  
  3. {  
  4.   DWORD dwWritten;  
  5.   BOOL bSuccess = FALSE;  
  6.     
  7.  //用WriteFile,从hStdInWrite写入数据,数据在in_buffer中,长度为dwSize  
  8.   bSuccess = WriteFile( hStdInWrite, in_buffer, dwSize, &dwWritten, NULL);  
  9.   return bSuccess;  
  10. }  

 

[html] view
plain
copy

 

  1. // 读出stdout  
  2. BOOL ReadFromPipe()  
  3. {  
  4.   char out_buffer[4096];  
  5.   DWORD dwRead;    
  6.   BOOL bSuccess = FALSE;  
  7.   
  8.    //用WriteFile,从hStdOutRead读出子进程stdout输出的数目,数据结果在out_buffer中,长度为dwRead  
  9.    bSuccess = ReadFile( hStdOutRead, out_buffer, BUFSIZE, &dwRead, NULL);  
  10.    if ((bSuccess) && (dwRead!=0))  //假如成功了,且长度>0  
  11.              {  
  12.                    // 此处参与你自己的代码  
  13.                    // 比如:将数据写入文件或出示到窗口中  
  14.              }  
  15.   return bSuccess;  
  16. }  

OK,到此原理写完了。为简化表明原理,上述代码省略了失误处理、甘休处理(如:CloseHandle等),具体可以瞻仰我的源码。

二、封装、实用的代码

上述进程有些麻烦,实际行使中,我封装成多少个函数:

第一定义三个回调函数 (就是函数指针类型)

//当stdin输入时,调用此函数。将要写的数目写入buf中,*p_size写为数据长度即可。
typedef void FuncIn(char *buf,int *p_size);

//当stdout,stderr输出时,调用此函数。可读取的数据在buf中,数据长度为size。
typedef void FuncOut(char *buf,int size);

//当子进度不断进度中,周期性调用此函数,设置p_abort可中断子进度。
typedef void FuncProcessing(int *p_abort);

接下来定义了八个函数

//执行一个命令行,重定向stdin, stdout,stderr。
//OnStdOut是回调函数指针,当有出口时,OnStdOut被调用。
//OnStdIn是回调函数指针,当输入时,OnStdIn被调用。
int ExecCommandEx(char *szCommandLine,char *CurrentDirectory,char
*Environment,unsigned short ShowWindow,
              FuncOut *OnStdOut,FuncProcessing *OnProcessing,FuncIn
*OnStdIn);

//执行一个命令行,重定向stdout,stderr。
//OnLineOut是回调函数指针,当有一行输出时,OnLineOut被调用。
int ExecCommandOutput(char *szCommandLine,char *Environment,unsigned
short ShowWindow,
                       FuncOut *OnLineOut,FuncProcessing
*OnProcessing);

//执行一个命令行,等待子进程截至,重回子进度的主次退出代码。不处理重定向。
int ExecCommandWait(char *szCommandLine,unsigned short
ShowWindow,FuncProcessing *OnProcessing);

//执行一个命令行,不等待子进度截至,即再次来到。不处理重定向。功效约等于Windows API  WinExec.
int ExecCommandNoWait(char *szCommandLine,unsigned short ShowWindow);

还定义了一个仓储数据的EXEC_INFO结构体及操作它的函数。

万事代码为C语言,在JExecution.c, JExecution.h七个文件中。只用到了Windows
API,没有用MFC及其余库。

三、使用方法

有了JExecution.c,使用就很有益于了。比如:

[html] view
plain
copy

 

  1. #include <stdio.h>  
  2. #include <windows.h>  
  3. #include “JExecution.h”  
  4.   
  5. //定义一个甩卖输出的回调函数  
  6. void OnLineOut(char *buf,int size)  
  7. {  
  8.    printf(“%s\n”, buf);  
  9. }  
  10.   
  11.   
  12. int main(int argc, char* argv[])  
  13. {  
  14.   int n;  
  15.   char *command;  
  16.   
  17.   command = “cmd.exe  /r dir/w “;   //这么些命令的意味就是调用DOS,执行dir命令,突显当前近年来下的文件  
  18.   n=ExecCommandOutput(command,NULL,SW_HIDE,OnLineOut,NULL);  
  19.   printf(“<Return:>%d”,n);  
  20. }  

相关文章