Jason
Jason

Reputation: 236

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

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.

Upvotes: 3

Views: 20602

Answers (2)

Jason
Jason

Reputation: 236

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.

Upvotes: 3

Mike Nakis
Mike Nakis

Reputation: 61995

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;

Upvotes: 3

Related Questions