Discussion:
Problems with redirect and format stdout and stderr for CreateProcess
(too old to reply)
l***@gmail.com
2007-11-14 13:13:28 UTC
Permalink
Hello,

I'm trying I'm trying to create a filter application that filters the
stdout and stderr for a process.
It should work like this:

C:>filterOutput.exe "echo hello"
<p><span class="stdout">hello</span></p>

In other words it should run the program "echo hello" and concatenate
strings in the beginning and end of stdout and stderr. This to
facilitate creation of a log.htm file when compiling an application
for example c:> filterOutput.exe "make project" > log.htm.


I'm using this approach:

Create two unnamed pipes with CreatePipe, assign them to STARTUPINFO
and use CreateProcess to create the process. Use a never ending loop
with PeekNamedPipe and ReadFile to extract the data from stdout and
stderr of the process. Format the data and use std::out to show it.

The problem:
When I run the program from Visual Studio 7.1 it works like a charm.
When I run the program from the command line nothing is outputted to
the console. I traced the error to the call PeekNamedPipe. This does
not fail but it always returns that there is no data in the pipe. How
is this possible? Is it because the HANDLE inheritance rule are wrong?
How can I make this work?


Thank you in advance
/Lucian

Here is the code:


// FilterOutput.cpp : Defines the entry point for the console
application.
//

#include "stdafx.h"
#include <io.h>
#include <string>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <iostream>
#include <tchar.h>
#include <afxwin.h>
#include <Windows.h>

#pragma hdrstop

#define bzero(a) memset(a,0,sizeof(a)) //easier -- shortcut

bool IsWinNT() //check if we're running NT
{
OSVERSIONINFO osv;
osv.dwOSVersionInfoSize = sizeof(osv);
GetVersionEx(&osv);
return (osv.dwPlatformId == VER_PLATFORM_WIN32_NT);
}

void ErrorMessage(char *str) //display detailed error info
{
LPVOID msg;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &msg,
0,
NULL
);
printf("%s: %s\n",str,msg);
LocalFree(msg);
}

std::string& trim2(std::string& str)
{
std::string::size_type pos = str.find_last_not_of('\n');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\n');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
pos = str.find_last_not_of('\r');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\r');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
return str;
}

HANDLE
newstdin,newstdout,read_stdout,newstderr,read_stderr,write_stdin; //
pipe handles

void close()
{
CloseHandle(newstdin);
CloseHandle(newstdout);
CloseHandle(read_stdout);
CloseHandle(newstderr);
CloseHandle(read_stderr);
CloseHandle(write_stdin);
}
//---------------------------------------------------------------------------
void main(int argc, char** argv)
{
char buf[1024]; //i/o buffer

STARTUPINFO si;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd; //security information for
pipes
PROCESS_INFORMATION pi;

if (IsWinNT()) //initialize security descriptor (Windows NT)
{
InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, true, NULL, false);
sa.lpSecurityDescriptor = &sd;
}
else sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = true; //allow inheritable handles

if (!CreatePipe(&newstdin,&write_stdin,&sa,0)) //create stdin pipe
{
ErrorMessage("CreatePipe");
//getch();
return;
}
if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) //create stdout pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
if (!CreatePipe(&read_stderr,&newstderr,&sa,0)) //create stderr pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}

GetStartupInfo(&si); //set startupinfo for the spawned process
/*
The dwFlags member tells CreateProcess how to make the process.
STARTF_USESTDHANDLES validates the hStd* members.
STARTF_USESHOWWINDOW
validates the wShowWindow member.
*/
si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = newstdout;
si.hStdError = newstderr; //set the new handles for the child
process
si.hStdInput = newstdin;
char* app_spawn = argv[1]; //sample, modify for your
//system

//spawn the child process
printf("hej1\n");
if (/*!
CreateProcess(NULL,app_spawn,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,
NULL,NULL,&si,&pi)*/
!CreateProcess( NULL, // No module name (use command line).
app_spawn , // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
TRUE, // Set handle inheritance to TRUE.
CREATE_NEW_CONSOLE,
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) )
{
ErrorMessage("CreateProcess");
close();
return;
}
printf("hej2\n");

unsigned long exit=0; //process exit code
unsigned long bread; //bytes read
unsigned long avail; //bytes available

