Dear all:
I ran into an odd behavior, and I wonder whether anyone has encountered it before and possibly knows a fix/workaround/correction. Rather than post lots of code right away, I will describe it first.
The program is a .C source with a regular int main(). If I use spawnv directly with the no wait flag, the path/command and the arguments (not interested in the env part), it works nicely. However, if I try to put the same logic into a subroutine, it works sometimes. My goal is to feel the PGP.EXE command line which has switches, e.g., '-h' for help. Some output goes to stdout, some to stderr. I use something close to Microsoft's help example on _pipe to capture both outputs. I already moved the program to the same directory to avoid path and directory name issues. Submitting PGP.EXE works, and I get the output to use '-h' for help. Submitting PGP.EXE with parameter 'abc' works, and I get the error that it's not a valid file. However, PGP.EXE with parameter '-h' hangs up and does not come back. Again, if I have that logic in the main() function directly, it works in all cases.
I do not assume that everyone has PGP.EXE, so my more general question is: Are some characters like '-' restricted, and why does it make a difference whether I use argv directly or the passed address from within the form
In case it matters, I reroute stdout with one pipe and stderr with another before the spawn, then release it after. I view the captured output in the debugger with a breakpoint on the final return (0) statement after reading the buffers into strings. When I try to run 'PGP.EXE -h', the program hangs on the spawnv command, so it is not whatever I coded afterwards.
I will try to streamline the logic to narrow it down to the essentials before posting the code, and I will try to make it as identical as possible (main vs. subroutine) to narrow down sources of errors. But after six straight hours of trying it on my own, I resort to this forum for good advice. Someone who has dealt with this problem may know right away where to look.
My compiler version is C++ .net 2002, but the code is ANSI C as a console application without any extras (no ATL, no MFC).
Thanks in advance,
Wolfgang

