質問

CreateProcessを使用してProcess ADB.Exeを作成すると、ReadFileでブロックされます。

void KillAdbProcess()
{
    DWORD aProcesses[1024], cbNeeded, cProcesses;
    unsigned int i;

    if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )
        return;

    cProcesses = cbNeeded / sizeof(DWORD);

    for ( i = 0; i < cProcesses; i++ )
        if( aProcesses[i] != 0 ){
            bool shouldKill =false;
            wchar_t szProcessName[MAX_PATH] = L"<unknown>";

                //Get a handle to the process.
                HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
                                   PROCESS_VM_READ | PROCESS_TERMINATE,
                                   FALSE, aProcesses[i] );
                if (NULL != hProcess )
                {
                    HMODULE hMod;
                    DWORD cbNeeded;

                    if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), 
                         &cbNeeded) )
                    {
                        GetModuleFileNameExW( hProcess, hMod, szProcessName, 
                                           sizeof(szProcessName)/sizeof(TCHAR));
                        int len = wcslen(szProcessName);
                        if(!wcscmp(L"\\adb.exe",szProcessName+len-8)){
                            shouldKill = true;
                        }

                    }
                }

                if(shouldKill) TerminateProcess(hProcess,0);
                CloseHandle( hProcess );
        }

} 

int testadb(){
    KillAdbProcess();
    char buff[4096] = {0};
    int len = sizeof(buff);
    DWORD exitCode = 0;

    SECURITY_ATTRIBUTES   sa;   
    ZeroMemory(&sa, sizeof(sa)); 
    sa.bInheritHandle = TRUE;   
    sa.lpSecurityDescriptor = NULL;   
    sa.nLength = sizeof(sa);

    HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
    // Create the child output pipe.
    if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0))
        return false;

    // Create new output read handle and the input write handles. Set
    // the Properties to FALSE. Otherwise, the child inherits the
    // properties and, as a result, non-closeable handles to the pipes
    // are created.
    if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
        GetCurrentProcess(),
        &hOutputRead, // Address of new handle.
        0,FALSE, // Make it uninheritable.
        DUPLICATE_SAME_ACCESS))
        return false;

    // Close inheritable copies of the handles you do not want to be
    // inherited.
    if (!CloseHandle(hOutputReadTmp)) return false;


    PROCESS_INFORMATION  pi; 
    ZeroMemory(&pi, sizeof(pi));   
    STARTUPINFOW  si;
    GetStartupInfoW(&si);

    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.wShowWindow = SW_HIDE;
    si.hStdInput   = NULL;
    if(buff) {
        si.hStdOutput   =   hOutputWrite;   
        si.hStdError = hOutputWrite;
    } else {
        si.hStdOutput   =  NULL;   
        si.hStdError = NULL;
    }

    wchar_t cmdBuf[512] = L"adb.exe start-server";
    if( !::CreateProcessW(NULL, cmdBuf, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi) )
    {
        exitCode = -1;
        goto exit;
    }

    ::CloseHandle(hOutputWrite);
    hOutputWrite = NULL;

    len--; //keep it for string end char.
    DWORD dwBytes = 0;
    DWORD dwHasRead = 0;
    while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
    {
        printf("read byte=%d\n",dwBytes);
        if(0 == dwBytes) break;
        dwHasRead += dwBytes;
        //GetExitCodeProcess(pi.hProcess, &exitCode);
        //if(STILL_ACTIVE != exitCode) break;
        if(dwHasRead >= len) break;
    }
    buff[dwHasRead] = 0;


    ::GetExitCodeProcess(pi.hProcess, &exitCode);

exit:


    if(hOutputRead) ::CloseHandle(hOutputRead);
    if(hOutputWrite) ::CloseHandle(hOutputWrite);

    ::CloseHandle(pi.hProcess);
    ::CloseHandle(pi.hThread);
    return 0;
}

コードを変更した場合

while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
    printf("read byte=%d\n",dwBytes);
    if(0 == dwBytes) break;
    dwHasRead += dwBytes;
    GetExitCodeProcess(pi.hProcess, &exitCode);
    if(STILL_ACTIVE != exitCode) break;
    if(dwHasRead >= len) break;
}

動作しますが、printfコードを削除すると再びブロックされます。

while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
    if(0 == dwBytes) break;
    dwHasRead += dwBytes;
    GetExitCodeProcess(pi.hProcess, &exitCode);
    if(STILL_ACTIVE != exitCode) break;
    if(dwHasRead >= len) break;
}

adb.exeのコードには、Belowsのようなコードが表示されます。

