用父进程交互式地操作子进程标准输入输出的问题。

lonelyhawl 2011-05-15 11:00:54
我用下面的代码创建一个子进程,并用两个管道去重定向它的标准输入输出。子进程是一个交互式的命令行程序,其流程为:接受标准输入,运算,将结果送标准输出,然后继续接受输入,再远算输出,一直重复。父进程利用这两个管道来操作子进程的标准输入输出,其流程为写子进程标准输入管道,即让子进程接受标准输入,然后读子进程标准输出管道,接收子进程标准输入,然后再重复此过程。发现了一些有趣的问题:1,printf在重定向管道后不靠谱,比如,子进程的prinf内容不能被管道读取到,我猜想是存在buffer没有写到管道中的问题,在子进程中用fflush(stdout)后,管道可以读取到。2,在子进程中用c++的std::cout没有此问题,子进程的流程是 std::cin>>x, 再std::cout<<y,再std::cin>>x,std::cout<<y,依次重复,在第一次std::cout<<y后,它后面的std::cin>>x居然会在父进程还没开始写管道时就执行,只不过读出来的数据是空,这与子进程单独运行不一样,单独运行时,在第二次std::cin>>x时会阻塞在那等待标准输入,而在标准输入被重定向到管道后,它在第一次std::cin>>x时已经读取了管道内容了,在第二次std::cin>>x时,它没有等待父进程写管道,而是直接就读取空串返回了,在第一次之后的std::cin>>x之前加上fflush(stdin)后,就没有此问题了。我试验在父进程中来做flush管道的操作,都不能解决此问题。
我想与大家讨论的是,如果我想写一个父进程来交互式地控制子进程的标准输入输出,有没有办法只在父进程的代码上做文章,使父进程能顺利地与子进程交互,不管子进程是用scanf, printf, cin, cout还是其它的做标准输入输出的方法。毕竟对于有些已经存在的程序,它没有用cin, cout,也没有在cin前做fflush(stdin)的操作?
另外顺便问下大家,我在VS2003中加了两个工程,一个子进程,一个父进程,有没有方法让我可以同时调试这两个进程,比如我将子进程工程设置为父进程工程的reference,父进程设为启动工程,在子进程的代码处打上断点,然后运行就可以在子进程代码的断点处停下。如果是dll,这个完全没有问题,也许进程就不行,不晓得大家知道有方法这样做没?我记得注册表中好像有个目录,在里面加上子进程的可执行文件名,就可以设置其启动,调试参数,如果将它设置为在VS2003中启动,也许就可以调试它了,不知这样是否可行?

文字能力不强,篇幅多了,希望大家见谅。

启动子进程代码:
PROCESS_INFORMATION pi;
bool BridgeStart(char* target_name)
{
BOOL bSuccess; /* BOOL return code for APIs */
SECURITY_ATTRIBUTES sa;
STARTUPINFO si;


/* set up the security attributes for the anonymous pipe */
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

// create anonymous pipe
bSuccess = CreatePipe(&hHostRead, &hSlaveWrite, &sa, 0);
//PERR(bSuccess, "CreatePipe");

// create anonymous pipe
bSuccess = CreatePipe(&hSlaveRead, &hHostWrite, &sa, 0);
//PERR(bSuccess, "CreatePipe");

/* Set up the STARTUPINFO structure for the CreateProcess() call */
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = hSlaveRead;
si.hStdOutput = hSlaveWrite;
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);

/* Set up the PROCESS_INFORMATION structure for the CreateProcess() call */
ZeroMemory( &pi, sizeof(pi) );

// Start the child process.
bSuccess = CreateProcess(NULL, // No module name (use command line)
LPTSTR(target_name), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
TRUE, // Set handle inheritance to TRUE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi); // Pointer to PROCESS_INFORMATION structure
//PERR(bSuccess, "CreateProcess");

if (bSuccess)
return true;
else
return false;
}

