6

子プロセスのコマンドライン出力をパイプ経由で読み取ろうとする関数を作成しました。これは、MSDN のリダイレクトされた入力と出力を使用した子プロセスの作成の記事の単純なサブセットである必要がありますが、明らかに何らかのエラーを犯しています。

以下の ReadFile(...) 呼び出しは、子プロセスの終了を通知する WaitForSingleObject(...) 呼び出しの前または後に配置しても、永久にブロックされます。

「非同期ReadFileを使用する」ことを提案するすべての回答を読みましたが、パイプでそれがどのように達成されるかについて誰かが私にアイデアを与えることができれば、その提案を受け入れます。この場合に非同期 I/O が必要な理由はわかりませんが。

#include "stdafx.h"
#include <string>
#include <windows.h>

unsigned int launch( const std::string & cmdline );

int _tmain(int argc, _TCHAR* argv[])
{
    launch( std::string("C:/windows/system32/help.exe") );  
    return 0;
}

void print_error( unsigned int err )
{
    char* msg = NULL;
    FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        err,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPSTR)&msg,
        0, NULL );
    std::cout << "------ Begin Error Msg ------" << std::endl;
    std::cout << msg << std::endl;
    std::cout << "------  End Error Msg  ------" << std::endl;

    LocalFree( msg );
}

unsigned int launch( const std::string & cmdline )
{
    TCHAR cl[_MAX_PATH*sizeof(TCHAR)]; 
    memset( cl, 0, sizeof(cl) );
    cmdline.copy( cl, (_MAX_PATH*sizeof(TCHAR)) - 1);    

    HANDLE stdoutReadHandle = NULL;
    HANDLE stdoutWriteHandle = NULL;

    SECURITY_ATTRIBUTES saAttr; 
    memset( &saAttr, 0, sizeof(saAttr) ); 
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
    saAttr.bInheritHandle = TRUE; 
    saAttr.lpSecurityDescriptor = NULL; 

    // Create a pipe for the child process's STDOUT. 
    if ( ! CreatePipe(&stdoutReadHandle, &stdoutWriteHandle, &saAttr, 5000) )      
        throw std::runtime_error( "StdoutRd CreatePipe" ); 
    // Ensure the read handle to the pipe for STDOUT is not inherited.
    if ( ! SetHandleInformation(stdoutReadHandle, HANDLE_FLAG_INHERIT, 0) )
        throw std::runtime_error( "Stdout SetHandleInformation" ); 

    STARTUPINFO startupInfo; 
    memset( &startupInfo, 0, sizeof(startupInfo) ); 
    startupInfo.cb = sizeof(startupInfo);
    startupInfo.hStdError = stdoutWriteHandle;
    startupInfo.hStdOutput = stdoutWriteHandle;
    startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    startupInfo.dwFlags |= STARTF_USESTDHANDLES;

    char* rawEnvVars = GetEnvironmentStringsA();

    //__asm _emit 0xcc;

    PROCESS_INFORMATION processInfo;
    memset( &processInfo, 0, sizeof(processInfo) );

    std::cout << "Start [" << cmdline << "]" << std::endl;
    if ( CreateProcessA( 0, &cl[0], 0, 0, false, 
        CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, 
        rawEnvVars, 0, &startupInfo, &processInfo ) )
    {       
        //CloseHandle( stdoutWriteHandle );
        DWORD wordsRead;
        char tBuf[257] = {'\0'};
        bool success = true;
        std::string outBuf("");
        unsigned int t;
        while(success) {
            //__asm _emit 0xcc;
            std::cout << "Just before ReadFile(...)" << std::endl;
            success = ReadFile( stdoutReadHandle, tBuf, 256, &wordsRead, NULL);
            (t=GetLastError())?print_error(t):t=t;
            std::cout << "Just after ReadFile(...) | read " << wordsRead<< std::endl;
            std::cout << ".";
            if( success == false ) break;
            outBuf += tBuf;
            tBuf[0] = '\0';
        }
        std::cout << "output = [" << outBuf << "]" << std::endl;


        if ( WaitForSingleObject( processInfo.hProcess, INFINITE ) == WAIT_OBJECT_0 )
        {
            unsigned int exitcode = 0;
            GetExitCodeProcess( processInfo.hProcess, (LPDWORD)&exitcode );                
            std::cout << "exitcode = [" << exitcode << "]" << std::endl;

            //__asm _emit 0xcc;
            CloseHandle( processInfo.hProcess );
            CloseHandle( processInfo.hThread );

            return exitcode;
        }
    }
    else
    {
        DWORD procErr = GetLastError();
        std::cout << "FAILED TO CREATE PROCESS!" << std::endl;
        print_error( procErr );
    }
    return -1;
} // end launch()
4

2 に答える 2

10

コードにはいくつかのバグがありますが、最も重要なのはFALSEへのbInheritHandles引数に指定したことCreateProcessです。ハンドルを継承しない場合、新しいプロセスはパイプを使用できません。ハンドルを継承するには、bInheritHandles引数が必要でTRUE あり、ハンドルで継承が有効になっている必要があります。

