该代码应运行一个批处理文件并返回其输出。我验证了批处理文件正在运行,但未读取输出。它以管道破裂错误退出。

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个手柄?在您的情况下,hStdErrorhStdOutput将是同一文件对象的不同句柄。甚至同步文件对象的序列化都是每个文件对象,而不是每个句柄。如果我们想要单独的过程错误和正常输出,则存在的意义上hStdErrorhStdOutput具有不同的管道。但对同一个文件没有不同的句柄。

您创建5个(!)不同的管道手柄。确实有两个管道句柄:在异步管道的情况下,或者在您的具体情况下,当一侧只写而另一侧只读时。都使用PIPE_ACCESS_DUPLEX。不需要hStdInputStdOutput的单独句柄-两者的相同句柄(具有PIPE_ACCESS_DUPLEX和读/写访问权限)就可以了。

仅在同步管道的情况下才需要使用不同的句柄进行读取和写入。因为所有同步操作都已序列化-新
在上一个操作结束之前,操作不会开始。这可能导致死锁(即使另一端使用异步句柄)。例如,使用同步io首次调用读取,然后使用单独的线程调用写入(在同一句柄上)。但写入操作不会开始执行(在io manager中将被阻止,直到之前的读取操作尚未完成)。如果另一侧首先等待某些数据,则在调用写入之前-它永远不会得到该数据(另一侧的写入在读取完成后才开始,只有在我们向管道中写入某些内容后才开始)。如果我们使用异步管道-读/写未序列化-可以并发执行。结果永远不会陷入僵局。在大多数情况下,具有像cmd这样的同步子对象的异步父对象也足够了(您自己对读/写操作进行了序列化)。而且您的代码无论如何都不会写入管道-因此,假设另一边什么都没读,而只是写。在这种情况下,即使完全同步(从两侧)管道对,也不会出现死锁。

同样CreatePipe是非常糟糕的设计api-不允许创建这样的管道对(读/写,全双工)。需要使用CreateNamedPipeW + CreateFileW代替。 (从win7开始,可能会创建未命名的管道对,但是为此需要使用ZwCreateNamedPipeFileCreateNamedPipeW-不能这样做)

工作代码示例

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/

10-09 06:02
查看更多