读写子进程输入输出管道代码:
int BridgeRead(char* buf, int len)
{
BOOL bSuccess; /* BOOL return code for APIs */
DWORD n; /* number of bytes read or to be written */


bSuccess = ReadFile(hHostRead, /* read handle */
buf, /* buffer for incoming data */
len, /* number of bytes to read */
&n, /* number of bytes actually read */
NULL); /* no overlapped reading */

if (!bSuccess && (GetLastError() == ERROR_BROKEN_PIPE))
return(-1);
else
return(n);
}

int BridgeWrite(char* buf, int len)
{
BOOL bSuccess; /* BOOL return code for APIs */
DWORD n; /* number of bytes read or to be written */

bSuccess = WriteFile(hHostWrite,/* write handle */
buf, /* buffer to write */
len, /* number of bytes to write */
&n, /* number of bytes actually written */
NULL); /* no overlapped writing */

if (!bSuccess && (GetLastError() == ERROR_BROKEN_PIPE))
return(-1);
else
return(n);
}

子进程代码
int _tmain(int argc, _TCHAR* argv[])
{
int a,b;
while(1)
{
fflush(stdin);//如果此行去掉,重定向输入输出到管道后就会出问题。
std::cin >> a >> b;
std::cout << a + b << std::endl;
}
return 0;
}

...全文
1289 10 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
pangxuesong 2013-10-17
  • 打赏
  • 举报
回复
请问楼主最后是如何解决这个问题的?我已经被这个问题困扰半个月了!每天都在查资料。。。
rossini23 2011-07-27
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 lonelyhawl 的回复:]

引用 7 楼 afairycell 的回复:
没怎么看你上面的东西,残余的话可以清空一下或者上锁之类的读取完整先。还有子进程只含有创建时当前环境。

其实我真正想做的是,不改变子进程的原有代码,做一个父进程的程序,它可以随意地创建任何命令行的子进程并接管它的标准输入输出,这样任何人写的子程序,只要在命令行下能够运行正常,也能被父进程创建并利用管道重定向操作其标准输入输出,像在命令行下一样运……
[/Quote]

Tcl Expect就是个与子进程交互输入输出的例子。
Expect 4 Windows里面使用的方法是Debug子进程,然后给一些关键函数(WriteConsoleA等,printf最后也会调用这个函数)打断点的方法获取其输出。然后通过inject子进程,用共享内存的方法向子进程发送输入。
楼主可以参考一下,稍微有点复杂。源码见下面链接:
http://sourceforge.net/projects/expect/files/Expect%204%20Windows/

lonelyhawl 2011-05-19
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 afairycell 的回复:]
没怎么看你上面的东西,残余的话可以清空一下或者上锁之类的读取完整先。还有子进程只含有创建时当前环境。
[/Quote]
其实我真正想做的是,不改变子进程的原有代码,做一个父进程的程序,它可以随意地创建任何命令行的子进程并接管它的标准输入输出,这样任何人写的子程序,只要在命令行下能够运行正常,也能被父进程创建并利用管道重定向操作其标准输入输出,像在命令行下一样运行。
afairycell 2011-05-18
  • 打赏
  • 举报
回复
没怎么看你上面的东西,残余的话可以清空一下或者上锁之类的读取完整先。还有子进程只含有创建时当前环境。
lonelyhawl 2011-05-18
  • 打赏
  • 举报
回复
整理了一下思路,我觉得管道的输入在被子进程的std::cin得到后,还留有残余,但那残余不晓得是什么,在第二次std::cin接受输入时,还没来得及等父进程往管道灌东西,就读取残余了。父进程是写了类似的东西给管道,比如子进程std::cin<< a << b,父进程就写 "1 2\n"这个字符串到管道中,写的size是5。如果不写"\n",子进程还等待接受输入,写了"\n"就向后运行,莫非残余是"\n"?
lonelyhawl 2011-05-16
  • 打赏
  • 举报
