该代码应运行一个批处理文件并返回其输出。我验证了批处理文件正在运行,但未读取输出。它以管道破裂错误退出。
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}
有什么建议么?
最佳答案
您在呼叫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
将是同一文件对象的不同句柄。甚至同步文件对象的序列化都是每个文件对象,而不是每个句柄。如果我们想要单独的过程错误和正常输出,则存在的意义上hStdError
和hStdOutput
具有不同的管道。但对同一个文件没有不同的句柄。
您创建5个(!)不同的管道手柄。确实有两个管道句柄:在异步管道的情况下,或者在您的具体情况下,当一侧只写而另一侧只读时。都使用PIPE_ACCESS_DUPLEX
。不需要hStdInput
和StdOutput
的单独句柄-两者的相同句柄(具有PIPE_ACCESS_DUPLEX
和读/写访问权限)就可以了。
仅在同步管道的情况下才需要使用不同的句柄进行读取和写入。因为所有同步操作都已序列化-新
在上一个操作结束之前,操作不会开始。这可能导致死锁(即使另一端使用异步句柄)。例如,使用同步io首次调用读取,然后使用单独的线程调用写入(在同一句柄上)。但写入操作不会开始执行(在io manager中将被阻止,直到之前的读取操作尚未完成)。如果另一侧首先等待某些数据,则在调用写入之前-它永远不会得到该数据(另一侧的写入在读取完成后才开始,只有在我们向管道中写入某些内容后才开始)。如果我们使用异步管道-读/写未序列化-可以并发执行。结果永远不会陷入僵局。在大多数情况下,具有像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
中设置关于c++ - 如何运行批处理文件并读取输出,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49327749/