その他の問題:

  • 指定していますCREATE_UNICODE_ENVIRONMENTが、ANSI 環境ブロックを渡しています。環境ブロックを渡してシステムにコピーさせる方が簡単であることに注意してNULLください。環境ブロックに Unicode 文字が含まれている可能性があるため、ドキュメントに記載されているように、この場合lpEnvironmentも指定する必要があります。CREATE_UNICODE_ENVIRONMENT

  • 同様に、CreateProcessA を呼び出す場合は、STARTUPINFOA を使用する必要があります。

  • ループのたびにゼロで終了しないtBufため、出力バッファーに偽の余分な文字が含まれます。

  • 読み取りループに入る前に閉じる必要がありstdoutWriteHandleます。そうしないと、サブプロセスがいつ終了するかわかりません。(または、非同期 IO を使用して、プロセスの終了を明示的に確認することもできます。)

  • GetLastError()ReadFileAPI 関数が成功した場合、 は定義されていないため、が返された場合にのみ呼び出す必要がありますFALSE。(もちろん、この場合、エラー コードを操作していないため、これは純粋に表面的なものです。)

参考までに、コードの修正版を次に示します。私はそれを普通の C に変えました (ごめんなさい!) それは私がよく知っているからです。Unicode モードでコンパイルしてテストしましたが、ANSI モードでも変更なしで動作するはずです。

#define _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <stdio.h>

void launch(const char * cmdline_in)
{
    PROCESS_INFORMATION processInfo;
    STARTUPINFOA startupInfo; 
    SECURITY_ATTRIBUTES saAttr; 

    HANDLE stdoutReadHandle = NULL;
    HANDLE stdoutWriteHandle = NULL;

    char cmdline[256];
    char outbuf[32768];
    DWORD bytes_read;
    char tBuf[257];

    DWORD exitcode;

    strcpy_s(cmdline, sizeof(cmdline), cmdline_in);

    memset(&saAttr, 0, sizeof(saAttr));
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
    saAttr.bInheritHandle = TRUE; 
    saAttr.lpSecurityDescriptor = NULL; 

    // Create a pipe for the child process's STDOUT. 
    if (!CreatePipe(&stdoutReadHandle, &stdoutWriteHandle, &saAttr, 5000))
    {
        printf("CreatePipe: %u\n", GetLastError());
        return;
    }

    // Ensure the read handle to the pipe for STDOUT is not inherited.
    if (!SetHandleInformation(stdoutReadHandle, HANDLE_FLAG_INHERIT, 0))
    {
        printf("SetHandleInformation: %u\n", GetLastError());
        return;
    }

    memset(&startupInfo, 0, sizeof(startupInfo));
    startupInfo.cb = sizeof(startupInfo);
    startupInfo.hStdError = stdoutWriteHandle;
    startupInfo.hStdOutput = stdoutWriteHandle;
    startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    startupInfo.dwFlags |= STARTF_USESTDHANDLES;

    // memset(&processInfo, 0, sizeof(processInfo));  // Not actually necessary

    printf("Starting.\n");

    if (!CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
        CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, NULL, 0, &startupInfo, &processInfo))
    {
        printf("CreateProcessA: %u\n", GetLastError());
        return;
    }

    CloseHandle(stdoutWriteHandle);

    strcpy_s(outbuf, sizeof(outbuf), "");

    for (;;) {
        printf("Just before ReadFile(...)\n");
        if (!ReadFile(stdoutReadHandle, tBuf, 256, &bytes_read, NULL))
        {
            printf("ReadFile: %u\n", GetLastError());
            break;
        }
        printf("Just after ReadFile, read %u byte(s)\n", bytes_read);
        if (bytes_read > 0)
        {
            tBuf[bytes_read] = '\0';
            strcat_s(outbuf, sizeof(outbuf), tBuf);
        }
    }

    printf("Output: %s\n", outbuf);

    if (WaitForSingleObject(processInfo.hProcess, INFINITE) != WAIT_OBJECT_0)
    {
        printf("WaitForSingleObject: %u\n", GetLastError());
        return;
    }

    if (!GetExitCodeProcess(processInfo.hProcess, &exitcode))
    {
        printf("GetExitCodeProcess: %u\n", GetLastError());
        return;
    }

    printf("Exit code: %u\n", exitcode);

    CloseHandle( processInfo.hProcess );
    CloseHandle( processInfo.hThread );

    return;
}

int main(int argc, char** argv)
{
    launch("C:\\windows\\system32\\help.exe");
    return 0;
}
于 2012-06-04T21:33:39.010 に答える
1

NULL に設定した ReadFile() への「LPOVERLAPPED lpOverlapped」パラメータがあります。唯一の方法は、パイプで重複した I/O を許可し、「overlapped.hEvent」に WaitForSingleObject() を使用することです。

もう 1 つの方法は、ConnectNamedPipe関数を使用して、パイプの OVERLAPPED 構造体を作成することです。

于 2012-06-03T07:05:34.520 に答える