我编写了一个函数,试图通过管道读取子进程的命令行输出。这应该是the MSDN Creating a Child Process with Redirected Input and Output article的一个简单子集,但我显然是在犯某种错误。
下面的ReadFile(...)调用永远阻塞,无论我将它放在WaitForSingleObject(...)调用之前或之后,它应该指示子进程的结束。
我已经阅读了所有建议“使用异步ReadFile”的答案,如果有人可以让我知道如何在管道上完成,我对这个建议持开放态度。虽然我不明白为什么在这种情况下需要异步I / O.
#include "stdafx.h"
#include <string>
#include <windows.h>
unsigned int launch( const std::string & cmdline );
int _tmain(int argc, _TCHAR* argv[])
{
launch( std::string("C:/windows/system32/help.exe") );
return 0;
}
void print_error( unsigned int err )
{
char* msg = NULL;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&msg,
0, NULL );
std::cout << "------ Begin Error Msg ------" << std::endl;
std::cout << msg << std::endl;
std::cout << "------ End Error Msg ------" << std::endl;
LocalFree( msg );
}
unsigned int launch( const std::string & cmdline )
{
TCHAR cl[_MAX_PATH*sizeof(TCHAR)];
memset( cl, 0, sizeof(cl) );
cmdline.copy( cl, (_MAX_PATH*sizeof(TCHAR)) - 1);
HANDLE stdoutReadHandle = NULL;
HANDLE stdoutWriteHandle = NULL;
SECURITY_ATTRIBUTES saAttr;
memset( &saAttr, 0, sizeof(saAttr) );
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if ( ! CreatePipe(&stdoutReadHandle, &stdoutWriteHandle, &saAttr, 5000) )
throw std::runtime_error( "StdoutRd CreatePipe" );
// Ensure the read handle to the pipe for STDOUT is not inherited.
if ( ! SetHandleInformation(stdoutReadHandle, HANDLE_FLAG_INHERIT, 0) )
throw std::runtime_error( "Stdout SetHandleInformation" );
STARTUPINFO startupInfo;
memset( &startupInfo, 0, sizeof(startupInfo) );
startupInfo.cb = sizeof(startupInfo);
startupInfo.hStdError = stdoutWriteHandle;
startupInfo.hStdOutput = stdoutWriteHandle;
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startupInfo.dwFlags |= STARTF_USESTDHANDLES;
char* rawEnvVars = GetEnvironmentStringsA();
//__asm _emit 0xcc;
PROCESS_INFORMATION processInfo;
memset( &processInfo, 0, sizeof(processInfo) );
std::cout << "Start [" << cmdline << "]" << std::endl;
if ( CreateProcessA( 0, &cl[0], 0, 0, false,
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
rawEnvVars, 0, &startupInfo, &processInfo ) )
{
//CloseHandle( stdoutWriteHandle );
DWORD wordsRead;
char tBuf[257] = {'\0'};
bool success = true;
std::string outBuf("");
unsigned int t;
while(success) {
//__asm _emit 0xcc;
std::cout << "Just before ReadFile(...)" << std::endl;
success = ReadFile( stdoutReadHandle, tBuf, 256, &wordsRead, NULL);
(t=GetLastError())?print_error(t):t=t;
std::cout << "Just after ReadFile(...) | read " << wordsRead<< std::endl;
std::cout << ".";
if( success == false ) break;
outBuf += tBuf;
tBuf[0] = '\0';
}
std::cout << "output = [" << outBuf << "]" << std::endl;
if ( WaitForSingleObject( processInfo.hProcess, INFINITE ) == WAIT_OBJECT_0 )
{
unsigned int exitcode = 0;
GetExitCodeProcess( processInfo.hProcess, (LPDWORD)&exitcode );
std::cout << "exitcode = [" << exitcode << "]" << std::endl;
//__asm _emit 0xcc;
CloseHandle( processInfo.hProcess );
CloseHandle( processInfo.hThread );
return exitcode;
}
}
else
{
DWORD procErr = GetLastError();
std::cout << "FAILED TO CREATE PROCESS!" << std::endl;
print_error( procErr );
}
return -1;
} // end launch()
答案 0 :(得分:9)
您的代码中存在一些错误,但最重要的是您已为FALSE
的{{1}}参数指定了bInheritHandles
。如果新进程没有继承它的句柄,则它不能使用该管道。为了继承句柄,CreateProcess
参数必须是bInheritHandles
并且句柄必须启用继承。
其他问题:
您指定TRUE
但传递ANSI环境块。请注意,为CREATE_UNICODE_ENVIRONMENT
传递NULL
更容易,并让系统为您复制环境块。在这种情况下,即使您正在以Unicode模式进行编译,也无需指定CREATE_UNICODE_ENVIRONMENT。
同样,如果你正在调用CreateProcessA,你应该使用STARTUPINFOA。
每次循环时都不会对lpEnvironment
进行零终止,因此输出缓冲区中会出现虚假的额外字符。
您需要在进入读取循环之前关闭tBuf
,否则您将无法知道子进程何时退出。 (或者您可以使用异步IO并明确检查进程退出。)
stdoutWriteHandle
未定义,因此只有在GetLastError()
返回ReadFile
时才应调用它。 (当然,在这种情况下,这纯粹是装饰性的,因为您没有对错误代码采取行动。)
供参考,这是我更正的代码版本。我把它变成了普通的C(对不起!),因为那是我熟悉的。我在Unicode模式下编译和测试,但我认为它也可以在ANSI模式下无需修改。
FALSE
答案 1 :(得分:1)
ReadFile()有一个“LPOVERLAPPED lpOverlapped”参数,您已将其设置为NULL。看起来唯一的方法是允许管道上的重叠I / O,然后使用WaitForSingleObject()作为“overlapped.hEvent”。
另一种方法是使用ConnectNamedPipe函数并为管道创建OVERLAPPED结构。