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

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

重定向所做的工作都在父进程,但需要子进程遵守下面的规则:

子进程程序在输出代码后,等待输入之前需要调用fflush(stdout)函数,这样把输出的内容放入缓冲区,父进程才能及时的读到输出数据。

不遵守以上规则就没办法实现有效的交互了,cmd.exe是遵守这个规则的典范,大部分控制台程序都不遵守这个规则。今天我试图给Google的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 plaincopy

  1. #include <windows.h>
  2. //定义句柄: 构成stdin管道的两端句柄

  3. HANDLE  hStdInRead;         //子进程用的stdin的读入端

  4. HANDLE  hStdInWrite;        //主程序用的stdin的读入端
  5. //定义句柄: 构成stdout管道的两端句柄

  6. HANDLE  hStdOutRead;     ///主程序用的stdout的读入端

  7. HANDLE  hStdOutWrite;    ///子进程用的stdout的写入端
  8. //定义句柄: 构成stderr管道的句柄,由于stderr一般等于stdout,我们没有定义hStdErrRead,直接用hStdOutRead即可

  9. HANDLE  hStdErrWrite;       ///子进程用的stderr的写入端
  10. //定义一个用于产生子进程的STARTUPINFO结构体 (定义见CreateProcess,函数说明)

  11. STARTUPINFO siStartInfo;

  12. //定义一个用于产生子进程的PROCESS_INFORMATION结构体 (定义见CreateProcess,函数说明)

  13. PROCESS_INFORMATION piProcInfo;
  14. //产生一个用于stdin的管道,得到两个HANDLE:  hStdInRead用于子进程读出数据,hStdInWrite用于主程序写入数据

  15. //其中saAttr是一个STARTUPINFO结构体,定义见CreatePipe函数说明

  16. if   (!CreatePipe(&hStdInRead, &hStdInWrite,&saAttr, 0))

  17. return;
  18. //产生一个用于stdout的管道,得到两个HANDLE:  hStdInRead用于主程序读出数据,hStdInWrite用于子程序写入数据

  19. if  (!CreatePipe(&hStdOutRead, &hStdOutWrite,&saAttr, 0))

  20. return;

  21. //由于stderr一般就是stdout, 直接复制句柄hStdOutWrite,得到 hStdErrWrite

  22. if (!DuplicateHandle(GetCurrentProcess(), hStdOutWrite, GetCurrentProcess(), &hStdErrWrite, 0, TRUE, DUPLICATE_SAME_ACCESS))

  23. return;
  24. //对STARTUPINFO结构体赋值,对stdin,stdout,stderr的Handle设置为刚才得到的管道HANDLE

  25. ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );

  26. siStartInfo.cb = sizeof(STARTUPINFO);

  27. siStartInfo.dwFlags  |= STARTF_USESHOWWINDOW;

  28. siStartInfo.dwFlags  |= STARTF_USESTDHANDLES;

  29. siStartInfo.hStdOutput = hStdOutWrite;     //意思是:子进程的stdout输出到hStdOutWrite

  30. siStartInfo.hStdError  =  hStdErrWrite;        //意思是:子进程的stderr输出到hStdErrWrite

  31. siStartInfo.hStdInput  = hStdInRead;
  32. // 产生子进程,具体参数说明见CreateProcess函数

  33. bSuccess = CreateProcess(NULL,

  34. CommandLine,    // 子进程的命令行

  35. NULL,                   // process security attributes

  36. NULL,                   // primary thread security attributes

  37. TRUE,                   // handles are inherited

  38. 0,                          // creation flags

  39. NULL,                  // use parent‘s environment

  40. NULL,                  // use parent‘s current directory

  41. &siStartInfo,      // STARTUPINFO pointer

  42. &piProcInfo);     // receives PROCESS_INFORMATION
  43. //如果失败,退出

  44. if (!bSuccess ) return;
  45. //然后,就可以读写管道了

  46. //写入stdin,具体代码在一个WriteToPipe函数中

  47. WriteToPipe();
  48. //不断子检测进程有否结束

  49. while (GetExitCodeProcess(piProcInfo.hProcess,&process_exit_code))

  50. {

  51. //读stdout,stderr

  52. ReadFromPipe();

  53. //如果子进程结束,退出循环

  54. if (process_exit_code!=STILL_ACTIVE) break;

  55. }

