CreateProcess and CreatePipe to execute a process and return output as a string in VC++
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;
Jason
Updated on July 29, 2022Comments
-
Jason almost 2 years
I am trying to use
CreateProcess
andCreatePipe
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
, orSystem::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 over 12 yearsHi 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 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 over 12 yearsThank 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 over 12 yearsYou're doing it the hard way, not sure why. Use the System::Net::NetworkInformation::Ping class :)
-
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 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 fromCP_ACP
set for non-unicode GUI apps). Because .NET doesn't offer a ready option, you'll have to importGetOEMCP()
function fromkernel32.dll
, and then do this:myprocess->StandardOutputEncoding = Encoding->GetEncoding(GetOEMCP());
. -
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.