Console學習

1.Console Handle(控制檯句柄)

1.1 進程的三種標準句柄

    每個console進程都有standard input(STDIN), standard output(STDOUT), standard error(STDERR)三種句柄與之相關聯,當系統創建console進程時,系統默認地將該進程的STDIN該進程的控制檯的輸入緩衝區(input buffer)相關聯,將該進程的STDOUT,STDERR該進程的控制檯的活動屏幕緩衝區(active screen buffer)相關聯,也就是說standard input(STDIN), standard output(STDOUT), standard error(STDERR)三種句柄本身是與進程相關的,它們與進程的控制檯的輸入/輸出緩衝區沒有關聯!既然如此,我們的程序可以使用 SetStdHandle 來對進程的三種標準句柄進行重定向,比如可以重定向到文件等。注:我們可以使用 GetStdHandle 來獲得當前進程的三種標準句柄。

1.2 控制檯的輸入緩衝區(input buffer)和活動屏幕緩衝區(active screen buffer)

    進程可以通過 CrateFile 函數來獲得與該進程相關的控制檯的輸入緩衝區(input buffer)和活動屏幕緩衝區(active screen buffer),Use the CONIN$ (lpFileName of CreateFile) value to specify console input. Use the CONOUT$(lpFileName of CreateFile) value to specify console output. CONIN$ gets a handle to the console input buffer, even if the SetStdHandle function redirects the standard input handle. To get the standard input handle, use the GetStdHandle function. CONOUT$ gets a handle to the active screen buffer, even if SetStdHandle redirects the standard output handle. To get the standard output handle, use GetStdHandle.

1.3 創建新的屏幕緩衝區

    進程可以使用 CreateConsoleScreenBuffer 來創建一個新的屏幕緩衝區,使用 SetConsoleActiveScreenBuffer 來重新設置console screen buffer.

    Note that changing the active screen buffer does not affect the handle returned by GetStdHandle. Similarly, using SetStdHandle to change the STDOUT handle does not affect the active screen buffer.

2.獲得 Console Handle

應用程序可以通過下面了的代碼獲得Console輸入,輸出緩衝區的句柄

HANDLE hinput = INVALID_HANDLE_VALUE,houtput = INVALID_HANDLE_VALUE;

if(INVALID_HANDLE_VALUE == (hinput = CreateFile(TEXT("CONIN$"), GENERIC_READ,

    FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)))

{  

    return -1;

}  

if(INVALID_HANDLE_VALUE == (houtput = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE,

    FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)))

{  

    return -1;

}

CUI程序在啓動時其進程默認的標準輸入和標準輸出,標準錯誤句柄是關聯到,控制檯的輸入,輸出緩衝區的,我們可以通過 GetStdHandle 方法來獲得。

HANDLE stdinput, stdoutput, stderr;

stdinput = GetStdHandle(STD_INPUT_HANDLE);

stdoutput = GetStdHandle(STD_INPUT_HANDLE);

stderr = GetStdHandle(STD_ERROR_HANDLE);

考慮此時,理論上應該有stdinput == hinput, stdoutput == houtput, stderr == houtput,然而,事實上,卻並非如此。控制檯的輸入和輸出緩衝區相當於兩種資源,系統並沒有使用指針來讓我們引用這些資源,目的是爲了保護這些資源。而句柄它是進程句柄表的ENTRY的index,所以儘管stdinput,stdoutput,stderr和houtput,hinput不相同,但是其內部的的指針卻一定是指向相同的對應的緩衝區地址的,證明如下:

#include"../../Common/UnicodeSupport.h"

#include<windows.h>

#include<stdio.h>

int _tmain()

