I am trying to create a function that will spawn an instance of a program and then pipe some data into its STDIN and then read the process's output using C++. I have looked at an MSDN example located here which is rather confusing to me and when I try to use the example, I get some nasty error codes and it won't work.
HANDLE hWriteOUT, hReadOUT, hWriteIN, hReadIN;
SECURITY_ATTRIBUTES saPipe = {0};
PROCESS_INFORMATION procInfo = {0};
STARTUPINFO procSi;
DWORD dwWritten, dwRead;
char buf[512];
saPipe.nLength = sizeof(SECURITY_ATTRIBUTES);
saPipe.bInheritHandle = TRUE;
saPipe.lpSecurityDescriptor= NULL;
CreatePipe(&hReadOUT, &hWriteOUT, &saPipe, 0);
SetHandleInformation(hReadOUT, HANDLE_FLAG_INHERIT, 0);
CreatePipe(&hReadIN, &hWriteIN, &saPipe, 0);
SetHandleInformation(hReadIN, HANDLE_FLAG_INHERIT, 0);
ZeroMemory(&procSi, sizeof(STARTUPINFO));
procSi.cb = sizeof(STARTUPINFO);
procSi.hStdError = hWriteOUT;
procSi.hStdOutput = hWriteOUT;
procSi.hStdInput = hReadIN;
procSi.dwFlags |= STARTF_USESTDHANDLES;
CreateProcess(NULL, "cmd", NULL, NULL, TRUE, 0, NULL, NULL, &procSi, &procInfo);
//Gives me an error code of 18 but returns a 1 when a 0 indicates failure.
WriteFile(hWriteIN, "notepad", sizeof("notepad"), &dwWritten, NULL);
cout << GetLastError(); //This gives me error code 18 (ERROR_NO_MORE_FILES)
ReadFile(hReadOUT, buf, 512, &dwRead, NULL);
cout << buf; //This prints "Microsoft Windows [version 6.1.7601]
CloseHandle(hWriteIN);
The code fails to pipe the string "notepad" into cmd.exe but is successful in launching the command shell. If I look in task manager, there are several instances of command prompt spawned but no notepads. In addition, the ReadFile() function is the only one that seemed to have worked, but it's not even reading from the piped process (notepad that was supposed to be spawned) instead, it's reading from CMD. And even worse, it's truncating everything but the first line that it reads! (CMD prints the "Microsoft Windows....\n Copyright...\n C:\Users\Foo>...\n" but the `ReadFile() only grabs the first line)
The code is behaving as expected. There are a number of things you seem to be misunderstanding:
1) You need to send an ENTER ("\n") at the end of a command if you want cmd.exe to run it. Usually it is preferable to specify the command you want to run in CreateProcess, e.g., you could specify "cmd /c notepad" as the command line instead of just "cmd".
2) You've attached your pipes to the standard input and output of the cmd.exe process, so of course you see output from that process. If you don't want to see output from cmd.exe, don't run it; run the application you want directly, e.g., you could specify "notepad" as the command line to run.
3) When reading from a pipe, ReadFile only returns a single block of data at a time, so you need to call it in a loop.
4) Notepad is a GUI process, so it doesn't use stdin or stdout anyway. Presumably this was just a poorly chosen example and you actually want to run a command-line application?
5) Except as specifically documented, the error code (as returned by GetLastError) is only meaningful when a function has failed. None of the functions you are using are exceptions to this case, so there is no point in checking the error code unless the function returns zero to indicate that it has failed.
I know it's kinda too late to answer this question but there is also something else wrong with your code. it seems SetHandleInformation(hReadOUT, HANDLE_FLAG_INHERIT, 0); and SetHandleInformation(hReadIN, HANDLE_FLAG_INHERIT, 0); will break things. I'm not sure why, but after trying t run your code, cmd could not read values from pipe until I remove those two lines.
Related
So, on Windows, it seems execl() is broken in different ways across different versions of windows. I've spent a lot of time narrowing it down and debugging, and it doesn't really make sense, and I can only think there's something wrong with Microsoft's implementation of execl() (actually execlp() for me).
The only Windows version execlp() seems to work correctly on is 7.
On Windows 10, it works fine, until I compile with -mwindows in MinGW.
Then it just makes my program terminate with a zero exit code.
On Windows XP, it interprets spaces in argument parameters as separate arguments, despite the actual number of arguments being clearly specified by the nature of the function's prototype...
So, looks like I'll have to use some Windows native function and wrap it in "#ifdef WIN32"s.
What I really need is execl() (execlp isn't necessary) like behavior on Windows, in that it replaces the current process image with a new one, and keeps network descriptors open like execl() will.
I'm really at a loss as to what good options are, and while CreateProcess seems somewhat able to do some of it, I can't find enough info on what I'm trying to do.
Any help is appreciated. Thanks!
Unfortunately, you will need a completely different code path on windows, because in the win32 subsystem, creating a process is coupled together with loading and running a new image in a single call: CreateProcess().
In a typical posix scenario, you would fork() your new process, set up things like e.g. file descriptors and then exec*() the new binary. To achieve something similar in Windows, you must rely on the possibilities you get from CreateProcess(). For open files (or sockets), win32 uses "handles" and these can be marked inheritable, for example I do the following for a pipe:
HANDLE pin, pout;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = 1;
if (!CreatePipe(&pin, &pout, &sa, 0))
{
fprintf(stderr, "Error creating pipe: %lu\n", GetLastError());
return;
}
This way, the pipe's handles are inheritable. Then, when calling CreateProcess(), by passing 1 (or TRUE) for bInheritHandles, the new process inherits all handles marked this way. In this example, I do the following:
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.hStdInput = nul;
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.dwFlags |= STARTF_USESTDHANDLES;
// simple example, my production code looks different, e.g. quoting the command:
char cmdline[1024];
strcpy(cmdline, exename);
strcat(cmdline, " ");
snprintf(cmdline + strlen(cmdline), 1024 - strlen(cmdline), "%" PRIxPTR, (uintptr_t)pout);
// [...]
PROCESS_INFORMATION pi;
CreateProcess(0, cmdline, 0, 0, 1, 0, 0, 0, &si, &pi);
In the child, the pipe could be used like that:
uintptr_t testPipeHandleValue;
if (sscanf(argv[1], "%" SCNxPTR, &testPipeHandleValue) != 1)
{
exit(EXIT_FAILURE);
}
int testPipeFd = _open_osfhandle(
(intptr_t)testPipeHandleValue, _O_APPEND | _O_WRONLY);
FILE *testPipe = _fdopen(testPipeFd, "a");
setvbuf(testPipe, 0, _IONBF, 0);
Of course this will look different for a network socket, but I hope the general idea helps.
When I call CreateProcess in Windows, the new process doesn't seem to inherit the console of the calling process. I made a test program that runs "ruby xtest", xtest being a script that writes "hello" to standard output. I ran this test program from Emacs, and get no output. I also tried the following code calling GetStdHandle, but again, no output. Then I tried passing CREATE_NEW_CONSOLE in dwCreationFlags to CreateProcess, which made a whole new window with the Ruby output. Finally, I made a simple fork/exec
test program and compiled it using Cygwin's GCC. This program worked: the Ruby output showed up in Emacs as expected. I tried to decipher the Cygwin source code in http://cygwin.com/cgi-bin/cvsweb.cgi/src/winsup/cygwin/spawn.cc?rev=1.268&content-type=text/x-cvsweb-markup&cvsroot=src but failed. So, how do you make the new process inherit the console of the parent process such that the output from the child shows up as expected?
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
if(!CreateProcess(0, "ruby xtest", 0, 0, 1, 0, 0, 0, &si, &pi)) die("CreateProcess");
I know, this thread is rather old, however, I just ran into the same problem.
Just as for the TS, the console handle was inherited and working fine under Cygwin, but not on a Windows console. Instead, the output on stdout was neither shown, nor any error was reported. Inherited Pipe handles worked still fine.
I took me some time to identify the (now obvious) problem: CreateProcess() was called with CREATE_NO_WINDOW. Dropping this flag, console output is fine. (Though, according to the code of the TS, they never set this flag in the first place.)
Hope this might be helpful for people who also stumble across this thread, like myself.
According to Microsoft documentation, lpCommandLine (2. parameter):
The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.
When I stopped using a constant here it worked for me. I didn't need the STARTF_USESTDHANDLES and GetStdHandle thing.
This code from a console prg runs and outputs another console exe in the same console:
FillChar(SI, SizeOf(SI), 0);
SI.cb:=SizeOf(SI);
FillChar(PI, SizeOf(PI), 0);
if CreateProcess(nil, CmdLineVar, nil, nil, False, 0, nil, nil, SI, PI) then ...
I've done this by passing in pipes for hStdInput, hStdOutput, and hStdError and manually routing data from the hStdOutput and hStdError pipes to the console.
Not sure if debeige ever solved this, but I needed the same thing, but starting up another thread to listen to stdout output, just to put it on stdout seemed nuts to me.
The following works for me, and is slightly different than what he originally posted. I thought at first it wouldn't work if you don't set si.cb, but when I commented that in mine, it still worked, so... YMMV.
STARTUPINFO siStartInfo;
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = GetStdHandle(STD_OUTPUT_HANDLE);
siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
siStartInfo.hStdInput = g_hChildStd_IN_Rd; // my outgoing pipe
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(
NULL,
szCmdline,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&siStartInfo,
&piProcInfo);
Hi I want to execute a system command for allegro and wait till it completes, since I need to access the files generated by this command. My code is not working. Can someone please help? Is the command not running? It works if I do system(command.c_str()) but does not wait.
string command = "start allegro -expert -nographic -s " + fileName1 + " " + boardFile1;
bool ret;
bool retwait;
STARTUPINFO startupinfo;
GetStartupInfo (&startupinfo);
PROCESS_INFORMATION pro2info;
LPTSTR cmdL = (LPTSTR)command.c_str();
ret = CreateProcess(NULL, cmdL, NULL, NULL, false, CREATE_NEW_CONSOLE, NULL,NULL, &startupinfo, &pro2info);
cout<<"hProcess: "<<pro2info.hProcess<<endl;
cout<<"dwProcessId: "<<pro2info.dwProcessId <<endl;
//Want to wait till the command executes
while (retwait= WaitForSingleObject (pro2info.hProcess, INFINITE)!=WAIT_OBJECT_0)
cout<<"waitprocess:true"<<endl; //The process is finished;
CloseHandle (pro2info.hProcess);
I think you don't need "start" at the beginning of command, even if you are using the flag CREATE_NEW_CONSOLE. "start" works with system (because system works with batch command), but if you are creating a new process you should only specify the path to the image file. By the way, the only way to know what's going on is to check the return of CreateProcess (and eventually GetLastError()).
Also, that's not the best way to use WaitForSingleObject. You should catch WAIT_FAILED and use GetLastError(), otherwise your next post on SO will be: "Why isn't my program waiting?" :-)
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425%28v=vs.85%29.aspx
I am using CreateProcess() to run an external console application in Windows from my GUI application. I would like to somehow gather the output to know whether there were errors. Now I know I have to do something with hStdOutput, but I fail to understand what. I am new to c++ and an inexperienced programmer and I actually don't know what to do with a handle or how to light a pipe.
How do I get the output to some kind of variable (or file)?
This is what I have a the moment:
void email::run(string path,string cmd){
WCHAR * ppath=new(nothrow) WCHAR[path.length()*2];
memset(ppath,' ',path.length()*2);
WCHAR * pcmd= new(nothrow) WCHAR[cmd.length()*2];
memset(pcmd,' ',cmd.length()*2);
string tempstr;
ToWCHAR(path,ppath); //creates WCHAR from my std::string
ToWCHAR(cmd,pcmd);
STARTUPINFO info={sizeof(info)};
info.dwFlags = STARTF_USESHOWWINDOW; //hide process
PROCESS_INFORMATION processInfo;
if (CreateProcess(ppath,pcmd, NULL, NULL, FALSE, 0, NULL, NULL, &info, &processInfo))
{
::WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
delete[](ppath);
delete[](pcmd);
}
This code probably makes any decent programmer scream, but (I shouldn't even say it:) It works ;-)
The Question: How do I use hStdOutput to read the output to a file (for instance)?
Microsoft has an example in its knowledge base that demonstrates how to capture the output of a child console process. The basic principle is that the parent process creates pipes (one per standard handle to redirect) and passes the handles to CreateProcess.
The child process does not need to be modified for this to work, which is important if you do not have control over the child's source.
More information: How to spawn console processes with redirected standard handles
I need to restart the program that im working on after an update has been downloaded except im running into some issues.
If i use CreateProcess nothing happens, if i use ShellExecute i get an 0xC0150002 error and if i use ShellExecute with the command "runas" it works fine. I can start the command prompt fine using CreateProcess and ShellExecute just not the same exe again and dont want to use runas as this will elevate the exe.
Any Ideas?
Windows 7, visual studio 2008 c++
alt text http://lodle.net/shell_error.jpg
CreateProcess:
char exePath[255];
GetModuleFileName(NULL, exePath, 255);
size_t exePathLen = strlen(exePath);
for (size_t x=exePathLen; x>0; x--)
{
if (exePath[x] == '\\')
break;
else
exePath[x] = '\0';
}
char name[255];
GetModuleFileName(NULL, name, 255);
PROCESS_INFORMATION ProcInfo = {0};
STARTUPINFO StartupInfo = {0};
BOOL res = CreateProcess(name, "-wait", NULL, NULL, false, 0, NULL, exePath, &StartupInfo, &ProcInfo );
ShellExecute:
char exePath[255];
GetModuleFileName(NULL, exePath, 255);
size_t exePathLen = strlen(exePath);
for (size_t x=exePathLen; x>0; x--)
{
if (exePath[x] == '\\')
break;
else
exePath[x] = '\0';
}
char name[255];
GetModuleFileName(NULL, name, 255);
INT_PTR r = (INT_PTR)ShellExecute(NULL, "runas", name, "-wait", exePath, SW_SHOW);
CreateProcess() is an arcane beast. I remember unfondly my first frustrations with it. You should look at the Microsoft CreateProcess Example and the CreateProcess Page. (those links likely have a short lifetime, Googling CreateProcess should work just as well).
I can see 3 problems in your code.
StartupInfo must have "cb" set to the structure size:
STARTUPINFO StartupInfo = {0};
StartupInfo.cb = sizeof(StartupInfo);
The second argument requires both the command and the arguments to form the command line. Your program will see "-wait" as argv[0] and ignore it or pay it no mind.
char command[512];
sprintf(command, "%s -wait", name);
BOOL res = CreateProcess(name, command, // and as you had before
You don't look at GetLastError() if CreateProcess() fails (by returning a zero). It may have helped you but I suspect it would just say "invalid argument" or somesuch. Hey, there's only 10 of them to check, don't be lazy :-)
Another bug I committed is not closing the hProcess and/or hThread handles return in PROCESS_INFORMATION when I was done. I did do hProcess, but not hThread.
Looks like a manifest or registry question judging from the error code. If you can't get the actual error message string for more details, you might try:
moving every possible manifest file (Microsoft.VC80.CRT.manifest and the like) into your exe's directory, to ensure accessibility
cleanly and completely uninstall/wipe out old versions of DLL you may have installer newer versions of (I suggest: uninstall EVERY version, clean the registry with a sweep-clean tool such as Norton's, reinstall the new stuff from scratch)
What happens if you run the process using system()? It gives you less control, but you'll be running it from the same context you're running in. Also, Try monitoring the launch of your second process using ProcMon, it might give you the hint you need about the source of the failure.
Ok worked it all out in the end.
The first time my exe ran it used the default paths and as such loaded vld (a leak detector dll) from the default path. However in the exe i modified the dll path to be the bin folder ([app]\bin) when i restarted the exe using CreateProcess it picked up on a different vld dll (this was my mistake) that had incorrect side by side linkage and it was only after looking at event viewer that i worked it out.
Thanks for all your help.