如何在 C 语言中将输入和检索输出传递给子进程
Posted
技术标签:
【中文标题】如何在 C 语言中将输入和检索输出传递给子进程【英文标题】:How to pass an input and retrieve an output to a child process in C language 【发布时间】:2014-01-30 13:42:48 【问题描述】:我在 Windows 中有一个 exe 程序,它在终端中的工作方式如下
> program.exe parameter01 file
entry01 (user types entry01)
output01
entry02 (user types entry02)
output02
...
until the combination Ctrl+D is pressed.
我需要用 C 语言创建一个“子进程”,它能够运行程序并将条目发送到“子进程”并以 char[] 或字符串形式接收输出。
我知道我必须使用CreateProcess method,但我不知道如何像输入一样传递条目并检索输出,我该怎么做?
我见过这个using Java,但我需要用 C 语言实现这个功能。
【问题讨论】:
【参考方案1】:使用STARTUPINFO
结构
您必须设置属性hStdInput
。
越来越少,这就是你所需要的(它是 C++ 代码,它可能无法编译,但你会明白的):
std::string GetProcessOutput(HANDLE hStdOutProcess)
std::stringstream strStream;
char lpBuffer[2] = 0;
DWORD nBytesRead;
while(true)
BOOL bResult = ReadFile(hStdOutProcess, lpBuffer, sizeof(char), &nBytesRead, NULL);
if (bResult && nBytesRead)
strStream << lpBuffer;
else
break;
return strStream.str();
void RunAndGetOutout()
HANDLE hProcessStdOutRead = NULL;
HANDLE hProcessStdOutWrite = NULL;
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE; // allow to inherit handles from parent process
saAttr.lpSecurityDescriptor = NULL;
if(!CreatePipe(&hProcessStdOutRead, &hProcessStdOutWrite, &saAttr, 0))
return nResult;
if(!SetHandleInformation(hProcessStdOutRead, HANDLE_FLAG_INHERIT, 0))
return nResult;
STARTUPINFO startInfo;
PROCESS_INFORMATION processInfo;
char cmdLine[ MAX_PATH*2 +40] = 0;
char currentDir[MAX_PATH] = 0;
ZeroMemory(&startInfo, sizeof(startInfo));
startInfo.cb = sizeof(startInfo);
startInfo.hStdOutput = hProcessStdOutWrite; // set the handle
startInfo.dwFlags |= STARTF_USESTDHANDLES; // attention with this one
ZeroMemory(&processInfo, sizeof(processInfo));
GetCurrentDirectory(MAX_PATH, currentDir);
sprintf(cmdLine, "\"%s\" %s", (const char*)m_path2Process, (const char*)m_processArgs);
if(CreateProcess( NULL, cmdLine, NULL, NULL, TRUE, 0, NULL, currentDir, &startInfo, &processInfo))
cout << GetProcessOutput(hProcessStdOutRead) << endl;
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
CloseHandle(hProcessStdOutRead);
CloseHandle(hProcessStdOutWrite)
【讨论】:
【参考方案2】:您可以尝试使用重定向输入和输出创建子进程,我修改了找到的代码 here
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#define BUFSIZE 4096
/* child process's STDIN is the user input or data that you enter into the child process - READ */
HANDLE g_hChildStd_IN_Rd = NULL;
/* child process's STDIN is the user input or data that you enter into the child process - WRITE */
HANDLE g_hChildStd_IN_Wr = NULL;
/* child process's STDOUT is the program output or data that child process returns - READ */
HANDLE g_hChildStd_OUT_Rd = NULL;
/* child process's STDOUT is the program output or data that child process returns - WRITE */
HANDLE g_hChildStd_OUT_Wr = NULL;
void CreateChildProcess(void);
void WriteToPipe(CHAR chBuf[]);
void ReadFromPipe(void);
void ErrorExit(PTSTR);
int _tmain(int argc, TCHAR *argv[])
SECURITY_ATTRIBUTES saAttr;
printf("\n->Start of parent execution.\n");
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
//child process's STDOUT is the program output or data that child process returns
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
ErrorExit(TEXT("StdoutRd CreatePipe"));
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdout SetHandleInformation"));
//child process's STDIN is the user input or data that you enter into the child process
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
ErrorExit(TEXT("Stdin CreatePipe"));
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdin SetHandleInformation"));
// Create the child process.
CreateChildProcess();
/* variables */
char FAR *lpsz;
int cch;
CHAR chBuf[BUFSIZE];
DWORD dwRead = strlen(chBuf);
HANDLE hStdin;
BOOL bSuccess;
hStdin = GetStdHandle(STD_INPUT_HANDLE);
if (hStdin == INVALID_HANDLE_VALUE)
ExitProcess(1);
for (;;)
// Read from standard input and stop on error or no data.
bSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess || dwRead == 0)
break;
lpsz = &chBuf[0];
// Write to the pipe that is the standard input for a child process.
// Data is written to the pipe's buffers, so it is not necessary to wait
// until the child process is running before writing data.
WriteToPipe(lpsz);
printf("\n->Contents of %s written to child STDIN pipe.\n", argv[1]);
// Read from pipe that is the standard output for child process.
printf("\n->Contents of child process STDOUT:\n\n", argv[1]);
ReadFromPipe();
printf("\n->End of parent execution.\n");
// The remaining open handles are cleaned up when this process terminates.
// To avoid resource leaks in a larger application, close handles explicitly.
return 0;
void CreateChildProcess()
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
TCHAR szCmdline[] = TEXT("cmd.exe /c \"C:\\path\\to\\exe\\program.exe -parameter C:\\path\\to\\file\\file.txt\"");
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(NULL,
szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
ErrorExit(TEXT("CreateProcess"));
else
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
void WriteToPipe(CHAR chBuf[])
// Read from a file and write its contents to the pipe for the child's STDIN.
// Stop when there is no more data.
DWORD dwRead, dwWritten;
// CHAR chBuf[] = "hola\n";
dwRead = strlen(chBuf);
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf, dwRead, &dwWritten, NULL);
if (!bSuccess) ErrorExit(TEXT("StdInWr Cannot write into child process."));
/*
// Close the pipe handle so the child process stops reading.
if (!CloseHandle(g_hChildStd_IN_Wr))
ErrorExit(TEXT("StdInWr CloseHandle"));
*/
void ReadFromPipe(void)
// Read output from the child process's pipe for STDOUT
// and write to the parent process's pipe for STDOUT.
// Stop when there is no more data.
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
WORD wResult = 0;
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess || dwRead == 0) ErrorExit(TEXT("StdOutRd Cannot read child process's output."));
if (chBuf[0] == '+' && chBuf[1] == '?') printf("It's misspelled.");
else printf("It's spelled correctly.");
// bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
// if (!bSuccess) ErrorExit(TEXT("StdOutWr Cannot write into parent process's output."));
void ErrorExit(PTSTR lpszFunction)
// Format a readable error message, display a message box,
// and exit from the application.
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0, NULL);
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40)*sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
ExitProcess(1);
【讨论】:
在子进程退出之前,我是否必须避免在g_hChildStd_OUT_Wr
上调用CloseHandle
?如果我将bInheritHandles
设置为FALSE
怎么样?【参考方案3】:
基本上你需要在 Win32 平台上创建一个进程间通信系统。
您可以通过几种不同的方式来实现:管道、共享内存、IPC、WinSock、DDE...
所有这些功能都竞相为您提供最糟糕的 API,其中包含大量不一致和无用的参数、未标准化的返回代码和不连贯的函数名称。最重要的是,大约 1995 年的半 unicode 处理非常尴尬。
这是一个带有命名管道的示例。
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
// name of our glorious pipe
#define PIPE_NAME L"\\\\.\\pipe\\whatever" // bloody unicode string
// exit on fatal error
void panic(const char * msg)
fprintf(stderr, "***PANIC*** %s\n", msg);
exit(-1);
// father process
void father(const char * own_name) // name of our own executable to launch a copy of ourselve
printf("Father process starting\n");
// create a monodirectional father->child named pipe
HANDLE pipe = CreateNamedPipe (
PIPE_NAME, // name of the pipe
PIPE_ACCESS_OUTBOUND, // send only
PIPE_TYPE_BYTE, // send data as a byte stream
1, // only one instance
0, 0, 0, NULL); // default junk
if (pipe == INVALID_HANDLE_VALUE) panic("could not create pipe");
// spawn child process
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA( // using ASCII variant to be compatible with argv
own_name, // executable name (ourself)
"child", // command line. This will be seen as argv[0]
NULL, NULL, FALSE, // default junk
CREATE_NEW_CONSOLE, // launch in another console window
NULL, NULL, // more junk
&si, &pi)) // final useless junk
panic("could not create child process");
// connect to child process
BOOL result = ConnectNamedPipe(pipe, NULL);
if (!result) panic("could not connect to child process");
// talk to child
for (;;)
// read an input line
char line[100];
printf("Say something >");
if (fgets(line, sizeof(line), stdin) == NULL)
panic("could not read from standard input");
// exit on an empty line
if (!strcmp(line, "\n")) break;
// send the line to the child
DWORD written = 0;
if (!WriteFile(
pipe,
line, // sent data
strlen(line), // data length
&written, // bytes actually written
NULL))
panic("could not write to pipe");
// close the pipe
CloseHandle(pipe);
void child(void)
printf("Child process starting\n");
// retrieve communication pipe
HANDLE pipe = CreateFile(
PIPE_NAME, // name of the pipe
GENERIC_READ, // read ONLY access (or else the call will fail)
0, NULL, // junk
OPEN_EXISTING, // opens existing pipe
0, NULL); // more junk
if (pipe == INVALID_HANDLE_VALUE) panic("could not connect to the pipe");
// read father's input
for (;;)
char buffer[80];
DWORD read = 0;
if (!ReadFile(
pipe,
buffer, // read data
sizeof(buffer)-1, // max length (leave room for terminator)
&read, // bytes actually read
NULL))
break; // exit if the pipe has closed
// display what our father said
buffer[read] = '\0'; // make sure what we just read will be displayable as a string
printf("Father said: %s", buffer);
// close pipe
CloseHandle(pipe);
int main(int argc, char *argv[])
// wait for a <return> keypress on exit
atexit(getchar);
// decide whether we are the father or the child
if (!strcmp (argv[0], "child")) child();
else father(argv[0]);
printf("Done\n");
return 0;
抱歉,我找不到使用 Ctrl-D 作为退出信号的优雅方式。我能想到实现这一点的唯一方法是再进行几次极其乏味的系统调用,而且我对前景感到畏惧。
所以当用户输入一个空行时,父亲将终止。
通过关闭管道,父亲将触发读取错误,这将使孩子也摆脱痛苦(即,孩子在管道上出现读取错误时跳出阅读循环并死亡)。您可以轻松地让孩子对任何信息做出反应,如果这样可以更好地取悦您的老师。
如果您从 IDE 运行它,我已经添加了额外的等待另一个按键,以避免过于突然地关闭窗口。如果您不想这样做,只需删除 main()
开头的 atexit
。
【讨论】:
【参考方案4】:一个简单的例子:
#include <signal.h>
void SigQuit_Handle(int sig)
exit(1);
int main(int argc, char *argv[])
char buffer[1024];
signal( SIGQUIT, SigQuit_Handle );
signal( SIGINT, SIG_IGN ); // If you want to ignore Ctrl + C
while ( true )
fgets(buffer, sizeof(buffer), INPUT_BUFFER);
return 0;
编辑: 在处理线程的情况下,您可能需要包含 sys/types.h。
【讨论】:
???这与问题有什么关系?而且它甚至没有使用 Win32 API。以上是关于如何在 C 语言中将输入和检索输出传递给子进程的主要内容,如果未能解决你的问题,请参考以下文章