具体看一下WriteToPipe(), ReadFromPipe函数:

[html] view plaincopy

  1. //写入stdin

  2. BOOL WriteToPipe()

  3. {

  4. DWORD dwWritten;

  5. BOOL bSuccess = FALSE;
  6. //用WriteFile,从hStdInWrite写入数据,数据在in_buffer中,长度为dwSize

  7. bSuccess = WriteFile( hStdInWrite, in_buffer, dwSize, &dwWritten, NULL);

  8. return bSuccess;

  9. }

[html] view plaincopy

  1. // 读出stdout

  2. BOOL ReadFromPipe()

  3. {

  4. char out_buffer[4096];

  5. DWORD dwRead;

  6. BOOL bSuccess = FALSE;
  7. //用WriteFile,从hStdOutRead读出子进程stdout输出的数据,数据结果在out_buffer中,长度为dwRead

  8. bSuccess = ReadFile( hStdOutRead, out_buffer, BUFSIZE, &dwRead, NULL);

  9. if ((bSuccess) && (dwRead!=0))  //如果成功了,且长度>0

  10. {

  11. // 此处加入你自己的代码

  12. // 比如:将数据写入文件或显示到窗口中

  13. }

  14. return bSuccess;

  15. }

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 plaincopy

  1. #include <stdio.h>

  2. #include <windows.h>

  3. #include "JExecution.h"
  4. //定义一个处理输出的回调函数

  5. void OnLineOut(char *buf,int size)

  6. {

  7. printf("%s\n", buf);

  8. }
  9. int main(int argc, char* argv[])

  10. {

  11. int n;

  12. char *command;
  13. command = "cmd.exe  /r dir/w ";   //这个命令的意思就是调用DOS,执行dir命令,显示当前目前下的文件

  14. n=ExecCommandOutput(command,NULL,SW_HIDE,OnLineOut,NULL);

  15. printf("<Return:>%d",n);

  16. }

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

时间: 2024-08-03 10:36:50

重定向子进程控制台程序的输入输出的相关文章

Createprocess控制台程序输出重定向

在Windows编程中,并非每一个应用程序都需要一个图形用户界面(GUI),很多情况下,我们可以编写一个控制台应用程序,这样程序更小,加载更快,传输时间也短,同时也丝毫不牺牲程序应有的功能.这种程序特别适合那些在后台运行的程序,比如压缩.杀毒.上传下载等等.如果我们的确需要在GUI执行这些程序,以完成某些比如类似于磁盘格式化的功能,我们可以在GUI程序中创建一个新的进程,调用这些已有的控制台应用程序,帮助完成这些功能.然而令人失望的是,我们每次加载这些控制台应用程序时,图形程序总会在加载的过程中

java笔记--重定向输出流实现程序输出到日志

重定向输出流实现程序输出到日志 利用System类中的setOut()方法,实现输出流的重定向,把它指向一个文件输出流,从而实现日志功能.即: 程序运行后绘制控制台提示运行结束信息,但是在运行过程中的步骤都保存到了日志文件中. 主要方法setOut(): 重新分配System类的标准输出流. public static void setOut(PrintStream out) out表示新的输出流对象 setErr() 重新分配System类的标准错误输出流. public static voi

调用控制台程序的方法以及注意事项

