此代码应运行批处理文件并返回其输出。我验证批处理文件正在运行,但未读取输出。它以管道损坏错误退出。
vector<string> getDrawingNames(const string &projectName) {
logFile << "starting getDrawingNames" <<endl;
vector<string> drwNames;
HANDLE hOutputRead, hOutputWrite, hErrorWrite;
HANDLE hInputWrite, hInputRead;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
logFile << "creating pipes" << endl;
::CreatePipe(&hOutputRead, &hOutputWrite, &sa, 0);
::CreatePipe(&hInputRead, &hInputWrite, &sa, 0);
::DuplicateHandle(::GetCurrentProcess(), hOutputWrite, ::GetCurrentProcess(), &hErrorWrite, 0, TRUE, DUPLICATE_SAME_ACCESS);
::SetHandleInformation(hOutputRead, HANDLE_FLAG_INHERIT, 0);
::SetHandleInformation(hInputWrite, HANDLE_FLAG_INHERIT, 0);
logFile << "setting startup info" << endl;
STARTUPINFOA startWinInfo;
memset(&startWinInfo, 0, sizeof(STARTUPINFOA));
startWinInfo.cb = sizeof(startWinInfo);
startWinInfo.dwFlags = STARTF_USESTDHANDLES;
startWinInfo.hStdOutput = hOutputWrite;
startWinInfo.hStdInput = hInputRead;
startWinInfo.hStdError = hErrorWrite;
PROCESS_INFORMATION procHandles;
char * cmdname = "C:\\Windows\\System32\\cmd.exe";
char * cmdargs = "/C \"C:\\Users\\Greg\\Documents\\Visual Studio 2015\\Projects\\DimExtractor\\getDrawingNames.bat\"";
DWORD procFlags;
DWORD waitStatus = 0;
DWORD procStatus = 0;
DWORD winErrCode;
DWORD inloop = 1;
procFlags = (CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP);
procHandles.hProcess = INVALID_HANDLE_VALUE;
procHandles.hThread = INVALID_HANDLE_VALUE;
procHandles.dwProcessId = 0;
procHandles.dwThreadId = 0;
logFile << "about to CreateProcessA..." << endl;
BOOL result = CreateProcessA(cmdname, cmdargs, NULL, NULL, 0, procFlags, NULL, NULL, &startWinInfo, &procHandles);
if (result == 0)
{
logFile << "problem with CreateProcessA, error=" << GetLastError() << endl;
::CloseHandle(hOutputWrite);
::CloseHandle(hInputRead);
::CloseHandle(hErrorWrite);
::CloseHandle(hOutputRead);
::CloseHandle(hInputWrite);
return drwNames;
}
logFile << "closing handles..." << endl;
::CloseHandle(procHandles.hThread); // we don't need it
// close handles we passed -> now the process is responsible for closing them
::CloseHandle(hOutputWrite);
::CloseHandle(hInputRead);
::CloseHandle(hErrorWrite);
// read pipe until the process terminates
int iResult = 0;
char strBuffer[256];
DWORD rd;
logFile << "reading output..." << endl;
while (true)
{
logFile << "about to ReadFile..." << endl;
if (!ReadFile(hOutputRead, strBuffer, 256, &rd, NULL))
{
logFile << "problem with ReadFile, error=" << GetLastError() << endl;
if (::GetLastError() == ERROR_BROKEN_PIPE) {
logFile << "error was a broken pipe" << endl;
break; // terminated
}
else
{
logFile << "error was something other than a broke pipe" << endl;
iResult = -1;
break;
}
}
INT iTest = IS_TEXT_UNICODE_CONTROLS;
if (::IsTextUnicode(strBuffer, rd, &iTest)) {
logFile << strBuffer;
wprintf((wchar_t *)strBuffer);
}
else {
logFile << strBuffer;
printf((char *)strBuffer);
}
}
logFile << "closing handles2" << endl;
::CloseHandle(procHandles.hProcess);
::CloseHandle(hOutputRead);
::CloseHandle(hInputWrite);
logFile << "returning" << endl;
return drwNames;
}
忽略现在的返回值。我正在尝试验证正在读取批处理文件的输出。它以管道损坏错误退出。我不明白为什么。
如果我手动运行批处理文件,则输出如下:
C:\Users\Greg\Documents\Visual Studio 2015\Projects\DimExtractor>getDrawingNames.bat
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 85 100 30 100 55 30 55 0:00:01 --:--:-- 0:00:01 319
Our auth: "-48438904427905703"
Drawing Names for Project P314_557_001 =>[{"NAME":"314.557.001"}]
Logout=>{"auth": null}
有什么建议吗?
答案 0 :(得分:1)
您在通话CreateProcessA
中的错误 - 您使用bInheritHandles = 0
。因此,任何管道句柄都不会被cmd继承。它写入隐形控制台并退出。
你在电话::CloseHandle(hOutputWrite);
之后自我打破了hOutputRead
- 服务器管道端断了,连接到最后后客户端管道端关闭了。如果hOutputWrite
将由cmd继承 - 您的hOutputRead
只会在两者之后被破坏 - 您和cm关闭hOutputWrite
。但是因为cmd没有得到它 - 它在你关闭自己的副本之后就被打破了。在ReadFile
您刚刚获得ERROR_BROKEN_PIPE
如果你没有打电话给::CloseHandle(hOutputWrite);
- 当然hOutputRead
不会被打破,但是ReadFile(hOutputRead..)
永远不会返回,因为没有人写信给hOutputWrite
。
因此,如果您在通话bInheritHandles = true
中更改为CreateProcess
,您的代码将更快地开始工作为例外。
然而有些注意事项:
致电::DuplicateHandle(::GetCurrentProcess(), hOutputWrite, ::GetCurrentProcess(), &hErrorWrite, 0, TRUE, DUPLICATE_SAME_ACCESS)
- 绝对毫无意义 - 你可以hErrorWrite = hOutputWrite
做同样的效果。重复句柄不创建新对象 - 它只创建同一对象的新句柄(指针)。在这种情况下,相同管道末端有2个句柄?在您的情况下,hStdError
和hStdOutput
将是同一文件对象的不同句柄。甚至同步文件对象的序列化是每个文件对象,但不是每个句柄。如果我们想要单独的过程错误和正常输出,则exists sense对hStdError
和hStdOutput
具有不同的管道。但是没有意义上对相同的文件有不同的句柄。
您创建5(!)个不同的管道句柄。真的有2个管道处理:如果异步管道或在你的具体情况下,一方只写,另一方只读。都是PIPE_ACCESS_DUPLEX
。不需要hStdInput
和StdOutput
的单独句柄 - 两者(具有PIPE_ACCESS_DUPLEX
和读/写访问权限)的相同句柄就好了。
使用不同的句柄进行读写只能在同步管道的情况下使用。因为所有同步操作都是序列化的 - 新的 操作直到上一个结束才开始。这可能会导致死锁(即使另一方使用异步句柄)。例如side,它使用同步io第一次调用read而不是单独的线程调用write(在同一个句柄上)。但写不开始执行(将在io管理器中被阻止,直到之前的读取未完成)。如果另一方在调用写入之前首先等待某些数据 - 它从未得到这些数据(在读取完成后开始写入另一侧,仅在我们向管道写入内容后才完成)。如果我们使用异步管道 - 读/写未序列化 - 可以并发执行。结果永远不会陷入僵局。在大多数情况下也会有足够的异步父与同步子像 cmd (它自己序列化读/写操作)。并且你的代码无论如何都没有写入管道 - 所以假设另一方没有读,但只写。在这种情况下,即使使用完全同步(从两侧)管道对,也不会出现死锁。
同样CreatePipe
是非常糟糕的设计api - 不要创建这样的管道对(读/写,全双工)。需要使用CreateNamedPipeW
+ CreateFileW
代替。 (从win7开始可能创建未命名的管道对,但为此需要使用ZwCreateNamedPipeFile
,CreateNamedPipeW
- 无法执行此操作)
工作代码示例
ULONG CreatePipeAnonymousPair(PHANDLE phServerPipe, PHANDLE phClientPipe)
{
static LONG s;
if (!s)
{
ULONG seed = GetTickCount();
InterlockedCompareExchange(&s, RtlRandomEx(&seed), 0);
}
WCHAR name[64];
swprintf(name, L"\\\\.\\Pipe\\Win32Pipes.%08x.%08x", GetCurrentProcessId(), InterlockedIncrement(&s));
HANDLE hServerPipe = CreateNamedPipeW(name,
PIPE_ACCESS_DUPLEX|FILE_READ_DATA|FILE_WRITE_DATA,
PIPE_TYPE_BYTE|PIPE_READMODE_BYTE, 1, 0, 0, 0, 0);
if (hServerPipe != INVALID_HANDLE_VALUE)
{
static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
HANDLE hClientPipe = CreateFileW(name, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0);
if (hClientPipe != INVALID_HANDLE_VALUE)
{
*phServerPipe = hServerPipe, *phClientPipe = hClientPipe;
return NOERROR;
}
CloseHandle(hServerPipe);
}
return GetLastError();
}
void PrintOem(PSTR buf, ULONG cb)
{
if (int cchWideChar = MultiByteToWideChar(CP_OEMCP, 0, buf, cb, 0, 0))
{
PWSTR wz = (PWSTR)alloca(cchWideChar * sizeof(WCHAR));
if (MultiByteToWideChar(CP_OEMCP, 0, buf, cb, wz, cchWideChar))
{
if (ULONG cbMultiByte = WideCharToMultiByte(CP_ACP, 0, wz, cchWideChar, 0, 0, 0, 0))
{
PSTR sz = (PSTR)alloca(cbMultiByte);
if (WideCharToMultiByte(CP_ACP, 0, wz, cchWideChar, sz, cbMultiByte, 0, 0))
{
DbgPrint("%.*s", cbMultiByte, sz);
}
}
}
}
}
ULONG ExecCmd(PWSTR cmdline, PCWSTR CurrentDirectory)
{
WCHAR ApplicationName[MAX_PATH];
if (!GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
return GetLastError();
}
STARTUPINFOEXW si = { { sizeof(si) } };
PROCESS_INFORMATION pi;
HANDLE hPipe;
ULONG err = CreatePipeAnonymousPair(&hPipe, &si.StartupInfo.hStdError);
if (!err)
{
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
si.StartupInfo.hStdInput = si.StartupInfo.hStdOutput = si.StartupInfo.hStdError;
ULONG dwCreationFlags = CREATE_NO_WINDOW;
//++ optional
BOOL fInit = FALSE;
SIZE_T Size;
if (!InitializeProcThreadAttributeList(0, 1, 0, &Size) &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
InitializeProcThreadAttributeList(si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(Size), 1, 0, &Size))
{
fInit = TRUE;
if (UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
&si.StartupInfo.hStdError, sizeof(HANDLE), 0, 0))
{
dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
}
}
//-- optional
if (CreateProcessW(ApplicationName, cmdline, 0, 0, TRUE, dwCreationFlags, 0,
CurrentDirectory, &si.StartupInfo, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
else
{
err = GetLastError();
}
if (fInit)
{
DeleteProcThreadAttributeList(si.lpAttributeList);
}
CloseHandle(si.StartupInfo.hStdError);
if (!err)
{
CHAR buf[0x1000], *sz;
ULONG dwBytes, cb;
while (ReadFile(hPipe, buf, sizeof(buf), &dwBytes, 0) && dwBytes)
{
sz = buf;
do
{
PrintOem(sz, cb = min(dwBytes, 256));
} while (sz += cb, dwBytes -= cb);
}
}
CloseHandle(hPipe);
}
return err;
}
cmdline
通常与"/c some.bat"
或"/c \"so me.bat\""
相似。我们可以在cmdline中设置(使用完整路径)或在CurrentDirectory