周星星 阅读(1431) 评论(0)

#include <windows.h>
#include <tchar.h>
typedef void (__stdcall* SNIFFER_THREAD_START_ROUTINE)( HANDLE hRead, HANDLE hWrite, LPVOID userfunparam );
struct SnfParam_
{
    SNIFFER_THREAD_START_ROUTINE userfunction;
    HANDLE hRead;
    HANDLE hWrite;
    LPVOID userfunparam;
};
DWORD __stdcall sniffer_( LPVOID lpParam )
{
    SnfParam_& ps = *(SnfParam_*)lpParam;
    (*ps.userfunction)( ps.hRead, ps.hWrite, ps.userfunparam );
    return 0;
}
#define IF_BAD_GOTO(EXPR,LABEL) if( !(EXPR) ) goto LABEL
#define CHECK(EXPR)             if( !(EXPR) ) goto error
#define CLOSEHANDLE(H)          if(INVALID_HANDLE_VALUE!=H) CloseHandle(H)
bool redirector( LPCTSTR cmd, SNIFFER_THREAD_START_ROUTINE lpSnfStartAddress, LPVOID lpSnfParam, LPDWORD lpRetVal=0, WORD wShowWindow=SW_HIDE
               , LPSECURITY_ATTRIBUTES lpCmdProcessAttributes=0,  LPSECURITY_ATTRIBUTES lpCmdThreadAttributes=0, DWORD dwCmdCreationFlags=NORMAL_PRIORITY_CLASS, LPVOID lpCmdEnvironment=0, LPCTSTR lpCmdCurrentDirectory=0
               , LPSECURITY_ATTRIBUTES lpSfrThreadAttributes=0, SIZE_T dwSfrStackSize=0 // sniffer
         )
{
    HANDLE hInput=INVALID_HANDLE_VALUE, hOutput=INVALID_HANDLE_VALUE, hError=INVALID_HANDLE_VALUE;
    HANDLE hRead_=INVALID_HANDLE_VALUE, hWrite_=INVALID_HANDLE_VALUE, hRead=INVALID_HANDLE_VALUE , hWrite=INVALID_HANDLE_VALUE;
    SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), 0, TRUE };
    HANDLE hProc = GetCurrentProcess();
    SnfParam_ sp;
    HANDLE hSnf = INVALID_HANDLE_VALUE;
    STARTUPINFO si;
    LPTSTR cmdline = 0;

    // 创建管道
    CHECK( CreatePipe(&hInput,&hWrite_,&sa,0) );
    CHECK( CreatePipe(&hRead_,&hOutput,&sa,0) );
    CHECK( DuplicateHandle(hProc,hOutput,hProc,&hError,0,TRUE,DUPLICATE_SAME_ACCESS) );  // 防止被获取数据的进程关闭hOutput时hError同时实效
    CHECK( DuplicateHandle(hProc,hRead_,hProc,&hRead,0,FALSE,DUPLICATE_SAME_ACCESS) );   // 不可继承
    CHECK( DuplicateHandle(hProc,hWrite_,hProc,&hWrite,0,FALSE,DUPLICATE_SAME_ACCESS) ); // 不可继承
    CloseHandle(hRead_); hRead_=INVALID_HANDLE_VALUE;
    CloseHandle(hWrite_); hWrite_=INVALID_HANDLE_VALUE;

    // 创建获取数据的线程
    sp.userfunction = lpSnfStartAddress;
    sp.userfunparam = lpSnfParam;
    sp.hRead        = hRead;
    sp.hWrite       = hWrite;
    hSnf = CreateThread( lpSfrThreadAttributes, dwSfrStackSize, &sniffer_, &sp, 0, 0 );
    CHECK( hSnf );

    // 创建被获取数据的进程
    GetStartupInfo( &si );
    si.dwFlags     = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
    si.wShowWindow = wShowWindow;
    si.hStdInput   = hInput;
    si.hStdOutput  = hOutput;
    si.hStdError   = hError;
    PROCESS_INFORMATION pi;
    cmdline = _tcsdup( cmd );
    CHECK( cmdline );
    CHECK( CreateProcess(NULL,cmdline,lpCmdProcessAttributes,lpCmdThreadAttributes,TRUE,dwCmdCreationFlags,lpCmdEnvironment,lpCmdCurrentDirectory,&si,&pi) );
    free(cmdline); cmdline=0;

    // 等待被获取数据的进程退出
    CHECK( WAIT_FAILED != WaitForSingleObject(pi.hProcess,INFINITE) );
    // 获取被获取数据的进程退出码
    if(lpRetVal) GetExitCodeProcess(pi.hProcess,lpRetVal);
    // 关闭被获取数据的进程句柄
    CloseHandle(pi.hThread ); pi.hThread =INVALID_HANDLE_VALUE;
    CloseHandle(pi.hProcess); pi.hProcess=INVALID_HANDLE_VALUE;
    
    // 关闭被获取数据的进程使用到的句柄
    CloseHandle(hInput ); hInput =INVALID_HANDLE_VALUE;
    CloseHandle(hOutput); hOutput=INVALID_HANDLE_VALUE;
    CloseHandle(hError ); hError =INVALID_HANDLE_VALUE;

    // 等待获取数据的线程退出
    CHECK( WAIT_FAILED != WaitForSingleObject(hSnf,INFINITE) );
    // 关闭获取数据的线程句柄
    CloseHandle(hSnf); hSnf=INVALID_HANDLE_VALUE;

    // 关闭获取数据的线程使用到的句柄
    CloseHandle(hRead ); hRead =INVALID_HANDLE_VALUE;
    CloseHandle(hWrite); hWrite=INVALID_HANDLE_VALUE;

    // 返回
    return true;

