如何在 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 语言中将输入和检索输出传递给子进程的主要内容,如果未能解决你的问题,请参考以下文章

golang 热重启

杀死父进程后将标准输入传递给子进程

检索通过输入属性传递给子组件的值

如何在Vue2中将数据从父级传递给子级

Linux学习-进程管理

如何在不使用角度的ng的情况下将输入值从父组件传递给子组件?