如何运行批处理文件和读取输出

时间:2018-03-16 18:54:39

标签: c++ winapi

此代码应运行批处理文件并返回其输出。我验证批处理文件正在运行,但未读取输出。它以管道损坏错误退出。

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}

有什么建议吗?

1 个答案:

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

您创建5(!)个不同的管道句柄。真的有2个管道处理:如果异步管道或在你的具体情况下,一方只写,另一方只读。都是PIPE_ACCESS_DUPLEX。不需要hStdInputStdOutput的单独句柄 - 两者(具有PIPE_ACCESS_DUPLEX和读/写访问权限)的相同句柄就好了。

使用不同的句柄进行读写只能在同步管道的情况下使用。因为所有同步操作都是序列化的 - 新的 操作直到上一个结束才开始。这可能会导致死锁(即使另一方使用异步句柄)。例如side,它使用同步io第一次调用read而不是单独的线程调用write(在同一个句柄上)。但写不开始执行(将在io管理器中被阻止,直到之前的读取未完成)。如果另一方在调用写入之前首先等待某些数据 - 它从未得到这些数据(在读取完成后开始写入另一侧,仅在我们向管道写入内容后才完成)。如果我们使用异步管道 - 读/写未序列化 - 可以并发执行。结果永远不会陷入僵局。在大多数情况下也会有足够的异步父与同步子像 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

中设置它