CreateProcess and CreatePipe to execute a process and return output as a string in VC++

18,477

Solution 1

Thanks to Hans Passant for the lead that got me to this clear and simple piece of code that does exactly what I was looking for.

/// this namespace call is necessary for the rest of the code to work
using namespace System::Diagnostics;
using namespace System::Text;/// added for encoding

Process^ myprocess = gcnew Process;
Encoding^ Encoding;/// added for encoding
Encoding->GetEncoding(GetOEMCP());/// added for encoding

myprocess->StartInfo->FileName = "ping.exe";
myprocess->StartInfo->Arguments = "127.0.0.1";
myprocess->StartInfo->UseShellExecute = false;

/// added the next line to keep a new window from opening
myprocess->StartInfo->CreateNoWindow = true;

myprocess->StartInfo->RedirectStandardOutput = true;
myprocess->StartInfo->StandardOutputEncoding = Encoding;/// added for encoding
myprocess->Start();

String^ output = gcnew String( myprocess->StandardOutput->ReadToEnd() );

myprocess->WaitForExit();

/// OutputBox is the name of a Windows Forms text box in my app.
OutputBox->Text = output;

EDIT: Added encoding information. See above code.

Solution 2

You are not making correct use of the ::ReadFile() function.

Read about it here: http://msdn.microsoft.com/en-us/library/ms891445.aspx

Basically, you want to fail with an error if the function ever does not return TRUE, and you want to keep looping until it yields a zero reDword.

Also, ::ReadFile() will not zero-terminate your data for you, so you have to do it yourself, like this: buf[reDword] = '\0'; (make sure your buf is 101 chars long before doing that.)

EDIT: Since I was asked to provide some example code, here it is, though I have not gone through the trouble of actually compiling it to make sure it works, so please beware of syntax errors, and generally consider it only as a rough pointer to the direction in which it should be done:

#define BUFFER_SIZE 100
string csoutput;
for( ;; )
{
    char buf[BUFFER_SIZE+1];
    DWORD redword; 
    if( !::ReadFile(rPipe,buf,BUFFER_SIZE,&redword,0) )
    {
        DWORD error = ::GetLastError();
        //throw new Exception( "Error " + error ); //or something similar
    }
    if( redword == 0 )
        break;
    buf[redword] = '\0';
    string cstemp = buf;
    csoutput += cstemp;
}
return csoutput;
Share:
18,477
Jason
Author by

Jason

Updated on July 29, 2022

Comments

  • Jason
    Jason almost 2 years

    I am trying to use CreateProcess and CreatePipe to execute a process from within a Windows Forms C++/CLR application in Visual Studio 2010.

    From within my Windows forms app I want to execute a child process (console app) and return the output as a std::string, std::wstring, or System::String^ within my Windows forms app. Additionally, I do not want the newly created child process to spawn a window.

    The console application is of my own creation, so I have control of it's source too.

    I have seen the following examples, but I do not understand how to modify the code to accomplish what I am trying to do:

    The MSDN code appears to be written as two console apps, one calling the other. The code is confusing to me. I've only been working in C++ for about 4 months, so I still don't understand everything. It appears to reference a text file, which I don't need to do.

    Is there a simpler way to do this than MSDN's 200+ lines of code or kgui's 300+ lines of code?

    The answer here was helpful, but over simplistic. I was hoping to see a basic source example (one that doesn't involve hundreds of lines of complex code would be preferable). I would have used the MFC code, but I had difficulty adapting it to my purposes (I'm not using MFC).

    Following is my adaptation of the code from Code Project:

    string ExecuteExternalFile(string csExeName, string csArguments)
    {
      string csExecute;
      csExecute=csExeName + " " + csArguments;
    
      SECURITY_ATTRIBUTES secattr; 
      ZeroMemory(&secattr,sizeof(secattr));
      secattr.nLength = sizeof(secattr);
      secattr.bInheritHandle = TRUE;
    
      HANDLE rPipe, wPipe;
    
      //Create pipes to write and read data
    
      CreatePipe(&rPipe,&wPipe,&secattr,0);
      //
    
      STARTUPINFO sInfo; 
      ZeroMemory(&sInfo,sizeof(sInfo));
      PROCESS_INFORMATION pInfo; 
      ZeroMemory(&pInfo,sizeof(pInfo));
      sInfo.cb=sizeof(sInfo);
      sInfo.dwFlags=STARTF_USESTDHANDLES;
      sInfo.hStdInput=NULL; 
      sInfo.hStdOutput=wPipe; 
      sInfo.hStdError=wPipe;
    
      //Create the process here.
    
      CreateProcess(0,(LPWSTR)csExecute.c_str(),0,0,TRUE,NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW,0,0,&sInfo,&pInfo);
      CloseHandle(wPipe);
    
      //now read the output pipe here.
    
      char buf[100];
      DWORD reDword; 
      string m_csOutput,csTemp;
      BOOL res;
      do
      {
                      res=::ReadFile(rPipe,buf,100,&reDword,0);
                      csTemp=buf;
                      m_csOutput+=csTemp;
      }while(res);
      return m_csOutput;
    }
    

    I have tried using this from within my Windows Forms app, and while it compiles ok and doesn't cause any errors, it doesn't seem to work either. I have no idea why.

    This is how I executed the above code:

    std::string ping = ExecuteExternalFile("ping.exe", "127.0.0.1");
    

    It did not appear to do anything, except that on the first execution it give a very strange 3 characters as an output, then on subsequent executions, nothing.

  • Jason
    Jason over 12 years
    Hi Mike. Thank you for your quick response. Is it possible to show me a code example as you would write it into the above code?
  • Mike Nakis
    Mike Nakis over 12 years
    @Jason I edited the answer to provide some code example. I am not saying that the incorrect use of ReadFile() is necessarily your problem; if it is not, please say so, and we can try to figure out other reasons why it might not be working.
  • Jason
    Jason over 12 years
    Thank you for providing that code. Unfortunately I still get no output string from that function. The suggestion by Hans did lead me to a solution, which I will post when the time limit expires.
  • user1703401
    user1703401 over 12 years
    You're doing it the hard way, not sure why. Use the System::Net::NetworkInformation::Ping class :)
  • Jason
    Jason over 12 years
    @HansPassant - Actually, the ping was just an example to test this code. It could have been anything with an output. Thanks again for the help. The above really is exactly what was needed.
  • aelveborn
    aelveborn over 12 years
    @Jason I believe that was a joke in the form of a reference to the previous Hans'es comment. Still, you really should add decoding to this code (console apps use CP_OEMCP codepage, which may be different from CP_ACP set for non-unicode GUI apps). Because .NET doesn't offer a ready option, you'll have to import GetOEMCP() function from kernel32.dll, and then do this: myprocess->StandardOutputEncoding = Encoding->GetEncoding(GetOEMCP());.
  • Jason
    Jason over 12 years
    @GSerg - Thank you both for the tips, and also thank you for improving the HTML formatting of my question GSerg! :) I edited my answer based on your suggestion. If you see something amiss, please let me know.