bzero(buf);
for(;;) //main program loop
{
GetExitCodeProcess(pi.hProcess,&exit); //while the process is
running
if (exit != STILL_ACTIVE)
break;
if(!PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stdout");
}
//check to see if there is any data to read from stdout
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stdout,buf,1023,&bread,NULL); //read the stdout
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej3\n");
}
}
else {
ReadFile(read_stdout,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej4\n");
}
}/*else{
printf("not stdout read\n");
}*/
if(!PeekNamedPipe(read_stderr,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stderr");
}
//check to see if there is any data to read from stderr
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stderr,buf,1023,&bread,NULL); //read the stderr
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej5\n");
}
}
else {
ReadFile(read_stderr,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej6\n");
}
}/*else{
printf("not stderr read\n");
}*/
if (kbhit()) //check for user input.
{
bzero(buf);
*buf = (char)getche();
//printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send it to stdin
if (*buf == '\r') {
*buf = '\n';
printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send an extra newline
char,
//if necessary
}
}
}
close();
}
Joseph M. Newcomer
2007-11-15 02:23:58 UTC
Permalink
See below...
Post by l***@gmail.com
Hello,
I'm trying I'm trying to create a filter application that filters the
stdout and stderr for a process.
C:>filterOutput.exe "echo hello"
<p><span class="stdout">hello</span></p>
In other words it should run the program "echo hello" and concatenate
strings in the beginning and end of stdout and stderr. This to
facilitate creation of a log.htm file when compiling an application
for example c:> filterOutput.exe "make project" > log.htm.
Create two unnamed pipes with CreatePipe, assign them to STARTUPINFO
and use CreateProcess to create the process. Use a never ending loop
with PeekNamedPipe and ReadFile to extract the data from stdout and
stderr of the process. Format the data and use std::out to show it.
****
I get suspicous about never-ending loops and PeekNamedPipe. Why doesn't the loop end when
the pipe closes? Why do you need to peek?
****
Post by l***@gmail.com
When I run the program from Visual Studio 7.1 it works like a charm.
When I run the program from the command line nothing is outputted to
the console. I traced the error to the call PeekNamedPipe. This does
not fail but it always returns that there is no data in the pipe. How
is this possible? Is it because the HANDLE inheritance rule are wrong?
How can I make this work?
Thank you in advance
/Lucian
// FilterOutput.cpp : Defines the entry point for the console
application.
//
#include "stdafx.h"
#include <io.h>
#include <string>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <iostream>
#include <tchar.h>
#include <afxwin.h>
#include <Windows.h>
#pragma hdrstop
#define bzero(a) memset(a,0,sizeof(a)) //easier -- shortcut
bool IsWinNT() //check if we're running NT
{
OSVERSIONINFO osv;
osv.dwOSVersionInfoSize = sizeof(osv);
GetVersionEx(&osv);
return (osv.dwPlatformId == VER_PLATFORM_WIN32_NT);
}
void ErrorMessage(char *str) //display detailed error info
{
LPVOID msg;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &msg,
0,
NULL
);
printf("%s: %s\n",str,msg);
LocalFree(msg);
}
std::string& trim2(std::string& str)
{
std::string::size_type pos = str.find_last_not_of('\n');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\n');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
pos = str.find_last_not_of('\r');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\r');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
return str;
}
HANDLE
newstdin,newstdout,read_stdout,newstderr,read_stderr,write_stdin; //
pipe handles
****
THis should be written as
HANDLE newstdin;
HANDLE newstdout;
HANDLE read_stdout;
HANDLE newstderr,
HANDLE read_stderr;
HANDLE write_stdin;

the above compressed mess, that doesn't even have whitespace after the commas, is
unreadable
****
Post by l***@gmail.com
void close()
{
CloseHandle(newstdin);
CloseHandle(newstdout);
CloseHandle(read_stdout);
CloseHandle(newstderr);
CloseHandle(read_stderr);
CloseHandle(write_stdin);
}
//---------------------------------------------------------------------------
void main(int argc, char** argv)
{
char buf[1024]; //i/o buffer
STARTUPINFO si;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd; //security information for
pipes
PROCESS_INFORMATION pi;
if (IsWinNT()) //initialize security descriptor (Windows NT)
{
InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, true, NULL, false);
sa.lpSecurityDescriptor = &sd;
}
else sa.lpSecurityDescriptor = NULL;
****
Have you heard of indentation and vertical whitespace? Don't jam things together to make
them unreadable!

