本文介绍了如何运行批处理文件并读取输出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这段代码应该运行一个批处理文件并返回它的输出.我验证了批处理文件正在运行,但未读取输出.它退出时出现管道损坏错误.

This code is supposed to run a batch file and return its output. I verified the batch file is running but the output is not read. It exits with a broken pipe error.

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;
}

暂时忽略返回值.我只是想验证正在读取批处理文件的输出.它退出时出现管道损坏错误.我不明白为什么.

Ignore the return values for now. I'm just trying to verify the output from the batch file is being read. It exits with a broken pipe error. I don't get why.

如果我手动运行它,这是批处理文件的输出:

Here is the output of the batch file if I run it manually:

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 继承的.它写入隐形控制台并退出.

your error in call CreateProcessA - you use bInheritHandles = 0. as result any your pipe handles not inherited by cmd. it write to invisible console and exit.

you from self side after call ::CloseHandle(hOutputWrite); 破坏了 hOutputRead - 服务器管道末端被破坏,在 last 之后连接到它的客户端管道端关闭.如果 hOutputWrite 将被 cmd 继承 - 你的 hOutputRead 只有在你和 cm 关闭 hOutputWrite 之后才会被破坏.但是因为 cmd 没有得到它 - 它在你关闭自己的副本后就坏了.而在 ReadFile 上,你刚刚得到 ERROR_BROKEN_PIPE

you from self side after call ::CloseHandle(hOutputWrite); broke the hOutputRead - the server pipe end was broken, after last connected to it client pipe end is closed. if hOutputWrite will be inherited by cmd - your hOutputRead will broken only after both - you and cm close hOutputWrite. but because cmd not got it - it broken just after your close own copy. and on ReadFile you just got ERROR_BROKEN_PIPE

如果你不调用 ::CloseHandle(hOutputWrite); - 当然 hOutputRead 不会被破坏,但是 ReadFile(hOutputRead..) 永远不会返回,因为没有人写入 hOutputWrite.

if you not call ::CloseHandle(hOutputWrite); - of course hOutputRead will be not broken, but ReadFile(hOutputRead..) never return because nobody write to hOutputWrite.

因此,如果您在调用 CreateProcess 时更改为 bInheritHandles = true,则您的代码将更快地开始工作,但除外.

so if you change to bInheritHandles = true in call CreateProcess your code faster of all begin work as excepted.

但是有些注意:

call ::DuplicateHandle(::GetCurrentProcess(), hOutputWrite, ::GetCurrentProcess(), &hErrorWrite, 0, TRUE, DUPLICATE_SAME_ACCESS) - 绝对没有意义 - 你可以做 hErrorWrite= hOutputWrite 效果相同.重复句柄不会创建新对象 - 它只会创建指向同一对象的新句柄(指针).在这种情况下,相同 管端有 2 个手柄吗?在您的情况下, hStdErrorhStdOutput 将是同一文件对象的不同句柄.甚至同步文件对象的序列化是每个文件对象,而不是每个句柄.如果我们想要单独的进程错误和正常输出,存在意义对于 hStdErrorhStdOutput 有不同的管道.但没有意义对相同文件有不同的句柄.

call ::DuplicateHandle(::GetCurrentProcess(), hOutputWrite, ::GetCurrentProcess(), &hErrorWrite, 0, TRUE, DUPLICATE_SAME_ACCESS) - absolute senseless - you can do hErrorWrite = hOutputWrite with the same effect. duplicate handle not create new object - it only create new handle (pointer) to the same object. for what have 2 handles for the same pipe end in this case ? in your case hStdError and hStdOutput will be different handles to the same file object. even serialization for synchronous file object was per file object, but not per handle. exist sense have different pipes for hStdError and hStdOutput if we want separate process error and normal output. but no sense have different handles to the same file.

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

you create 5 (!) different pipe handles. really enough have 2 pipe handles: in case asynchronous pipes or in your concrete case when one side only write and another side only read. both with PIPE_ACCESS_DUPLEX. not need separate handles for hStdInput and StdOutput - the same handle for both (with PIPE_ACCESS_DUPLEX and read/write access) just fine.

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

use different handles for read and write can be need only in case synchronous pipes. because all synchronous operation is serialized - newoperation not begin until previous is end. this can cause deadlock (even if another side use asynchronous handles). for example side, which used synchronous io first call read and than in separate thread call write (on the same handle). but write not begin execute (will be blocked in io manager, until previous read not finished). if another side first wait on some data, before call write - it never got this data (write on another side begin after read finished, which finished only after we write something to pipe). if we use asynchronous pipes - read/write not serialized - can execute in concurrent. as result never be deadlock. also in most case will be enough have asynchronous parent with synchronous child like cmd (which yourself serialize read/write operation). and your code anyway nothing write to pipes - so assume that another side nothing read, but only write. in this case also will be no deadlock even with full synchronous (from both side) pipe pair.

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

also CreatePipe is very bad designed api - not let create such pipe pair (read/write, full duplex). need use CreateNamedPipeW + CreateFileW instead. (begin from win7 possible create un-named pipe pair, but for this need use ZwCreateNamedPipeFile, CreateNamedPipeW - can not do this)

工作代码示例

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 中设置的 bat 路径(使用完整路径)或在 CurrentDirectory

the cmdline usually like "/c some.bat" or "/c \"so me.bat\"". path to bat we can set in cmdline (use full path) or set it in CurrentDirectory

这篇关于如何运行批处理文件并读取输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-12 16:26