Issue with spawnv (tried spawnl, spawnlp and spawnvp, too)
Ken Tucker MVP
Dear Brian:
Thanks for the reply and the test. I only used 'cmd' or 'cmd.exe', so it probably did not find it using spawnv. The choice of spawnv or spawnvp was just the latest snapshot of a couple of tests, should have made it the same. In my application, I will know the exact path. I wonder though whether I can detect that the program exists and not submit it blindly where it may hang up...
PGP is a licensed program I can use at/for work. Use www.pgp.com to find out more, but it is not shareware and it is not exactly cheap for end users either. It turns out though that the key to my above problem was the buffer with P_WAIT. The help put out more than 512 bytes and less than 4096, so one worked and the other one did not. The best explanation I have - since I am new to pipes - is that the parent waits for the child and the child cannot write to the buffer, so I deadlock. I used the P_NOWAIT example coding in the help on '_pipe' in VC++ and it appears to work. I ran into issues, too, where a string/argv and argv/string conversion forces me to put in double backslashes so that C interprets it right as I want to pass the parameters to the routine as a string, not pointer array. Little tedious stuff that took hours out of my life but is hardly worth mentioning.
I really only use the C part of this compiler, and I may want to port the app to UNIX or some other platform later. Of course, I will run into portability issues there, too.
Help on help: Is there a way to mark this posting as complete or solved :-)
Regards,
Wolfgang
Sianny
Wolfgang,
I've had my share of struggles with this kind of code also.
I don't have pgp.exe, but running either version of main() runs correctly with cmd.exe. I needed to provided a fully-qualified path to cmd.exe to the spawnv version, however.
Here's the correct output for the first version of main(), which surrounds the received stdout and stderr with exclamation points.
E:\tempprojects\wolfgang\debug>wolfgang C:\WINDOWS\system32\cmd.exe /c dir
Out: ! Volume in drive E is Disk2
Volume Serial Number is 54CA-9EDD
Directory of E:\tempprojects\wolfgang\debug
03/15/2006 04:22 PM <DIR> .
03/15/2006 04:22 PM <DIR> ..
03/15/2006 04:30 PM 49,152 wolfgang.exe
03/15/2006 04:30 PM 434,240 wolfgang.ilk
03/15/2006 04:30 PM 551,936 wolfgang.pdb
3 File(s) 1,035,328 bytes
2 Dir(s) 40,581,881,856 bytes free!
Err: !!
Does it hang on cmd.exe for you, or just on pgp.exe Could you point me to pgp.exe
I don't believe there is any special processing of hyphen characters.
To address your concern about the size of stdout and stderr text, I would make no assumption about the size of the output. I would enter a loop to read 4096 (whatever) characters at a time, null-terminating each chunk, and appending the result to a std::string object. Looking back at your code, I can see that you've attempted something like this, but your out_res is a fixed buffer. The advantage of using std::string is that it will automatically grow when you use the append() function.
Finally, I'm unclear on why you use spawnv in one version and spawnvp in another. Spawnvp is usually a better choice since it uses the PATH environment variable to locate the executable.
Brian
duck123
Hi Wolfgang,
I guess you are doing something related with security (encryption), so lets see.
What happens if you call any other command line program with a switch like yours (-h) and in the same conditions (using spanwv with no wait, from within a subroutine and with the stdoutput redirection)
This is just to see if this occurs in a generic fashion.
If the program doesn't hang, the problem could be a conflict with your PGP.EXE module.
If hangs again, we can focus on spawn functions and stdouput redirection to see if there is any weird issue.
=)
Mircea Cimpoi
In my version, I use P_NOWAIT, then pipe in any stuff into the process' stdin, then do a _cwait, then read the stderr/stdout.
I agree with you... this process/pipe stuff is tedious and requires guesswork to really get it right. I've gotten deadlock problems also.
I've written a couple classes ProcessBase and PipeClass (Pipe was taken), that hopefully soon will work against Cygwin and Linux--I've had to put in some temporary hacks because ironically the Windows version (as I've written it) runs more reliably than the *nix one. ProcessBase is meant to be a base class for a class that wants to wrap an app (trying to take old crickety programs written in Fortran and pretend that it can play nice in a .NET ecosystem). It also has an option to send and receive text interactively. PipeClass encapsulates all the busywork for pipes. I am not really sure if it's ready to give out, but if you're desperate you can email me and we can talk it over.
Moderators can mark this thread as solved. I've been recently anointed as such, so I'll go ahead and do that.
Brian
alinx
Dear Harold:
Thanks for the fast reply. I can pass the string '-h' to a 'hello world' program I wrote, but I don't parse the command line for flags, and I don't write to stderr (pgp -h writes to stderr only), so there may be more to recreating the error with another program. At this point I am not sure whether it's the spawnv, the pipes or the combination thereof. It just baffles me that it works from main() and not from say sub(), which indicates that the program I submit does not play much of a role. I thought I could say something like:
int main(int argc, char *argv[]) {
sub( argv );
}
void sub( char **argv ) {
spawnv( P_WAIT, argv[1], &argv[1] );
}
which would be equivalent to:
int main(int argc, char *argv[]) {
spawnv( P_WAIT, argv[1], &argv[1] );
}
(Note: I just typed this without syntax check, so I may be a little off.)
I also noticed that 'cmd /c dir' or 'cmd /c dir *.*' does not return anything, but at least it does not hang up. I am trying to submit more or less any program with parameters from a routine other than main() - pgp.exe (the command line version) is just one example.
Here is the code. It runs as is, but if you rename main to xmain and vv., it hangs up. The command parameters are 'pgp.exe -h'.
#include
<windows.h>#include
<process.h>#include
<fcntl.h>#include
<stdio.h>#include
<io.h>#define
OUT_BUFF_SIZE 1024#define
ERR_BUFF_SIZE 1024#define
READ_HANDLE 0#define
WRITE_HANDLE 1int
spawn_command( char **, char *, int, char *, int );int
xmain(int argc, char* argv[]){
intret_code;
charout_result[32*OUT_BUFF_SIZE+1],
err_result[16*ERR_BUFF_SIZE+1];
if( argc >= 2 ){
ret_code = spawn_command( argv, out_result, 32, err_result, 16 );
printf( "Out: !%s!\n", out_result );
printf( "Err: !%s!\n", err_result );
return ret_code;}
else return -1;}
int
spawn_command( char* argv[], char *out_res, int out_size, char *err_res, int err_size ){
inti,
hStdOut,
hStdOutPipe[2],
hStdErr,
hStdErrPipe[2],
nRead;
char*ptr,
szBufferOut[OUT_BUFF_SIZE+1],
szBufferErr[ERR_BUFF_SIZE+1];
HANDLE
hProcess;
// Initialize*out_res = 0;
*err_res = 0;
// Create the pipes if( _pipe( hStdOutPipe, 512, O_BINARY | O_NOINHERIT ) == -1 ) return 1; if( _pipe( hStdErrPipe, 512, O_BINARY | O_NOINHERIT ) == -1 ) return 2; // Store handles of original stdout and stderrhStdOut = _dup( _fileno( stdout ) );
hStdErr = _dup( _fileno( stderr ) );
// Duplicate write end of pipe to stdout handle if( _dup2( hStdOutPipe[WRITE_HANDLE], _fileno( stdout ) ) != 0 ) return 3; if( _dup2( hStdErrPipe[WRITE_HANDLE], _fileno( stderr ) ) != 0 ) return 4; // Close write end of local pipesclose( hStdOutPipe[WRITE_HANDLE] );
close( hStdErrPipe[WRITE_HANDLE] );
// Spawn processhProcess = (HANDLE) spawnv( P_WAIT, argv[1], (
const char* const*) &argv[1] ); // Restore original handles if( _dup2( hStdOut, _fileno( stdout ) ) != 0 ) return 5; if( _dup2( hStdErr, _fileno( stderr ) ) != 0 ) return 6; // Close local copies of original stdout and stderrclose( hStdOut );
close( hStdErr );
// Copy stdout buffer to string for( i = 0, ptr = out_res; i < out_size; i++ ){
nRead = read( hStdOutPipe[READ_HANDLE], szBufferOut, OUT_BUFF_SIZE );
if( nRead ){
memcpy( ptr, szBufferOut, nRead );
ptr[nRead] = 0;
ptr += nRead;
}
else break;}
// Remove CR/LFs from the end--ptr;
while( ( ptr >= out_res ) && ( *ptr == '\r' || *ptr == '\n' ) )*ptr-- = 0;
// Copy stderr buffer to string for( i = 0, ptr = err_res; i < err_size; i++ ){
nRead = read( hStdErrPipe[READ_HANDLE], szBufferErr, ERR_BUFF_SIZE );
if( nRead ){
memcpy( ptr, szBufferErr, nRead );
ptr[nRead] = 0;
ptr += nRead;
}
else break;}
// Remove CR/LFs from the end--ptr;
while( ( ptr >= err_res ) && ( *ptr == '\r' || *ptr == '\n' ) )*ptr-- = 0;
// Done return 0;}
int
main(int argc, char* argv[]){
intnExitCode = STILL_ACTIVE;
if( argc >= 2 ){
HANDLE
hProcess;
inthStdOut,
hStdOutPipe[2],
hStdErr,
hStdErrPipe[2],
nOutRead,
nErrRead;
charszBufferOut[OUT_BUFF_SIZE+1],
szBufferErr[ERR_BUFF_SIZE+1];
// Create the pipe if( _pipe( hStdOutPipe, 4096, O_BINARY | O_NOINHERIT ) == -1 ) return 1; if( _pipe( hStdErrPipe, 4096, O_BINARY | O_NOINHERIT ) == -1 ) return 1; // Initialize*szBufferOut = 0;
*szBufferErr = 0;
// Duplicate stdout handle (next line will close original)hStdOut = _dup( _fileno( stdout ) );
hStdErr = _dup( _fileno( stderr ) );
// Duplicate write end of pipe to stdout handle if( _dup2( hStdOutPipe[WRITE_HANDLE], _fileno( stdout ) ) != 0 ) return 2; if( _dup2( hStdErrPipe[WRITE_HANDLE], _fileno( stderr ) ) != 0 ) return 2; // Close original write end of pipeclose( hStdOutPipe[WRITE_HANDLE] );
close( hStdErrPipe[WRITE_HANDLE] );
// Spawn processhProcess = (HANDLE) spawnvp( P_WAIT, argv[1], (
const char* const*) &argv[1] ); // Duplicate copy of original stdout back into stdout if( _dup2( hStdOut, _fileno( stdout ) ) != 0 ) return 3; if( _dup2( hStdErr, _fileno( stderr ) ) != 0 ) return 3; // Close duplicate copy of original stdoutclose( hStdOut );
close( hStdErr );
// Read pipesnErrRead = read( hStdErrPipe[READ_HANDLE], szBufferErr, ERR_BUFF_SIZE );
nOutRead = read( hStdOutPipe[READ_HANDLE], szBufferOut, OUT_BUFF_SIZE );
}
return 0;}
Thanks,
Wolfgang
mausau