我们常常用遇到调用第三方库,但是该库文件没有做好内存管理,会发生内存泄露的情况. 这个时候我们可以在控制台中调用该库,然后在代码中调用控制台程序,以此控制台的来管理内存. public class ConsoleMethod { private static int _time = 10 * 60 * 1000; public static string InvokeConsole(string parameter, string programName, string programAddre

Windows控制台程序“选定模式”的问题

最近用Nodejs写了个代理程序,一直用的好好的,木有问题,今天突然发现不能用了,使用telnet去连代理的端口也能连通,可是服务就是不能正常使用,提示连接超时. 当时猜测是Nodejs的某个地方阻塞了,分析了下代码,怎么也想不明白不能有阻塞的地方啊. 又是各种倒腾,后来发现一个奇怪的问题,我把鼠标放到Nodejs的控制台上,默认进入了编辑模式,而且整个应用的标题变成了"选定 XXXX"这种形式,我想退出程序,需要按两次Ctrl+C,难道是这的问题? 又来有测试了下,果真还是这的问题,

VS2017新建windows控制台程序打印中文乱码问题

最近刚换上VS2017,由于手头又要做个MFC的程序,所以写控制台程序做功能测试,然后发现居然乱码了. 于是用VS2017新建windows控制台应用程序,在main函数种加一句printf("你好");后,运行结果依然乱码 用notapad++打开该文件后,点击菜单栏的编码一项,发现是UTF-8无BOM格式编码,然后改成以ANSI格式编码后 也就是说VS是用UTF-8来编码代码文件的,编译出的程序中字符串也是按照UTF-8编码的,而控制台却是按照ANSI编码来理解的. 打个比方,A用

.net开发环境的认识,控制台程序的创建,输出,输入,定义变量,变量赋值,值覆盖,值拼接,值打印 两种数据类型,整形类型转换

首先感谢向立凯老师带我走进.net这个很好的软件开发程序.通过两天的学习在这个领域的了解也多了很多,让大家先了解一下c#语言 c#是一种全新且简单.安全.快捷面向对象的程序设计语言.是专门为.net应用开发的语言..net的开发都基于一个统一的开发环境 Visual Studio.net.下面我们来看看怎么在Visual Studio.net.下创建以新的项目: 1.打开 2.新建一个项目 通过执行文件-新建-项目菜单命令,会弹出一个新建项目对话框.在这个对话框中选择开发语言为c#,选择框架版本

2017-2-17 c#基础学习 (控制台程序的创建,输出,输入,定义变量,变量赋值,值覆盖,值拼接,值打印)

1 控制台程序的创建 > 新建项目  ,选择 c#,  框架选择4.0 , 选择控制应用台程序, 选择文件保存位置 修改名字. 2 c#输出与输入 >在main函数中编写代码 >在编写时可以先插入Console.ReadLine();防止程序闪退 > Console.Write("实例语句");//不换行输出 Console.WriteLine("示例语句");//换行输出 Console.ReadLine();//等待用户输入 防止闪退 结

控制台程序添加滚轮滑动支持

控制台程序默认只能通过拖动滚动条来查看窗口中打印的内容,操作起来十分不方便. 本文通过多线程技术为控制台窗体添加鼠标滚轮滑动功能.值得注意的是,在有内容输出时,窗口会自动定位到输出的光标处: 这种情况最好是先暂停住主线程,然后再滚动鼠标查看打印的内容,查看完毕后,再继续执行主线程. 首先,需要让控制台程序的屏幕缓冲区高度 > 窗口高度(此时窗口右侧会产生滚动条),否则无需滚动窗口. 下列代码实现了如下功能: (1)滚动鼠标滑动窗口 (2)按空格键,暂停/继续主线程 #include <wind

利用管道获取控制台程序的标准输出

1.该程序调用控制台程序hello.exe,通过管道获取到hello.exe的标准输出数据,并打印到当前程序的标准输出. #include <Windows.h> #include <iostream> #include <string> using namespace std; void invoke(string exe); int main(int argc, char* argv[]) { string exe = "hello.exe";