error:
    CLOSEHANDLE( pi.hProcess );
    CLOSEHANDLE( pi.hThread  );
    free( cmdline );
    CLOSEHANDLE( hSnf    );
    CLOSEHANDLE( hInput  );
    CLOSEHANDLE( hOutput );
    CLOSEHANDLE( hError  );
    CLOSEHANDLE( hRead   );
    CLOSEHANDLE( hWrite  );
    CLOSEHANDLE( hRead_  );
    CLOSEHANDLE( hWrite_ );
    return false;
}


#include <iostream>
#include <string>
#include <cassert>
using namespace std;
void __stdcall foo( HANDLE hRead, HANDLE hWrite, LPVOID userfunparam )
{
    TCHAR buf[100] = _T("test\n");
    DWORD dwRead;

    BOOL ret = WriteFile( hWrite, buf, (DWORD)_tcslen(buf)*sizeof(buf[0]), &dwRead, 0 );
    assert( ret && dwRead==_tcslen(buf)*sizeof(buf[0]) );

    while( ReadFile(hRead,buf,sizeof(buf),&dwRead,0) )
    {
        _tprintf( _T("%.*s"), dwRead/sizeof(buf[0]), buf );
    }
}
int main( int argc, char** agrv )
{
    if( argc == 1 )
    {
        cout << "----------------------------" << endl;
        DWORD ret;
        BOOL f = redirector( _T("debug\\cpp16.exe x"), &foo, 0, &ret );
        cout << "----------------------------" << endl;
        if( f )
            cout << "redirector success, and cmd return " << ret << '.' << endl;
        else
            cout << "redirector failed." << endl;
    }
    else
    {
        string str;
        cin >> str;
        cout << str << endl;
    }
    
    return 0;
}

几个疑问:
1。如果使用UNICODE方式,那么ReadFile读到的不是 0074 0065 0073 0074 000d 000a,而是 0074 0065 0073 0074 0a0d,还有可能是更奇怪的结果。按理应当加上 cmd.exe /U /C,但还是错误。
2。如果被获取数据的程序同时使用 cout(有缓冲标准输出),cerr(无缓冲错误输出), clog(有缓冲错误输出) ,fprintf(stdout,...),fprintf(stderr,...)那么获取到的次序有点混乱。用 cmd.exe /C app 1>log.txt 2>&1 也同样如此,估计是没办法了。
3。对于直接写屏函数的输出,比如 _cputs,那么无法获取到。用 cmd.exe /C app 1>log.txt 2>&1 也同样如此,估计是没办法了。
4。对于DOS16程序,向标准错误设备的输出,比如 fprintf(stderr,...) ,在 xp 下可以获取到,但在 vista 下不行。

发表评论
切换编辑模式