【问题标题】:How to pass an input and retrieve an output to a child process in C language如何在 C 语言中将输入和检索输出传递给子进程
【发布时间】: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 语言实现这个功能。

【问题讨论】:

    标签: c createprocess child-process


    【解决方案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。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-08-17
        • 2011-02-26
        • 1970-01-01
        • 2016-02-02
        • 2021-06-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多