{

    HANDLE hinput = INVALID_HANDLE_VALUE,houtput = INVALID_HANDLE_VALUE;

    if(INVALID_HANDLE_VALUE == (hinput = CreateFile(TEXT("CONIN$"), GENERIC_READ,

       FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)))

    {  

        return -1;

    }

    if(INVALID_HANDLE_VALUE == (houtput = CreateFile(TEXT("CONOUT$"),   GENERIC_WRITE,FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)))

    {  

        return -1;

    }

    if(GetStdHandle(STD_INPUT_HANDLE) != hinput)

    {

        _tprintf_s(TEXT("input handle is not same\n"));

    }

    if(GetStdHandle(STD_OUTPUT_HANDLE) != houtput)

    {

        _tprintf_s(TEXT("output handle is not same\n"));

    }

    LPCTSTR lpBuffer = TEXT("hello, world");

    DWORD nums;

    WriteConsole(houtput, lpBuffer, 13, &nums, NULL);

    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, 13, &nums, NULL);

    _tprintf_s(TEXT("\nhoutput = %x stdoutput = %x\n"), houtput, GetStdHandle(STD_OUTPUT_HANDLE));

    _tprintf_s(TEXT("hinput = %x stdinput = %x\n"), hinput, GetStdHandle(STD_INPUT_HANDLE));

    return 0;

}  

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 

//運行以上程序,輸出的結果如下:

input handle is not same

output handle is not same

hello, world hello, world

houtput = 13 stdoutput = b

binput = 7 stdinput = 3

------------------------------------------------------------------------

1.4 關於 CRT(c/c++ runtime library)中Stream I/O中的stdout,stdin,stderr和每個console進程都有standard input(STDIN), standard output(STDOUT), standard error(STDERR)三種句柄之間的關係。

struct _iobuf {

        char *_ptr; 

        int   _cnt;

        char *_base; // 待輸出的字符串的地址

        int   _flag;

        int   _file;

        int   _charbuf;

        int   _bufsiz;

        char *_tmpfname;

        };

typedef struct _iobuf FILE;

在stdio.h文件中我們找到了如下定義:

_CRTIMP extern FILE _iob[];

#define stdin  (&_iob[0])

#define stdout (&_iob[1])

#define stderr (&_iob[2])

可以看出 stdin, stdout, stder 都是 FILE* 類型,也就是 struct _iobuf* 類型, 顧名思義,FILE 類型的變量中存放着 I/O Stream 的緩衝區信息(緩衝區基址,緩衝區的大小,待輸出字符串的大小等).

CRT(c/c++ runtime library)中Stream I/O中的stdout,stdin,stderr和每個console 進程都有的 standard input(STDIN), standard output(STDOUT), standard error(STDERR)三種句柄之間的關係是沒有任何關係,就是指它們之間並不存在這某種映射關係,使得stdout變化時STDOUT也發生變化。

但是,當我們對CRT中的stdout, stdin, stderr進行重定向的時候,C-RunTime中Stream I/O將對進程的三種標準句柄(STDIN,STDOUT,STDERR)也進行重定向,使其正確地定向到CRT中定向的文件中去。而當我們對進程的三種標準句柄(STDIN,STDOUT,STDERR)進行重定向時,CRT中的stdout,stdin,stderr不會進行重定向。

從邏輯角度上來理解這種重定向行爲,Mircorsoft的控制檯(Console)API先於其C/C++運行時庫中的流類(I/O Stream)API被設計和開發出來,並且C/C++運行時庫中的流類(I/O Stream)API實際上是對控制檯(Console)API的二次封裝。控制檯(Console)API中的函數(例如:進行重定向的函數:SetStdHandle),自然不會知道C/C++運行時庫中的流類(I/O Stream)API中的任何細節,所以當我們對進程的三種標準句柄(STDIN,STDOUT,STDERR)進行重定向時(使用SetStdHandle),CRT中的stdout,stdin,stderr不會進行重定向,這是自然的。而當設計C/C++運行時庫中的流類(I/O Stream)API時,其提供的函數freopen,自然可以調用SetStdHandle對進程的標準句柄進行重定向。

C/C++運行時庫中的流類(I/O Stream)API設計思路推測:

printf等輸出函數,使用默認的 stdout(FILE*) 進行輸出時,其底層必然使用 CreateFile(TEXT("CONOUT$"),GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING, 0, NULL)獲得屏幕緩衝區句柄進行輸出(使用WriteFile)。

以下代碼,對上述推斷進行了證明:

************************************************************************