#if ADB_HOST
int launch_server()
{
#ifdef HAVE_WIN32_PROC
    /* we need to start the server in the background                    */
    /* we create a PIPE that will be used to wait for the server's "OK" */
    /* message since the pipe handles must be inheritable, we use a     */
    /* security attribute                                               */
    HANDLE                pipe_read, pipe_write;
    SECURITY_ATTRIBUTES   sa;
    STARTUPINFO           startup;
    PROCESS_INFORMATION   pinfo;
    char                  program_path[ MAX_PATH ];
    int                   ret;

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    /* create pipe, and ensure its read handle isn't inheritable */
    ret = CreatePipe( &pipe_read, &pipe_write, &sa, 0 );
    if (!ret) {
        fprintf(stderr, "CreatePipe() failure, error %ld\n", GetLastError() );
        return -1;
    }

    SetHandleInformation( pipe_read, HANDLE_FLAG_INHERIT, 0 );

    ZeroMemory( &startup, sizeof(startup) );
    startup.cb = sizeof(startup);
    startup.hStdInput  = GetStdHandle( STD_INPUT_HANDLE );
    startup.hStdOutput = pipe_write;
    startup.hStdError  = GetStdHandle( STD_ERROR_HANDLE );
    startup.dwFlags    = STARTF_USESTDHANDLES;

    ZeroMemory( &pinfo, sizeof(pinfo) );

    /* get path of current program */
    GetModuleFileName( NULL, program_path, sizeof(program_path) );

    ret = CreateProcess(
            program_path,                              /* program path  */
            "adb fork-server server",
                                    /* the fork-server argument will set the
                                       debug = 2 in the child           */
            NULL,                   /* process handle is not inheritable */
            NULL,                    /* thread handle is not inheritable */
            TRUE,                          /* yes, inherit some handles */
            DETACHED_PROCESS, /* the new process doesn't have a console */
            NULL,                     /* use parent's environment block */
            NULL,                    /* use parent's starting directory */
            &startup,                 /* startup info, i.e. std handles */
            &pinfo );

    CloseHandle( pipe_write );

    if (!ret) {
        fprintf(stderr, "CreateProcess failure, error %ld\n", GetLastError() );
        CloseHandle( pipe_read );
        return -1;
    }

    CloseHandle( pinfo.hProcess );
    CloseHandle( pinfo.hThread );

    /* wait for the "OK\n" message */
    {
        char  temp[3];
        DWORD  count;

        ret = ReadFile( pipe_read, temp, 3, &count, NULL );
        CloseHandle( pipe_read );
        if ( !ret ) {
            fprintf(stderr, "could not read ok from ADB Server, error = %ld\n", GetLastError() );
            return -1;
        }
        if (count != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
            fprintf(stderr, "ADB server didn't ACK\n" );
            return -1;
        }
    }
#elif defined(HAVE_FORKEXEC)
    char    path[PATH_MAX];
    int     fd[2];

    // set up a pipe so the child can tell us when it is ready.
    // fd[0] will be parent's end, and fd[1] will get mapped to stderr in the child.
    if (pipe(fd)) {
        fprintf(stderr, "pipe failed in launch_server, errno: %d\n", errno);
        return -1;
    }
    get_my_path(path);
    pid_t pid = fork();
    if(pid < 0) return -1;

    if (pid == 0) {
        // child side of the fork

        // redirect stderr to the pipe
        // we use stderr instead of stdout due to stdout's buffering behavior.
        adb_close(fd[0]);
        dup2(fd[1], STDERR_FILENO);
        adb_close(fd[1]);

        // child process
        int result = execl(path, "adb", "fork-server", "server", NULL);
        // this should not return
        fprintf(stderr, "OOPS! execl returned %d, errno: %d\n", result, errno);
    } else  {
        // parent side of the fork

        char  temp[3];

        temp[0] = 'A'; temp[1] = 'B'; temp[2] = 'C';
        // wait for the "OK\n" message
        adb_close(fd[1]);
        int ret = adb_read(fd[0], temp, 3);
        adb_close(fd[0]);
        if (ret < 0) {
            fprintf(stderr, "could not read ok from ADB Server, errno = %d\n", errno);
            return -1;
        }
        if (ret != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
            fprintf(stderr, "ADB server didn't ACK\n" );
            return -1;
        }

        setsid();
    }
#else
#error "cannot implement background server start on this platform"
#endif
    return 0;
}
#endif

ADB.EXEの子プロセスは、ADB.EXEのハンドルを継承すると思います。ADB.EXEの子プロセスが終了しない場合、ReadFileは永遠にブロックされます。しかし、私が「ADB.Exe Start-Server」を指揮するとき、すべては大丈夫です。では、WindowsコマンドはCreateProcessとReadFileをどのように呼び出しますか?

役に立ちましたか?

解決

私は答えを見つけました: 任意のコンソールの入力/出力のリダイレクト-CodeProject.

コンソールプロセスの入力/出力をリダイレクトする手法は、非常にサンプルです。StartupInfo構造を介したCreateProcess()APIにより、子コンソールベースのプロセスの標準ハンドルをリダイレクトできます。そのため、これらのハンドルをパイプハンドル、ファイルハンドル、または読み書きできるハンドルに設定できます。この手法の詳細は、MSDN:Howto:リダイレクトされた標準ハンドルを使用したスポーンコンソールプロセスで明確に説明されています。

ただし、MSDNのサンプルコードには2つの大きな問題があります。まず、子プロセスが最初に出力を送信してから、入力を待ってから、出力バッファーを洗い流して出口を洗い流すと想定しています。子プロセスがそのように動作しない場合、親プロセスはハングアップされます。この理由は、子どものプロセスが出力または出口を送信するまで、readfile()関数がブロックされたままです。

第二に、Windows 9xで16ビットコンソール(コンソールベースのMS-DOSアプリケーションを含む)をリダイレクトする問題があります。子プロセスが終了した後でもReadFileはブロックされたままです。 Windows NT/XPでは、Child ProcessがDOSアプリケーションである場合、ERROR_BROKEN_PIPEに設定されたERRORコードを使用して、ReadFileは常にFalseを返します。

readfileのブロック問題の解決

親プロセスがReadFileによってブロックされないようにするために、ファイルハンドルを子プロセスにSTDOUTとして渡してから、このファイルを監視するだけです。より簡単な方法は、readfile()を呼び出す前にpeeknamedpipe()関数を呼び出すことです。 PeeknamedPipe関数は、パイプ内のデータに関する情報をチェックし、すぐに返します。パイプに利用可能なデータがない場合は、ReadFileに電話しないでください。

ReadFileの前にPeeknamedPipeを呼び出すことにより、Windows 9xで16ビットコンソールをリダイレクトするブロック問題も解決します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top