回复
看我前面的描述吧,管道建立肯定是成功了的,但对不同的子进程,会有不同的问题,请仔细阅读前面的东西吧。
oyljerry 2011-05-16
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 lonelyhawl 的回复:]
东西有点多,兄台慢慢看。我晓得msdn上那个例子,我的问题是这样,我想写一个程序,它可以启动其它命令行程序,但这些命令行程序不一定是我写的,有可能用的是scanf, printf之类,也有可能用std::cin, std::cout之类,我现在用我这个程序去接管这些命令行程序的标准输入输出时遇到了问题,问题是上面描述的,希望慢慢看一下,我会不停给这个帖子加分的。
[/Quote]
其他程序一般管道能够接收到一些标准输入输出的信息,你先看是否建立成功,其次要看你目标程序是否用到标准输入输出等
lonelyhawl 2011-05-16
  • 打赏
  • 举报
回复
东西有点多,兄台慢慢看。我晓得msdn上那个例子,我的问题是这样,我想写一个程序,它可以启动其它命令行程序,但这些命令行程序不一定是我写的,有可能用的是scanf, printf之类,也有可能用std::cin, std::cout之类,我现在用我这个程序去接管这些命令行程序的标准输入输出时遇到了问题,问题是上面描述的,希望慢慢看一下,我会不停给这个帖子加分的。
oyljerry 2011-05-16
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 lonelyhawl 的回复:]
看我前面的描述吧,管道建立肯定是成功了的,但对不同的子进程,会有不同的问题,请仔细阅读前面的东西吧。
[/Quote]
很多问题说了,这个管道需要子进程的配合,如果子进程不使用fflush(stdin)等操作,父进程就会有影响.
hztj2005 2011-05-16
  • 打赏
  • 举报
回复
太乱,看不明白你的意思。
MSDN上有一个管道交换数据的例子,使用的是双管道,一个负责读,一个写。
很好用。

一个子进程,一个父进程同时调试,看过这样的例子,好像是是在同一个project,做2个解决方案。还是2个project,做1个解决方案,没记清。
本PDF电子书包含上下两册,共1576页,带目录,高清非扫描版本。 作者: 毛德操 胡希明 丛书名: Linux内核源代码情景分析 出版社:浙江大学出版社 目录 第1章 预备知识 1.1 Linux内核简介. 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码中的C语言代码 1.5 Linux内核源代码中的汇编语言代码 第2章 存储管理 2.1 Linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户堆栈的扩展 2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2.9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 中断、异常和系统调用 3.1 X86 CPU对中断的硬件支持 3.2 中断向量表IDT的初始化 3.3 中断请求队列的初始化 3.4 中断的响应和服务 3.5 软中断与Bottom Half 3.6 页面异常的进入和返回 3.7 时钟中断 3.8 系统调用 3.9 系统调用号与跳转表 第4章 进程进程调度 4.1 进程四要素 4.2 进程三部曲:创建、执行与消亡 4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核中的互斥操作 第5章 文件系统 5.1 概述 5.2 从路径名到目标节点 5.3 访问权限与文件安全性 5.4 文件系统的安装和拆卸 5.5 文件的打开与关闭 5.6 文件的写与读 5.7 其他文件操作 5.8 特殊文件系统/proc 第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6.5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 第7章基于socket的进程间通信 7.1系统调用socket() 7.2函数sys—socket()——创建插口 7.3函数sys—bind()——指定插口地址 7.4函数sys—listen()——设定server插口 7.5函数sys—accept()——接受连接请求 7.6函数sys—connect()——请求连接 7.7报文的接收与发送 7.8插口的关闭 7.9其他 第8章设备驱动 8.1概述 8.2系统调用mknod() 8.3可安装模块 8.4PCI总线 8.5块设备的驱动 8.6字符设备驱动概述 8.7终端设备与汉字信息处理 8.8控制台的驱动 8.9通用串行外部总线USB 8.10系统调用select()以及异步输入/输出 8.11设备文件系统devfs 第9章多处理器SMP系统结构 9.1概述 9.2SMP结构中的互斥问题 9.3高速缓存与内存的一致性 9.4SMP结构中的中断机制 9.5SMP结构中的进程调度 9.6SMP系统的引导 第10章系统引导和初始化 10.1系统引导过程概述 10.2系统初始化(第一阶段) 10.3系统初始化(第二阶段) 10.4系统初始化(第三阶段) 10.5系统的关闭和重引导

15,473

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 进程/线程/DLL
社区管理员
  • 进程/线程/DLL社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