else
sa.lpSecurityDescriptor = NULL;
<blank link here>
*****
Post by l***@gmail.com
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = true; //allow inheritable handles
if (!CreatePipe(&newstdin,&write_stdin,&sa,0)) //create stdin pipe
{
ErrorMessage("CreatePipe");
//getch();
return;
}
if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) //create stdout pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
if (!CreatePipe(&read_stderr,&newstderr,&sa,0)) //create stderr pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
GetStartupInfo(&si); //set startupinfo for the spawned process
/*
The dwFlags member tells CreateProcess how to make the process.
STARTF_USESTDHANDLES validates the hStd* members.
***
No, it tells the CreateProcess call to USE the standard handles. Validation does not
actually happen.
****
Post by l***@gmail.com
STARTF_USESHOWWINDOW
validates the wShowWindow member.
****
No, it tells CreateProcess to USE the wShowWindow member; it doesn't validate anything!
****
Post by l***@gmail.com
*/
si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = newstdout;
si.hStdError = newstderr; //set the new handles for the child
process
si.hStdInput = newstdin;
char* app_spawn = argv[1]; //sample, modify for your
****
Why are you using obsolete data types lilke 'char'?
****
Post by l***@gmail.com
//system
//spawn the child process
printf("hej1\n");
if (/*!
CreateProcess(NULL,app_spawn,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,
NULL,NULL,&si,&pi)*/
****
Why is this useless piece of text even present? Delete it.
****
Post by l***@gmail.com
!CreateProcess( NULL, // No module name (use command line).
app_spawn , // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
TRUE, // Set handle inheritance to TRUE.
CREATE_NEW_CONSOLE,
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) )
{
ErrorMessage("CreateProcess");
close();
return;
}
printf("hej2\n");
unsigned long exit=0; //process exit code
unsigned long bread; //bytes read
unsigned long avail; //bytes available
****
I do not recall any of these being defined as 'unsigned long'. For example the bytes read
is DEFINITELY specified as DWORD. Never use hand-expansion of the proper types to
something you think they might be.
*****
Post by l***@gmail.com
bzero(buf);
****
What possible value is this? You're about to read data into the buffer, there is no
reason to zero it out.
****
Post by l***@gmail.com
for(;;) //main program loop
{
GetExitCodeProcess(pi.hProcess,&exit); //while the process is
running
if (exit != STILL_ACTIVE)
break;
****
This is so wrong. Just wait for the pipe to break. Anything that polls
GetExitCodeProcess for STILL_ACTIVE is suspect
****
Post by l***@gmail.com
if(!PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stdout");
}
****
Why not just do a ReadFile here? There's no reason to do a PeekNamedPipe.
*****
Post by l***@gmail.com
//check to see if there is any data to read from stdout
if (bread != 0)
{
bzero(buf);
****
You don't need to zero out a buffer you are about to fill.

Note that the ReadFile would work anyway. There's no reason for such complex code here
****
Post by l***@gmail.com
if (avail > 1023)
****
What is this value 1023? Have you never heard of #define? Lose every instance of 1023
and use a named constant in its place!
*****
Post by l***@gmail.com
{
while (bread >= 1023)
{
ReadFile(read_stdout,buf,1023,&bread,NULL); //read the stdout
pipe
std::string tmp = trim2(std::string(buf));
****
This doesn't even make sense. You are reading some long string of bytes, perhaps eight or
ten lines, and just randomly jamming HTML on each side of it. In fact, according to this
code, if you get
Volume in drive C has no label.
Volume Serial Number is 64D1-F51D

Directory of C:\Documents and Settings\email

11/11/2007 02:18 AM <DIR> .
11/11/2007 02:18 AM <DIR> ..
10/19/2005 11:13 PM 22 52.zip
12/24/2004 10:27 PM 212,808 a.prn
05/08/2007 09:02 AM 0 abc
05/08/2007 09:01 AM 0 abc#
05/08/2007 09:02 AM 0 abc%
05/08/2007 09:02 AM 0 abc&
06/19/2007 11:40 AM <DIR> Desktop
10/15/2007 12:19 PM <DIR> Favorites
11/02/2007 03:04 PM <DIR> My Documents
11/11/2007 02:20 AM 3,407,872 NTUSER.DAT
11/06/2003 11:47 AM 0 ping
09/30/2005 12:38 PM 19,662,407 ProView 52.zip
10/19/2005 11:13 PM 22 ProvView 52.zip
03/03/2003 07:14 PM <DIR> Start Menu

I would expect to get output of the form

<p><span class="stdout"> Volume in drive C has no label.\r\n Volume Serial
Number\r\n is 64D1-F51D\r\n\r\n Directory of C:\Documents and
Settings\email\r\n\r\n11/11/2007 02:18 AM <DIR</span></p><p><span
class="stdout">>\r\n\r\n\r\n .\r\n11/11/2007 02:18 AM <DIR> ..\r\n10/19/2005
11:13 PM 22 52.zip\r\n12/24/2004 10:27 PM 212,808
a.prn\r\n05/08/2007 09:02 AM 0 abc\r\n05/08/2007 09:01 AM 0
ab\r\n</span></p>

and so on. You could even get a sequence like

\r</span></p><p><span class="stdout">\n

since you have done nothing to parse lines or look for linefeeds.
****
Post by l***@gmail.com
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
****
What is this doing? Why do you need to zero out anything at all?
****
Post by l***@gmail.com
printf("hej3\n");
}
}
else {
ReadFile(read_stdout,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
*****
I'm not sure what trim2 is doing, but it looks like you have assumed that ReadFile gives
you a single line without the \r\n. It doesn't. It gives you a bunch of bytes, period.
*****
Post by l***@gmail.com
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej4\n");
}
}/*else{
printf("not stdout read\n");
}*/
****
This is all very strange. I'd rewrite this from scratch to use mutliple threads and/or
asynchronous I/O.
****
Post by l***@gmail.com
if(!PeekNamedPipe(read_stderr,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stderr");
}
//check to see if there is any data to read from stderr
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stderr,buf,1023,&bread,NULL); //read the stderr
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej5\n");
}
}
else {
ReadFile(read_stderr,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej6\n");
}
}/*else{
printf("not stderr read\n");
}*/
if (kbhit()) //check for user input.
{
bzero(buf);
*buf = (char)getche();
//printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send it to stdin
if (*buf == '\r') {
*buf = '\n';
printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send an extra newline
char,
//if necessary
}
}
}
close();
}
Joseph M. Newcomer [MVP]
email: ***@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
l***@gmail.com
2007-11-15 12:08:20 UTC
Permalink
I get suspicous about never-ending loops and PeekNamedPipe. Why doesn't the loop end when the pipe closes? Why do you need to peek?
*********
I need to peek because if you do a ReadFile it halts the for-loop
until something is available on the pipe.
*********
the above compressed mess, that doesn't even have whitespace after the commas, is
unreadable

