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

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;
}

...全文
1213 10 打赏 收藏 转发到动态 举报
写回复
用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个解决方案,没记清。

15,471

社区成员

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

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