//使用Console API SetStdHandle 進行重定向

    if(!SetStdHandle(STD_OUTPUT_HANDLE, CreateFile(TEXT("filex.txt"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)))

    {

        _tprintf_s(TEXT("set stdhandle fail.\n"));

    }

    LPCTSTR lpBuffer = TEXT("hello, world");

    DWORD nums;

    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, 13 * sizeof(TCHAR), &nums, NULL);

    _tprintf_s(TEXT("I'm in child process.\n"));

    _ftprintf_s(stdout, TEXT("This is go to file."));

注意:以上代碼使用SetStdHandle進行重定向後,_tprintf_s和_ftprintf_s仍在屏幕上輸出

************************************************************************

************************************************************************

//使用CRT流類API _tfreopen_s 進行重定向

LPCTSTR lpBuffer = TEXT("hello, world");

DWORD nums;

    FILE *fwrite;

    _tfreopen_s(&fwrite, TEXT("file.txt"), TEXT("w"), stdout);

    _tprintf_s(TEXT("I'm in child process.\n"));

    _ftprintf_s(fwrite, TEXT("This is go to file."));

   

    if(!WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, 13, &nums, NULL))

    {_tprintf_s(TEXT("stdoutput fail."));}

    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, 13 * sizeof(TCHAR), &nums, NULL);

注意: 紅色代碼執行後也將字符串輸出到文件file.txt中,可見_tfreopen_s的確對Console的STDOUT也進行了重定向

************************************************************************

當使用以下代碼創建新的Console進程時

CreateProcess(TEXT("child.exe"), NULL, NULL, NULL, FALSE,

    DETACHED_PROCESS, NULL, NULL, &si, &pi);

注意標記DETACHED_PROCESS,它表示 for console processes, the new process does not inherit its parent's console (the default), and the new process is not attached to any console.So the new process can call the AllocConsole function at a later time to create a console.由於在創建新的進程時使用了DETACHED_PROCESS,則新進程在進行C/C++運行時庫初始化時自然無法將stdout,stdin,stderr與有效地控制檯(Console)輸入/輸出句柄進行關聯,從而使得當使用AllocConsole創建新的控制檯(Console)之後,仍然不能使用printf這樣的函數(高級流類API,因爲printf類函數默認使用stdout進行輸出,然而stdout此時卻是無效的。),此時向控制檯輸出函數是可以有兩種方法:

方法一:使用CreateFile and WriteConsole

    HANDLE houtput;

    if(INVALID_HANDLE_VALUE == (houtput = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE,

        FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)))

    {  

        _tprintf_s(TEXT("get console output handle fail."));

        return -1;

    }

    LPCTSTR lpBuffer = TEXT("write go to child.txt.\n");

    DWORD nums;

    WriteConsole(houtput, lpBuffer, lstrlen(lpBuffer), &nums, NULL);

方法二:使用 Console and Port I/O

(MSDN:http://127.0.0.1:47873/help/1-1704/ms.help?method=page&id=67FFEFD4-45B3-4BE0-9833-D8D26AC7C4E2&product=VS&productVersion=100&topicVersion=100&locale=ZH-CN&topicLocale=ZH-CN)

例如方法 _cprintf, _cscanf 等等,這類函數直接向當前Console寫入值。實際上這些方法內部會使用方法一來完成真正的功能!

方法三:對 stdin, stdout, stderr 進行重定向,從而使其有效,讓後就可使用printf類API了。

    FILE *fwrite;

    _tfreopen_s(&fwrite, TEXT("child.txt"), TEXT("w"), stdout);

    _tprintf_s(TEXT("child process.\n"));

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 

 

文件句柄和FILE*之間的轉換

FILE* HANDLE2FILE(HANDLE hFile)

{

    FILE *pfile = reinterpret_cast<FILE*>(malloc(sizeof(FILE)));

    if(!pfile)

    {

        return NULL;

    }

   

    int descriptor = _open_osfhandle(reinterpret_cast<intptr_t>(hFile), _O_TEXT | _O_APPEND);

    if(-1 == descriptor)

    {

        return NULL;

    }

   

    pfile = _tfdopen(descriptor, TEXT("w+"));

    if(!pfile)

    {

        return NULL;

    }

   

    return pfile;

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章