*********
This is test of concept and not a finished version. I did not
implement this actually I just got it from here.
http://dn.codegear.com/article/10387
*********
Have you heard of indentation and vertical whitespace? Don't jam things together to make them unreadable!
*********
For me vertical white-space is a waist of space which leads to poor
readability. Again this is just a test of concept.
*********
Why not just do a ReadFile here? There's no reason to do a PeekNamedPipe.
*********
I need to peek because if you do a ReadFile it halts the for-loop
until something is available on the pipe. This must pool until some of
the two pipes has something.
*********
What is this value 1023? Have you never heard of #define? Lose every instance of 1023 and use a named constant in its place!
*********
Have you ever heard about C++ and enums/const ? What kind of
programmer uses #define in a C++? Again this is just a test of
concept, when I make it work I will improve the syntax.
*********
<p><span class="stdout"> Volume in drive C has no label.\r\n Volume Serial
Number\r\n is 64D1-F51D\r\n\r\n Directory of C:\Documents and
Settings\email\r\n\r\n11/11/2007 02:18 AM <DIR</span></p><p><span
class="stdout">>\r\n\r\n\r\n .\r\n11/11/2007 02:18 AM <DIR> ..
\r\n10/19/2005
11:13 PM 22 52.zip\r\n12/24/2004 10:27 PM
212,808
a.prn\r\n05/08/2007 09:02 AM 0 abc\r\n05/08/2007
09:01 AM 0
ab\r\n</span></p>
and so on. You could even get a sequence like
\r</span></p><p><span class="stdout">\n
*********
Html does not care about carriage return. You do not have to have a
<p>...</p> on each line!
*********
I'm not sure what trim2 is doing, but it looks like you have assumed that ReadFile gives you a single line without the \r\n. It doesn't. It gives you a bunch of bytes, period.
*********
This is just to make the output of a single line a bit more
"beautiful". I'm quite aware that an output from a program can contain
several lines.
*********
This is all very strange. I'd rewrite this from scratch to use mutliple threads and/or asynchronous I/O.
*********
How do you do that? Instead of complaining of the syntax of the code
you could have pointed me in right direction with an example. But if
you are so anal about the syntax without caring about functionality
then don't bother to write anything at all.

*********
Les
2007-11-15 12:45:42 UTC
Permalink
Post by l***@gmail.com
This is test of concept and not a finished version. I did not
implement this actually I just got it from here.
http://dn.codegear.com/article/10387
*********
It would have helped if you had stated this in your first post.
It is usually assumed that when someone asks an _unqualified_ question here
that the code is real but has problems, hence the comments about style and
readability.

(by "unqualified" I mean the post does not give any information whether the
poster is a newbie, whether the code is test only, proof of concept etc.)

Take a look at Joe's mvp tips website for info on threads and/or async I/O

Les
l***@gmail.com
2007-11-15 14:28:09 UTC
Permalink
Post by Les
Post by l***@gmail.com
This is test of concept and not a finished version. I did not
implement this actually I just got it from here.
http://dn.codegear.com/article/10387
*********
It would have helped if you had stated this in your first post.
It is usually assumed that when someone asks an _unqualified_ question here
that the code is real but has problems, hence the comments about style and
readability.
(by "unqualified" I mean the post does not give any information whether the
poster is a newbie, whether the code is test only, proof of concept etc.)
Take a look at Joe's mvp tips website for info on threads and/or async I/O
Les
Post by l***@gmail.com
This is test of concept and not a finished version. I did not
implement this actually I just got it from here.
http://dn.codegear.com/article/10387
*********
It would have helped if you had stated this in your first post.
It is usually assumed that when someone asks an _unqualified_ question here
that the code is real but has problems, hence the comments about style and
readability.
(by "unqualified" I mean the post does not give any information whether the
poster is a newbie, whether the code is test only, proof of concept etc.)
Take a look at Joe's mvp tips website for info on threads and/or async I/O
Les
Ok I admit I should have mentioned it but I thought that the question
Post by Les
When I run the program from Visual Studio 7.1 it works like a charm.
When I run the program from the command line nothing is outputted to
the console. I traced the error to the call PeekNamedPipe. This does
not fail but it always returns that there is no data in the pipe. How
is this possible? Is it because the HANDLE inheritance rule are wrong?
How can I make this work?
Joseph M. Newcomer
2007-11-16 02:24:09 UTC
Permalink
The problem is that there is no async mode for unnamed pipes, which is a real pain.

I have no idea why an unused security descriptor is being created, or even what possible
value a security descriptor could have in such a program.
joe
Post by Les
Post by l***@gmail.com
This is test of concept and not a finished version. I did not
implement this actually I just got it from here.
http://dn.codegear.com/article/10387
*********
It would have helped if you had stated this in your first post.
It is usually assumed that when someone asks an _unqualified_ question here
that the code is real but has problems, hence the comments about style and
readability.
(by "unqualified" I mean the post does not give any information whether the
poster is a newbie, whether the code is test only, proof of concept etc.)
Take a look at Joe's mvp tips website for info on threads and/or async I/O
Les
Joseph M. Newcomer [MVP]
email: ***@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
Joseph M. Newcomer
2007-11-16 02:22:53 UTC
Permalink
Post by l***@gmail.com
I get suspicous about never-ending loops and PeekNamedPipe. Why doesn't the loop end when the pipe closes? Why do you need to peek?
*********
I need to peek because if you do a ReadFile it halts the for-loop
until something is available on the pipe.
*****
I would suggest doing a WaitForMultipleObjects on the set of pipes. Otherwise, this sucks
down 100% of the CPU
****
Post by l***@gmail.com
*********
the above compressed mess, that doesn't even have whitespace after the commas, is
unreadable
*********
This is test of concept and not a finished version. I did not
implement this actually I just got it from here.
http://dn.codegear.com/article/10387
*********
Have you heard of indentation and vertical whitespace? Don't jam things together to make them unreadable!
*********
For me vertical white-space is a waist of space which leads to poor
readability. Again this is just a test of concept.
*****
Vertical white space is a visual delimiter to separate concepts. My worst
proof-of-concept code doesn't look this bad.
****
Post by l***@gmail.com
*********
Why not just do a ReadFile here? There's no reason to do a PeekNamedPipe.
*********
I need to peek because if you do a ReadFile it halts the for-loop
until something is available on the pipe. This must pool until some of
the two pipes has something.
*********
*****
WFMO on the handles of the pipes should work
****
Post by l***@gmail.com
What is this value 1023? Have you never heard of #define? Lose every instance of 1023 and use a named constant in its place!
*********
Have you ever heard about C++ and enums/const ? What kind of
programmer uses #define in a C++? Again this is just a test of
concept, when I make it work I will improve the syntax.
*********
****
Proof of concept is not an excuse for bad coding.

I figured that anyone who wrote 1023 would be challenged by static const int.
****
Post by l***@gmail.com
<p><span class="stdout"> Volume in drive C has no label.\r\n Volume Serial
Number\r\n is 64D1-F51D\r\n\r\n Directory of C:\Documents and
Settings\email\r\n\r\n11/11/2007 02:18 AM <DIR</span></p><p><span
class="stdout">>\r\n\r\n\r\n .\r\n11/11/2007 02:18 AM <DIR> ..
\r\n10/19/2005
11:13 PM 22 52.zip\r\n12/24/2004 10:27 PM
212,808
a.prn\r\n05/08/2007 09:02 AM 0 abc\r\n05/08/2007
09:01 AM 0
ab\r\n</span></p>
and so on. You could even get a sequence like
\r</span></p><p><span class="stdout">\n
*********
Html does not care about carriage return. You do not have to have a
<p>...</p> on each line!
*********
I'm not sure what trim2 is doing, but it looks like you have assumed that ReadFile gives you a single line without the \r\n. It doesn't. It gives you a bunch of bytes, period.
*********
This is just to make the output of a single line a bit more
"beautiful". I'm quite aware that an output from a program can contain
several lines.
*********
This is all very strange. I'd rewrite this from scratch to use mutliple threads and/or asynchronous I/O.
*********
How do you do that? Instead of complaining of the syntax of the code
you could have pointed me in right direction with an example. But if
you are so anal about the syntax without caring about functionality
then don't bother to write anything at all.
*********
****
I'm working on a better idea.
*****
Joseph M. Newcomer [MVP]
email: ***@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
Joseph M. Newcomer
2007-11-19 15:49:50 UTC
Permalink
See my new essay:

http://www.flounder.com/console_threads.htm

My only concession to sloppiness in such a typical example of "proof of concept" is that I
put all the messages as literal strings instead of moving them to the STRINGTABLE
resource. Proof-of-concept does not justify poor programming style. The rest of this
pretty much follows my standard programming guidelines, and is what I would do if I were
writing a little test app for myself.

Key here is that this app does not poll and does not consume additional CPU time if there
is nothing to do.

Sorry it took so long to get this finished, but I had a very full schedule this past week,
a combination of teaching and external commitments, such as herding people around a
dinosaur exhibit. http://www.carnegiemnh.org/, and it took about a day of writing to
document the details of the code.

joe
Post by l***@gmail.com
Hello,
I'm trying I'm trying to create a filter application that filters the
stdout and stderr for a process.
C:>filterOutput.exe "echo hello"
<p><span class="stdout">hello</span></p>
In other words it should run the program "echo hello" and concatenate
strings in the beginning and end of stdout and stderr. This to
facilitate creation of a log.htm file when compiling an application
for example c:> filterOutput.exe "make project" > log.htm.
Create two unnamed pipes with CreatePipe, assign them to STARTUPINFO
and use CreateProcess to create the process. Use a never ending loop
with PeekNamedPipe and ReadFile to extract the data from stdout and
stderr of the process. Format the data and use std::out to show it.
When I run the program from Visual Studio 7.1 it works like a charm.
When I run the program from the command line nothing is outputted to
the console. I traced the error to the call PeekNamedPipe. This does
not fail but it always returns that there is no data in the pipe. How
is this possible? Is it because the HANDLE inheritance rule are wrong?
How can I make this work?
Thank you in advance
/Lucian
// FilterOutput.cpp : Defines the entry point for the console
application.
//
#include "stdafx.h"
#include <io.h>
#include <string>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <iostream>
#include <tchar.h>
#include <afxwin.h>
#include <Windows.h>
#pragma hdrstop
#define bzero(a) memset(a,0,sizeof(a)) //easier -- shortcut
bool IsWinNT() //check if we're running NT
{
OSVERSIONINFO osv;
osv.dwOSVersionInfoSize = sizeof(osv);
GetVersionEx(&osv);
return (osv.dwPlatformId == VER_PLATFORM_WIN32_NT);
}
void ErrorMessage(char *str) //display detailed error info
{
LPVOID msg;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &msg,
0,
NULL
);
printf("%s: %s\n",str,msg);
LocalFree(msg);
}
std::string& trim2(std::string& str)
{
std::string::size_type pos = str.find_last_not_of('\n');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\n');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
pos = str.find_last_not_of('\r');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\r');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
return str;
}
HANDLE
newstdin,newstdout,read_stdout,newstderr,read_stderr,write_stdin; //
pipe handles
void close()
{
CloseHandle(newstdin);
CloseHandle(newstdout);
CloseHandle(read_stdout);
CloseHandle(newstderr);
CloseHandle(read_stderr);
CloseHandle(write_stdin);
}
//---------------------------------------------------------------------------
void main(int argc, char** argv)
{
char buf[1024]; //i/o buffer
STARTUPINFO si;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd; //security information for
pipes
PROCESS_INFORMATION pi;
if (IsWinNT()) //initialize security descriptor (Windows NT)
{
InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, true, NULL, false);
sa.lpSecurityDescriptor = &sd;
}
else sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = true; //allow inheritable handles
if (!CreatePipe(&newstdin,&write_stdin,&sa,0)) //create stdin pipe
{
ErrorMessage("CreatePipe");
//getch();
return;
}
if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) //create stdout pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
if (!CreatePipe(&read_stderr,&newstderr,&sa,0)) //create stderr pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
GetStartupInfo(&si); //set startupinfo for the spawned process
/*
The dwFlags member tells CreateProcess how to make the process.
STARTF_USESTDHANDLES validates the hStd* members.
STARTF_USESHOWWINDOW
validates the wShowWindow member.
*/
si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = newstdout;
si.hStdError = newstderr; //set the new handles for the child
process
si.hStdInput = newstdin;
char* app_spawn = argv[1]; //sample, modify for your
//system
//spawn the child process
printf("hej1\n");
if (/*!
CreateProcess(NULL,app_spawn,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,
NULL,NULL,&si,&pi)*/
!CreateProcess( NULL, // No module name (use command line).
app_spawn , // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
TRUE, // Set handle inheritance to TRUE.
CREATE_NEW_CONSOLE,
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) )
{
ErrorMessage("CreateProcess");
close();
return;
}
printf("hej2\n");
unsigned long exit=0; //process exit code
unsigned long bread; //bytes read
unsigned long avail; //bytes available
bzero(buf);
for(;;) //main program loop
{
GetExitCodeProcess(pi.hProcess,&exit); //while the process is
running
if (exit != STILL_ACTIVE)
break;
if(!PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stdout");
}
//check to see if there is any data to read from stdout
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stdout,buf,1023,&bread,NULL); //read the stdout
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej3\n");
}
}
else {
ReadFile(read_stdout,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej4\n");
}
}/*else{
printf("not stdout read\n");
}*/
if(!PeekNamedPipe(read_stderr,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stderr");
}
//check to see if there is any data to read from stderr
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stderr,buf,1023,&bread,NULL); //read the stderr
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej5\n");
}
}
else {
ReadFile(read_stderr,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej6\n");
}
}/*else{
printf("not stderr read\n");
}*/
if (kbhit()) //check for user input.
{
bzero(buf);
*buf = (char)getche();
//printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send it to stdin
if (*buf == '\r') {
*buf = '\n';
printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send an extra newline
char,
//if necessary
}
}
}
close();
}
Joseph M. Newcomer [MVP]
email: ***@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
l***@gmail.com
2007-11-26 16:26:12 UTC
Permalink
Thank you for the response.
I downloaded the project and tried it, but there is a problem:
if you run a process that is running for a long time and outputs
continuously to std::err std::out then the program waits until the
process finish.

But I would like to read all the time from std::err and std::out
because I was thinking of using this for compiling applications and
redirect the output to a log, and at the same time see the progress of
the process in a console window.

In the while-loop that waits for completion shouldn't be ok to use a
PeekNamedPipe to see if there is data to read from std::out?
Post by Joseph M. Newcomer
http://www.flounder.com/console_threads.htm
My only concession to sloppiness in such a typical example of "proof of concept" is that I
put all the messages as literal strings instead of moving them to the STRINGTABLE
resource. Proof-of-concept does not justify poor programming style. The rest of this
pretty much follows my standard programming guidelines, and is what I would do if I were
writing a little test app for myself.
Key here is that this app does not poll and does not consume additional CPU time if there
is nothing to do.
Sorry it took so long to get this finished, but I had a very full schedule this past week,
a combination of teaching and external commitments, such as herding people around a
dinosaur exhibit.http://www.carnegiemnh.org/, and it took about a day of writing to
document the details of the code.
joe
Post by l***@gmail.com
Hello,
I'm trying I'm trying to create a filter application that filters the
stdout and stderr for a process.
C:>filterOutput.exe "echo hello"
<p><span class="stdout">hello</span></p>
In other words it should run the program "echo hello" and concatenate
strings in the beginning and end of stdout and stderr. This to
facilitate creation of a log.htm file when compiling an application
for example c:> filterOutput.exe "make project" > log.htm.
Create two unnamed pipes with CreatePipe, assign them to STARTUPINFO
and use CreateProcess to create the process. Use a never ending loop
with PeekNamedPipe and ReadFile to extract the data from stdout and
stderr of the process. Format the data and use std::out to show it.
When I run the program from Visual Studio 7.1 it works like a charm.
When I run the program from the command line nothing is outputted to
the console. I traced the error to the call PeekNamedPipe. This does
not fail but it always returns that there is no data in the pipe. How
is this possible? Is it because the HANDLE inheritance rule are wrong?
How can I make this work?
Thank you in advance
/Lucian
// FilterOutput.cpp : Defines the entry point for the console
application.
//
#include "stdafx.h"
#include <io.h>
#include <string>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <iostream>
#include <tchar.h>
#include <afxwin.h>
#include <Windows.h>
#pragma hdrstop
#define bzero(a) memset(a,0,sizeof(a)) //easier -- shortcut
bool IsWinNT() //check if we're running NT
{
OSVERSIONINFO osv;
osv.dwOSVersionInfoSize = sizeof(osv);
GetVersionEx(&osv);
return (osv.dwPlatformId == VER_PLATFORM_WIN32_NT);
}
void ErrorMessage(char *str) //display detailed error info
{
LPVOID msg;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &msg,
0,
NULL
);
printf("%s: %s\n",str,msg);
LocalFree(msg);
}
std::string& trim2(std::string& str)
{
std::string::size_type pos = str.find_last_not_of('\n');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\n');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
pos = str.find_last_not_of('\r');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\r');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
return str;
}
HANDLE
newstdin,newstdout,read_stdout,newstderr,read_stderr,write_stdin; //
pipe handles
void close()
{
CloseHandle(newstdin);
CloseHandle(newstdout);
CloseHandle(read_stdout);
CloseHandle(newstderr);
CloseHandle(read_stderr);
CloseHandle(write_stdin);
}
//---------------------------------------------------------------------------
void main(int argc, char** argv)
{
char buf[1024]; //i/o buffer
STARTUPINFO si;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd; //security information for
pipes
PROCESS_INFORMATION pi;
if (IsWinNT()) //initialize security descriptor (Windows NT)
{
InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, true, NULL, false);
sa.lpSecurityDescriptor = &sd;
}
else sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = true; //allow inheritable handles
if (!CreatePipe(&newstdin,&write_stdin,&sa,0)) //create stdin pipe
{
ErrorMessage("CreatePipe");
//getch();
return;
}
if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) //create stdout pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
if (!CreatePipe(&read_stderr,&newstderr,&sa,0)) //create stderr pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
GetStartupInfo(&si); //set startupinfo for the spawned process
/*
The dwFlags member tells CreateProcess how to make the process.
STARTF_USESTDHANDLES validates the hStd* members.
STARTF_USESHOWWINDOW
validates the wShowWindow member.
*/
si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = newstdout;
si.hStdError = newstderr; //set the new handles for the child
process
si.hStdInput = newstdin;
char* app_spawn = argv[1]; //sample, modify for your
//system
//spawn the child process
printf("hej1\n");
if (/*!
CreateProcess(NULL,app_spawn,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,
NULL,NULL,&si,&pi)*/
!CreateProcess( NULL, // No module name (use command line).
app_spawn , // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
TRUE, // Set handle inheritance to TRUE.
CREATE_NEW_CONSOLE,
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) )
{
ErrorMessage("CreateProcess");
close();
return;
}
printf("hej2\n");
unsigned long exit=0; //process exit code
unsigned long bread; //bytes read
unsigned long avail; //bytes available
bzero(buf);
for(;;) //main program loop
{
GetExitCodeProcess(pi.hProcess,&exit); //while the process is
running
if (exit != STILL_ACTIVE)
break;
if(!PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stdout");
}
//check to see if there is any data to read from stdout
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stdout,buf,1023,&bread,NULL); //read the stdout
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej3\n");
}
}
else {
ReadFile(read_stdout,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej4\n");
}
}/*else{
printf("not stdout read\n");
}*/
if(!PeekNamedPipe(read_stderr,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stderr");
}
//check to see if there is any data to read from stderr
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stderr,buf,1023,&bread,NULL); //read the stderr
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej5\n");
}
}
else {
ReadFile(read_stderr,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej6\n");
}
}/*else{
printf("not stderr read\n");
}*/
if (kbhit()) //check for user input.
{
bzero(buf);
*buf = (char)getche();
//printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send it to stdin
if (*buf == '\r') {
*buf = '\n';
printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send an extra newline
char,
//if necessary
}
}
}
close();
}
Joseph M. Newcomer [MVP]
Web:http://www.flounder.com
MVP Tips:http://www.flounder.com/mvp_tips.htm
Joseph M. Newcomer
2007-11-27 03:11:45 UTC
Permalink
Note that I set the buffer sizes rather small, so you get the buffers more readily. The
problem is that as soon as you introduce PeekNamedPipe, you end up consuming 100% of the
CPU.

The parent program *should* wait until the child process finishes; if it quit sooner, it
would lose the output from the child process.

The program reads continuously from stderr and stdout (not std::err and std::out), and it
represents what I use when I have to redirect output from a child compiler to a logging
window (in fact, the process class on my MVP Tips site, which might be the one from
codeproject, which has many of my articles) was created to log the output from a child
compiler to an error window much like VS.NET does.

There's no need to peek, because if there is data, the ReadFile will get it anyway.
joe
Post by l***@gmail.com
Thank you for the response.
if you run a process that is running for a long time and outputs
continuously to std::err std::out then the program waits until the
process finish.
But I would like to read all the time from std::err and std::out
because I was thinking of using this for compiling applications and
redirect the output to a log, and at the same time see the progress of
the process in a console window.
In the while-loop that waits for completion shouldn't be ok to use a
PeekNamedPipe to see if there is data to read from std::out?
Post by Joseph M. Newcomer
http://www.flounder.com/console_threads.htm
My only concession to sloppiness in such a typical example of "proof of concept" is that I
put all the messages as literal strings instead of moving them to the STRINGTABLE
resource. Proof-of-concept does not justify poor programming style. The rest of this
pretty much follows my standard programming guidelines, and is what I would do if I were
writing a little test app for myself.
Key here is that this app does not poll and does not consume additional CPU time if there
is nothing to do.
Sorry it took so long to get this finished, but I had a very full schedule this past week,
a combination of teaching and external commitments, such as herding people around a
dinosaur exhibit.http://www.carnegiemnh.org/, and it took about a day of writing to
document the details of the code.
joe
Post by l***@gmail.com
Hello,
I'm trying I'm trying to create a filter application that filters the
stdout and stderr for a process.
C:>filterOutput.exe "echo hello"
<p><span class="stdout">hello</span></p>
In other words it should run the program "echo hello" and concatenate
strings in the beginning and end of stdout and stderr. This to
facilitate creation of a log.htm file when compiling an application
for example c:> filterOutput.exe "make project" > log.htm.
Create two unnamed pipes with CreatePipe, assign them to STARTUPINFO
and use CreateProcess to create the process. Use a never ending loop
with PeekNamedPipe and ReadFile to extract the data from stdout and
stderr of the process. Format the data and use std::out to show it.
When I run the program from Visual Studio 7.1 it works like a charm.
When I run the program from the command line nothing is outputted to
the console. I traced the error to the call PeekNamedPipe. This does
not fail but it always returns that there is no data in the pipe. How
is this possible? Is it because the HANDLE inheritance rule are wrong?
How can I make this work?
Thank you in advance
/Lucian
// FilterOutput.cpp : Defines the entry point for the console
application.
//
#include "stdafx.h"
#include <io.h>
#include <string>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <iostream>
#include <tchar.h>
#include <afxwin.h>
#include <Windows.h>
#pragma hdrstop
#define bzero(a) memset(a,0,sizeof(a)) //easier -- shortcut
bool IsWinNT() //check if we're running NT
{
OSVERSIONINFO osv;
osv.dwOSVersionInfoSize = sizeof(osv);
GetVersionEx(&osv);
return (osv.dwPlatformId == VER_PLATFORM_WIN32_NT);
}
void ErrorMessage(char *str) //display detailed error info
{
LPVOID msg;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &msg,
0,
NULL
);
printf("%s: %s\n",str,msg);
LocalFree(msg);
}
std::string& trim2(std::string& str)
{
std::string::size_type pos = str.find_last_not_of('\n');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\n');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
pos = str.find_last_not_of('\r');
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of('\r');
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
return str;
}
HANDLE
newstdin,newstdout,read_stdout,newstderr,read_stderr,write_stdin; //
pipe handles
void close()
{
CloseHandle(newstdin);
CloseHandle(newstdout);
CloseHandle(read_stdout);
CloseHandle(newstderr);
CloseHandle(read_stderr);
CloseHandle(write_stdin);
}
//---------------------------------------------------------------------------
void main(int argc, char** argv)
{
char buf[1024]; //i/o buffer
STARTUPINFO si;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd; //security information for
pipes
PROCESS_INFORMATION pi;
if (IsWinNT()) //initialize security descriptor (Windows NT)
{
InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, true, NULL, false);
sa.lpSecurityDescriptor = &sd;
}
else sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = true; //allow inheritable handles
if (!CreatePipe(&newstdin,&write_stdin,&sa,0)) //create stdin pipe
{
ErrorMessage("CreatePipe");
//getch();
return;
}
if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) //create stdout pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
if (!CreatePipe(&read_stderr,&newstderr,&sa,0)) //create stderr pipe
{
ErrorMessage("CreatePipe");
//getch();
close();
return;
}
GetStartupInfo(&si); //set startupinfo for the spawned process
/*
The dwFlags member tells CreateProcess how to make the process.
STARTF_USESTDHANDLES validates the hStd* members.
STARTF_USESHOWWINDOW
validates the wShowWindow member.
*/
si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = newstdout;
si.hStdError = newstderr; //set the new handles for the child
process
si.hStdInput = newstdin;
char* app_spawn = argv[1]; //sample, modify for your
//system
//spawn the child process
printf("hej1\n");
if (/*!
CreateProcess(NULL,app_spawn,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,
NULL,NULL,&si,&pi)*/
!CreateProcess( NULL, // No module name (use command line).
app_spawn , // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
TRUE, // Set handle inheritance to TRUE.
CREATE_NEW_CONSOLE,
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) )
{
ErrorMessage("CreateProcess");
close();
return;
}
printf("hej2\n");
unsigned long exit=0; //process exit code
unsigned long bread; //bytes read
unsigned long avail; //bytes available
bzero(buf);
for(;;) //main program loop
{
GetExitCodeProcess(pi.hProcess,&exit); //while the process is
running
if (exit != STILL_ACTIVE)
break;
if(!PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stdout");
}
//check to see if there is any data to read from stdout
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stdout,buf,1023,&bread,NULL); //read the stdout
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej3\n");
}
}
else {
ReadFile(read_stdout,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej4\n");
}
}/*else{
printf("not stdout read\n");
}*/
if(!PeekNamedPipe(read_stderr,buf,1023,&bread,&avail,NULL))
{
ErrorMessage("PeekNamedPipe stderr");
}
//check to see if there is any data to read from stderr
if (bread != 0)
{
bzero(buf);
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stderr,buf,1023,&bread,NULL); //read the stderr
pipe
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
bzero(buf);
printf("hej5\n");
}
}
else {
ReadFile(read_stderr,buf,1023,&bread,NULL);
std::string tmp = trim2(std::string(buf));
std::cout << tmp << std::endl;
std::cerr << "<p><span class=\"stdout\">" << tmp << "</span></p>"
<< std::endl;
printf("hej6\n");
}
}/*else{
printf("not stderr read\n");
}*/
if (kbhit()) //check for user input.
{
bzero(buf);
*buf = (char)getche();
//printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send it to stdin
if (*buf == '\r') {
*buf = '\n';
printf("%c",*buf);
WriteFile(write_stdin,buf,1,&bread,NULL); //send an extra newline
char,
//if necessary
}
}
}
close();
}
Joseph M. Newcomer [MVP]
Web:http://www.flounder.com
MVP Tips:http://www.flounder.com/mvp_tips.htm
Joseph M. Newcomer [MVP]
email: ***@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm

Loading...